Skip to main content

x_win/
lib.rs

1#![deny(unsafe_op_in_unsafe_fn)]
2#![deny(clippy::all)]
3
4#[cfg(target_os = "macos")]
5#[macro_use]
6extern crate objc2;
7
8#[cfg(target_os = "macos")]
9#[macro_use]
10extern crate core;
11
12mod common;
13
14#[cfg(target_os = "windows")]
15mod win32;
16
17#[cfg(target_os = "linux")]
18mod linux;
19
20#[cfg(target_os = "macos")]
21mod macos;
22
23#[cfg(target_os = "windows")]
24use win32::init_platform_api;
25
26#[cfg(target_os = "linux")]
27use linux::init_platform_api;
28
29#[cfg(target_os = "macos")]
30use macos::init_platform_api;
31
32#[cfg(all(feature = "macos_permission", target_os = "macos"))]
33/// Handle screen record permission
34/// <div class="warning">important Work only with macOS 10.15+</div>
35/// To use this function you need to add `macos_permission` feature
36pub use macos::permission;
37
38pub use common::{
39  api::{empty_entity, os_name, Api},
40  result::Result,
41  x_win_struct::{
42    icon_info::IconInfo, process_info::ProcessInfo, usage_info::UsageInfo, window_info::WindowInfo,
43    window_position::WindowPosition,
44  },
45};
46
47/**
48 * Recover icon of window.
49 * Return `IconInfo`
50 */
51pub fn get_window_icon(window_info: &WindowInfo) -> Result<IconInfo> {
52  let api = init_platform_api();
53  let icon_info = api.get_app_icon(window_info)?;
54  Ok(icon_info)
55}
56
57/**
58 * Recover browser url of window.
59 * Return `String`
60 */
61pub fn get_browser_url(window_info: &WindowInfo) -> Result<String> {
62  let api = init_platform_api();
63  let browser_url = api.get_browser_url(window_info)?;
64  Ok(browser_url)
65}
66
67/**
68 * Retrieve information the about currently active window.
69 * Return `WindowInfo` containing details about a specific active window.
70 */
71pub fn get_active_window() -> Result<WindowInfo> {
72  let api = init_platform_api();
73  let active_window = api.get_active_window()?;
74  Ok(active_window)
75}
76
77/**
78 * Retrieve information about the currently open windows.
79 * Return `Vec<WindowInfo>` each containing details about a specific open window.
80 */
81pub fn get_open_windows() -> Result<Vec<WindowInfo>> {
82  let api = init_platform_api();
83  let open_windows = api.get_open_windows()?;
84  Ok(open_windows)
85}
86
87/**
88 * Install "@mininben90/x-win" Gnome extension required for Linux using Gnome > 41.
89 * This function will write extension files needed to correctly detect working windows with Wayland desktop environment.
90 * **Restart session will be require to install the gnome extension.**
91 */
92pub fn install_extension() -> Result<bool> {
93  #[cfg(not(target_os = "linux"))]
94  {
95    Ok(false)
96  }
97  #[cfg(target_os = "linux")]
98  {
99    linux::gnome_install_extension()
100  }
101}
102
103/**
104 * Uninstall "@mininben90/x-win" Gnome extension.
105 * This function will disable and remove extension files.
106 * **Restart session will be require to remove the gnome extension.**
107 */
108pub fn uninstall_extension() -> Result<bool> {
109  #[cfg(not(target_os = "linux"))]
110  {
111    Ok(false)
112  }
113  #[cfg(target_os = "linux")]
114  {
115    linux::gnome_uninstall_extension()
116  }
117}
118
119/**
120 * Enable Gnome extension required for Linux using Gnome > 41.
121 * This function will enable extension needed to correctly detect working windows with Wayland desktop environment.
122 */
123pub fn enable_extension() -> Result<bool> {
124  #[cfg(not(target_os = "linux"))]
125  {
126    Ok(false)
127  }
128  #[cfg(target_os = "linux")]
129  {
130    linux::gnome_enable_extension()
131  }
132}
133
134/**
135 * Disable Gnome extension required for Linux using Gnome > 41.
136 * This function will disable extension needed to correctly detect working windows with Wayland desktop environment.
137 */
138pub fn disable_extension() -> Result<bool> {
139  #[cfg(not(target_os = "linux"))]
140  {
141    Ok(false)
142  }
143  #[cfg(target_os = "linux")]
144  {
145    linux::gnome_disable_extension()
146  }
147}
148
149/**
150 * Return true of false if gnome extension is enabled for Linux using Gnome > 41.
151 * This function will return true or false if the extension is set to enabled on extension info. Working only with Wayland windows manager.
152 */
153pub fn is_enabled_extension() -> Result<bool> {
154  #[cfg(not(target_os = "linux"))]
155  {
156    Ok(false)
157  }
158  #[cfg(target_os = "linux")]
159  {
160    linux::gnome_is_enabled_extension()
161  }
162}
163
164/**
165 * Return true of false the extensions is installed for Linux using Gnome > 41.
166 * This function will return true or false if the extension is correctly installed. Working only with Wayland windows manager.
167 */
168pub fn is_installed_extension() -> Result<bool> {
169  #[cfg(not(target_os = "linux"))]
170  {
171    Ok(false)
172  }
173  #[cfg(target_os = "linux")]
174  {
175    linux::gnome_is_installed_extension()
176  }
177}
178
179#[cfg(test)]
180mod tests {
181  use super::*;
182  #[cfg(not(target_os = "linux"))]
183  use std::process::Command;
184  #[cfg(not(target_os = "linux"))]
185  use std::{thread, time};
186
187  #[cfg(not(target_os = "linux"))]
188  struct TestContext;
189
190  #[cfg(not(target_os = "linux"))]
191  impl TestContext {
192    fn setup() -> Self {
193      let output = if cfg!(target_os = "windows") {
194        Command::new("cmd")
195          .args([
196            "/C",
197            "start",
198            "microsoft-edge:https://github.com",
199            "--no-first-run",
200            "--restore-last-session",
201          ])
202          .output()
203          .expect("failed to execute process")
204      } else {
205        Command::new("open")
206          .args(["-a", "Safari", "https://github.com"])
207          .output()
208          .expect("failed to execute process")
209      };
210      println!(
211        "[START] Command Status: {:?}; Command stdout: {:?}; Command stderr: {:?}",
212        output.status,
213        std::str::from_utf8(&output.stdout).unwrap_or("Error when convert output"),
214        std::str::from_utf8(&output.stderr).unwrap_or("Error when convert stderr")
215      );
216      thread::sleep(time::Duration::from_secs(3));
217      TestContext
218    }
219  }
220
221  #[cfg(not(target_os = "linux"))]
222  impl Drop for TestContext {
223    fn drop(&mut self) {
224      let output = if cfg!(target_os = "windows") {
225        Command::new("cmd")
226          .args(["/C", "taskkill", "/f", "/im", "msedge.exe"])
227          .output()
228          .expect("failed to execute process")
229      } else {
230        Command::new("killall")
231          .args(["Safari"])
232          .output()
233          .expect("failed to execute process")
234      };
235      println!(
236        "[DONE] Command Status: {:?}; Command stdout: {:?}; Command stderr: {:?}",
237        output.status,
238        std::str::from_utf8(&output.stdout).unwrap_or("Error when convert output"),
239        std::str::from_utf8(&output.stderr).unwrap_or("Error when convert stderr")
240      );
241      thread::sleep(time::Duration::from_secs(3));
242    }
243  }
244
245  fn test_osname() -> String {
246    #[cfg(target_os = "linux")]
247    {
248      r#"linux"#.to_owned()
249    }
250    #[cfg(target_os = "macos")]
251    {
252      r#"darwin"#.to_owned()
253    }
254    #[cfg(target_os = "windows")]
255    {
256      r#"win32"#.to_owned()
257    }
258  }
259
260  fn test_struct(window_info: WindowInfo) -> Result<()> {
261    assert_ne!(window_info.id, 0);
262    assert_ne!(window_info.title, String::from(""));
263    #[cfg(target_os = "linux")]
264    assert_eq!(window_info.os, r#"linux"#);
265    #[cfg(target_os = "macos")]
266    assert_eq!(window_info.os, r#"darwin"#);
267    #[cfg(target_os = "windows")]
268    assert_eq!(window_info.os, r#"win32"#);
269    Ok(())
270  }
271
272  #[test]
273  fn test_get_active_window() -> Result<()> {
274    match get_active_window() {
275      Ok(window_info) => test_struct(window_info),
276      Err(err) => Err(err),
277    }
278  }
279
280  #[test]
281  fn test_get_open_windows() -> Result<()> {
282    match get_open_windows() {
283      Ok(open_windows) => {
284        assert_ne!(open_windows.len(), 0);
285
286        if let Some(window_info) = open_windows.first() {
287          test_struct(window_info.to_owned())
288        } else {
289          Err("No open window".into())
290        }
291      }
292      Err(err) => Err(err),
293    }
294  }
295
296  #[test]
297  fn test_os_name() -> Result<()> {
298    let os_name = os_name();
299    assert_eq!(os_name, test_osname());
300    Ok(())
301  }
302
303  #[test]
304  fn test_empty_entity() -> Result<()> {
305    let window_info = empty_entity();
306    assert_eq!(window_info.id, 0);
307    assert_eq!(window_info.title, String::from(""));
308    assert_eq!(window_info.os, test_osname());
309    Ok(())
310  }
311
312  #[test]
313  fn test_get_window_icon_from_active_window() -> Result<()> {
314    match get_active_window() {
315      Ok(window_info) => match get_window_icon(&window_info) {
316        Ok(icon_info) => {
317          assert_ne!(icon_info.data, "");
318          assert_ne!(icon_info.height, 0);
319          assert_ne!(icon_info.width, 0);
320          Ok(())
321        }
322        Err(err) => Err(err),
323      },
324      Err(err) => Err(err),
325    }
326  }
327
328  #[test]
329  fn test_get_window_icon_from_open_windows() -> Result<()> {
330    match get_open_windows() {
331      Ok(open_windows) => match open_windows.first() {
332        Some(window_info) => match get_window_icon(&window_info) {
333          Ok(icon_info) => {
334            assert_ne!(icon_info.data, "");
335            assert_ne!(icon_info.height, 0);
336            assert_ne!(icon_info.width, 0);
337            Ok(())
338          }
339          Err(err) => Err(err),
340        },
341        None => Err("No open window".into()),
342      },
343      Err(err) => Err(err),
344    }
345  }
346
347  #[cfg(not(target_os = "linux"))]
348  #[test]
349  #[ignore = "Not working on ci/cd"]
350  fn test_get_browser_url_from_active_window() -> Result<()> {
351    #[allow(unused)]
352    let _context: TestContext = TestContext::setup();
353    match get_active_window() {
354      Ok(window_info) => match get_browser_url(&window_info) {
355        Ok(url) => {
356          assert!(url.starts_with("http"));
357          Ok(())
358        }
359        Err(err) => Err(err),
360      },
361      Err(err) => Err(err),
362    }
363  }
364
365  #[cfg(not(target_os = "linux"))]
366  #[test]
367  #[ignore = "Not working on ci/cd"]
368  fn test_get_browser_url_from_open_windows() -> Result<()> {
369    #[allow(unused)]
370    let _context = TestContext::setup();
371    match get_open_windows() {
372      Ok(open_windows) => {
373        assert_ne!(open_windows.len(), 0);
374        match open_windows.first() {
375          Some(window_info) => match get_browser_url(window_info) {
376            Ok(url) => {
377              assert!(url.starts_with("http"));
378              Ok(())
379            }
380            Err(err) => Err(err),
381          },
382          None => Err("No open window".into()),
383        }
384      }
385      Err(err) => Err(err),
386    }
387  }
388
389  #[cfg(target_os = "linux")]
390  #[test]
391  #[ignore = "Not working on ci/cd"]
392  fn test_get_browser_url_from_active_window() -> Result<()> {
393    #[allow(unused)]
394    match get_active_window() {
395      Ok(window_info) => match get_browser_url(&window_info) {
396        Ok(url) => {
397          assert!(url.eq("URL recovery not supported on Linux distribution!"));
398          Ok(())
399        }
400        Err(err) => Err(err),
401      },
402      Err(err) => Err(err),
403    }
404  }
405
406  #[cfg(target_os = "linux")]
407  #[test]
408  #[ignore = "Not working on ci/cd"]
409  fn test_get_browser_url_from_open_windows() -> Result<()> {
410    #[allow(unused)]
411    match get_open_windows() {
412      Ok(open_windows) => {
413        assert_ne!(open_windows.len(), 0);
414        match open_windows.first() {
415          Some(window_info) => match get_browser_url(window_info) {
416            Ok(url) => {
417              assert!(url.eq("URL recovery not supported on Linux distribution!"));
418              Ok(())
419            }
420            Err(err) => Err(err),
421          },
422          None => Err("No open window".into()),
423        }
424      }
425      Err(err) => Err(err),
426    }
427  }
428
429  #[cfg(all(feature = "macos_permission", target_os = "macos"))]
430  #[test]
431  #[ignore = "Not working on ci/cd"]
432  fn test_check_screen_record_permission() -> Result<()> {
433    use macos::permission::check_screen_record_permission;
434    let value = check_screen_record_permission();
435    assert_eq!(value, true);
436    Ok(())
437  }
438
439  #[cfg(all(feature = "macos_permission", target_os = "macos"))]
440  #[test]
441  #[ignore = "Not working on ci/cd"]
442  fn test_request_screen_record_permission() -> Result<()> {
443    use macos::permission::request_screen_record_permission;
444    let value = request_screen_record_permission();
445    assert_eq!(value, true);
446    Ok(())
447  }
448
449  #[cfg(target_os = "linux")]
450  #[test]
451  #[ignore = "Not working on ci/cd"]
452  fn test_install_extension() -> Result<()> {
453    let value = install_extension()?;
454    assert_eq!(value, true);
455    Ok(())
456  }
457
458  #[cfg(target_os = "linux")]
459  #[test]
460  #[ignore = "Not working on ci/cd"]
461  fn test_uninstall_extension() -> Result<()> {
462    let value = uninstall_extension()?;
463    assert_eq!(value, true);
464    Ok(())
465  }
466
467  #[cfg(target_os = "linux")]
468  #[test]
469  #[ignore = "Not working on ci/cd"]
470  fn test_enable_extension() -> Result<()> {
471    let value = enable_extension()?;
472    assert_eq!(value, true);
473    Ok(())
474  }
475
476  #[cfg(target_os = "linux")]
477  #[test]
478  #[ignore = "Not working on ci/cd"]
479  fn test_disable_extension() -> Result<()> {
480    let value = disable_extension()?;
481    assert_eq!(value, true);
482    Ok(())
483  }
484
485  #[cfg(target_os = "linux")]
486  #[test]
487  #[ignore = "Not working on ci/cd"]
488  fn test_is_enabled_extension() -> Result<()> {
489    let value = is_enabled_extension()?;
490    assert_eq!(value, true);
491    Ok(())
492  }
493
494  #[cfg(target_os = "linux")]
495  #[test]
496  #[ignore = "Not working on ci/cd"]
497  fn test_is_installed_extension() -> Result<()> {
498    let value = is_installed_extension()?;
499    assert_eq!(value, true);
500    Ok(())
501  }
502}