{"slug": "hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn", "title": "Hiding an Electron window from screen share: 4 things setContentProtection doesn't tell you", "summary": "The team behind Natively, a desktop AI meeting assistant, documented four gaps in Electron's setContentProtection API that must be handled to keep an overlay window out of screen shares and recordings. Their approach applies protection across every live window, dedupes repeated calls to avoid Windows DWM churn, reasserts the macOS sharingType after app.dock.hide() resets it, and syncs skipTaskbar on Windows so the taskbar button doesn't leak into full-screen shares. The code ships in Natively's open-source repository.", "body_md": "[Natively](https://heynatively.com) is a desktop AI assistant for meetings and interviews. It listens to the call, transcribes it, and shows answers and notes in a small overlay. People use it while they share their screen, so the overlay has an obvious requirement: when the user turns on Undetectable mode, the overlay must not appear in the share or the recording.\n\nElectron gives you one line for this:\n\n```\nwin.setContentProtection(true);\n```\n\nOn macOS it sets the window's `sharingType` to `NSWindowSharingNone`. On Windows it calls `SetWindowDisplayAffinity` with `WDA_EXCLUDEFROMCAPTURE` (Windows 10 2004 and later). That line gets you most of the way. Here is what it took to get the rest, from code that ships in Natively today.\n\nAn \"overlay\" in a real app is several windows: the answer panel, a pill, a toggle, a popover catcher, sometimes a launcher. If one of them is missed, that one shows up in the share.\n\nWe keep a single method that walks every live window and pushes the same value:\n\n``` js\nprivate applyContentProtection(enable: boolean): void {\n  const windows = [\n    this.launcherWindow,\n    this.overlayWindow,\n    this.pillWindow,\n    this.toggleWindow,\n    this.popoverCatcher,\n  ];\n  windows.forEach((win) => {\n    if (win && !win.isDestroyed()) win.setContentProtection(enable);\n  });\n}\n```\n\nAny window created later (settings, model picker, screenshot cropper) reads the current state when it is created, so a window opened mid-meeting doesn't slip through.\n\nOur setter was called from several places: the settings toggle, every overlay show, and a workaround around Win+Tab. On Windows, setting the same display affinity again and again churns DWM, and the window can go black or blank for a few hundred milliseconds. So the public setter does nothing when the value hasn't changed:\n\n```\npublic setContentProtection(enable: boolean): void {\n  if (this.contentProtection === enable) return;\n  this.contentProtection = enable;\n  this.applyContentProtection(enable);\n}\n```\n\nThis one cost us the most time. Undetectable mode also hides the app from the Dock with `app.dock.hide()`. That flips the app's activation policy, and WindowServer re-evaluates every window. It can quietly reset `sharingType` to the default, which means the overlay is visible in the share again. Your in-memory flag still says `true`, so the deduped setter above would skip the call.\n\nThe fix is a second method that skips the dedupe and pushes the current value to the OS again:\n\n```\npublic reassertContentProtection(): void {\n  this.applyContentProtection(this.contentProtection);\n}\n```\n\nCall it after every `app.dock.hide()` / `app.dock.show()`.\n\nOn macOS, hiding from the Dock removes the app's visible presence. On Windows, a window that is excluded from capture can still have a taskbar button with the app's name on it, and the taskbar is part of a full-screen share. Most of our windows are created with `skipTaskbar: true`. The launcher is the exception because in normal mode it is the main window, so we sync it with the mode:\n\n```\npublic syncLauncherTaskbarForStealth(): void {\n  if (process.platform !== 'win32') return;\n  this.launcherWindow?.setSkipTaskbar(!!this.appState.getUndetectable());\n}\n```\n\nThe macOS path doesn't use this at all. Forcing `skipTaskbar` there would change behaviour on a platform that handles it through the Dock.\n\nContent protection is a request to the OS compositor, and different capture APIs respect it differently. Modern APIs (ScreenCaptureKit on macOS, Windows Graphics Capture) honour it. Some older capture paths may not. So we test with the apps people really share from (Zoom, Google Meet in Chrome, Teams, OBS, the system screenshot tool) on both platforms, with the mode on and off, and we treat \"it was hidden on my machine\" as one data point, not proof.\n\nAll of this is in Natively's source on GitHub: [Natively-AI-assistant/natively-cluely-ai-assistant](https://github.com/Natively-AI-assistant/natively-cluely-ai-assistant), in `electron/WindowHelper.ts`. Natively runs on macOS and Windows and is free for personal use. You can bring your own AI keys or run models locally with Ollama. If you're building something with an always-on-top overlay, I hope this saves you an afternoon.\n\nDownload and docs: [heynatively.com](https://heynatively.com)", "url": "https://wpnews.pro/news/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn", "canonical_source": "https://dev.to/heynatively/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesnt-tell-you-525e", "published_at": "2026-11-12 22:11:34+00:00", "updated_at": "2026-11-12 22:22:51.939875+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "developer-tools"], "entities": ["Natively", "Electron", "macOS", "Windows", "Zoom", "Google Meet", "Microsoft Teams", "OBS"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn", "markdown": "https://wpnews.pro/news/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn.md", "text": "https://wpnews.pro/news/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn.txt", "jsonld": "https://wpnews.pro/news/hiding-an-electron-window-from-screen-share-4-things-setcontentprotection-doesn.jsonld"}}