From a5a73d1d9f15ff0482392e8d00c6cc33fa7237b5 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 27 Apr 2025 08:02:24 +1000 Subject: [PATCH] Add Content Protection feature. --- docs/src/content/docs/changelog.mdx | 3 ++- v3/examples/window/main.go | 12 +++++++++++ v3/pkg/application/webview_window.go | 10 ++++++++++ v3/pkg/application/webview_window_darwin.go | 16 +++++++++++++++ v3/pkg/application/webview_window_linux.go | 7 ++++--- v3/pkg/application/webview_window_options.go | 3 +++ v3/pkg/application/webview_window_windows.go | 11 ++++++++++ v3/pkg/application/window.go | 1 + v3/pkg/w32/user32.go | 3 ++- v3/pkg/w32/wda.go | 21 ++++++++++++++++++++ 10 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 v3/pkg/w32/wda.go diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 8dafa5bc1..96ad89d3a 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -75,13 +75,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) - Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) -- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in PR[https://github.com/wailsapp/wails/pull/3537] +- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in this [PR](https://github.com/wailsapp/wails/pull/3537) - Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134) - Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony) - Add Notification support by [@popaprozac] in [#4098](https://github.com/wailsapp/wails/pull/4098) -  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177) - Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony) - Add systray hide/show on Windows by [@leaanthony](https://github.com/leaanthony) +- Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241) ### Fixed diff --git a/v3/examples/window/main.go b/v3/examples/window/main.go index bda480ccf..31e353c41 100644 --- a/v3/examples/window/main.go +++ b/v3/examples/window/main.go @@ -118,6 +118,18 @@ func main() { windowCounter++ }) if runtime.GOOS != "linux" { + myMenu.Add("New WebviewWindow (Content Protection Enabled)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonDisabled, + ContentProtectionEnabled: true, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) myMenu.Add("New WebviewWindow (Disable Minimise)"). OnClick(func(ctx *application.Context) { app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 374c423b7..b19e74f2b 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -109,6 +109,7 @@ type ( hideMenuBar() toggleMenuBar() setMenu(menu *Menu) + setContentProtection(enabled bool) } ) @@ -486,6 +487,15 @@ func (w *WebviewWindow) SetResizable(b bool) Window { return w } +func (w *WebviewWindow) SetContentProtection(b bool) Window { + if w.impl == nil { + w.options.ContentProtectionEnabled = b + } else { + w.impl.setContentProtection(b) + } + return w +} + // Resizable returns true if the window is resizable. func (w *WebviewWindow) Resizable() bool { return !w.options.DisableResize diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index 28e693a23..c9a425c1a 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -811,6 +811,18 @@ static void setIgnoreMouseEvents(void *nsWindow, bool ignore) { [window setIgnoresMouseEvents:ignore]; } +static void setContentProtection(void *nsWindow, bool enabled) { + NSWindow *window = (__bridge NSWindow *)nsWindow; + if( ! [window respondsToSelector:@selector(setSharingType:)]) { + return; + } + + if( enabled ) { + [window setSharingType:NSWindowSharingReadOnly]; + } else { + [window setSharingType:NSWindowSharingNone]; + } + */ import "C" import ( @@ -1406,6 +1418,10 @@ func (w *macosWebviewWindow) setIgnoreMouseEvents(ignore bool) { C.setIgnoreMouseEvents(w.nsWindow, C.bool(ignore)) } +func (w *macosWebviewWindow) setContentProtection(enabled bool) { + C.setContectProtection(w.nsWindow, C.bool(enabled)) +} + func (w *macosWebviewWindow) cut() { } diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index 8c50c6117..5ace8368a 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -412,6 +412,7 @@ func (w *linuxWebviewWindow) setIgnoreMouseEvents(ignore bool) { w.ignoreMouse(w.ignoreMouseEvents) } -func (w *linuxWebviewWindow) showMenuBar() {} -func (w *linuxWebviewWindow) hideMenuBar() {} -func (w *linuxWebviewWindow) toggleMenuBar() {} +func (w *linuxWebviewWindow) showMenuBar() {} +func (w *linuxWebviewWindow) hideMenuBar() {} +func (w *linuxWebviewWindow) toggleMenuBar() {} +func (w *linuxWebviewWindow) setContentProtection(enabled bool) {} diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 04dae199e..b87843b57 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -139,6 +139,9 @@ type WebviewWindowOptions struct { // IgnoreMouseEvents will ignore mouse events in the window (Windows + Mac only) IgnoreMouseEvents bool + + // ContentProtectionEnabled specifies whether content protection is enabled, preventing screen capture and recording. + ContentProtectionEnabled bool } type RGBA struct { diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 89743b41e..e40c7c1a3 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -347,6 +347,9 @@ func (w *windowsWebviewWindow) run() { globalApplication.fatal("unable to create window") } + // Process ContentProtection + w.setContentProtection(w.parent.options.ContentProtectionEnabled) + // Ensure correct window size in case the scale factor of current screen is different from the initial one. // This could happen when using the default window position and the window launches on a secondary monitor. currentScreen, _ := w.getScreen() @@ -2086,3 +2089,11 @@ func (w *windowsWebviewWindow) hideMenuBar() { w32.SetMenu(w.hwnd, 0) } } + +func (w *windowsWebviewWindow) setContentProtection(enabled bool) { + var affinity uint32 = w32.WDA_EXCLUDEFROMCAPTURE + if !enabled { + affinity = w32.WDA_NONE + } + w32.SetWindowDisplayAffinity(w.hwnd, affinity) +} diff --git a/v3/pkg/application/window.go b/v3/pkg/application/window.go index 0d688126b..a7db78883 100644 --- a/v3/pkg/application/window.go +++ b/v3/pkg/application/window.go @@ -85,4 +85,5 @@ type Window interface { ZoomOut() ZoomReset() Window SetMenu(menu *Menu) + SetContentProtection(protection bool) Window } diff --git a/v3/pkg/w32/user32.go b/v3/pkg/w32/user32.go index 4117afd2e..bdba78c29 100644 --- a/v3/pkg/w32/user32.go +++ b/v3/pkg/w32/user32.go @@ -178,7 +178,8 @@ var ( procRedrawWindow = moduser32.NewProc("RedrawWindow") - procRegisterWindowMessageW = moduser32.NewProc("RegisterWindowMessageW") + procRegisterWindowMessageW = moduser32.NewProc("RegisterWindowMessageW") + procSetWindowDisplayAffinity = user32.NewProc("SetWindowDisplayAffinity") mainThread HANDLE ) diff --git a/v3/pkg/w32/wda.go b/v3/pkg/w32/wda.go new file mode 100644 index 000000000..0faf91a82 --- /dev/null +++ b/v3/pkg/w32/wda.go @@ -0,0 +1,21 @@ +//go:build windows + +package w32 + +const ( + WDA_NONE = 0x00000000 + WDA_MONITOR = 0x00000001 + WDA_EXCLUDEFROMCAPTURE = 0x00000011 // windows 10 2004+ +) + +func SetWindowDisplayAffinity(hwnd uintptr, affinity uint32) bool { + if affinity == WDA_EXCLUDEFROMCAPTURE && !IsWindowsVersionAtLeast(10, 0, 19041) { + // for older windows versions, use WDA_MONITOR + affinity = WDA_MONITOR + } + ret, _, _ := procSetWindowDisplayAffinity.Call( + hwnd, + uintptr(affinity), + ) + return ret != 0 +}