瀏覽代碼

feat: add `Window::set_enabled` and `Window::is_enabled` (#11154)

* feat: add `Window::set_enabled` and `Window::is_enabled`

closes #6660

* license headers

* fix build

* fix mobile and macos

* fix macos

* again

* unsafe

* fix macos is_enabled

* update example

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
Amr Bashir 10 月之前
父節點
當前提交
de7414aab9

+ 5 - 0
.changes/window-set-enabled-api.md

@@ -0,0 +1,5 @@
+---
+"@tauri-apps/api": "patch:feat"
+---
+
+Add `Window::setEnabled` and `Window::isEnabled` methods

+ 7 - 0
.changes/window-set-enabled.md

@@ -0,0 +1,7 @@
+---
+"tauri": "patch:feat"
+"tauri-runtime": "patch:feat"
+"tauri-runtime-wry": "patch:feat"
+---
+
+Add `Window::set_enabled` and `Window::is_enabled` methods

+ 1 - 1
Cargo.toml

@@ -30,8 +30,8 @@ members = [
 
   # examples
   "examples/file-associations/src-tauri",
-  "examples/api/src-tauri",
   "examples/resources/src-tauri",
+  "examples/api/src-tauri",
   "examples/api/src-tauri/tauri-plugin-sample",
 ]
 resolver = "2"

+ 3 - 0
crates/tauri-runtime-wry/Cargo.toml

@@ -49,9 +49,12 @@ percent-encoding = "2.1"
 objc2 = "0.5.2"
 objc2-foundation = { version = "0.2.2", features = [] }
 objc2-app-kit = { version = "0.2.2", features = [
+  "block2",
+  "NSApplication",
   "NSResponder",
   "NSView",
   "NSWindow",
+  "NSGraphics",
 ] }
 
 [target."cfg(target_os = \"android\")".dependencies]

+ 24 - 84
crates/tauri-runtime-wry/src/lib.rs

@@ -127,9 +127,11 @@ type IpcHandler = dyn Fn(Request<String>) + 'static;
   target_os = "openbsd"
 ))]
 mod undecorated_resizing;
-
 mod webview;
+mod window;
+
 pub use webview::Webview;
+use window::WindowExt as _;
 
 #[derive(Debug)]
 pub struct WebContext {
@@ -1166,9 +1168,11 @@ pub enum WindowMessage {
   GtkBox(Sender<GtkBox>),
   RawWindowHandle(Sender<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>>),
   Theme(Sender<Theme>),
+  IsEnabled(Sender<bool>),
   // Setters
   Center,
   RequestUserAttention(Option<UserAttentionTypeWrapper>),
+  SetEnabled(bool),
   SetResizable(bool),
   SetMaximizable(bool),
   SetMinimizable(bool),
@@ -1700,6 +1704,10 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
     window_getter!(self, WindowMessage::Theme)
   }
 
+  fn is_enabled(&self) -> Result<bool> {
+    window_getter!(self, WindowMessage::IsEnabled)
+  }
+
   #[cfg(any(
     target_os = "linux",
     target_os = "dragonfly",
@@ -1775,6 +1783,13 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
     )
   }
 
