Selaa lähdekoodia

feat(core): add hotkey to toggle devtools, closes #3776 (#3791)

Lucas Fernandes Nogueira 3 vuotta sitten
vanhempi
sitoutus
e05d718a7b

+ 6 - 0
.changes/devtools-apis.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime": minor
+"tauri-runtime-wry": minor
+---
+
+Added `close_devtools` and `is_devtools_open` APIs to the `Dispatch` trait.

+ 5 - 0
.changes/devtools-hotkey.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Toggle devtools when `Ctrl + Shift + I` or `Command + Option + I` is pressed.

+ 5 - 0
.changes/window-devtools-apis.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `close_devtools` and `is_devtools_open` APIs to the `Window` struct.

+ 33 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -984,8 +984,13 @@ unsafe impl Send for GtkWindow {}
 
 #[derive(Debug, Clone)]
 pub enum WindowMessage {
+  // Devtools
   #[cfg(any(debug_assertions, feature = "devtools"))]
   OpenDevTools,
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  CloseDevTools,
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  IsDevToolsOpen(Sender<bool>),
   // Getters
   ScaleFactor(Sender<f64>),
   InnerPosition(Sender<Result<PhysicalPosition<i32>>>),
@@ -1174,6 +1179,20 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
     );
   }
 
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn close_devtools(&self) {
+    let _ = send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::CloseDevTools),
+    );
+  }
+
+  /// Gets the devtools window's current open state.
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn is_devtools_open(&self) -> Result<bool> {
+    window_getter!(self, WindowMessage::IsDevToolsOpen)
+  }
+
   // Getters
 
   fn scale_factor(&self) -> Result<f64> {
@@ -2002,6 +2021,20 @@ fn handle_user_message<T: UserEvent>(
               w.open_devtools();
             }
           }
+          #[cfg(any(debug_assertions, feature = "devtools"))]
+          WindowMessage::CloseDevTools => {
+            if let WindowHandle::Webview(w) = &webview.inner {
+              w.close_devtools();
+            }
+          }
+          #[cfg(any(debug_assertions, feature = "devtools"))]
+          WindowMessage::IsDevToolsOpen(tx) => {
+            if let WindowHandle::Webview(w) = &webview.inner {
+              tx.send(w.is_devtools_open()).unwrap();
+            } else {
+              tx.send(false).unwrap();
+            }
+          }
           // Getters
           WindowMessage::ScaleFactor(tx) => tx.send(window.scale_factor()).unwrap(),
           WindowMessage::InnerPosition(tx) => tx

+ 66 - 64
core/tauri-runtime/src/lib.rs

@@ -284,33 +284,29 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
   fn create_window(
     &self,
     pending: PendingWindow<T, Self::Runtime>,
-  ) -> crate::Result<DetachedWindow<T, Self::Runtime>>;
+  ) -> Result<DetachedWindow<T, Self::Runtime>>;
 
   /// Run a task on the main thread.
-  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()>;
+  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
 
   #[cfg(all(windows, feature = "system-tray"))]
   #[cfg_attr(doc_cfg, doc(cfg(all(windows, feature = "system-tray"))))]
-  fn remove_system_tray(&self) -> crate::Result<()>;
+  fn remove_system_tray(&self) -> Result<()>;
 }
 
 /// A global shortcut manager.
 pub trait GlobalShortcutManager: Debug + Clone + Send + Sync {
   /// Whether the application has registered the given `accelerator`.
-  fn is_registered(&self, accelerator: &str) -> crate::Result<bool>;
+  fn is_registered(&self, accelerator: &str) -> Result<bool>;
 
   /// Register a global shortcut of `accelerator`.
-  fn register<F: Fn() + Send + 'static>(
-    &mut self,
-    accelerator: &str,
-    handler: F,
-  ) -> crate::Result<()>;
+  fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()>;
 
   /// Unregister all accelerators registered by the manager instance.
-  fn unregister_all(&mut self) -> crate::Result<()>;
+  fn unregister_all(&mut self) -> Result<()>;
 
   /// Unregister the provided `accelerator`.
-  fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
+  fn unregister(&mut self, accelerator: &str) -> Result<()>;
 }
 
 /// Clipboard manager.
