Add content protection checkbox to Settings window

This commit is contained in:
Fedor Indutny
2025-05-12 15:16:19 -07:00
committed by GitHub
parent a2c74c3a8b
commit bc3b6a07bb
10 changed files with 149 additions and 3 deletions

View File

@@ -6936,6 +6936,30 @@
"messageformat": "Restart", "messageformat": "Restart",
"description": "Button to restart Signal to apply language changes" "description": "Button to restart Signal to apply language changes"
}, },
"icu:Preferences__Privacy__Application": {
"messageformat": "Application",
"description": "Title of Application section in Settings > Privacy"
},
"icu:Preferences__content-protection--label": {
"messageformat": "Screen security",
"description": "Label of checkbox in Settings > Privacy > Application"
},
"icu:Preferences__content-protection--description": {
"messageformat": "Prevent screenshots of Signal on this computer for added privacy.",
"description": "Description of checkbox in Settings > Privacy > Application"
},
"icu:Preferences__content-protection__modal--title": {
"messageformat": "Disable screen security?",
"description": "Title of confirm disabling screen security modal in Settings > Privacy > Application"
},
"icu:Preferences__content-protection__modal--body": {
"messageformat": "If disabled, this may allow Microsoft Windows to capture screenshots of Signal and use them for features that may not be private.",
"description": "Body of confirm disabling screen security modal in Settings > Privacy > Application"
},
"icu:Preferences__content-protection__modal--disable": {
"messageformat": "Disable",
"description": "Text of the button confirm disabling screen security modal in Settings > Privacy > Application"
},
"icu:DialogUpdate--version-available": { "icu:DialogUpdate--version-available": {
"messageformat": "Update to version {version} available", "messageformat": "Update to version {version} available",
"description": "Tooltip for new update available" "description": "Tooltip for new update available"

View File

@@ -571,6 +571,15 @@ async function handleCommonWindowEvents(window: BrowserWindow) {
const focusInterval = setInterval(setWindowFocus, 10000); const focusInterval = setInterval(setWindowFocus, 10000);
window.on('closed', () => clearInterval(focusInterval)); window.on('closed', () => clearInterval(focusInterval));
const contentProtection = ephemeralConfig.get('contentProtection');
// Apply content protection by default on Windows, unless explicitly disabled
// by user in settings.
if (contentProtection ?? OS.isWindows()) {
window.once('ready-to-show', async () => {
window.setContentProtection(true);
});
}
await zoomFactorService.syncWindow(window); await zoomFactorService.syncWindow(window);
nativeThemeNotifier.addWindow(window); nativeThemeNotifier.addWindow(window);
@@ -2132,6 +2141,7 @@ app.on('ready', async () => {
'ephemeral-setting-changed', 'ephemeral-setting-changed',
sendPreferencesChangedEventToWindows sendPreferencesChangedEventToWindows
); );
settingsChannel.on('ephemeral-setting-changed', onEphemeralSettingChanged);
// We use this event only a single time to log the startup time of the app // We use this event only a single time to log the startup time of the app
// from when it's first ready until the loading screen disappears. // from when it's first ready until the loading screen disappears.
@@ -2902,6 +2912,20 @@ const sendPreferencesChangedEventToWindows = () => {
}; };
ipc.on('preferences-changed', sendPreferencesChangedEventToWindows); ipc.on('preferences-changed', sendPreferencesChangedEventToWindows);
const onEphemeralSettingChanged = (name: string) => {
if (name !== 'contentProtection') {
return;
}
const contentProtection = ephemeralConfig.get('contentProtection');
for (const window of activeWindows) {
if (typeof contentProtection === 'boolean') {
window.setContentProtection(contentProtection);
}
}
};
function maybeGetIncomingSignalRoute(argv: Array<string>) { function maybeGetIncomingSignalRoute(argv: Array<string>) {
for (const arg of argv) { for (const arg of argv) {
const route = parseSignalRoute(arg); const route = parseSignalRoute(arg);

View File

@@ -111,6 +111,7 @@ export default {
hasAutoLaunch: true, hasAutoLaunch: true,
hasCallNotifications: true, hasCallNotifications: true,
hasCallRingtoneNotification: false, hasCallRingtoneNotification: false,
hasContentProtection: false,
hasCountMutedConversations: false, hasCountMutedConversations: false,
hasHideMenuBar: false, hasHideMenuBar: false,
hasIncomingCallNotifications: true, hasIncomingCallNotifications: true,
@@ -136,6 +137,8 @@ export default {
isSyncSupported: true, isSyncSupported: true,
isSystemTraySupported: true, isSystemTraySupported: true,
isInternalUser: false, isInternalUser: false,
isContentProtectionSupported: true,
isContentProtectionNeeded: true,
isMinimizeToAndStartInSystemTraySupported: true, isMinimizeToAndStartInSystemTraySupported: true,
lastSyncTime: Date.now(), lastSyncTime: Date.now(),
localeOverride: null, localeOverride: null,
@@ -182,6 +185,7 @@ export default {
onCallRingtoneNotificationChange: action( onCallRingtoneNotificationChange: action(
'onCallRingtoneNotificationChange' 'onCallRingtoneNotificationChange'
), ),
onContentProtectionChange: action('onContentProtectionChange'),
onCountMutedConversationsChange: action('onCountMutedConversationsChange'), onCountMutedConversationsChange: action('onCountMutedConversationsChange'),
onEmojiSkinToneDefaultChange: action('onEmojiSkinToneDefaultChange'), onEmojiSkinToneDefaultChange: action('onEmojiSkinToneDefaultChange'),
onHasStoriesDisabledChanged: action('onHasStoriesDisabledChanged'), onHasStoriesDisabledChanged: action('onHasStoriesDisabledChanged'),

View File

@@ -96,6 +96,7 @@ export type PropsDataType = {
hasAutoLaunch: boolean; hasAutoLaunch: boolean;
hasCallNotifications: boolean; hasCallNotifications: boolean;
hasCallRingtoneNotification: boolean; hasCallRingtoneNotification: boolean;
hasContentProtection: boolean;
hasCountMutedConversations: boolean; hasCountMutedConversations: boolean;
hasHideMenuBar?: boolean; hasHideMenuBar?: boolean;
hasIncomingCallNotifications: boolean; hasIncomingCallNotifications: boolean;
@@ -145,6 +146,8 @@ export type PropsDataType = {
isSystemTraySupported: boolean; isSystemTraySupported: boolean;
isMinimizeToAndStartInSystemTraySupported: boolean; isMinimizeToAndStartInSystemTraySupported: boolean;
isInternalUser: boolean; isInternalUser: boolean;
isContentProtectionNeeded: boolean;
isContentProtectionSupported: boolean;
availableCameras: Array< availableCameras: Array<
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'> Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
@@ -189,6 +192,7 @@ type PropsFunctionType = {
onAutoLaunchChange: CheckboxChangeHandlerType; onAutoLaunchChange: CheckboxChangeHandlerType;
onCallNotificationsChange: CheckboxChangeHandlerType; onCallNotificationsChange: CheckboxChangeHandlerType;
onCallRingtoneNotificationChange: CheckboxChangeHandlerType; onCallRingtoneNotificationChange: CheckboxChangeHandlerType;
onContentProtectionChange: CheckboxChangeHandlerType;
onCountMutedConversationsChange: CheckboxChangeHandlerType; onCountMutedConversationsChange: CheckboxChangeHandlerType;
onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void; onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void;
onHasStoriesDisabledChanged: SelectChangeHandlerType<boolean>; onHasStoriesDisabledChanged: SelectChangeHandlerType<boolean>;
@@ -297,6 +301,7 @@ export function Preferences({
hasAutoLaunch, hasAutoLaunch,
hasCallNotifications, hasCallNotifications,
hasCallRingtoneNotification, hasCallRingtoneNotification,
hasContentProtection,
hasCountMutedConversations, hasCountMutedConversations,
hasHideMenuBar, hasHideMenuBar,
hasIncomingCallNotifications, hasIncomingCallNotifications,
@@ -326,6 +331,8 @@ export function Preferences({
isSystemTraySupported, isSystemTraySupported,
isMinimizeToAndStartInSystemTraySupported, isMinimizeToAndStartInSystemTraySupported,
isInternalUser, isInternalUser,
isContentProtectionNeeded,
isContentProtectionSupported,
lastSyncTime, lastSyncTime,
makeSyncRequest, makeSyncRequest,
notificationContent, notificationContent,
@@ -336,6 +343,7 @@ export function Preferences({
onAutoLaunchChange, onAutoLaunchChange,
onCallNotificationsChange, onCallNotificationsChange,
onCallRingtoneNotificationChange, onCallRingtoneNotificationChange,
onContentProtectionChange,
onCountMutedConversationsChange, onCountMutedConversationsChange,
onEmojiSkinToneDefaultChange, onEmojiSkinToneDefaultChange,
onHasStoriesDisabledChanged, onHasStoriesDisabledChanged,
@@ -392,6 +400,8 @@ export function Preferences({
const [confirmDelete, setConfirmDelete] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false);
const [confirmStoriesOff, setConfirmStoriesOff] = useState(false); const [confirmStoriesOff, setConfirmStoriesOff] = useState(false);
const [confirmContentProtection, setConfirmContentProtection] =
useState(false);
const [page, setPage] = useState<Page>(initialPage); const [page, setPage] = useState<Page>(initialPage);
const [showSyncFailed, setShowSyncFailed] = useState(false); const [showSyncFailed, setShowSyncFailed] = useState(false);
const [nowSyncing, setNowSyncing] = useState(false); const [nowSyncing, setNowSyncing] = useState(false);
@@ -460,6 +470,17 @@ export function Preferences({
[onSelectedMicrophoneChange, availableMicrophones] [onSelectedMicrophoneChange, availableMicrophones]
); );
const handleContentProtectionChange = useCallback(
(value: boolean) => {
if (value === true || !isContentProtectionNeeded) {
onContentProtectionChange(value);
} else {
setConfirmContentProtection(true);
}
},
[onContentProtectionChange, isContentProtectionNeeded]
);
const settingsPaneRef = useRef<HTMLDivElement | null>(null); const settingsPaneRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
const settingsPane = settingsPaneRef.current; const settingsPane = settingsPaneRef.current;
@@ -1365,6 +1386,41 @@ export function Preferences({
} }
/> />
</SettingsRow> </SettingsRow>
{isContentProtectionSupported && (
<SettingsRow title={i18n('icu:Preferences__Privacy__Application')}>
<Checkbox
checked={hasContentProtection}
description={i18n(
'icu:Preferences__content-protection--description'
)}
label={i18n('icu:Preferences__content-protection--label')}
moduleClassName="Preferences__checkbox"
name="contentProtection"
onChange={handleContentProtectionChange}
/>
</SettingsRow>
)}
{confirmContentProtection ? (
<ConfirmationDialog
dialogName="Preference.confirmContentProtection"
actions={[
{
action: () => onContentProtectionChange(false),
style: 'negative',
text: i18n(
'icu:Preferences__content-protection__modal--disable'
),
},
]}
i18n={i18n}
onClose={() => {
setConfirmContentProtection(false);
}}
title={i18n('icu:Preferences__content-protection__modal--title')}
>
{i18n('icu:Preferences__content-protection__modal--body')}
</ConfirmationDialog>
) : null}
<SettingsRow title={i18n('icu:Stories__title')}> <SettingsRow title={i18n('icu:Stories__title')}>
<Control <Control
left={ left={

View File

@@ -21,6 +21,7 @@ const EPHEMERAL_NAME_MAP = new Map([
['systemTraySetting', 'system-tray-setting'], ['systemTraySetting', 'system-tray-setting'],
['themeSetting', 'theme-setting'], ['themeSetting', 'theme-setting'],
['localeOverride', 'localeOverride'], ['localeOverride', 'localeOverride'],
['contentProtection', 'contentProtection'],
]); ]);
type ResponseQueueEntry = Readonly<{ type ResponseQueueEntry = Readonly<{
@@ -123,6 +124,7 @@ export class SettingsChannel extends EventEmitter {
this.#installEphemeralSetting('systemTraySetting'); this.#installEphemeralSetting('systemTraySetting');
this.#installEphemeralSetting('localeOverride'); this.#installEphemeralSetting('localeOverride');
this.#installEphemeralSetting('spellCheck'); this.#installEphemeralSetting('spellCheck');
this.#installEphemeralSetting('contentProtection');
installPermissionsHandler({ session: session.defaultSession, userConfig }); installPermissionsHandler({ session: session.defaultSession, userConfig });
@@ -288,7 +290,7 @@ export class SettingsChannel extends EventEmitter {
// Notify main to notify windows of preferences change. As for DB-backed // Notify main to notify windows of preferences change. As for DB-backed
// settings, those are set by the renderer, and afterwards the renderer IPC sends // settings, those are set by the renderer, and afterwards the renderer IPC sends
// to main the event 'preferences-changed'. // to main the event 'preferences-changed'.
this.emit('ephemeral-setting-changed'); this.emit('ephemeral-setting-changed', name);
const mainWindow = this.#mainWindow; const mainWindow = this.#mainWindow;
if (!mainWindow || !mainWindow.webContents) { if (!mainWindow || !mainWindow.webContents) {
@@ -308,7 +310,7 @@ export class SettingsChannel extends EventEmitter {
public override on( public override on(
type: 'ephemeral-setting-changed', type: 'ephemeral-setting-changed',
callback: () => void callback: (name: string) => void
): this; ): this;
public override on( public override on(
@@ -330,7 +332,10 @@ export class SettingsChannel extends EventEmitter {
value: string value: string
): boolean; ): boolean;
public override emit(type: 'ephemeral-setting-changed'): boolean; public override emit(
type: 'ephemeral-setting-changed',
name: string
): boolean;
public override emit( public override emit(
type: SettingChangeEventType<keyof SettingsValuesType>, type: SettingChangeEventType<keyof SettingsValuesType>,

View File

@@ -33,6 +33,12 @@ export const isDrawAttentionSupported = (OS: OSType): boolean => !OS.isMacOS();
export const isSystemTraySupported = (OS: OSType): boolean => export const isSystemTraySupported = (OS: OSType): boolean =>
OS.isWindows() || OS.isLinux(); OS.isWindows() || OS.isLinux();
export const isContentProtectionSupported = (OS: OSType): boolean =>
OS.isWindows() || OS.isMacOS();
export const isContentProtectionNeeded = (OS: OSType): boolean =>
OS.isWindows();
export const getDefaultSystemTraySetting = ( export const getDefaultSystemTraySetting = (
OS: OSType, OS: OSType,
appVersion: string appVersion: string

View File

@@ -192,6 +192,8 @@ type ValuesWithGetters = Omit<
| 'mediaPermissions' | 'mediaPermissions'
| 'mediaCameraPermissions' | 'mediaCameraPermissions'
| 'autoLaunch' | 'autoLaunch'
| 'spellCheck'
| 'contentProtection'
| 'systemTraySetting' | 'systemTraySetting'
>; >;
@@ -240,6 +242,7 @@ export type IPCEventsGettersType = {
getZoomFactor: () => Promise<ZoomFactorType>; getZoomFactor: () => Promise<ZoomFactorType>;
getLocaleOverride: () => Promise<string | null>; getLocaleOverride: () => Promise<string | null>;
getSpellCheck: () => Promise<boolean>; getSpellCheck: () => Promise<boolean>;
getContentProtection: () => Promise<boolean>;
getSystemTraySetting: () => Promise<SystemTraySetting>; getSystemTraySetting: () => Promise<SystemTraySetting>;
getThemeSetting: () => Promise<ThemeType>; getThemeSetting: () => Promise<ThemeType>;
// Events // Events
@@ -338,6 +341,12 @@ export function createIPCEvents(
account.captureChange('hasStoriesDisabled'); account.captureChange('hasStoriesDisabled');
window.textsecure.server?.onHasStoriesDisabledChange(value); window.textsecure.server?.onHasStoriesDisabledChange(value);
}, },
getContentProtection: () => {
return getEphemeralSetting('contentProtection');
},
setContentProtection: async (value: boolean) => {
await setEphemeralSetting('contentProtection', value);
},
getStoryViewReceiptsEnabled: () => { getStoryViewReceiptsEnabled: () => {
return ( return (
window.storage.get('storyViewReceiptsEnabled') ?? window.storage.get('storyViewReceiptsEnabled') ??

View File

@@ -26,6 +26,7 @@ export type ThemeType = 'light' | 'dark' | 'system';
export type EphemeralSettings = { export type EphemeralSettings = {
spellCheck: boolean; spellCheck: boolean;
contentProtection: boolean;
systemTraySetting: SystemTraySetting; systemTraySetting: SystemTraySetting;
themeSetting: ThemeType; themeSetting: ThemeType;
localeOverride: string | null; localeOverride: string | null;

View File

@@ -51,6 +51,7 @@ SettingsWindowProps.onRender(
hasAutoLaunch, hasAutoLaunch,
hasCallNotifications, hasCallNotifications,
hasCallRingtoneNotification, hasCallRingtoneNotification,
hasContentProtection,
hasCountMutedConversations, hasCountMutedConversations,
hasHideMenuBar, hasHideMenuBar,
hasIncomingCallNotifications, hasIncomingCallNotifications,
@@ -77,6 +78,8 @@ SettingsWindowProps.onRender(
isNotificationAttentionSupported, isNotificationAttentionSupported,
isSyncSupported, isSyncSupported,
isSystemTraySupported, isSystemTraySupported,
isContentProtectionSupported,
isContentProtectionNeeded,
isInternalUser, isInternalUser,
lastSyncTime, lastSyncTime,
makeSyncRequest, makeSyncRequest,
@@ -88,6 +91,7 @@ SettingsWindowProps.onRender(
onAutoLaunchChange, onAutoLaunchChange,
onCallNotificationsChange, onCallNotificationsChange,
onCallRingtoneNotificationChange, onCallRingtoneNotificationChange,
onContentProtectionChange,
onCountMutedConversationsChange, onCountMutedConversationsChange,
onEmojiSkinToneDefaultChange, onEmojiSkinToneDefaultChange,
onHasStoriesDisabledChanged, onHasStoriesDisabledChanged,
@@ -166,6 +170,7 @@ SettingsWindowProps.onRender(
hasAutoLaunch={hasAutoLaunch} hasAutoLaunch={hasAutoLaunch}
hasCallNotifications={hasCallNotifications} hasCallNotifications={hasCallNotifications}
hasCallRingtoneNotification={hasCallRingtoneNotification} hasCallRingtoneNotification={hasCallRingtoneNotification}
hasContentProtection={hasContentProtection}
hasCountMutedConversations={hasCountMutedConversations} hasCountMutedConversations={hasCountMutedConversations}
hasHideMenuBar={hasHideMenuBar} hasHideMenuBar={hasHideMenuBar}
hasIncomingCallNotifications={hasIncomingCallNotifications} hasIncomingCallNotifications={hasIncomingCallNotifications}
@@ -195,6 +200,8 @@ SettingsWindowProps.onRender(
isNotificationAttentionSupported={isNotificationAttentionSupported} isNotificationAttentionSupported={isNotificationAttentionSupported}
isSyncSupported={isSyncSupported} isSyncSupported={isSyncSupported}
isSystemTraySupported={isSystemTraySupported} isSystemTraySupported={isSystemTraySupported}
isContentProtectionSupported={isContentProtectionSupported}
isContentProtectionNeeded={isContentProtectionNeeded}
isInternalUser={isInternalUser} isInternalUser={isInternalUser}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}
localeOverride={localeOverride} localeOverride={localeOverride}
@@ -207,6 +214,7 @@ SettingsWindowProps.onRender(
onAutoLaunchChange={onAutoLaunchChange} onAutoLaunchChange={onAutoLaunchChange}
onCallNotificationsChange={onCallNotificationsChange} onCallNotificationsChange={onCallNotificationsChange}
onCallRingtoneNotificationChange={onCallRingtoneNotificationChange} onCallRingtoneNotificationChange={onCallRingtoneNotificationChange}
onContentProtectionChange={onContentProtectionChange}
onCountMutedConversationsChange={onCountMutedConversationsChange} onCountMutedConversationsChange={onCountMutedConversationsChange}
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange} onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
onHasStoriesDisabledChanged={onHasStoriesDisabledChanged} onHasStoriesDisabledChanged={onHasStoriesDisabledChanged}

View File

@@ -48,6 +48,7 @@ const settingNotificationSetting = createSetting('notificationSetting');
const settingRelayCalls = createSetting('alwaysRelayCalls'); const settingRelayCalls = createSetting('alwaysRelayCalls');
const settingSentMediaQuality = createSetting('sentMediaQualitySetting'); const settingSentMediaQuality = createSetting('sentMediaQualitySetting');
const settingSpellCheck = createSetting('spellCheck'); const settingSpellCheck = createSetting('spellCheck');
const settingContentProtection = createSetting('contentProtection');
const settingTextFormatting = createSetting('textFormatting'); const settingTextFormatting = createSetting('textFormatting');
const settingTheme = createSetting('themeSetting'); const settingTheme = createSetting('themeSetting');
const settingSystemTraySetting = createSetting('systemTraySetting'); const settingSystemTraySetting = createSetting('systemTraySetting');
@@ -176,6 +177,7 @@ async function renderPreferences() {
hasAutoLaunch, hasAutoLaunch,
hasCallNotifications, hasCallNotifications,
hasCallRingtoneNotification, hasCallRingtoneNotification,
hasContentProtection,
hasCountMutedConversations, hasCountMutedConversations,
hasHideMenuBar, hasHideMenuBar,
hasIncomingCallNotifications, hasIncomingCallNotifications,
@@ -223,6 +225,7 @@ async function renderPreferences() {
hasAutoLaunch: settingAutoLaunch.getValue(), hasAutoLaunch: settingAutoLaunch.getValue(),
hasCallNotifications: settingCallSystemNotification.getValue(), hasCallNotifications: settingCallSystemNotification.getValue(),
hasCallRingtoneNotification: settingCallRingtoneNotification.getValue(), hasCallRingtoneNotification: settingCallRingtoneNotification.getValue(),
hasContentProtection: settingContentProtection.getValue(),
hasCountMutedConversations: settingCountMutedConversations.getValue(), hasCountMutedConversations: settingCountMutedConversations.getValue(),
hasHideMenuBar: settingHideMenuBar.getValue(), hasHideMenuBar: settingHideMenuBar.getValue(),
hasIncomingCallNotifications: settingIncomingCallNotification.getValue(), hasIncomingCallNotifications: settingIncomingCallNotification.getValue(),
@@ -321,6 +324,7 @@ async function renderPreferences() {
hasAutoLaunch, hasAutoLaunch,
hasCallNotifications, hasCallNotifications,
hasCallRingtoneNotification, hasCallRingtoneNotification,
hasContentProtection,
hasCountMutedConversations, hasCountMutedConversations,
hasHideMenuBar, hasHideMenuBar,
hasIncomingCallNotifications, hasIncomingCallNotifications,
@@ -385,6 +389,8 @@ async function renderPreferences() {
isSyncSupported: !isSyncNotSupported, isSyncSupported: !isSyncNotSupported,
isInternalUser, isInternalUser,
isSystemTraySupported: Settings.isSystemTraySupported(OS), isSystemTraySupported: Settings.isSystemTraySupported(OS),
isContentProtectionSupported: Settings.isContentProtectionSupported(OS),
isContentProtectionNeeded: Settings.isContentProtectionNeeded(OS),
isMinimizeToAndStartInSystemTraySupported: isMinimizeToAndStartInSystemTraySupported:
Settings.isMinimizeToAndStartInSystemTraySupported(OS), Settings.isMinimizeToAndStartInSystemTraySupported(OS),
@@ -408,6 +414,9 @@ async function renderPreferences() {
onCallRingtoneNotificationChange: attachRenderCallback( onCallRingtoneNotificationChange: attachRenderCallback(
settingCallRingtoneNotification.setValue settingCallRingtoneNotification.setValue
), ),
onContentProtectionChange: attachRenderCallback(
settingContentProtection.setValue
),
onCountMutedConversationsChange: attachRenderCallback( onCountMutedConversationsChange: attachRenderCallback(
settingCountMutedConversations.setValue settingCountMutedConversations.setValue
), ),