// Monitor capture for full screen or monitor region capture (cross-platform). package backends import ( "fmt" "image" "runtime" "git.cloonar.com/openclawd/iso-bot/pkg/engine/capture" ) // MonitorConfig holds configuration for monitor capture. type MonitorConfig struct { // MonitorIndex specifies which monitor to capture (0-based). // -1 captures the primary monitor. MonitorIndex int `yaml:"monitor_index"` // Region defines a specific area to capture. // If nil, captures the entire monitor. Region *capture.Region `yaml:"region"` // IncludeCursor captures the mouse cursor. IncludeCursor bool `yaml:"include_cursor"` // Method specifies the capture method. // Options: "auto", "gdi", "dxgi", "x11", "wayland" Method string `yaml:"method"` } // MonitorSource captures from a monitor or screen region. type MonitorSource struct { config MonitorConfig monitors []MonitorInfo activeIndex int width int height int impl MonitorCaptureImpl } // MonitorInfo describes a display monitor. type MonitorInfo struct { Index int Name string X int Y int Width int Height int Primary bool ScaleFactor float64 } // MonitorCaptureImpl defines platform-specific capture implementation. type MonitorCaptureImpl interface { Initialize() error CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) Close() error } // NewMonitorSource creates a monitor capture source. func NewMonitorSource(configMap map[string]interface{}) (capture.Source, error) { var config MonitorConfig // Extract config from map if index, ok := configMap["monitor_index"].(int); ok { config.MonitorIndex = index } else { config.MonitorIndex = -1 // Primary monitor } if regionMap, ok := configMap["region"].(map[string]interface{}); ok { region := &capture.Region{} if x, ok := regionMap["x"].(int); ok { region.X = x } if y, ok := regionMap["y"].(int); ok { region.Y = y } if width, ok := regionMap["width"].(int); ok { region.Width = width } if height, ok := regionMap["height"].(int); ok { region.Height = height } config.Region = region } if cursor, ok := configMap["include_cursor"].(bool); ok { config.IncludeCursor = cursor } else { config.IncludeCursor = true } if method, ok := configMap["method"].(string); ok { config.Method = method } else { config.Method = "auto" } source := &MonitorSource{ config: config, } // Initialize platform-specific implementation if err := source.initImpl(); err != nil { return nil, fmt.Errorf("failed to initialize monitor capture: %w", err) } return source, nil } // Name returns a description of this capture source. func (m *MonitorSource) Name() string { if m.config.MonitorIndex >= 0 { return fmt.Sprintf("Monitor %d", m.config.MonitorIndex) } return "Primary Monitor" } // Capture grabs a single frame from the monitor. func (m *MonitorSource) Capture() (image.Image, error) { if m.impl == nil { return nil, fmt.Errorf("monitor capture not initialized") } monitor, err := m.getTargetMonitor() if err != nil { return nil, err } return m.impl.CaptureMonitor(monitor, m.config.Region) } // CaptureRegion grabs a sub-region of the monitor. func (m *MonitorSource) CaptureRegion(r capture.Region) (image.Image, error) { if m.impl == nil { return nil, fmt.Errorf("monitor capture not initialized") } monitor, err := m.getTargetMonitor() if err != nil { return nil, err } return m.impl.CaptureMonitor(monitor, &r) } // Size returns the monitor dimensions. func (m *MonitorSource) Size() (width, height int) { if m.config.Region != nil { return m.config.Region.Width, m.config.Region.Height } return m.width, m.height } // Close releases monitor capture resources. func (m *MonitorSource) Close() error { if m.impl != nil { return m.impl.Close() } return nil } // initImpl initializes the platform-specific capture implementation. func (m *MonitorSource) initImpl() error { var impl MonitorCaptureImpl var err error // Choose implementation based on platform and method switch { case m.config.Method == "auto": impl, err = m.createAutoImpl() case m.config.Method == "gdi" && runtime.GOOS == "windows": impl, err = NewGDICapture(m.config) case m.config.Method == "dxgi" && runtime.GOOS == "windows": impl, err = NewDXGICapture(m.config) case m.config.Method == "x11" && runtime.GOOS == "linux": impl, err = NewX11MonitorCapture(m.config) case m.config.Method == "wayland" && runtime.GOOS == "linux": impl, err = NewWaylandMonitorCapture(m.config) default: return fmt.Errorf("unsupported capture method %q for platform %s", m.config.Method, runtime.GOOS) } if err != nil { return err } m.impl = impl return m.impl.Initialize() } // createAutoImpl selects the best implementation for the current platform. func (m *MonitorSource) createAutoImpl() (MonitorCaptureImpl, error) { switch runtime.GOOS { case "windows": // Prefer DXGI for better performance, fallback to GDI if impl, err := NewDXGICapture(m.config); err == nil { return impl, nil } return NewGDICapture(m.config) case "linux": // Try Wayland first, fallback to X11 if impl, err := NewWaylandMonitorCapture(m.config); err == nil { return impl, nil } return NewX11MonitorCapture(m.config) case "darwin": return NewMacOSCapture(m.config) default: return nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS) } } // getTargetMonitor returns the monitor to capture based on configuration. func (m *MonitorSource) getTargetMonitor() (MonitorInfo, error) { if len(m.monitors) == 0 { return MonitorInfo{}, fmt.Errorf("no monitors detected") } if m.config.MonitorIndex == -1 { // Find primary monitor for _, monitor := range m.monitors { if monitor.Primary { return monitor, nil } } // If no primary found, use first monitor return m.monitors[0], nil } if m.config.MonitorIndex >= len(m.monitors) { return MonitorInfo{}, fmt.Errorf("monitor index %d out of range (have %d monitors)", m.config.MonitorIndex, len(m.monitors)) } return m.monitors[m.config.MonitorIndex], nil } // enumerateMonitors discovers available monitors. func (m *MonitorSource) enumerateMonitors() error { // TODO: Implement monitor enumeration for each platform // This would use platform-specific APIs to discover monitors m.monitors = []MonitorInfo{ { Index: 0, Name: "Primary Display", X: 0, Y: 0, Width: 1920, Height: 1080, Primary: true, ScaleFactor: 1.0, }, } return nil } // Platform-specific capture implementations (stubs for now) // NewGDICapture creates a GDI-based capture implementation for Windows. func NewGDICapture(config MonitorConfig) (MonitorCaptureImpl, error) { // TODO: Implement GDI capture using GetDC/BitBlt return &GDICapture{config: config}, nil } // NewDXGICapture creates a DXGI-based capture implementation for Windows. func NewDXGICapture(config MonitorConfig) (MonitorCaptureImpl, error) { // TODO: Implement DXGI Desktop Duplication API capture return &DXGICapture{config: config}, nil } // NewX11MonitorCapture creates an X11-based capture implementation for Linux. func NewX11MonitorCapture(config MonitorConfig) (MonitorCaptureImpl, error) { // TODO: Implement X11 root window capture using XGetImage return &X11MonitorCapture{config: config}, nil } // NewWaylandMonitorCapture creates a Wayland-based capture implementation for Linux. func NewWaylandMonitorCapture(config MonitorConfig) (MonitorCaptureImpl, error) { // TODO: Implement Wayland capture using PipeWire/xdg-desktop-portal return &WaylandMonitorCapture{config: config}, nil } // NewMacOSCapture creates a macOS capture implementation. func NewMacOSCapture(config MonitorConfig) (MonitorCaptureImpl, error) { // TODO: Implement macOS capture using CGDisplayCreateImage return &MacOSCapture{config: config}, nil } // Stub implementations type GDICapture struct{ config MonitorConfig } func (g *GDICapture) Initialize() error { return fmt.Errorf("GDI capture not implemented") } func (g *GDICapture) CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) { return nil, fmt.Errorf("GDI capture not implemented") } func (g *GDICapture) Close() error { return nil } type DXGICapture struct{ config MonitorConfig } func (d *DXGICapture) Initialize() error { return fmt.Errorf("DXGI capture not implemented") } func (d *DXGICapture) CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) { return nil, fmt.Errorf("DXGI capture not implemented") } func (d *DXGICapture) Close() error { return nil } type X11MonitorCapture struct{ config MonitorConfig } func (x *X11MonitorCapture) Initialize() error { return fmt.Errorf("X11 monitor capture not implemented") } func (x *X11MonitorCapture) CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) { return nil, fmt.Errorf("X11 monitor capture not implemented") } func (x *X11MonitorCapture) Close() error { return nil } type WaylandMonitorCapture struct{ config MonitorConfig } func (w *WaylandMonitorCapture) Initialize() error { return fmt.Errorf("Wayland monitor capture not implemented") } func (w *WaylandMonitorCapture) CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) { return nil, fmt.Errorf("Wayland monitor capture not implemented") } func (w *WaylandMonitorCapture) Close() error { return nil } type MacOSCapture struct{ config MonitorConfig } func (m *MacOSCapture) Initialize() error { return fmt.Errorf("macOS capture not implemented") } func (m *MacOSCapture) CaptureMonitor(monitor MonitorInfo, region *capture.Region) (image.Image, error) { return nil, fmt.Errorf("macOS capture not implemented") } func (m *MacOSCapture) Close() error { return nil }