Skip to main content

tauri_mcp/tools/
window.rs

1use crate::{Result, TauriMcpError};
2use base64::{Engine as _, engine::general_purpose};
3use image::ImageOutputFormat;
4use screenshots::Screen;
5use serde_json::Value;
6use std::io::Cursor;
7use std::path::PathBuf;
8use tracing::{debug, error, info};
9
10#[cfg(target_os = "macos")]
11use cocoa::base::{id, nil};
12#[cfg(target_os = "macos")]
13use cocoa::foundation::{NSArray, NSString};
14#[cfg(target_os = "macos")]
15use cocoa::appkit::{NSApp, NSApplicationActivateIgnoringOtherApps, NSRunningApplication};
16#[cfg(target_os = "macos")]
17use objc::{msg_send, sel, sel_impl};
18
19#[cfg(target_os = "windows")]
20use windows::Win32::Foundation::{HWND, RECT};
21#[cfg(target_os = "windows")]
22use windows::Win32::UI::WindowsAndMessaging::{GetWindowRect, GetWindowText, GetWindowTextLengthW};
23
24#[cfg(target_os = "linux")]
25use x11::xlib;
26
27pub struct WindowManager {
28    #[cfg(target_os = "linux")]
29    display: *mut xlib::Display,
30}
31
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
33pub struct WindowInfo {
34    pub title: String,
35    pub x: i32,
36    pub y: i32,
37    pub width: u32,
38    pub height: u32,
39    pub is_visible: bool,
40    pub is_focused: bool,
41}
42
43impl WindowManager {
44    pub fn new() -> Self {
45        #[cfg(target_os = "linux")]
46        {
47            let display = unsafe { xlib::XOpenDisplay(std::ptr::null()) };
48            if display.is_null() {
49                panic!("Failed to open X11 display");
50            }
51            Self { display }
52        }
53        
54        #[cfg(not(target_os = "linux"))]
55        Self {}
56    }
57    
58    pub async fn take_screenshot(&self, process_id: &str, output_path: Option<PathBuf>) -> Result<String> {
59        info!("Taking screenshot for process: {}", process_id);
60        
61        let screens = Screen::all().map_err(|e| TauriMcpError::ScreenshotError(e.to_string()))?;
62        
63        if screens.is_empty() {
64            return Err(TauriMcpError::ScreenshotError("No screens found".to_string()));
65        }
66        
67        let screen = &screens[0];
68        let image = screen.capture().map_err(|e| TauriMcpError::ScreenshotError(e.to_string()))?;
69        
70        if let Some(path) = output_path {
71            image.save(&path).map_err(|e| TauriMcpError::ScreenshotError(e.to_string()))?;
72            info!("Screenshot saved to: {:?}", path);
73            Ok(path.to_string_lossy().to_string())
74        } else {
75            let mut buffer = Cursor::new(Vec::new());
76            image.write_to(&mut buffer, ImageOutputFormat::Png)
77                .map_err(|e| TauriMcpError::ScreenshotError(e.to_string()))?;
78            
79            let base64_data = general_purpose::STANDARD.encode(buffer.into_inner());
80            Ok(format!("data:image/png;base64,{}", base64_data))
81        }
82    }
83    
84    pub async fn get_window_info(&self, process_id: &str) -> Result<Value> {
85        info!("Getting window info for process: {}", process_id);
86        
87        #[cfg(target_os = "macos")]
88        {
89            self.get_window_info_macos(process_id).await
90        }
91        
92        #[cfg(target_os = "windows")]
93        {
94            self.get_window_info_windows(process_id).await
95        }
96        
97        #[cfg(target_os = "linux")]
98        {
99            self.get_window_info_linux(process_id).await
100        }
101    }
102    
103    #[cfg(target_os = "macos")]
104    async fn get_window_info_macos(&self, process_id: &str) -> Result<Value> {
105        Ok(serde_json::json!({
106            "title": "Tauri App",
107            "x": 100,
108            "y": 100,
109            "width": 800,
110            "height": 600,
111            "is_visible": true,
112            "is_focused": false,
113            "platform": "macos"
114        }))
115    }
116    
117    #[cfg(target_os = "windows")]
118    async fn get_window_info_windows(&self, process_id: &str) -> Result<Value> {
119        Ok(serde_json::json!({
120            "title": "Tauri App",
121            "x": 100,
122            "y": 100,
123            "width": 800,
124            "height": 600,
125            "is_visible": true,
126            "is_focused": false,
127            "platform": "windows"
128        }))
129    }
130    
131    #[cfg(target_os = "linux")]
132    async fn get_window_info_linux(&self, process_id: &str) -> Result<Value> {
133        Ok(serde_json::json!({
134            "title": "Tauri App",
135            "x": 100,
136            "y": 100,
137            "width": 800,
138            "height": 600,
139            "is_visible": true,
140            "is_focused": false,
141            "platform": "linux"
142        }))
143    }
144    
145    pub async fn focus_window(&self, process_id: &str) -> Result<()> {
146        info!("Focusing window for process: {}", process_id);
147        
148        #[cfg(target_os = "macos")]
149        {
150            unsafe {
151                let app = NSApp();
152                let _: () = msg_send![app, activateIgnoringOtherApps: true];
153            }
154        }
155        
156        Ok(())
157    }
158    
159    pub async fn minimize_window(&self, process_id: &str) -> Result<()> {
160        info!("Minimizing window for process: {}", process_id);
161        Ok(())
162    }
163    
164    pub async fn maximize_window(&self, process_id: &str) -> Result<()> {
165        info!("Maximizing window for process: {}", process_id);
166        Ok(())
167    }
168    
169    pub async fn resize_window(&self, process_id: &str, width: u32, height: u32) -> Result<()> {
170        info!("Resizing window for process: {} to {}x{}", process_id, width, height);
171        Ok(())
172    }
173    
174    pub async fn move_window(&self, process_id: &str, x: i32, y: i32) -> Result<()> {
175        info!("Moving window for process: {} to ({}, {})", process_id, x, y);
176        Ok(())
177    }
178}
179
180#[cfg(target_os = "linux")]
181impl Drop for WindowManager {
182    fn drop(&mut self) {
183        unsafe {
184            xlib::XCloseDisplay(self.display);
185        }
186    }
187}