tauri_mcp/tools/
window.rs1use 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}