+  fn set_enabled(&self, enabled: bool) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::SetEnabled(enabled)),
+    )
+  }
+
   fn set_maximizable(&self, maximizable: bool) -> Result<()> {
     send_user_message(
       &self.context,
@@ -2865,40 +2880,10 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::Theme(tx) => {
             tx.send(map_theme(&window.theme())).unwrap();
           }
-          // Setters
-          WindowMessage::Center => {
-            #[cfg(not(target_os = "macos"))]
-            if let Some(monitor) = window.current_monitor() {
-              #[allow(unused_mut)]
-              let mut window_size = window.outer_size();
-              #[cfg(windows)]
-              if window.is_decorated() {
-                use windows::Win32::Foundation::RECT;
-                use windows::Win32::Graphics::Dwm::{
-                  DwmGetWindowAttribute, DWMWA_EXTENDED_FRAME_BOUNDS,
-                };
-                let mut rect = RECT::default();
-                let result = unsafe {
-                  DwmGetWindowAttribute(
-                    HWND(window.hwnd() as _),
-                    DWMWA_EXTENDED_FRAME_BOUNDS,
-                    &mut rect as *mut _ as *mut _,
-                    std::mem::size_of::<RECT>() as u32,
-                  )
-                };
-                if result.is_ok() {
-                  window_size.height = (rect.bottom - rect.top) as u32;
-                }
-              }
-              window.set_outer_position(calculate_window_center_position(window_size, monitor));
-            }
+          WindowMessage::IsEnabled(tx) => tx.send(window.is_enabled()).unwrap(),
 
-            #[cfg(target_os = "macos")]
-            {
-              let ns_window: &objc2_app_kit::NSWindow = unsafe { &*window.ns_window().cast() };
-              ns_window.center();
-            }
-          }
+          // Setters
+          WindowMessage::Center => window.center(),
           WindowMessage::RequestUserAttention(request_type) => {
             window.request_user_attention(request_type.map(|r| r.0));
           }
@@ -2919,6 +2904,7 @@ fn handle_user_message<T: UserEvent>(
           WindowMessage::Unmaximize => window.set_maximized(false),
           WindowMessage::Minimize => window.set_minimized(true),
           WindowMessage::Unminimize => window.set_minimized(false),
+          WindowMessage::SetEnabled(enabled) => window.set_enabled(enabled),
           WindowMessage::Show => window.set_visible(true),
           WindowMessage::Hide => window.set_visible(false),
           WindowMessage::Close => {
@@ -3421,7 +3407,7 @@ fn handle_user_message<T: UserEvent>(
         let surface = if is_window_transparent {
           if let Ok(context) = softbuffer::Context::new(window.clone()) {
             if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {
-              clear_window_surface(&window, &mut surface);
+              window.clear_surface(&mut surface);
               Some(surface)
             } else {
               None
@@ -3499,7 +3485,7 @@ fn handle_event_loop<T: UserEvent>(
           if window.is_window_transparent {
             if let Some(surface) = &mut window.surface {
               if let Some(window) = &window.inner {
-                clear_window_surface(window, surface)
+                window.clear_surface(surface);
               }
             }
           }
@@ -3842,7 +3828,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
           }
         }
       }
-      let position = calculate_window_center_position(window_size, monitor);
+      let position = window::calculate_window_center_position(window_size, monitor);
       let logical_position = position.to_logical::<f64>(scale_factor);
       window_builder = window_builder.position(logical_position.x, logical_position.y);
     }
@@ -3914,7 +3900,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
   let surface = if is_window_transparent {
     if let Ok(context) = softbuffer::Context::new(window.clone()) {
       if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) {
-        clear_window_surface(&window, &mut surface);
+        window.clear_surface(&mut surface);
         Some(surface)
       } else {
         None
@@ -4398,49 +4384,3 @@ fn inner_size(
 ) -> TaoPhysicalSize<u32> {
   window.inner_size()
 }
-
-fn calculate_window_center_position(
-  window_size: TaoPhysicalSize<u32>,
-  target_monitor: MonitorHandle,
-) -> TaoPhysicalPosition<i32> {
-  #[cfg(windows)]
-  {
-    use tao::platform::windows::MonitorHandleExtWindows;
-    use windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO};
-    let mut monitor_info = MONITORINFO {
-      cbSize: std::mem::size_of::<MONITORINFO>() as u32,
-      ..Default::default()
-    };
-    let status =
-      unsafe { GetMonitorInfoW(HMONITOR(target_monitor.hmonitor() as _), &mut monitor_info) };
-    if status.into() {
-      let available_width = monitor_info.rcWork.right - monitor_info.rcWork.left;
-      let available_height = monitor_info.rcWork.bottom - monitor_info.rcWork.top;
-      let x = (available_width - window_size.width as i32) / 2 + monitor_info.rcWork.left;
-      let y = (available_height - window_size.height as i32) / 2 + monitor_info.rcWork.top;
-      return TaoPhysicalPosition::new(x, y);
-    }
-  }
-  let screen_size = target_monitor.size();
-  let monitor_pos = target_monitor.position();
-  let x = (screen_size.width as i32 - window_size.width as i32) / 2 + monitor_pos.x;
-  let y = (screen_size.height as i32 - window_size.height as i32) / 2 + monitor_pos.y;
-  TaoPhysicalPosition::new(x, y)
-}
-
-#[cfg(windows)]
-fn clear_window_surface(
-  window: &Window,
-  surface: &mut softbuffer::Surface<Arc<Window>, Arc<Window>>,
-) {
-  let size = window.inner_size();
-  if let (Some(width), Some(height)) = (
-    std::num::NonZeroU32::new(size.width),
-    std::num::NonZeroU32::new(size.height),
-  ) {
-    surface.resize(width, height).unwrap();
-    let mut buffer = surface.buffer_mut().unwrap();
-    buffer.fill(0);
-    let _ = buffer.present();
-  }
-}

+ 31 - 0
crates/tauri-runtime-wry/src/window/linux.rs

@@ -0,0 +1,31 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use gtk::prelude::*;
+#[cfg(any(
+  target_os = "linux",
+  target_os = "dragonfly",
+  target_os = "freebsd",
+  target_os = "netbsd",
+  target_os = "openbsd"
+))]
+use tao::platform::unix::WindowExtUnix;
+
+impl super::WindowExt for tao::window::Window {
+  fn set_enabled(&self, enabled: bool) {
+    self.gtk_window().set_sensitive(enabled);
+  }
+
+  fn is_enabled(&self) -> bool {
+    self.gtk_window().is_sensitive()
+  }
+
+  fn center(&self) {
+    if let Some(monitor) = self.current_monitor() {
+      let window_size = self.outer_size();
+      let new_pos = super::calculate_window_center_position(window_size, monitor);
+      self.set_outer_position(new_pos);
+    }
+  }
+}

+ 43 - 0
crates/tauri-runtime-wry/src/window/macos.rs

@@ -0,0 +1,43 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use objc2_app_kit::{NSBackingStoreType, NSWindow, NSWindowStyleMask};
+use objc2_foundation::MainThreadMarker;
+use tao::platform::macos::WindowExtMacOS;
+
+impl super::WindowExt for tao::window::Window {
+  // based on electron implementation
+  // https://github.com/electron/electron/blob/15db63e26df3e3d59ce6281f030624f746518511/shell/browser/native_window_mac.mm#L474
+  fn set_enabled(&self, enabled: bool) {
+    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };
+    if !enabled {
+      let frame = ns_window.frame();
+      let mtm = MainThreadMarker::new()
+        .expect("`Window::set_enabled` can only be called on the main thread");
+      let sheet = unsafe {
+        NSWindow::initWithContentRect_styleMask_backing_defer(
+          mtm.alloc(),
+          frame,
+          NSWindowStyleMask::Titled,
+          NSBackingStoreType::NSBackingStoreBuffered,
+          false,
+        )
+      };
+      unsafe { sheet.setAlphaValue(0.5) };
+      unsafe { ns_window.beginSheet_completionHandler(&sheet, None) };
+    } else if let Some(attached) = unsafe { ns_window.attachedSheet() } {
+      unsafe { ns_window.endSheet(&attached) };
+    }
+  }
+
+  fn is_enabled(&self) -> bool {
+    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };
+    unsafe { ns_window.attachedSheet() }.is_none()
+  }
+
+  fn center(&self) {
+    let ns_window: &NSWindow = unsafe { &*self.ns_window().cast() };
+    ns_window.center();
+  }
+}

+ 88 - 0
crates/tauri-runtime-wry/src/window/mod.rs

@@ -0,0 +1,88 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#[cfg(any(
+  target_os = "linux",
+  target_os = "dragonfly",
+  target_os = "freebsd",
+  target_os = "netbsd",
+  target_os = "openbsd"
+))]
+mod linux;
+#[cfg(target_os = "macos")]
+mod macos;
+#[cfg(windows)]
+mod windows;
+
+pub trait WindowExt {
+  /// Enable or disable the window
+  ///
+  /// ## Platform-specific:
+  ///
+  /// - **Android / iOS**: Unsupported.
+  fn set_enabled(&self, enabled: bool);
+
+  /// Whether the window is enabled or disabled.
+  ///
+  /// ## Platform-specific:
+  ///
+  /// - **Android / iOS**: Unsupported, always returns `true`.
+  fn is_enabled(&self) -> bool;
+
+  /// Center the window
+  ///
+  /// ## Platform-specific:
+  ///
+  /// - **Android / iOS**: Unsupported.
+  fn center(&self) {}
+
+  /// Clears the window sufrace. i.e make it it transparent.
+  #[cfg(windows)]
+  fn clear_surface(
+    &self,
+    surface: &mut softbuffer::Surface<
+      std::sync::Arc<tao::window::Window>,
+      std::sync::Arc<tao::window::Window>,
+    >,
+  );
+}
+
+#[cfg(mobile)]
+impl WindowExt for tao::window::Window {
+  fn set_enabled(&self, _: bool) {}
+  fn is_enabled(&self) -> bool {
+    true
+  }
+}
+
+pub fn calculate_window_center_position(
+  window_size: tao::dpi::PhysicalSize<u32>,
+  target_monitor: tao::monitor::MonitorHandle,
+) -> tao::dpi::PhysicalPosition<i32> {
+  #[cfg(windows)]
+  {
+    use ::windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO};
+    use tao::platform::windows::MonitorHandleExtWindows;
+
+    let mut monitor_info = MONITORINFO {
+      cbSize: std::mem::size_of::<MONITORINFO>() as u32,
+      ..Default::default()
+    };
+    let hmonitor = target_monitor.hmonitor();
+    let status = unsafe { GetMonitorInfoW(HMONITOR(hmonitor as _), &mut monitor_info) };
+    if status.into() {
+      let available_width = monitor_info.rcWork.right - monitor_info.rcWork.left;
+      let available_height = monitor_info.rcWork.bottom - monitor_info.rcWork.top;
+      let x = (available_width - window_size.width as i32) / 2 + monitor_info.rcWork.left;
+      let y = (available_height - window_size.height as i32) / 2 + monitor_info.rcWork.top;
+      return tao::dpi::PhysicalPosition::new(x, y);
+    }
+  }
+
+  let screen_size = target_monitor.size();
+  let monitor_pos = target_monitor.position();
+  let x = (screen_size.width as i32 - window_size.width as i32) / 2 + monitor_pos.x;
+  let y = (screen_size.height as i32 - window_size.height as i32) / 2 + monitor_pos.y;
+  tao::dpi::PhysicalPosition::new(x, y)
+}