@@ -342,12 +338,12 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
   type EventLoopProxy: EventLoopProxy<T>;
 
   /// Creates a new webview runtime. Must be used on the main thread.
-  fn new() -> crate::Result<Self>;
+  fn new() -> Result<Self>;
 
   /// Creates a new webview runtime on any thread.
   #[cfg(any(windows, target_os = "linux"))]
   #[cfg_attr(doc_cfg, doc(cfg(any(windows, target_os = "linux"))))]
-  fn new_any_thread() -> crate::Result<Self>;
+  fn new_any_thread() -> Result<Self>;
 
   /// Creates an `EventLoopProxy` that can be used to dispatch user events to the main event loop.
   fn create_proxy(&self) -> Self::EventLoopProxy;
@@ -362,15 +358,12 @@ pub trait Runtime<T: UserEvent>: Debug + Sized + 'static {
   fn clipboard_manager(&self) -> Self::ClipboardManager;
 
   /// Create a new webview window.
-  fn create_window(
-    &self,
-    pending: PendingWindow<T, Self>,
-  ) -> crate::Result<DetachedWindow<T, Self>>;
+  fn create_window(&self, pending: PendingWindow<T, Self>) -> Result<DetachedWindow<T, Self>>;
 
   /// Adds the icon to the system tray with the specified menu items.
   #[cfg(feature = "system-tray")]
   #[cfg_attr(doc_cfg, doc(cfg(feature = "system-tray")))]
-  fn system_tray(&self, system_tray: SystemTray) -> crate::Result<Self::TrayHandler>;
+  fn system_tray(&self, system_tray: SystemTray) -> Result<Self::TrayHandler>;
 
   /// Registers a system tray event handler.
   #[cfg(feature = "system-tray")]
@@ -398,7 +391,7 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   type WindowBuilder: WindowBuilder;
 
   /// Run a task on the main thread.
-  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> crate::Result<()>;
+  fn run_on_main_thread<F: FnOnce() + Send + 'static>(&self, f: F) -> Result<()>;
 
   /// Registers a window event handler.
   fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) -> Uuid;
@@ -406,68 +399,77 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   /// Registers a window event handler.
   fn on_menu_event<F: Fn(&window::MenuEvent) + Send + 'static>(&self, f: F) -> Uuid;
 
+  /// Open the web inspector which is usually called devtools.
   #[cfg(any(debug_assertions, feature = "devtools"))]
   fn open_devtools(&self);
 
+  /// Close the web inspector which is usually called devtools.
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn close_devtools(&self);
+
+  /// Gets the devtools window's current open state.
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn is_devtools_open(&self) -> Result<bool>;
+
   // GETTERS
 
   /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
-  fn scale_factor(&self) -> crate::Result<f64>;
+  fn scale_factor(&self) -> Result<f64>;
 
   /// Returns the position of the top-left hand corner of the window's client area relative to the top-left hand corner of the desktop.
-  fn inner_position(&self) -> crate::Result<PhysicalPosition<i32>>;
+  fn inner_position(&self) -> Result<PhysicalPosition<i32>>;
 
   /// Returns the position of the top-left hand corner of the window relative to the top-left hand corner of the desktop.
-  fn outer_position(&self) -> crate::Result<PhysicalPosition<i32>>;
+  fn outer_position(&self) -> Result<PhysicalPosition<i32>>;
 
   /// Returns the physical size of the window's client area.
   ///
   /// The client area is the content of the window, excluding the title bar and borders.
-  fn inner_size(&self) -> crate::Result<PhysicalSize<u32>>;
+  fn inner_size(&self) -> Result<PhysicalSize<u32>>;
 
   /// Returns the physical size of the entire window.
   ///
   /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), use inner_size instead.
-  fn outer_size(&self) -> crate::Result<PhysicalSize<u32>>;
+  fn outer_size(&self) -> Result<PhysicalSize<u32>>;
 
   /// Gets the window's current fullscreen state.
-  fn is_fullscreen(&self) -> crate::Result<bool>;
+  fn is_fullscreen(&self) -> Result<bool>;
 
   /// Gets the window's current maximized state.
