Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/crossover.js
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ const syncSettings = ( options = preferences.preferences ) => {

} )

// Sync screen-capture exclusion across all windows (#359)
windows.syncContentProtection()
Comment on lines +412 to +413

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To maintain consistency with the rest of the syncSettings function (which reads all configuration values from the passed options object rather than directly from the global preferences store), we should pass options.app?.hideFromScreenCapture to windows.syncContentProtection().

Suggested change
// Sync screen-capture exclusion across all windows (#359)
windows.syncContentProtection()
// Sync screen-capture exclusion across all windows (#359)
windows.syncContentProtection( options.app?.hideFromScreenCapture )


set.startOnBoot()

// Reset all custom shortcuts
Expand Down
8 changes: 8 additions & 0 deletions src/main/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const getDefaults = () => ( {
updates: [ 'updates' ],
sounds: [ 'sounds' ],
gpu: [ 'gpu' ],
hideFromScreenCapture: [],
startUnlocked: [ 'startUnlocked' ],
boot: [],
appSize: 'normal',
Expand Down Expand Up @@ -571,6 +572,13 @@ const preferencesConfig = {
options: [ { label: 'This switch runs the GPU process in the same process as the browser', value: 'gpuprocess' } ],
help: 'This can help avoid issues with transparency.',
},
{
label: 'Hide From Screen Recording',
key: 'hideFromScreenCapture',
type: 'checkbox',
options: [ { label: 'Exclude the crosshair from screen captures and recordings', value: 'hideFromScreenCapture' } ],
help: 'Prevents the crosshair from showing up in screen recordings (e.g. GeForce Instant Replay, OBS, screenshots). The crosshair remains visible on-screen. Requires Windows 10 or later for full effect.',
},
{
label: 'Run App On System Start',
key: 'boot',
Expand Down
43 changes: 43 additions & 0 deletions src/main/windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { app, BrowserWindow, screen } = require( 'electron' )

const { activeWindow, centerWindow, is } = require( './util' )
const { APP_HEIGHT, APP_WIDTH, MAX_SHADOW_WINDOWS, APP_ASPECT_RATIO } = require( '../config/config.js' )
const { checkboxTrue } = require( '../config/utils.js' )
const { productName } = require( '../../package.json' )
const dock = require( './dock.js' )
const log = require( './log.js' )
Expand Down Expand Up @@ -77,6 +78,43 @@ const getActiveWindow = () => {

}

// Toggle whether a window is excluded from screen captures / recordings (#359)
// Uses Electron's setContentProtection, which maps to WDA_MONITOR / WDA_EXCLUDEFROMCAPTURE
// on Windows and NSWindowSharingNone on macOS.
const applyContentProtection = ( win, enabled = checkboxTrue( preferences.value( 'app.hideFromScreenCapture' ), 'hideFromScreenCapture' ) ) => {

if ( !win || win.isDestroyed() ) {

return

}

try {

win.setContentProtection( Boolean( enabled ) )

} catch ( error ) {

log.error( `setContentProtection failed: ${error?.message}` )

}

}

// Apply the current hideFromScreenCapture preference to every crosshair window
const syncContentProtection = () => {

const enabled = checkboxTrue( preferences.value( 'app.hideFromScreenCapture' ), 'hideFromScreenCapture' )
applyContentProtection( windows.win, enabled )

for ( const currentWindow of windows.shadowWindows ) {

applyContentProtection( currentWindow, enabled )

}

}
Comment on lines +84 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are a few improvements we can make to applyContentProtection and syncContentProtection for better safety, platform compatibility, and consistency:

  1. Defensive Null/Undefined Guard: checkboxTrue checks typeof value === 'object' && value.includes(key). In JavaScript, typeof null is "object", so if the preference value is ever null, it will throw a TypeError: Cannot read properties of null (reading 'includes') and crash. Providing a fallback default array || [] prevents this.
  2. Platform Compatibility (Linux): setContentProtection is only supported on macOS and Windows. On Linux, win.setContentProtection is undefined. Calling it will throw a TypeError which gets caught and logged as an error. To avoid throwing exceptions and flooding the logs on Linux, we should check if win.setContentProtection is a function before calling it.
  3. Consistency with syncSettings: To align with how other settings are synced using the passed options object, syncContentProtection should accept an optional value parameter.
const applyContentProtection = ( win, enabled = checkboxTrue( preferences.value( 'app.hideFromScreenCapture' ) || [], 'hideFromScreenCapture' ) ) => {

	if ( !win || win.isDestroyed() || typeof win.setContentProtection !== 'function' ) {

		return

	}

	try {

		win.setContentProtection( Boolean( enabled ) )

	} catch ( error ) {

		log.error( `setContentProtection failed: ${error?.message}` )

	}

}

// Apply the current hideFromScreenCapture preference to every crosshair window
const syncContentProtection = ( value ) => {

	const enabled = checkboxTrue( value !== undefined ? value : ( preferences.value( 'app.hideFromScreenCapture' ) || [] ), 'hideFromScreenCapture' )
	applyContentProtection( windows.win, enabled )

	for ( const currentWindow of windows.shadowWindows ) {

		applyContentProtection( currentWindow, enabled )

	}

}


// Prevent window from being garbage collected
// Default no shadow window
const create = ( { isShadowWindow } = { isShadowWindow: false } ) => {
Expand Down Expand Up @@ -141,6 +179,9 @@ const create = ( { isShadowWindow } = { isShadowWindow: false } ) => {
win.setAlwaysOnTop( true, 'screen-saver', 1 )
win.setFullScreenable( false )

// Exclude the crosshair from screen recordings / captures when enabled (#359)
applyContentProtection( win )

win.once( 'ready-to-show', () => {

log.info( 'Event: Ready to show' )
Expand Down Expand Up @@ -594,6 +635,8 @@ const unregister = () => {

const windows = {

applyContentProtection,
syncContentProtection,
init,
load,
each,
Expand Down