+ 64 - 0
crates/tauri-runtime-wry/src/window/windows.rs

@@ -0,0 +1,64 @@
+// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use windows::Win32::{
+  Foundation::{HWND, RECT},
+  Graphics::Dwm::{DwmGetWindowAttribute, DWMWA_EXTENDED_FRAME_BOUNDS},
+  UI::Input::KeyboardAndMouse::{EnableWindow, IsWindowEnabled},
+};
+
+use tao::platform::windows::WindowExtWindows;
+
+impl super::WindowExt for tao::window::Window {
+  fn set_enabled(&self, enabled: bool) {
+    let _ = unsafe { EnableWindow(HWND(self.hwnd() as _), enabled) };
+  }
+
+  fn is_enabled(&self) -> bool {
+    unsafe { IsWindowEnabled(HWND(self.hwnd() as _)) }.as_bool()
+  }
+
+  fn center(&self) {
+    if let Some(monitor) = self.current_monitor() {
+      let mut window_size = self.outer_size();
+
+      if self.is_decorated() {
+        let mut rect = RECT::default();
+        let result = unsafe {
+          DwmGetWindowAttribute(
+            HWND(self.hwnd() as _),
+            DWMWA_EXTENDED_FRAME_BOUNDS,
+            &mut rect as *mut _ as *mut _,
+            std::mem::size_of::<RECT>() as u32,
+          )
+        };
+        if result.is_ok() {
+          window_size.height = (rect.bottom - rect.top) as u32;
+        }
+      }
+
+      let new_pos = super::calculate_window_center_position(window_size, monitor);
+      self.set_outer_position(new_pos);
+    }
+  }
+
+  fn clear_surface(
+    &self,
+    surface: &mut softbuffer::Surface<
+      std::sync::Arc<tao::window::Window>,
+      std::sync::Arc<tao::window::Window>,
+    >,
+  ) {
+    let size = self.inner_size();
+    if let (Some(width), Some(height)) = (
+      std::num::NonZeroU32::new(size.width),
+      std::num::NonZeroU32::new(size.height),
+    ) {
+      surface.resize(width, height).unwrap();
+      let mut buffer = surface.buffer_mut().unwrap();
+      buffer.fill(0);
+      let _ = buffer.present();
+    }
+  }
+}

