Prechádzať zdrojové kódy

feat: add content protection api, closes #5132 (#5513)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Amr Bashir 2 rokov pred
rodič
commit
4ab5545b7a

+ 5 - 0
.changes/content-protection-api.md

@@ -0,0 +1,5 @@
+---
+"api": "minor"
+---
+
+Added the `WindowOptions::contentProtected` option and `WebviewWindow#setContentProtected` to change it at runtime.

+ 5 - 0
.changes/content-protection-utils.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": "patch"
+---
+
+Added the `content_protected` option to the window configuration.

+ 7 - 0
.changes/content-protection.md

@@ -0,0 +1,7 @@
+---
+"tauri": "minor"
+"tauri-runtime": "minor"
+"tauri-runtime-wry": "minor"
+---
+
+Added the `content_protected` option when creating a window and `Window::set_content_protected` to change it at runtime.

+ 13 - 0
core/config-schema/schema.json

@@ -105,6 +105,7 @@
             "print": false,
             "requestUserAttention": false,
             "setAlwaysOnTop": false,
+            "setContentProtected": false,
             "setCursorGrab": false,
             "setCursorIcon": false,
             "setCursorPosition": false,
@@ -378,6 +379,7 @@
               "print": false,
               "requestUserAttention": false,
               "setAlwaysOnTop": false,
+              "setContentProtected": false,
               "setCursorGrab": false,
               "setCursorIcon": false,
               "setCursorPosition": false,
@@ -641,6 +643,11 @@
           "default": false,
           "type": "boolean"
         },
+        "contentProtected": {
+          "description": "Prevents the window contents from being captured by other apps.",
+          "default": false,
+          "type": "boolean"
+        },
         "skipTaskbar": {
           "description": "If `true`, hides the window icon from the taskbar on Windows and Linux.",
           "default": false,
@@ -1661,6 +1668,7 @@
             "print": false,
             "requestUserAttention": false,
             "setAlwaysOnTop": false,
+            "setContentProtected": false,
             "setCursorGrab": false,
             "setCursorIcon": false,
             "setCursorPosition": false,
@@ -2011,6 +2019,11 @@
           "default": false,
           "type": "boolean"
         },
