//go:build linux // Wayland screen capture using PipeWire and xdg-desktop-portal. package backends import ( "fmt" "image" "git.cloonar.com/openclawd/iso-bot/pkg/engine/capture" ) // WaylandConfig holds configuration for Wayland screen capture. type WaylandConfig struct { // Output is the Wayland output name to capture (e.g., "DP-1", "HDMI-A-1"). // If empty, captures the primary output. Output string `yaml:"output"` // UsePipeWire enables PipeWire-based capture for better performance. // Falls back to xdg-desktop-portal screencasting if false. UsePipeWire bool `yaml:"use_pipewire"` // WindowTitle attempts to capture a specific window (if supported). // Note: Wayland has limited window-specific capture due to security model. WindowTitle string `yaml:"window_title"` // Framerate sets the desired capture framerate. Framerate int `yaml:"framerate"` } // WaylandSource captures screen content on Wayland using PipeWire/xdg-desktop-portal. type WaylandSource struct { config WaylandConfig pipeWireCtx uintptr // PipeWire context stream uintptr // PipeWire stream width int height int active bool } // NewWaylandSource creates a Wayland screen capture source. func NewWaylandSource(configMap map[string]interface{}) (capture.Source, error) { var config WaylandConfig // Extract config from map if output, ok := configMap["output"].(string); ok { config.Output = output } if usePW, ok := configMap["use_pipewire"].(bool); ok { config.UsePipeWire = usePW } else { config.UsePipeWire = true // Default to PipeWire } if title, ok := configMap["window_title"].(string); ok { config.WindowTitle = title } if fps, ok := configMap["framerate"].(int); ok { config.Framerate = fps } else { config.Framerate = 30 // Default framerate } return &WaylandSource{ config: config, }, nil } // Name returns a description of this capture source. func (w *WaylandSource) Name() string { if w.config.Output != "" { return fmt.Sprintf("Wayland Output: %s", w.config.Output) } if w.config.WindowTitle != "" { return fmt.Sprintf("Wayland Window: %s", w.config.WindowTitle) } return "Wayland Screen Capture" } // Capture grabs a single frame from Wayland. func (w *WaylandSource) Capture() (image.Image, error) { if !w.active { if err := w.startCapture(); err != nil { return nil, fmt.Errorf("failed to start Wayland capture: %w", err) } } // TODO: Implement Wayland screen capture // 1. Use xdg-desktop-portal ScreenCast interface to request screen sharing // 2. Get PipeWire stream from portal response // 3. Connect to PipeWire stream and read video buffers // 4. Convert PipeWire buffer to Go image.Image return nil, fmt.Errorf("Wayland capture not implemented yet") } // CaptureRegion grabs a sub-region of the screen. func (w *WaylandSource) CaptureRegion(r capture.Region) (image.Image, error) { // TODO: Implement region capture // Capture full screen then crop to region, as Wayland doesn't support partial capture fullFrame, err := w.Capture() if err != nil { return nil, err } // Crop the image to the specified region bounds := image.Rect(r.X, r.Y, r.X+r.Width, r.Y+r.Height) return fullFrame.(interface{ SubImage(r image.Rectangle) image.Image }).SubImage(bounds), nil } // Size returns the screen dimensions. func (w *WaylandSource) Size() (width, height int) { // TODO: Get actual screen size from Wayland output info return w.width, w.height } // Close releases Wayland resources. func (w *WaylandSource) Close() error { w.active = false // TODO: Close PipeWire stream and context return nil } // startCapture initiates the screen capture session. func (w *WaylandSource) startCapture() error { // TODO: Implement capture initialization // 1. Connect to xdg-desktop-portal via D-Bus // 2. Call CreateSession on org.freedesktop.portal.ScreenCast interface // 3. Configure capture source (screen or window) // 4. Start capture session // 5. Extract PipeWire node ID from response // 6. Connect to PipeWire and setup stream callbacks w.active = true return fmt.Errorf("Wayland capture initialization not implemented") } // initPipeWire sets up PipeWire connection and stream. func (w *WaylandSource) initPipeWire(nodeID uint32) error { // TODO: Implement PipeWire initialization // 1. Create PipeWire main loop and context // 2. Create stream with video format constraints // 3. Connect stream to the portal-provided node // 4. Setup stream callbacks for buffer processing return fmt.Errorf("PipeWire initialization not implemented") } // requestScreenCast uses xdg-desktop-portal to request screen access. func (w *WaylandSource) requestScreenCast() (uint32, error) { // TODO: Implement D-Bus communication with xdg-desktop-portal // 1. Create D-Bus connection // 2. Call org.freedesktop.portal.ScreenCast.CreateSession // 3. Call SelectSources with appropriate source types // 4. Call Start to begin capture // 5. Parse response to get PipeWire node ID return 0, fmt.Errorf("ScreenCast portal request not implemented") } // processPipeWireBuffer converts PipeWire video buffer to image. func (w *WaylandSource) processPipeWireBuffer(buffer uintptr) (image.Image, error) { // TODO: Implement buffer processing // 1. Extract video format info (width, height, stride, pixel format) // 2. Map buffer memory // 3. Convert pixel data to image.RGBA // 4. Handle different pixel formats (BGRA, RGBA, etc.) return nil, fmt.Errorf("PipeWire buffer processing not implemented") }