+ 11 - 0
crates/tauri-runtime/src/lib.rs

@@ -603,6 +603,10 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
 
   /// Gets the window's current visibility state.
   fn is_visible(&self) -> Result<bool>;
+
+  /// Whether the window is enabled or disable.
+  fn is_enabled(&self) -> Result<bool>;
+
   /// Gets the window's current title.
   fn title(&self) -> Result<String>;
 
@@ -676,6 +680,13 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
   /// Updates the window resizable flag.
   fn set_resizable(&self, resizable: bool) -> Result<()>;
 
+  /// Enable or disable the window.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **Android / iOS**: Unsupported.
+  fn set_enabled(&self, enabled: bool) -> Result<()>;
+
   /// Updates the window's native maximize button state.
   ///
   /// ## Platform-specific

+ 2 - 0
crates/tauri/build.rs

@@ -57,6 +57,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
       ("is_minimizable", true),
       ("is_closable", true),
       ("is_visible", true),
+      ("is_enabled", true),
       ("title", true),
       ("current_monitor", true),
       ("primary_monitor", true),
@@ -67,6 +68,7 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
       // setters
       ("center", false),
       ("request_user_attention", false),
+      ("set_enabled", false),
       ("set_resizable", false),
       ("set_maximizable", false),
       ("set_minimizable", false),