-  fn is_maximized(&self) -> crate::Result<bool>;
+  fn is_maximized(&self) -> Result<bool>;
 
   /// Gets the window’s current decoration state.
-  fn is_decorated(&self) -> crate::Result<bool>;
+  fn is_decorated(&self) -> Result<bool>;
 
   /// Gets the window’s current resizable state.
-  fn is_resizable(&self) -> crate::Result<bool>;
+  fn is_resizable(&self) -> Result<bool>;
 
   /// Gets the window's current vibility state.
-  fn is_visible(&self) -> crate::Result<bool>;
+  fn is_visible(&self) -> Result<bool>;
 
   /// Gets the window menu current visibility state.
-  fn is_menu_visible(&self) -> crate::Result<bool>;
+  fn is_menu_visible(&self) -> Result<bool>;
 
   /// Returns the monitor on which the window currently resides.
   ///
   /// Returns None if current monitor can't be detected.
-  fn current_monitor(&self) -> crate::Result<Option<Monitor>>;
+  fn current_monitor(&self) -> Result<Option<Monitor>>;
 
   /// Returns the primary monitor of the system.
   ///
   /// Returns None if it can't identify any monitor as a primary one.
-  fn primary_monitor(&self) -> crate::Result<Option<Monitor>>;
+  fn primary_monitor(&self) -> Result<Option<Monitor>>;
 
   /// Returns the list of all the monitors available on the system.
-  fn available_monitors(&self) -> crate::Result<Vec<Monitor>>;
+  fn available_monitors(&self) -> Result<Vec<Monitor>>;
 
   /// Returns the native handle that is used by this window.
   #[cfg(windows)]
-  fn hwnd(&self) -> crate::Result<HWND>;
+  fn hwnd(&self) -> Result<HWND>;
 
   /// Returns the native handle that is used by this window.
   #[cfg(target_os = "macos")]
-  fn ns_window(&self) -> crate::Result<*mut std::ffi::c_void>;
+  fn ns_window(&self) -> Result<*mut std::ffi::c_void>;
 
   /// Returns the `ApplicatonWindow` from gtk crate that is used by this window.
   #[cfg(any(
@@ -477,96 +479,96 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
     target_os = "netbsd",
     target_os = "openbsd"
   ))]
-  fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow>;
+  fn gtk_window(&self) -> Result<gtk::ApplicationWindow>;
 
   // SETTERS
 
   /// Centers the window.
-  fn center(&self) -> crate::Result<()>;
+  fn center(&self) -> Result<()>;
 
   /// Opens the dialog to prints the contents of the webview.
-  fn print(&self) -> crate::Result<()>;
+  fn print(&self) -> Result<()>;
 
   /// Requests user attention to the window.
   ///
   /// Providing `None` will unset the request for user attention.
-  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> crate::Result<()>;
+  fn request_user_attention(&self, request_type: Option<UserAttentionType>) -> Result<()>;
 
   /// Create a new webview window.
   fn create_window(
     &mut self,
     pending: PendingWindow<T, Self::Runtime>,
-  ) -> crate::Result<DetachedWindow<T, Self::Runtime>>;
+  ) -> Result<DetachedWindow<T, Self::Runtime>>;
 
   /// Updates the window resizable flag.
-  fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
+  fn set_resizable(&self, resizable: bool) -> Result<()>;
 
   /// Updates the window title.
-  fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
+  fn set_title<S: Into<String>>(&self, title: S) -> Result<()>;
 
   /// Maximizes the window.
-  fn maximize(&self) -> crate::Result<()>;
+  fn maximize(&self) -> Result<()>;
 
   /// Unmaximizes the window.
-  fn unmaximize(&self) -> crate::Result<()>;
+  fn unmaximize(&self) -> Result<()>;
 
   /// Minimizes the window.
-  fn minimize(&self) -> crate::Result<()>;
+  fn minimize(&self) -> Result<()>;
 
   /// Unminimizes the window.
-  fn unminimize(&self) -> crate::Result<()>;
+  fn unminimize(&self) -> Result<()>;
 
   /// Shows the window menu.
