313 lines
No EOL
9.6 KiB
Go
313 lines
No EOL
9.6 KiB
Go
// 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 } |