+ 0 - 52
crates/tauri/permissions/webview/autogenerated/reference.md

@@ -123,32 +123,6 @@ Denies the get_all_webviews command without any pre-configured scope.
 <tr>
 <td>
 
-`core:webview:allow-hide-webview`
-
-</td>
-<td>
-
-Enables the hide_webview command without any pre-configured scope.
-
-</td>
-</tr>
-
-<tr>
-<td>
-
-`core:webview:deny-hide-webview`
-
-</td>
-<td>
-
-Denies the hide_webview command without any pre-configured scope.
-
-</td>
-</tr>
-
-<tr>
-<td>
-
 `core:webview:allow-internal-toggle-devtools`
 
 </td>
@@ -331,32 +305,6 @@ Denies the set_webview_zoom command without any pre-configured scope.
 <tr>
 <td>
 
-`core:webview:allow-show-webview`
-
-</td>
-<td>
-
-Enables the show_webview command without any pre-configured scope.
-
-</td>
-</tr>
-
-<tr>
-<td>
-
-`core:webview:deny-show-webview`
-
-</td>
-<td>
-
-Denies the show_webview command without any pre-configured scope.
-
-</td>
-</tr>
-
-<tr>
-<td>
-
 `core:webview:allow-webview-close`
 
 </td>

+ 53 - 0
crates/tauri/permissions/window/autogenerated/reference.md

@@ -18,6 +18,7 @@ Default permissions for the plugin.
 - `allow-is-minimizable`
 - `allow-is-closable`
 - `allow-is-visible`
+- `allow-is-enabled`
 - `allow-title`
 - `allow-current-monitor`
 - `allow-primary-monitor`
@@ -403,6 +404,32 @@ Denies the is_decorated command without any pre-configured scope.
 <tr>
 <td>
 
+`core:window:allow-is-enabled`
+
+</td>
+<td>
+
+Enables the is_enabled command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
+`core:window:deny-is-enabled`
+
+</td>
+<td>
+
+Denies the is_enabled command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
 `core:window:allow-is-focused`
 
 </td>
@@ -1079,6 +1106,32 @@ Denies the set_effects command without any pre-configured scope.
 <tr>
 <td>
 
+`core:window:allow-set-enabled`
+
+</td>
+<td>
+
+Enables the set_enabled command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
+`core:window:deny-set-enabled`
+
+</td>
+<td>
+
+Denies the set_enabled command without any pre-configured scope.
+
+</td>
+</tr>
+
+<tr>
+<td>
+
 `core:window:allow-set-focus`
 
 </td>

File diff suppressed because it is too large
+ 0 - 0
crates/tauri/scripts/bundle.global.js


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

@@ -971,6 +971,14 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
   fn set_theme(&self, theme: Option<Theme>) -> Result<()> {
     Ok(())
   }
+
+  fn set_enabled(&self, enabled: bool) -> Result<()> {
+    Ok(())
+  }
+
+  fn is_enabled(&self) -> Result<bool> {
+    Ok(true)
+  }
 }
 
 #[derive(Debug, Clone)]

+ 10 - 0
crates/tauri/src/webview/webview_window.rs

