| 1 | //! The web inspector, for the site's page in the window: off unless |
| 2 | //! someone turns on Developer tools in settings, for when a page goes |
| 3 | //! wrong and they want its console. With it on, View › Developer tools |
| 4 | //! (Ctrl+Shift+I, or ⌥⌘I on macOS), F12 on the page (bridge.js), and the |
| 5 | //! site's own `g1tDesktop.devtools.open()` open it; with it off, none of |
| 6 | //! them do anything. |
| 7 | //! |
| 8 | //! Release builds have the inspector only through Tauri's `devtools` |
| 9 | //! feature (Cargo.toml). Whether a web view has it at all is decided when |
| 10 | //! the web view is made. On Windows, WebView2 lets it change while running, |
| 11 | //! its own F12, Ctrl+Shift+I and Inspect with it, so the setting takes |
| 12 | //! effect at once. WebKit, on macOS and Linux, keeps what it was made with, |
| 13 | //! and its Inspect Element with it, until g1t restarts; the settings then |
| 14 | //! offer the restart, as they do for the title bar. |
| 15 | |
| 16 | use std::sync::Mutex; |
| 17 | |
| 18 | use tauri::menu::MenuItem; |
| 19 | use tauri::{AppHandle, Webview, Wry}; |
| 20 | |
| 21 | use crate::config; |
| 22 | |
| 23 | /// Whether the setting is on now. |
| 24 | pub fn on(app: &AppHandle) -> bool { |
| 25 | config::settings(app).developer_tools |
| 26 | } |
| 27 | |
| 28 | /// Whether the site's web view was made with the inspector, which WebKit |
| 29 | /// keeps until g1t restarts: decided once, as it is made. |
| 30 | static STARTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new(); |
| 31 | |
| 32 | /// Whether this run's site web view has the inspector. |
| 33 | pub fn started(app: &AppHandle) -> bool { |
| 34 | *STARTED.get_or_init(|| on(app)) |
| 35 | } |
| 36 | |
| 37 | /// The setting differs from what the site's web view was made with, and |
| 38 | /// only a restart changes that: never on Windows, where it changes at once. |
| 39 | pub fn changed(app: &AppHandle) -> bool { |
| 40 | !cfg!(windows) && started(app) != on(app) |
| 41 | } |
| 42 | |
| 43 | /// The menu's Developer tools rows, kept so a change of the setting can |
| 44 | /// turn them on or off; a row that is off has no shortcut either. |
| 45 | static ITEMS: Mutex<Vec<MenuItem<Wry>>> = Mutex::new(Vec::new()); |
| 46 | |
| 47 | /// The menu row, with its shortcut: Ctrl+Shift+I, or ⌥⌘I on macOS. |
| 48 | pub fn item(app: &AppHandle) -> tauri::Result<MenuItem<Wry>> { |
| 49 | let shortcut = if cfg!(target_os = "macos") { "Cmd+Alt+I" } else { "Ctrl+Shift+I" }; |
| 50 | let item = MenuItem::with_id(app, "devtools", "Developer tools", on(app), Some(shortcut))?; |
| 51 | ITEMS.lock().unwrap().push(item.clone()); |
| 52 | Ok(item) |
| 53 | } |
| 54 | |
| 55 | /// Whether the inspector opens now: the setting is on, and the site's web |
| 56 | /// view was made with it, or is WebView2's, which takes it at once. |
| 57 | pub fn usable(app: &AppHandle) -> bool { |
| 58 | on(app) && !changed(app) |
| 59 | } |
| 60 | |
| 61 | /// The setting changed: the menu rows follow it, and so does WebView2's |
| 62 | /// own way in. Turned off, an inspector that is open closes where the |
| 63 | /// system can close it (not on Windows, whose WebView2 has no way to). |
| 64 | pub fn setting_changed(app: &AppHandle) { |
| 65 | let on = on(app); |
| 66 | for item in ITEMS.lock().unwrap().iter() { |
| 67 | let _ = item.set_enabled(usable(app)); |
| 68 | } |
| 69 | allow(app, on); |
| 70 | if !on && let Some(site) = crate::frame::site(app) { |
| 71 | site.close_devtools(); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /// Lets WebView2 open the inspector, from its own F12, Ctrl+Shift+I and |
| 76 | /// Inspect as well as when g1t asks, or not. WebKit keeps what its web |
| 77 | /// view was made with. |
| 78 | #[cfg(windows)] |
| 79 | pub fn allow(app: &AppHandle, on: bool) { |
| 80 | let Some(site) = crate::frame::site(app) else { return }; |
| 81 | let _ = site.with_webview(move |webview| { |
| 82 | // SAFETY: the controller is the site's, alive while the web view is, |
| 83 | // and this runs on the thread that owns it. |
| 84 | let _ = unsafe { webview.controller().CoreWebView2().and_then(|core| core.Settings()).and_then(|settings| settings.SetAreDevToolsEnabled(on)) }; |
| 85 | }); |
| 86 | } |
| 87 | |
| 88 | #[cfg(not(windows))] |
| 89 | pub fn allow(_app: &AppHandle, _on: bool) {} |
| 90 | |
| 91 | /// The site's web view, while the inspector opens: an error otherwise, |
| 92 | /// which the site's `devtools.open()` sees. |
| 93 | fn site_if_on(app: &AppHandle) -> Result<Webview, String> { |
| 94 | if !on(app) { |
| 95 | return Err("Developer tools are off. Turn them on in the app's settings, under Window.".into()); |
| 96 | } |
| 97 | if changed(app) { |
| 98 | return Err("Developer tools turn on when g1t restarts.".into()); |
| 99 | } |
| 100 | crate::frame::site(app).ok_or_else(|| "g1t's page is not open".into()) |
| 101 | } |
| 102 | |
| 103 | /// Opens the inspector for the site's page. |
| 104 | pub fn open(app: &AppHandle) -> Result<(), String> { |
| 105 | site_if_on(app)?.open_devtools(); |
| 106 | Ok(()) |
| 107 | } |
| 108 | |
| 109 | /// The menu row and the shortcuts: opens the inspector, or closes it if it |
| 110 | /// is open. WebView2 can't tell whether it is, nor close it, so on |
| 111 | /// Windows it only opens, and the inspector's own window closes it. |
| 112 | pub fn toggle(app: &AppHandle) -> Result<(), String> { |
| 113 | let site = site_if_on(app)?; |
| 114 | if site.is_devtools_open() { site.close_devtools() } else { site.open_devtools() } |
| 115 | Ok(()) |
| 116 | } |