-  fn show_menu(&self) -> crate::Result<()>;
+  fn show_menu(&self) -> Result<()>;
 
   /// Hides the window menu.
-  fn hide_menu(&self) -> crate::Result<()>;
+  fn hide_menu(&self) -> Result<()>;
 
   /// Shows the window.
-  fn show(&self) -> crate::Result<()>;
+  fn show(&self) -> Result<()>;
 
   /// Hides the window.
-  fn hide(&self) -> crate::Result<()>;
+  fn hide(&self) -> Result<()>;
 
   /// Closes the window.
-  fn close(&self) -> crate::Result<()>;
+  fn close(&self) -> Result<()>;
 
   /// Updates the hasDecorations flag.
-  fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
+  fn set_decorations(&self, decorations: bool) -> Result<()>;
 
   /// Updates the window alwaysOnTop flag.
-  fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
+  fn set_always_on_top(&self, always_on_top: bool) -> Result<()>;
 
   /// Resizes the window.
-  fn set_size(&self, size: Size) -> crate::Result<()>;
+  fn set_size(&self, size: Size) -> Result<()>;
 
   /// Updates the window min size.
-  fn set_min_size(&self, size: Option<Size>) -> crate::Result<()>;
+  fn set_min_size(&self, size: Option<Size>) -> Result<()>;
 
   /// Updates the window max size.
-  fn set_max_size(&self, size: Option<Size>) -> crate::Result<()>;
+  fn set_max_size(&self, size: Option<Size>) -> Result<()>;
 
   /// Updates the window position.
-  fn set_position(&self, position: Position) -> crate::Result<()>;
+  fn set_position(&self, position: Position) -> Result<()>;
 
   /// Updates the window fullscreen state.
-  fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
+  fn set_fullscreen(&self, fullscreen: bool) -> Result<()>;
 
   /// Bring the window to front and focus.
-  fn set_focus(&self) -> crate::Result<()>;
+  fn set_focus(&self) -> Result<()>;
 
   /// Updates the window icon.
-  fn set_icon(&self, icon: WindowIcon) -> crate::Result<()>;
+  fn set_icon(&self, icon: WindowIcon) -> Result<()>;
 
   /// Whether to show the window icon in the task bar or not.
-  fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()>;
+  fn set_skip_taskbar(&self, skip: bool) -> Result<()>;
 
   /// Starts dragging the window.
-  fn start_dragging(&self) -> crate::Result<()>;
+  fn start_dragging(&self) -> Result<()>;
 
   /// Executes javascript on the window this [`Dispatch`] represents.
-  fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
+  fn eval_script<S: Into<String>>(&self, script: S) -> Result<()>;
 
   /// Applies the specified `update` to the menu item associated with the given `id`.
-  fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> crate::Result<()>;
+  fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()>;
 }

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
core/tauri/scripts/hotkey.js


+ 4 - 0
core/tauri/scripts/init.js

@@ -2,6 +2,10 @@
   if (window.location.origin.startsWith(__TEMPLATE_origin__)) {
     __RAW_freeze_prototype__
 
+    ;(function () {
+      __RAW_hotkeys__
+    })()
+
     __RAW_pattern_script__
 
     __RAW_ipc_script__

+ 11 - 0
core/tauri/src/endpoints/window.rs

@@ -99,6 +99,9 @@ pub enum WindowManagerCmd {
   // internals
   #[serde(rename = "__toggleMaximize")]
   InternalToggleMaximize,
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  #[serde(rename = "__toggleDevtools")]
+  InternalToggleDevtools,
 }
 
 impl WindowManagerCmd {
@@ -279,6 +282,14 @@ impl Cmd {
           }
         }
       }
+      #[cfg(any(debug_assertions, feature = "devtools"))]
+      WindowManagerCmd::InternalToggleDevtools => {
+        if window.is_devtools_open() {
+          window.close_devtools();
+        } else {
+          window.open_devtools();
+        }
+      }
       #[allow(unreachable_patterns)]
       _ => return Err(cmd.into_allowlist_error()),
     }

+ 31 - 0
core/tauri/src/manager.rs