@@ -1157,6 +1157,11 @@ impl<R: Runtime> WebviewWindow<R> {
     self.window.is_resizable()
   }
 
+  /// Whether the window is enabled or disabled.
+  pub fn is_enabled(&self) -> crate::Result<bool> {
+    self.webview.window().is_enabled()
+  }
+
   /// Gets the window's native maximize button state
   ///
   /// ## Platform-specific
@@ -1322,6 +1327,11 @@ impl<R: Runtime> WebviewWindow<R> {
     self.window.set_resizable(resizable)
   }
 
+  /// Enable or disable the window.
+  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
+    self.webview.window().set_enabled(enabled)
+  }
+
   /// Determines if this window's native maximize button should be enabled.
   /// If resizable is set to false, this setting is ignored.
   ///

+ 14 - 0
crates/tauri/src/window/mod.rs

@@ -1370,6 +1370,11 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.is_resizable().map_err(Into::into)
   }
 
+  /// Whether the window is enabled or disabled.
+  pub fn is_enabled(&self) -> crate::Result<bool> {
+    self.window.dispatcher.is_enabled().map_err(Into::into)
+  }
+
   /// Gets the window's native maximize button state
   ///
   /// ## Platform-specific
@@ -1650,6 +1655,15 @@ impl<R: Runtime> Window<R> {
       .map_err(Into::into)
   }
 
+  /// Enable or disable the window.
+  pub fn set_enabled(&self, enabled: bool) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_enabled(enabled)
+      .map_err(Into::into)
+  }
+
   /// Maximizes this window.
   pub fn maximize(&self) -> crate::Result<()> {
     self.window.dispatcher.maximize().map_err(Into::into)

+ 4 - 0
crates/tauri/src/window/plugin.rs

@@ -92,6 +92,7 @@ mod desktop_commands {
   getter!(is_minimizable, bool);
   getter!(is_closable, bool);
   getter!(is_visible, bool);
+  getter!(is_enabled, bool);
   getter!(title, String);
   getter!(current_monitor, Option<Monitor>);
   getter!(primary_monitor, Option<Monitor>);
@@ -139,6 +140,7 @@ mod desktop_commands {
   setter!(set_title_bar_style, TitleBarStyle);
   setter!(set_size_constraints, WindowSizeConstraints);
   setter!(set_theme, Option<Theme>);
+  setter!(set_enabled, bool);
 
   #[command(root = "crate")]
   pub async fn set_icon<R: Runtime>(
@@ -240,6 +242,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::is_minimizable,
             desktop_commands::is_closable,
             desktop_commands::is_visible,
+            desktop_commands::is_enabled,
             desktop_commands::title,
             desktop_commands::current_monitor,
             desktop_commands::primary_monitor,
@@ -276,6 +279,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
             desktop_commands::set_position,
             desktop_commands::set_fullscreen,
             desktop_commands::set_focus,
+            desktop_commands::set_enabled,
             desktop_commands::set_skip_taskbar,
             desktop_commands::set_cursor_grab,
             desktop_commands::set_cursor_visible,

+ 2 - 0
examples/api/src-tauri/capabilities/run-app.json

@@ -4,6 +4,8 @@
   "description": "permissions to run the app",
   "windows": ["main", "main-*"],
   "permissions": [
+    "core:window:allow-is-enabled",
+    "core:window:allow-set-enabled",
     {
       "identifier": "allow-log-operation",
       "allow": [

+ 45 - 14
examples/api/src/views/Window.svelte

@@ -138,16 +138,42 @@
 
   let windowIconPath
 
-  function setTitle_() {
+  function setTitle() {
     webviewMap[selectedWebview].setTitle(windowTitle)
   }
 
-  function hide_() {
-    webviewMap[selectedWebview].hide()
-    setTimeout(webviewMap[selectedWebview].show, 2000)
+  async function hide() {
+    let visible = await webviewMap[selectedWebview].isVisible()
+    onMessage('window is ' + (visible ? 'visible' : 'invisible'))
+    await webviewMap[selectedWebview].hide()
+
+    setTimeout(async () => {
+      visible = await webviewMap[selectedWebview].isVisible()
+      onMessage('window is ' + (visible ? 'visible' : 'invisible'))
+
+      await webviewMap[selectedWebview].show()
+      visible = await webviewMap[selectedWebview].isVisible()
+      onMessage('window is ' + (visible ? 'visible' : 'invisible'))
+    }, 2000)
+  }
+
+  async function disable() {
+    let enabled = await webviewMap[selectedWebview].isEnabled()
+    onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
+
+    await webviewMap[selectedWebview].setEnabled(false)
+
+    setTimeout(async () => {
+      enabled = await webviewMap[selectedWebview].isEnabled()
+      onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
+
+      await webviewMap[selectedWebview].setEnabled(true)
+      enabled = await webviewMap[selectedWebview].isEnabled()
+      onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
+    }, 2000)
   }
 
-  function minimize_() {
+  function minimize() {
     webviewMap[selectedWebview].minimize()
     setTimeout(webviewMap[selectedWebview].unminimize, 2000)
   }
@@ -200,7 +226,7 @@
     resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
   }
 
-  async function requestUserAttention_() {
+  async function requestUserAttention() {
     await webviewMap[selectedWebview].minimize()
     await webviewMap[selectedWebview].requestUserAttention(
       UserAttentionType.Critical
@@ -363,7 +389,7 @@
       </div>
       <div class="grid gap-1 grow">
         <h4 class="my-2">Set Window Title</h4>
-        <form class="flex gap-2" on:submit|preventDefault={setTitle_}>
+        <form class="flex gap-2" on:submit|preventDefault={setTitle}>
           <input class="input flex-1 min-w-10" bind:value={windowTitle} />
           <button class="btn" type="submit">Set</button>
         </form>
@@ -380,20 +406,23 @@
       <button
         class="btn"
         title="Unminimizes after 2 seconds"
-        on:click={minimize_}
+        on:click={minimize}
       >
         Minimize
       </button>
+      <button class="btn" title="Visible again after 2 seconds" on:click={hide}>
+        Hide
+      </button>
       <button
         class="btn"
-        title="Visible again after 2 seconds"
-        on:click={hide_}
+        title="Enabled again after 2 seconds"
+        on:click={disable}
       >
-        Hide
+        Disable
       </button>
       <button
         class="btn"
-        on:click={requestUserAttention_}
+        on:click={requestUserAttention}
         title="Minimizes the window, requests attention for 3s and then resets it"
         >Request attention</button
       >
@@ -531,14 +560,16 @@
           Inner Logical Size
         </div>
         <span>Width: {innerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
-        <span>Height: {innerSize.toLogical(scaleFactor).height.toFixed(3)}</span>
+        <span>Height: {innerSize.toLogical(scaleFactor).height.toFixed(3)}</span
+        >
       </div>
       <div>
         <div class="text-accent dark:text-darkAccent font-700 m-block-1">
           Outer Logical Size
         </div>
         <span>Width: {outerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
-        <span>Height: {outerSize.toLogical(scaleFactor).height.toFixed(3)}</span>
+        <span>Height: {outerSize.toLogical(scaleFactor).height.toFixed(3)}</span
+        >
       </div>
       <div>
         <div class="text-accent dark:text-darkAccent font-700 m-block-1">

+ 37 - 0
packages/api/src/window.ts

@@ -870,6 +870,43 @@ class Window {
     })
   }
 
+  /**
+   * Enable or disable the window.
+   * @example
+   * ```typescript
+   * import { getCurrentWindow } from '@tauri-apps/api/window';
+   * await getCurrentWindow().setEnabled(false);
+   * ```
+   *
+   * @returns A promise indicating the success or failure of the operation.
+   *
+   * @since 2.0.0
+   */
+  async setEnabled(enabled: boolean): Promise<void> {
+    return invoke('plugin:window|set_enabled', {
+      label: this.label,
+      value: enabled
+    })
+  }
+
+  /**
+   * Whether the window is enabled or disabled.
+   * @example
+   * ```typescript
+   * import { getCurrentWindow } from '@tauri-apps/api/window';
+   * await getCurrentWindow().setEnabled(false);
+   * ```
+   *
+   * @returns A promise indicating the success or failure of the operation.
+   *
+   * @since 2.0.0
+   */
+  async isEnabled(): Promise<boolean> {
+    return invoke('plugin:window|is_enabled', {
+      label: this.label
+    })
+  }
+
   /**
    * Sets whether the window's native maximize button is enabled or not.
    * If resizable is set to false, this setting is ignored.

Some files were not shown because too many files changed in this diff