+        "setContentProtected": {
+          "description": "Allows preventing the window contents from being captured by other apps.",
+          "default": false,
+          "type": "boolean"
+        },
         "setSize": {
           "description": "Allows setting the window size.",
           "default": false,

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

@@ -705,6 +705,7 @@ impl WindowBuilder for WindowBuilderWrapper {
       .decorations(config.decorations)
       .maximized(config.maximized)
       .always_on_top(config.always_on_top)
+      .content_protected(config.content_protected)
       .skip_taskbar(config.skip_taskbar)
       .theme(config.theme);
 
@@ -839,6 +840,11 @@ impl WindowBuilder for WindowBuilderWrapper {
     self
   }
 
+  fn content_protected(mut self, protected: bool) -> Self {
+    self.inner = self.inner.with_content_protection(protected);
+    self
+  }
+
   #[cfg(windows)]
   fn parent_window(mut self, parent: HWND) -> Self {
     self.inner = self.inner.with_parent_window(parent);
@@ -1059,6 +1065,7 @@ pub enum WindowMessage {
   Close,
   SetDecorations(bool),
   SetAlwaysOnTop(bool),
+  SetContentProtected(bool),
   SetSize(Size),
   SetMinSize(Option<Size>),
   SetMaxSize(Option<Size>),
@@ -1436,6 +1443,16 @@ impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
     )
   }
 
+  fn set_content_protected(&self, protected: bool) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(
+        self.window_id,
+        WindowMessage::SetContentProtected(protected),
+      ),
+    )
+  }
+
   fn set_size(&self, size: Size) -> Result<()> {
     send_user_message(
       &self.context,
@@ -2399,6 +2416,9 @@ fn handle_user_message<T: UserEvent>(
             }
             WindowMessage::SetDecorations(decorations) => window.set_decorations(decorations),
             WindowMessage::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top),
+            WindowMessage::SetContentProtected(protected) => {
+              window.set_content_protection(protected)
+            }
             WindowMessage::SetSize(size) => {
               window.set_inner_size(SizeWrapper::from(size).0);
             }

+ 3 - 0
core/tauri-runtime/src/lib.rs

@@ -628,6 +628,9 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   /// Updates the window alwaysOnTop flag.
   fn set_always_on_top(&self, always_on_top: bool) -> Result<()>;
 
+  /// Prevents the window contents from being captured by other apps.
+  fn set_content_protected(&self, protected: bool) -> Result<()>;
+
   /// Resizes the window.
   fn set_size(&self, size: Size) -> Result<()>;
 

+ 4 - 0
core/tauri-runtime/src/webview.rs

@@ -172,6 +172,10 @@ pub trait WindowBuilder: WindowBuilderBase {
   #[must_use]
   fn always_on_top(self, always_on_top: bool) -> Self;
 
+  /// Prevents the window contents from being captured by other apps.
+  #[must_use]
+  fn content_protected(self, protected: bool) -> Self;
+
   /// Sets the window icon.
   fn icon(self, icon: Icon) -> crate::Result<Self>;
 

+ 16 - 0
core/tauri-utils/src/config.rs

@@ -859,6 +859,9 @@ pub struct WindowConfig {
   /// Whether the window should always be on top of other windows.
   #[serde(default, alias = "always-on-top")]
   pub always_on_top: bool,
+  /// Prevents the window contents from being captured by other apps.
+  #[serde(default, alias = "content-protected")]
+  pub content_protected: bool,
   /// If `true`, hides the window icon from the taskbar on Windows and Linux.
   #[serde(default, alias = "skip-taskbar")]
   pub skip_taskbar: bool,
@@ -908,6 +911,7 @@ impl Default for WindowConfig {
       visible: default_visible(),
       decorations: default_decorations(),
       always_on_top: false,
+      content_protected: false,
       skip_taskbar: false,
       theme: None,
       title_bar_style: Default::default(),
@@ -1329,6 +1333,9 @@ pub struct WindowAllowlistConfig {
   /// Allows setting the always_on_top flag of the window.
   #[serde(default, alias = "set-always-on-top")]
   pub set_always_on_top: bool,
+  /// Allows preventing the window contents from being captured by other apps.
+  #[serde(default, alias = "set-content-protected")]
+  pub set_content_protected: bool,
   /// Allows setting the window size.
   #[serde(default, alias = "set-size")]
   pub set_size: bool,
@@ -1394,6 +1401,7 @@ impl Allowlist for WindowAllowlistConfig {
       close: true,
       set_decorations: true,
       set_always_on_top: true,
+      set_content_protected: false,
       set_size: true,
       set_min_size: true,
       set_max_size: true,
@@ -1444,6 +1452,12 @@ impl Allowlist for WindowAllowlistConfig {
         set_always_on_top,
         "window-set-always-on-top"
       );
+      check_feature!(
+        self,
+        features,
+        set_content_protected,
+        "window-set-content-protected"
+      );
       check_feature!(self, features, set_size, "window-set-size");
       check_feature!(self, features, set_min_size, "window-set-min-size");
       check_feature!(self, features, set_max_size, "window-set-max-size");
@@ -3040,6 +3054,7 @@ mod build {
       let visible = self.visible;
       let decorations = self.decorations;
       let always_on_top = self.always_on_top;
+      let content_protected = self.content_protected;
       let skip_taskbar = self.skip_taskbar;
       let theme = opt_lit(self.theme.as_ref());
       let title_bar_style = &self.title_bar_style;
@@ -3072,6 +3087,7 @@ mod build {
         visible,
         decorations,
         always_on_top,
+        content_protected,
         skip_taskbar,
         theme,
         title_bar_style,

+ 2 - 0
core/tauri/Cargo.toml

@@ -240,6 +240,7 @@ window-all = [
   "window-close",
   "window-set-decorations",
   "window-set-always-on-top",
+  "window-set-content-protected",
   "window-set-size",
   "window-set-min-size",
   "window-set-max-size",
@@ -270,6 +271,7 @@ window-hide = [ ]
 window-close = [ ]
 window-set-decorations = [ ]
 window-set-always-on-top = [ ]
+window-set-content-protected = [ ]
 window-set-size = [ ]
 window-set-min-size = [ ]
 window-set-max-size = [ ]

+ 1 - 0
core/tauri/build.rs

@@ -81,6 +81,7 @@ fn main() {
       "close",
       "set-decorations",
       "set-always-on-top",
+      "set-content-protected",
       "set-size",
       "set-min-size",
       "set-max-size",

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
core/tauri/scripts/bundle.global.js


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

@@ -103,6 +103,8 @@ pub enum WindowManagerCmd {
   #[cfg(window_set_always_on_top)]
   #[serde(rename_all = "camelCase")]
   SetAlwaysOnTop(bool),
+  #[cfg(window_set_content_protected)]
+  SetContentProtected(bool),
   #[cfg(window_set_size)]
   SetSize(Size),
   #[cfg(window_set_min_size)]
@@ -164,6 +166,9 @@ pub fn into_allowlist_error(variant: &str) -> crate::Error {
     "close" => crate::Error::ApiNotAllowlisted("window > close".to_string()),
     "setDecorations" => crate::Error::ApiNotAllowlisted("window > setDecorations".to_string()),
     "setAlwaysOnTop" => crate::Error::ApiNotAllowlisted("window > setAlwaysOnTop".to_string()),
+    "setContentProtected" => {
+      crate::Error::ApiNotAllowlisted("window > setContentProtected".to_string())
+    }
     "setSize" => crate::Error::ApiNotAllowlisted("window > setSize".to_string()),
     "setMinSize" => crate::Error::ApiNotAllowlisted("window > setMinSize".to_string()),
     "setMaxSize" => crate::Error::ApiNotAllowlisted("window > setMaxSize".to_string()),
@@ -299,6 +304,10 @@ impl Cmd {
       WindowManagerCmd::SetDecorations(decorations) => window.set_decorations(decorations)?,
       #[cfg(window_set_always_on_top)]
       WindowManagerCmd::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top)?,
+      #[cfg(window_set_content_protected)]
+      WindowManagerCmd::SetContentProtected(protected) => {
+        window.set_content_protected(protected)?
+      }
       #[cfg(window_set_size)]
       WindowManagerCmd::SetSize(size) => window.set_size(size)?,
       #[cfg(window_set_min_size)]

+ 1 - 0
core/tauri/src/lib.rs

@@ -134,6 +134,7 @@
 //! - **window-close**: Enables the [`close` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#close).
 //! - **window-set-decorations**: Enables the [`setDecorations` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setdecorations).
 //! - **window-set-always-on-top**: Enables the [`setAlwaysOnTop` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setalwaysontop).
+//! - **window-set-content-protected**: Enables the [`setContentProtected` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setcontentprotected).
 //! - **window-set-size**: Enables the [`setSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setsize).
 //! - **window-set-min-size**: Enables the [`setMinSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setminsize).
 //! - **window-set-max-size**: Enables the [`setMaxSize` API](https://tauri.app/en/docs/api/js/classes/window.WebviewWindow#setmaxsize).

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

@@ -250,6 +250,10 @@ impl WindowBuilder for MockWindowBuilder {
     self
   }
 
+  fn content_protected(self, protected: bool) -> Self {
+    self
+  }
+
   fn icon(self, icon: Icon) -> Result<Self> {
     Ok(self)
   }
@@ -489,6 +493,10 @@ impl<T: UserEvent> Dispatch<T> for MockDispatcher {
     Ok(())
   }
 
+  fn set_content_protected(&self, protected: bool) -> Result<()> {
+    Ok(())
+  }
+
   fn set_size(&self, size: Size) -> Result<()> {
     Ok(())
   }

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

@@ -398,6 +398,13 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
     self
   }
 
+  /// Whether the window should always be on top of other windows.
+  #[must_use]
+  pub fn content_protected(mut self, protected: bool) -> Self {
+    self.window_builder = self.window_builder.content_protected(protected);
+    self
+  }
+
   /// Sets the window icon.
   pub fn icon(mut self, icon: Icon) -> crate::Result<Self> {
     self.window_builder = self.window_builder.icon(icon.try_into()?)?;
@@ -1125,6 +1132,15 @@ impl<R: Runtime> Window<R> {
       .map_err(Into::into)
   }
 
+  /// Prevents the window contents from being captured by other apps.
+  pub fn set_content_protected(&self, protected: bool) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .set_content_protected(protected)
+      .map_err(Into::into)
+  }
+
   /// Resizes this window.
   pub fn set_size<S: Into<Size>>(&self, size: S) -> crate::Result<()> {
     self

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
examples/api/dist/assets/index.js


+ 2 - 1
examples/api/src-tauri/src/lib.rs

@@ -65,7 +65,8 @@ impl AppBuilder {
           .user_agent("Tauri API")
           .title("Tauri API Validation")
           .inner_size(1000., 800.)
-          .min_inner_size(600., 400.);
+          .min_inner_size(600., 400.)
+          .content_protected(true);
 
         #[cfg(target_os = "windows")]
         {

+ 6 - 0
examples/api/src/views/Window.svelte

@@ -66,6 +66,7 @@
   let maximized = false
   let decorations = true
   let alwaysOnTop = false
+  let contentProtected = true
   let fullscreen = false
   let width = null
   let height = null
@@ -182,6 +183,7 @@
     : windowMap[selectedWindow]?.unmaximize()
   $: windowMap[selectedWindow]?.setDecorations(decorations)
   $: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop)
+  $: windowMap[selectedWindow]?.setContentProtected(contentProtected)
   $: windowMap[selectedWindow]?.setFullscreen(fullscreen)
 
   $: width &&
@@ -286,6 +288,10 @@
         Always on top
         <input type="checkbox" bind:checked={alwaysOnTop} />
       </label>
+      <label>
+        Content protected
+        <input type="checkbox" bind:checked={contentProtected} />
+      </label>
       <label>
         Fullscreen
         <input type="checkbox" bind:checked={fullscreen} />

+ 33 - 0
tooling/api/src/window.ts

@@ -28,6 +28,7 @@
  *         "close": true,
  *         "setDecorations": true,
  *         "setAlwaysOnTop": true,
+ *         "setContentProtected": true,
  *         "setSize": true,
  *         "setMinSize": true,
  *         "setMaxSize": true,
@@ -1142,6 +1143,34 @@ class WindowManager extends WebviewWindowHandle {
     })
   }
 
+  /**
+   * Prevents the window contents from being captured by other apps.
+   * @example
+   * ```typescript
+   * import { appWindow } from '@tauri-apps/api/window';
+   * await appWindow.setContentProtected(true);
+   * ```
+   *
+   * @returns A promise indicating the success or failure of the operation.
+   *
+   * @since 1.2.0
+   */
+  async setContentProtected(protected_: boolean): Promise<void> {
+    return invokeTauriCommand({
+      __tauriModule: 'Window',
+      message: {
+        cmd: 'manage',
+        data: {
+          label: this.label,
+          cmd: {
+            type: 'setContentProtected',
+            payload: protected_
+          }
+        }
+      }
+    })
+  }
+
   /**
    * Resizes the window with a new inner size.
    * @example
@@ -2073,6 +2102,10 @@ interface WindowOptions {
   decorations?: boolean
   /** Whether the window should always be on top of other windows or not. */
   alwaysOnTop?: boolean
+  /** Prevents the window contents from being captured by other apps. */
+  contentProtected?: boolean
+  /** Prevents the window contents from being captured by other apps. */
+  contentPortected?: boolean
   /** Whether or not the window icon should be added to the taskbar. */
   skipTaskbar?: boolean
   /**

+ 13 - 0
tooling/cli/schema.json

@@ -105,6 +105,7 @@
             "print": false,
             "requestUserAttention": false,
             "setAlwaysOnTop": false,
+            "setContentProtected": false,
             "setCursorGrab": false,
             "setCursorIcon": false,
             "setCursorPosition": false,
@@ -378,6 +379,7 @@
               "print": false,
               "requestUserAttention": false,
               "setAlwaysOnTop": false,
+              "setContentProtected": false,
               "setCursorGrab": false,
               "setCursorIcon": false,
               "setCursorPosition": false,
@@ -641,6 +643,11 @@
           "default": false,
           "type": "boolean"
         },
+        "contentProtected": {
+          "description": "Prevents the window contents from being captured by other apps.",
+          "default": false,
+          "type": "boolean"
+        },
         "skipTaskbar": {
           "description": "If `true`, hides the window icon from the taskbar on Windows and Linux.",
           "default": false,
@@ -1661,6 +1668,7 @@
             "print": false,
             "requestUserAttention": false,
             "setAlwaysOnTop": false,
+            "setContentProtected": false,
             "setCursorGrab": false,
             "setCursorIcon": false,
             "setCursorPosition": false,
@@ -2011,6 +2019,11 @@
           "default": false,
           "type": "boolean"
         },
+        "setContentProtected": {
+          "description": "Allows preventing the window contents from being captured by other apps.",
+          "default": false,
+          "type": "boolean"
+        },
         "setSize": {
           "description": "Allows setting the window size.",
           "default": false,

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov