webp-screenshot-rust 1.0.0

High-performance cross-platform screenshot capture library with WebP encoding, hardware acceleration, and multi-monitor support for Windows, macOS, and Linux
Documentation
//! Wayland-based screen capture for Linux
//!
//! Note: Wayland screen capture is more restricted than X11 due to security model.
//! Requires compositor support and user permission via xdg-desktop-portal.

#[cfg(feature = "wayland")]
use crate::{
    error::{CaptureError, CaptureResult},
    types::{CaptureRegion, DisplayInfo, PixelFormat, RawImage},
};

#[cfg(feature = "wayland")]
use wayland_client::{
    protocol::{wl_output, wl_registry},
    Connection, Dispatch, QueueHandle,
};

/// Wayland capture implementation
#[cfg(feature = "wayland")]
pub struct WaylandCapture {
    connection: Connection,
    outputs: Vec<WaylandOutput>,
}

#[cfg(feature = "wayland")]
struct WaylandOutput {
    output: wl_output::WlOutput,
    name: String,
    width: u32,
    height: u32,
    x: i32,
    y: i32,
    scale: i32,
    refresh_rate: u32,
}

#[cfg(feature = "wayland")]
impl WaylandCapture {
    /// Create a new Wayland capturer
    pub fn new() -> CaptureResult<Self> {
        // Connect to Wayland compositor
        let connection = Connection::connect_to_env().map_err(|e| {
            CaptureError::PlatformError(format!("Failed to connect to Wayland: {}", e))
        })?;

        // Get the display outputs
        let outputs = Self::enumerate_outputs(&connection)?;

        Ok(Self {
            connection,
            outputs,
        })
    }

    /// Enumerate Wayland outputs
    fn enumerate_outputs(connection: &Connection) -> CaptureResult<Vec<WaylandOutput>> {
        // This is a simplified implementation
        // Full implementation would use wayland-client properly with event queue

        // For now, return a placeholder
        // In production, this would:
        // 1. Create event queue
        // 2. Get registry
        // 3. Bind to wl_output interface
        // 4. Listen for output events
        // 5. Collect output information

        Ok(Vec::new())
    }

    /// Get display information
    pub fn get_displays(&self) -> CaptureResult<Vec<DisplayInfo>> {
        if self.outputs.is_empty() {
            // Fallback for when we can't enumerate Wayland outputs
            // Try to use xdg-desktop-portal or return error
            return self.get_displays_via_portal();
        }

        Ok(self
            .outputs
            .iter()
            .enumerate()
            .map(|(index, output)| DisplayInfo {
                index,
                name: output.name.clone(),
                width: output.width,
                height: output.height,
                x: output.x,
                y: output.y,
                scale_factor: output.scale as f32,
                is_primary: index == 0,
                refresh_rate: output.refresh_rate,
                color_depth: 32,
            })
            .collect())
    }

    /// Get displays via xdg-desktop-portal
    fn get_displays_via_portal(&self) -> CaptureResult<Vec<DisplayInfo>> {
        // Use D-Bus to communicate with xdg-desktop-portal
        // This requires the dbus crate and portal implementation

        #[cfg(feature = "dbus")]
        {
            use dbus::blocking::Connection;
            use std::time::Duration;

            let conn = Connection::new_session().map_err(|e| {
                CaptureError::PlatformError(format!("Failed to connect to D-Bus: {}", e))
            })?;

            // Call org.freedesktop.portal.ScreenCast
            // This is simplified - actual implementation would be more complex

            // For now, return a default display
            Ok(vec![DisplayInfo {
                index: 0,
                name: "Wayland Display".to_string(),
                width: 1920,
                height: 1080,
                x: 0,
                y: 0,
                scale_factor: 1.0,
                is_primary: true,
                refresh_rate: 60,
                color_depth: 32,
            }])
        }

        #[cfg(not(feature = "dbus"))]
        {
            Err(CaptureError::PlatformError(
                "Wayland capture requires D-Bus support (enable 'dbus' feature)".to_string(),
            ))
        }
    }

    /// Capture a display
    pub fn capture_display(&self, display_index: usize) -> CaptureResult<RawImage> {
        // Wayland screen capture typically requires:
        // 1. Permission from user via xdg-desktop-portal
        // 2. PipeWire stream setup
        // 3. Frame buffer sharing

        // This is a complex process that varies by compositor
        // For now, we'll use the portal method

        self.capture_via_portal(display_index)
    }

    /// Capture a region
    pub fn capture_region(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
        // Similar to capture_display but with region selection
        // Most Wayland compositors don't support arbitrary region capture
        // without going through the portal

        Err(CaptureError::CaptureFailed(
            "Wayland region capture requires portal implementation".to_string(),
        ))
    }

    /// Capture via xdg-desktop-portal
    fn capture_via_portal(&self, _display_index: usize) -> CaptureResult<RawImage> {
        #[cfg(feature = "dbus")]
        {
            // This would:
            // 1. Request screenshot permission via portal
            // 2. Get PipeWire stream handle
            // 3. Connect to PipeWire
            // 4. Receive frame buffer
            // 5. Convert to our RawImage format

            // Simplified error for now
            Err(CaptureError::CaptureFailed(
                "Portal-based capture not yet fully implemented".to_string(),
            ))
        }

        #[cfg(not(feature = "dbus"))]
        {
            Err(CaptureError::PlatformError(
                "Wayland capture requires D-Bus support".to_string(),
            ))
        }
    }

    /// Check if screencasting is available
    pub fn is_screencast_available(&self) -> bool {
        // Check if xdg-desktop-portal and PipeWire are available
        #[cfg(feature = "dbus")]
        {
            if let Ok(conn) = dbus::blocking::Connection::new_session() {
                // Check if portal service exists
                let proxy = conn.with_proxy(
                    "org.freedesktop.portal.Desktop",
                    "/org/freedesktop/portal/desktop",
                    std::time::Duration::from_millis(100),
                );

                // Try to introspect
                use dbus::blocking::stdintf::org_freedesktop_dbus::Introspectable;
                proxy.introspect().is_ok()
            } else {
                false
            }
        }

        #[cfg(not(feature = "dbus"))]
        false
    }
}

// Note: Full Wayland implementation would require:
// 1. Proper Wayland protocol implementation
// 2. wlr-screencopy protocol for wlroots-based compositors
// 3. PipeWire integration for GNOME/KDE
// 4. xdg-desktop-portal integration
// 5. Permission handling
// 6. Format conversion from compositor-specific formats

// Stub for when Wayland feature is not enabled
#[cfg(not(feature = "wayland"))]
pub struct WaylandCapture;

#[cfg(not(feature = "wayland"))]
impl WaylandCapture {
    pub fn new() -> CaptureResult<Self> {
        Err(CaptureError::PlatformError(
            "Wayland support not enabled (compile with 'wayland' feature)".to_string(),
        ))
    }

    pub fn get_displays(&self) -> CaptureResult<Vec<crate::types::DisplayInfo>> {
        unreachable!()
    }

    pub fn capture_display(&self, _display_index: usize) -> CaptureResult<crate::types::RawImage> {
        unreachable!()
    }

    pub fn capture_region(&self, _region: crate::types::CaptureRegion) -> CaptureResult<crate::types::RawImage> {
        unreachable!()
    }
}