Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ core-graphics = "0.22.3"
objc = "0.2.7"
xcap = "0.0.4"

[target.'cfg(target_os = "linux")'.dependencies]
xcap = "0.0.4"

[target.'cfg(target_os = "windows")'.dependencies]
win-screenshot = "4.0.5"

Expand Down
189 changes: 135 additions & 54 deletions src/platform/unix.rs
Original file line number Diff line number Diff line change
@@ -1,72 +1,153 @@
use crate::models::ScreenshotResponse;
use crate::{Error, Result};
use image;
use log::{debug, info, error};
use tauri::Runtime;

// Import shared functionality
use crate::desktop::ScreenshotContext;
use crate::platform::shared::handle_screenshot_task;
use crate::desktop::{ScreenshotContext, create_success_response};
use crate::platform::shared::{get_window_title, handle_screenshot_task};
use crate::shared::ScreenshotParams;
use crate::tools::take_screenshot::process_image;

// Unix-specific implementation for taking screenshots (fallback for non-macOS Unix systems)
// Linux/Unix implementation for taking screenshots using xcap
pub async fn take_screenshot<R: Runtime>(
params: ScreenshotParams,
window_context: ScreenshotContext<R>,
) -> Result<ScreenshotResponse> {
// Clone necessary values from params for use in the closure
// Clone necessary parameters for use in the closure
let params_clone = params.clone();
let window_clone = window_context.window.clone();
let quality = params.quality.unwrap_or(85) as u8;
let max_width = params.max_width.map(|w| w as u32).unwrap_or(0);
let window_label = params
.window_label
.clone()
.unwrap_or_else(|| "main".to_string());

// Get application name from params or use a default
let application_name = params.application_name.clone().unwrap_or_else(|| "".to_string());

handle_screenshot_task(move || {
let script = format!(
r#"
(function() {{
try {{
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');

// Set dimensions to match the window content
let width = window.innerWidth;
let height = window.innerHeight;

// Apply max width constraint if specified
if ({max_width} > 0 && width > {max_width}) {{
const aspectRatio = width / height;
width = {max_width};
height = width / aspectRatio;
}}

canvas.width = width;
canvas.height = height;

// Draw only the document to the canvas (not the OS chrome/window)
context.drawImage(document.documentElement, 0, 0, width, height);

// Convert canvas to base64 image with specified quality
return canvas.toDataURL('image/jpeg', {quality}/100);
}} catch (err) {{
console.error('Screenshot error:', err);
return null;
}}
}})();
"#,
max_width = max_width,
quality = quality
// Get the window title to help identify the right window
let window_title = get_window_title(&window_clone)?;

info!("[TAURI-MCP] Looking for window with title: {} (label: {})", window_title, window_label);

// Get all windows using xcap
let xcap_windows = match xcap::Window::all() {
Ok(windows) => windows,
Err(e) => return Err(Error::WindowOperationFailed(format!("Failed to get window list: {}", e))),
};

info!("[TAURI-MCP] Found {} windows through xcap", xcap_windows.len());

// Find the target window
if let Some(window) = find_window(&xcap_windows, &window_title, &application_name) {
// Capture image directly from the window
let image = match window.capture_image() {
Ok(img) => img,
Err(e) => return Err(Error::WindowOperationFailed(format!("Failed to capture window image: {}", e))),
};

info!("[TAURI-MCP] Successfully captured window image: {}x{}",
image.width(), image.height());

// Convert to DynamicImage for further processing
let dynamic_image = image::DynamicImage::ImageRgba8(image);

// Process the image
match process_image(dynamic_image, &params_clone) {
Ok(data_url) => Ok(create_success_response(data_url)),
Err(e) => Err(e),
}
} else {
// No window found
Err(Error::WindowOperationFailed("Window not found using any detection method. Please ensure the window is visible and not minimized.".to_string()))
}
}).await
}

// Helper function to find the window in the xcap window list
fn find_window(xcap_windows: &[xcap::Window], window_title: &str, application_name: &str) -> Option<xcap::Window> {
let application_name_lower = application_name.to_lowercase();
let window_title_lower = window_title.to_lowercase();

debug!(
"[TAURI-MCP] Searching for window with title: '{}' (case-insensitive)",
window_title
);

// Evaluate the JavaScript in the webview
match window_clone.eval(&script) {
Ok(_) => {
// In Tauri 2.x, we can't get the result from eval, so we return a sample image
Ok(ScreenshotResponse {
data: Some("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAb/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ALAKD//Z".to_string()),
success: true,
error: None,
})
},
Err(e) => Err(Error::WindowOperationFailed(format!("Failed to execute screenshot script: {}", e)))
// Debug all windows to help with troubleshooting
debug!("[TAURI-MCP] ============= ALL WINDOWS =============");
for window in xcap_windows {
if !window.is_minimized() {
debug!(
"[TAURI-MCP] Window: title='{}', app_name='{}'",
window.title(),
window.app_name()
);
}
}
}).await
}
debug!("[TAURI-MCP] ======================================");

// Step 1: First pass - direct application name match (highest priority)
if !application_name_lower.is_empty() {
for window in xcap_windows {
if window.is_minimized() {
continue;
}

// Add any other Unix-specific functionality here
let app_name = window.app_name().to_lowercase();

// Direct match for application name
if app_name.contains(&application_name_lower) {
info!(
"[TAURI-MCP] Found window by app name: '{}'",
window.app_name()
);
return Some(window.clone());
}
}
}

// Step 2: Try matching by window title
for window in xcap_windows {
if window.is_minimized() {
continue;
}

let title = window.title().to_lowercase();

// Match by window title
if title.contains(&window_title_lower) || window_title_lower.contains(&title) {
info!(
"[TAURI-MCP] Found window by title: '{}'",
window.title()
);
return Some(window.clone());
}
}

// Step 3: Try partial app name match with title
for window in xcap_windows {
if window.is_minimized() {
continue;
}

let app_name = window.app_name().to_lowercase();

// Check if app name appears in the requested title or vice versa
if app_name.contains(&window_title_lower) || window_title_lower.contains(&app_name) {
info!(
"[TAURI-MCP] Found window by partial app name match: '{}'",
window.app_name()
);
return Some(window.clone());
}
}

error!(
"[TAURI-MCP] No matching window found for '{}' or '{}'",
window_title, application_name
);
None
}