@@ -880,6 +880,8 @@ impl<R: Runtime> WindowManager<R> {
       plugin_initialization_script: &'a str,
       #[raw]
       freeze_prototype: &'a str,
+      #[raw]
+      hotkeys: &'a str,
     }
 
     let bundle_script = if with_global_tauri {
@@ -894,6 +896,34 @@ impl<R: Runtime> WindowManager<R> {
       ""
     };
 
+    #[cfg(any(debug_assertions, feature = "devtools"))]
+    let hotkeys = &format!(
+      "
+      {};
+      window.hotkeys('{}', () => {{
+        window.__TAURI_INVOKE__('tauri', {{
+          __tauriModule: 'Window',
+          message: {{
+            cmd: 'manage',
+            data: {{
+              cmd: {{
+                type: '__toggleDevtools'
+              }}
+            }}
+          }}
+        }});
+      }});
+    ",
+      include_str!("../scripts/hotkey.js"),
+      if cfg!(target_os = "macos") {
+        "command+option+i"
+      } else {
+        "ctrl+shift+i"
+      }
+    );
+    #[cfg(not(any(debug_assertions, feature = "devtools")))]
+    let hotkeys = "";
+
     InitJavascript {
       origin: self.get_browser_origin(),
       pattern_script,
@@ -913,6 +943,7 @@ impl<R: Runtime> WindowManager<R> {
       event_initialization_script: &self.event_initialization_script(),
       plugin_initialization_script,
       freeze_prototype,
+      hotkeys,
     }
     .render_default(&Default::default())
     .map(|s| s.into_string())

+ 8 - 0
core/tauri/src/test/mock_runtime.rs

@@ -277,6 +277,14 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
   #[cfg(any(debug_assertions, feature = "devtools"))]
   fn open_devtools(&self) {}
 
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn close_devtools(&self) {}
+
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  fn is_devtools_open(&self) -> Result<bool> {
+    Ok(false)
+  }
+
   fn scale_factor(&self) -> Result<f64> {
     Ok(1.0)
   }

+ 68 - 0
core/tauri/src/window.rs

@@ -812,6 +812,74 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.open_devtools();
   }
 
+  /// Closes the developer tools window (Web Inspector).
+  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **macOS:** This is a private API on macOS,
+  /// so you cannot use this if your application will be published on the App Store.
+  /// - **Windows:** Unsupported.
+  ///
+  /// # Examples
+  ///
+  /// ```rust,no_run
+  /// use tauri::Manager;
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     #[cfg(debug_assertions)]
+  ///     {
+  ///       let window = app.get_window("main").unwrap();
+  ///       window.open_devtools();
+  ///       std::thread::spawn(move || {
+  ///         std::thread::sleep(std::time::Duration::from_secs(10));
+  ///         window.close_devtools();
+  ///       });
+  ///     }
+  ///     Ok(())
+  ///   });
+  /// ```
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))]
+  pub fn close_devtools(&self) {
+    self.window.dispatcher.close_devtools();
+  }
+
+  /// Checks if the developer tools window (Web Inspector) is opened.
+  /// The devtools is only enabled on debug builds or with the `devtools` feature flag.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **macOS:** This is a private API on macOS,
+  /// so you cannot use this if your application will be published on the App Store.
+  /// - **Windows:** Unsupported.
+  ///
+  /// # Examples
+  ///
+  /// ```rust,no_run
+  /// use tauri::Manager;
+  /// tauri::Builder::default()
+  ///   .setup(|app| {
+  ///     #[cfg(debug_assertions)]
+  ///     {
+  ///       let window = app.get_window("main").unwrap();
+  ///       if !window.is_devtools_open() {
+  ///         window.open_devtools();
+  ///       }
+  ///     }
+  ///     Ok(())
+  ///   });
+  /// ```
+  #[cfg(any(debug_assertions, feature = "devtools"))]
+  #[cfg_attr(doc_cfg, doc(cfg(any(debug_assertions, feature = "devtools"))))]
+  pub fn is_devtools_open(&self) -> bool {
+    self
+      .window
+      .dispatcher
+      .is_devtools_open()
+      .unwrap_or_default()
+  }
+
   // Getters
 
   /// Gets a handle to the window menu.

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä