From f92be05b15cb4c206e0ceda8a7fb4fdd11a547b9 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
Date: Tue, 5 Jul 2022 09:44:53 -0700
Subject: [PATCH] Titlebar fixes
---
.storybook/preview-head.html | 19 ++++++++--
app/config.ts | 1 +
app/main.ts | 28 +++++++-------
sticker-creator/app/index.tsx | 9 ++---
sticker-creator/components/ConfirmModal.scss | 6 +--
sticker-creator/root.tsx | 3 +-
stylesheets/_mixins.scss | 2 +-
stylesheets/_modules.scss | 26 ++++++-------
stylesheets/_titlebar.scss | 14 ++++++-
stylesheets/components/MediaEditor.scss | 4 +-
stylesheets/components/Stories.scss | 2 +-
stylesheets/components/StoryCreator.scss | 2 +-
stylesheets/components/TitleBarContainer.scss | 38 +++++++++++++++----
ts/OS.ts | 8 ++--
ts/background.ts | 6 ++-
ts/components/About.tsx | 9 ++---
ts/components/App.tsx | 9 ++---
ts/components/DebugLogWindow.stories.tsx | 3 +-
ts/components/DebugLogWindow.tsx | 12 ++----
ts/components/Preferences.stories.tsx | 3 +-
ts/components/Preferences.tsx | 9 ++---
ts/components/TitleBarContainer.tsx | 34 ++++++++---------
.../conversation/Timeline.stories.tsx | 5 ---
ts/components/conversation/Timeline.tsx | 8 +++-
ts/hooks/useIsWindowActive.ts | 23 +++++++++++
ts/manage_full_screen_class.ts | 10 +++--
ts/models/conversations.ts | 6 +--
ts/services/ActiveWindowService.ts | 18 +++++++++
ts/services/notifications.ts | 2 +-
ts/set_os_class.ts | 4 ++
ts/state/smart/App.tsx | 4 +-
ts/state/smart/CallManager.tsx | 3 +-
ts/types/RendererConfig.ts | 1 +
ts/views/conversation_view.tsx | 2 +-
ts/window.d.ts | 6 +--
ts/windows/about/preload.ts | 3 +-
ts/windows/context.ts | 12 ++++--
ts/windows/debuglog/preload.ts | 3 +-
ts/windows/main/phase1-ipc.ts | 9 +++--
ts/windows/main/phase2-dependencies.ts | 10 -----
ts/windows/settings/preload.ts | 3 +-
41 files changed, 225 insertions(+), 154 deletions(-)
create mode 100644 ts/hooks/useIsWindowActive.ts
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
index 912fcdae2..98c5172db 100644
--- a/.storybook/preview-head.html
+++ b/.storybook/preview-head.html
@@ -9,6 +9,9 @@
type="text/css"
/>
diff --git a/app/config.ts b/app/config.ts
index 7991a2d78..7a4bb3c04 100644
--- a/app/config.ts
+++ b/app/config.ts
@@ -34,6 +34,7 @@ if (getEnvironment() === Environment.Production) {
process.env.SUPPRESS_NO_CONFIG_WARNING = '';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '';
process.env.SIGNAL_ENABLE_HTTP = '';
+ process.env.CUSTOM_TITLEBAR = '';
}
// We load config after we've made our modifications to NODE_ENV
diff --git a/app/main.ts b/app/main.ts
index 738de01c7..544c93869 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -432,6 +432,7 @@ async function prepareUrl(
// Only used by the main window
isMainWindowFullScreen: Boolean(mainWindow?.isFullScreen()),
+ isMainWindowMaximized: Boolean(mainWindow?.isMaximized()),
// Only for tests
argv: JSON.stringify(process.argv),
@@ -499,6 +500,17 @@ function handleCommonWindowEvents(
activeWindows.add(window);
window.on('closed', () => activeWindows.delete(window));
+ const setWindowFocus = () => {
+ window.webContents.send('set-window-focus', window.isFocused());
+ };
+ window.on('focus', setWindowFocus);
+ window.on('blur', setWindowFocus);
+
+ window.once('ready-to-show', setWindowFocus);
+ // This is a fallback in case we drop an event for some reason.
+ const focusInterval = setInterval(setWindowFocus, 10000);
+ window.on('closed', () => clearInterval(focusInterval));
+
// Works only for mainWindow because it has `enablePreferredSizeMode`
let lastZoomFactor = window.webContents.getZoomFactor();
const onZoomChanged = () => {
@@ -600,12 +612,12 @@ const mainTitleBarStyle =
? ('default' as const)
: ('hidden' as const);
-const nonMainTitleBarStyle = OS.isWindows()
+const nonMainTitleBarStyle = OS.hasCustomTitleBar()
? ('hidden' as const)
: ('default' as const);
async function getTitleBarOverlay(): Promise {
- if (!OS.isWindows()) {
+ if (!OS.hasCustomTitleBar()) {
return false;
}
@@ -782,18 +794,6 @@ async function createWindow() {
mainWindow.on('resize', captureWindowStats);
mainWindow.on('move', captureWindowStats);
- const setWindowFocus = () => {
- if (!mainWindow) {
- return;
- }
- mainWindow.webContents.send('set-window-focus', mainWindow.isFocused());
- };
- mainWindow.on('focus', setWindowFocus);
- mainWindow.on('blur', setWindowFocus);
- mainWindow.once('ready-to-show', setWindowFocus);
- // This is a fallback in case we drop an event for some reason.
- setInterval(setWindowFocus, 10000);
-
if (getEnvironment() === Environment.Test) {
mainWindow.loadURL(await prepareFileUrl([__dirname, '../test/index.html']));
} else {
diff --git a/sticker-creator/app/index.tsx b/sticker-creator/app/index.tsx
index 68c6b503f..dbbb16cb4 100644
--- a/sticker-creator/app/index.tsx
+++ b/sticker-creator/app/index.tsx
@@ -16,15 +16,13 @@ import type { ExecuteMenuRoleType } from '../../ts/components/TitleBarContainer'
import { useTheme } from '../../ts/hooks/useTheme';
export type AppPropsType = Readonly<{
- platform: string;
executeMenuRole: ExecuteMenuRoleType;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
}>;
export const App = ({
- platform,
executeMenuRole,
- isWindows11,
+ hasCustomTitleBar,
}: AppPropsType): JSX.Element => {
const i18n = useI18n();
const theme = useTheme();
@@ -32,8 +30,7 @@ export const App = ({
return (
diff --git a/sticker-creator/components/ConfirmModal.scss b/sticker-creator/components/ConfirmModal.scss
index 0c220f61d..89d1bbdb1 100644
--- a/sticker-creator/components/ConfirmModal.scss
+++ b/sticker-creator/components/ConfirmModal.scss
@@ -3,12 +3,12 @@
.facade {
background: rgba(0, 0, 0, 0.33);
- width: 100vw;
+ width: var(--window-width);
height: var(--window-height);
display: flex;
justify-content: center;
align-items: center;
position: fixed;
- left: 0;
- top: 0;
+ left: var(--window-border);
+ top: var(--titlebar-height);
}
diff --git a/sticker-creator/root.tsx b/sticker-creator/root.tsx
index b17489ac0..fb4714d92 100644
--- a/sticker-creator/root.tsx
+++ b/sticker-creator/root.tsx
@@ -18,8 +18,7 @@ const ColdRoot = () => (
diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss
index 36db94624..627e487c4 100644
--- a/stylesheets/_mixins.scss
+++ b/stylesheets/_mixins.scss
@@ -672,11 +672,11 @@
@mixin install-screen {
align-items: center;
display: flex;
+ width: var(--window-width);
height: var(--window-height);
justify-content: center;
line-height: 30px;
user-select: none;
- width: 100vw;
@include light-theme {
background: $color-gray-02;
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 9412641a6..755881742 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -4167,12 +4167,12 @@ button.module-image__border-overlay:focus {
&__overlay {
display: flex;
+ width: var(--window-width);
height: var(--window-height);
justify-content: flex-end;
left: 0;
position: absolute;
top: 0;
- width: 100vw;
z-index: $z-index-popup;
}
@@ -5857,7 +5857,7 @@ button.module-image__border-overlay:focus {
position: fixed;
left: 0;
top: 0;
- width: 100vw;
+ width: var(--window-width);
height: var(--window-height);
display: flex;
justify-content: center;
@@ -7457,25 +7457,25 @@ button.module-image__border-overlay:focus {
.module-modal-host__overlay {
background: $color-black-alpha-40;
+ width: var(--window-width);
height: var(--window-height);
- left: 0;
- position: absolute;
- top: 0;
- width: 100vw;
+ left: var(--window-border);
+ top: var(--titlebar-height);
+ position: fixed;
z-index: $z-index-popup-overlay;
}
.module-modal-host__overlay-container {
display: flex;
flex-direction: column;
+ width: var(--window-width);
height: var(--window-height);
+ left: var(--window-border);
+ top: var(--titlebar-height);
justify-content: center;
- left: 0;
overflow: hidden;
padding: 20px;
- position: absolute;
- top: 0;
- width: 100vw;
+ position: fixed;
z-index: $z-index-popup-overlay;
}
@@ -7612,9 +7612,9 @@ button.module-image__border-overlay:focus {
.module-progress-dialog__overlay {
background: $color-black-alpha-40;
position: fixed;
- left: 0;
- top: 0;
- width: 100vw;
+ left: var(--window-border);
+ top: var(--titlebar-height);
+ width: var(--window-width);
height: var(--window-height);
display: flex;
justify-content: center;
diff --git a/stylesheets/_titlebar.scss b/stylesheets/_titlebar.scss
index f0d879eb2..83056fb88 100644
--- a/stylesheets/_titlebar.scss
+++ b/stylesheets/_titlebar.scss
@@ -17,10 +17,20 @@ body {
}
--window-height: 100vh;
+ --window-width: 100vw;
+ --unscaled-window-border: 0px;
+ --window-border: calc(var(--unscaled-window-border) / var(--zoom-factor));
--titlebar-height: 0px;
- &.os-windows:not(.full-screen) {
+ &.os-has-custom-titlebar:not(.full-screen) {
+ &:not(.maximized) {
+ --unscaled-window-border: 1px;
+ }
+
--titlebar-height: calc(28px / var(--zoom-factor));
- --window-height: calc(100vh - var(--titlebar-height));
+ --window-width: calc(100vw - 2 * var(--window-border));
+ --window-height: calc(
+ 100vh - var(--titlebar-height) - 2 * var(--window-border)
+ );
}
}
diff --git a/stylesheets/components/MediaEditor.scss b/stylesheets/components/MediaEditor.scss
index 6289611f5..79d0eb1e6 100644
--- a/stylesheets/components/MediaEditor.scss
+++ b/stylesheets/components/MediaEditor.scss
@@ -7,12 +7,12 @@
background: $color-gray-95;
display: flex;
flex-direction: column;
+ width: var(--window-width);
height: var(--window-height);
left: 0;
- position: absolute;
top: var(--titlebar-height);
+ position: absolute;
user-select: none;
- width: 100vw;
z-index: $z-index-popup-overlay;
&__container {
diff --git a/stylesheets/components/Stories.scss b/stylesheets/components/Stories.scss
index 649ca5295..7b015986c 100644
--- a/stylesheets/components/Stories.scss
+++ b/stylesheets/components/Stories.scss
@@ -19,7 +19,7 @@
flex-direction: column;
height: 100%;
width: 380px;
- padding-top: 42px;
+ padding-top: calc(14px + var(--title-bar-drag-area-height));
&__header {
align-items: center;
diff --git a/stylesheets/components/StoryCreator.scss b/stylesheets/components/StoryCreator.scss
index ad6b7f530..faeda4800 100644
--- a/stylesheets/components/StoryCreator.scss
+++ b/stylesheets/components/StoryCreator.scss
@@ -11,12 +11,12 @@
background: $color-gray-95;
display: flex;
flex-direction: column;
+ width: var(--window-width);
height: 100vh;
left: 0;
position: absolute;
top: 0;
user-select: none;
- width: 100vw;
z-index: $z-index-popup-overlay;
&__container {
diff --git a/stylesheets/components/TitleBarContainer.scss b/stylesheets/components/TitleBarContainer.scss
index 316c832a4..e6f1c1658 100644
--- a/stylesheets/components/TitleBarContainer.scss
+++ b/stylesheets/components/TitleBarContainer.scss
@@ -6,14 +6,41 @@
flex-direction: column;
height: 100vh;
- &__title {
+ --border-color: transparent;
+
+ &--active {
+ --border-color: transparent;
+ }
+
+ border: var(--window-border) solid var(--border-color);
+
+ @mixin titlebar-position {
position: fixed;
top: 0;
left: 0;
+
width: calc(100vw * var(--zoom-factor));
z-index: $z-index-window-controls;
transform: scale(calc(1 / var(--zoom-factor)));
transform-origin: 0 0;
+ }
+
+ // Draw bottom-less border frame around titlebar to prevent border-bottom
+ // color from leaking to corners.
+ &:after {
+ content: '';
+
+ @include titlebar-position;
+
+ height: calc(var(--titlebar-height) * var(--zoom-factor));
+
+ border: var(--unscaled-window-border) solid var(--border-color);
+ border-bottom: none;
+ }
+
+ &__title {
+ @include titlebar-position;
+ border: var(--unscaled-window-border) solid transparent;
// This matches the inline styles of frameless-titlebar
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
@@ -23,16 +50,13 @@
& button {
font-family: inherit;
}
+ }
- // Shift titlebar down 1px on Windows 11 because otherwise window border
- // will cover it.
- &--extra-padding {
- padding-top: 1px;
- }
+ &__padding {
+ height: calc(var(--titlebar-height) - var(--window-border));
}
&__content {
- margin-top: var(--titlebar-height);
height: var(--window-height);
position: relative;
}
diff --git a/ts/OS.ts b/ts/OS.ts
index 1049f852e..0f19966a8 100644
--- a/ts/OS.ts
+++ b/ts/OS.ts
@@ -16,7 +16,7 @@ export const isWindows = (minVersion?: string): boolean => {
return is.undefined(minVersion) ? true : semver.gte(osRelease, minVersion);
};
-export const isWindows11 = (): boolean => {
- // See https://docs.microsoft.com/en-us/answers/questions/586619/windows-11-build-ver-is-still-10022000194.html
- return isWindows('10.0.22000');
-};
+
+// Windows 10 and above
+export const hasCustomTitleBar = (): boolean =>
+ isWindows('10.0.0') || Boolean(process.env.CUSTOM_TITLEBAR);
diff --git a/ts/background.ts b/ts/background.ts
index b6846f5cb..d1792d26f 100644
--- a/ts/background.ts
+++ b/ts/background.ts
@@ -1835,7 +1835,9 @@ export async function startApp(): Promise {
window.reduxActions.app.openInstaller();
}
- window.registerForActive(() => notificationService.clear());
+ const { activeWindowService } = window.SignalContext;
+
+ activeWindowService.registerForActive(() => notificationService.clear());
window.addEventListener('unload', () => notificationService.fastClear());
notificationService.on('click', (id, messageId) => {
@@ -1848,7 +1850,7 @@ export async function startApp(): Promise {
});
// Maybe refresh remote configuration when we become active
- window.registerForActive(async () => {
+ activeWindowService.registerForActive(async () => {
strictAssert(server !== undefined, 'WebAPI not ready');
try {
diff --git a/ts/components/About.tsx b/ts/components/About.tsx
index f79b02861..d1c559f0f 100644
--- a/ts/components/About.tsx
+++ b/ts/components/About.tsx
@@ -14,8 +14,7 @@ export type PropsType = {
environment: string;
i18n: LocalizerType;
version: string;
- platform: string;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
};
@@ -24,8 +23,7 @@ export const About = ({
i18n,
environment,
version,
- platform,
- isWindows11,
+ hasCustomTitleBar,
executeMenuRole,
}: PropsType): JSX.Element => {
useEscapeHandling(closeAbout);
@@ -34,8 +32,7 @@ export const About = ({
return (
diff --git a/ts/components/App.tsx b/ts/components/App.tsx
index 0aaf185a8..cda1e1a74 100644
--- a/ts/components/App.tsx
+++ b/ts/components/App.tsx
@@ -36,8 +36,7 @@ type PropsType = {
isMaximized: boolean;
isFullScreen: boolean;
menuOptions: MenuOptionsType;
- platform: string;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
hideMenuBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
@@ -59,11 +58,10 @@ export const App = ({
isFullScreen,
isMaximized,
isShowingStoriesView,
- isWindows11,
+ hasCustomTitleBar,
localeMessages,
menuOptions,
openInbox,
- platform,
registerSingleDevice,
renderCallManager,
renderCustomizingPreferredReactionsModal,
@@ -152,8 +150,7 @@ export const App = ({
theme={theme}
isMaximized={isMaximized}
isFullScreen={isFullScreen}
- platform={platform}
- isWindows11={isWindows11}
+ hasCustomTitleBar={hasCustomTitleBar}
executeMenuRole={executeMenuRole}
titleBarDoubleClick={titleBarDoubleClick}
hasMenu
diff --git a/ts/components/DebugLogWindow.stories.tsx b/ts/components/DebugLogWindow.stories.tsx
index 7a424ed4f..7118a5bbe 100644
--- a/ts/components/DebugLogWindow.stories.tsx
+++ b/ts/components/DebugLogWindow.stories.tsx
@@ -26,8 +26,7 @@ const createProps = (): PropsType => ({
return 'https://picsum.photos/1800/900';
},
executeMenuRole: action('executeMenuRole'),
- platform: 'win32',
- isWindows11: false,
+ hasCustomTitleBar: true,
});
export default {
diff --git a/ts/components/DebugLogWindow.tsx b/ts/components/DebugLogWindow.tsx
index ece3b652b..26bf221e4 100644
--- a/ts/components/DebugLogWindow.tsx
+++ b/ts/components/DebugLogWindow.tsx
@@ -31,8 +31,7 @@ export type PropsType = {
i18n: LocalizerType;
fetchLogs: () => Promise;
uploadLogs: (logs: string) => Promise;
- platform: string;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
};
@@ -48,8 +47,7 @@ export const DebugLogWindow = ({
i18n,
fetchLogs,
uploadLogs,
- platform,
- isWindows11,
+ hasCustomTitleBar,
executeMenuRole,
}: PropsType): JSX.Element => {
const [loadState, setLoadState] = useState(LoadState.NotStarted);
@@ -147,8 +145,7 @@ export const DebugLogWindow = ({
return (
@@ -191,8 +188,7 @@ export const DebugLogWindow = ({
return (
diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx
index 58290e641..9263132a1 100644
--- a/ts/components/Preferences.stories.tsx
+++ b/ts/components/Preferences.stories.tsx
@@ -158,8 +158,7 @@ const createProps = (): PropsType => ({
i18n,
executeMenuRole: action('executeMenuRole'),
- platform: 'win32',
- isWindows11: false,
+ hasCustomTitleBar: true,
});
export default {
diff --git a/ts/components/Preferences.tsx b/ts/components/Preferences.tsx
index ce6463b2e..5a426323e 100644
--- a/ts/components/Preferences.tsx
+++ b/ts/components/Preferences.tsx
@@ -102,8 +102,7 @@ export type PropsType = {
value: CustomColorType;
}
) => unknown;
- platform: string;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
// Limited support features
@@ -230,7 +229,7 @@ export const Preferences = ({
isNotificationAttentionSupported,
isSyncSupported,
isSystemTraySupported,
- isWindows11,
+ hasCustomTitleBar,
lastSyncTime,
makeSyncRequest,
notificationContent,
@@ -258,7 +257,6 @@ export const Preferences = ({
onThemeChange,
onUniversalExpireTimerChange,
onZoomFactorChange,
- platform,
removeCustomColor,
removeCustomColorOnConversations,
resetAllChatColors,
@@ -1028,8 +1026,7 @@ export const Preferences = ({
return (
diff --git a/ts/components/TitleBarContainer.tsx b/ts/components/TitleBarContainer.tsx
index 27e867da0..273776aef 100644
--- a/ts/components/TitleBarContainer.tsx
+++ b/ts/components/TitleBarContainer.tsx
@@ -12,6 +12,7 @@ import { createTemplate } from '../../app/menu';
import { ThemeType } from '../types/Util';
import type { LocaleMessagesType } from '../types/I18N';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
+import { useIsWindowActive } from '../hooks/useIsWindowActive';
export type MenuPropsType = Readonly<{
hasMenu: true;
@@ -28,9 +29,8 @@ export type PropsType = Readonly<{
theme: ThemeType;
isMaximized?: boolean;
isFullScreen?: boolean;
- isWindows11: boolean;
+ hasCustomTitleBar: boolean;
hideMenuBar?: boolean;
- platform: string;
executeMenuRole: ExecuteMenuRoleType;
titleBarDoubleClick?: () => void;
children: ReactNode;
@@ -116,16 +116,17 @@ export const TitleBarContainer = (props: PropsType): JSX.Element => {
theme,
isMaximized,
isFullScreen,
- isWindows11,
+ hasCustomTitleBar,
hideMenuBar,
executeMenuRole,
titleBarDoubleClick,
children,
hasMenu,
- platform,
iconSrc = 'images/icon_32.png',
} = props;
+ const isWindowActive = useIsWindowActive();
+
const titleBarTheme = useMemo(
() => ({
bar: {
@@ -201,7 +202,7 @@ export const TitleBarContainer = (props: PropsType): JSX.Element => {
[theme, hideMenuBar]
);
- if (platform !== 'win32' || isFullScreen) {
+ if (!hasCustomTitleBar || isFullScreen) {
return <>{children}>;
}
@@ -236,17 +237,18 @@ export const TitleBarContainer = (props: PropsType): JSX.Element => {
}
return (
-
-
+
+ {children}
- // Add a pixel of padding on non-maximized Windows 11 titlebar.
- isWindows11 && !isMaximized
- ? 'TitleBarContainer__title--extra-padding'
- : null
- )}
- platform={platform}
+ {
onDoubleClick={titleBarDoubleClick}
hideControls
/>
-
- {children}
);
};
diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx
index 22268d5ad..4ba4cf161 100644
--- a/ts/components/conversation/Timeline.stories.tsx
+++ b/ts/components/conversation/Timeline.stories.tsx
@@ -36,11 +36,6 @@ export default {
// eslint-disable-next-line
const noop = () => {};
-Object.assign(window, {
- registerForActive: noop,
- unregisterForActive: noop,
-});
-
const items: Record = {
'id-1': {
type: 'message',
diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx
index 451dc3989..145f4aebe 100644
--- a/ts/components/conversation/Timeline.tsx
+++ b/ts/components/conversation/Timeline.tsx
@@ -573,7 +573,9 @@ export class Timeline extends React.Component<
this.updateIntersectionObserver();
- window.registerForActive(this.markNewestBottomVisibleMessageRead);
+ window.SignalContext.activeWindowService.registerForActive(
+ this.markNewestBottomVisibleMessageRead
+ );
this.delayedPeekTimeout = setTimeout(() => {
const { id, peekGroupCallForTheFirstTime } = this.props;
@@ -590,7 +592,9 @@ export class Timeline extends React.Component<
public override componentWillUnmount(): void {
const { delayedPeekTimeout, peekInterval } = this;
- window.unregisterForActive(this.markNewestBottomVisibleMessageRead);
+ window.SignalContext.activeWindowService.unregisterForActive(
+ this.markNewestBottomVisibleMessageRead
+ );
this.intersectionObserver?.disconnect();
diff --git a/ts/hooks/useIsWindowActive.ts b/ts/hooks/useIsWindowActive.ts
new file mode 100644
index 000000000..046c2dac1
--- /dev/null
+++ b/ts/hooks/useIsWindowActive.ts
@@ -0,0 +1,23 @@
+// Copyright 2022 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { useEffect, useState } from 'react';
+
+export function useIsWindowActive(): boolean {
+ const { activeWindowService } = window.SignalContext;
+ const [isActive, setIsActive] = useState(activeWindowService.isActive());
+
+ useEffect(() => {
+ const update = (newIsActive: boolean): void => {
+ setIsActive(newIsActive);
+ };
+
+ activeWindowService.registerForChange(update);
+
+ return () => {
+ activeWindowService.unregisterForChange(update);
+ };
+ }, [activeWindowService]);
+
+ return isActive;
+}
diff --git a/ts/manage_full_screen_class.ts b/ts/manage_full_screen_class.ts
index 4184cbe63..59751de51 100644
--- a/ts/manage_full_screen_class.ts
+++ b/ts/manage_full_screen_class.ts
@@ -1,10 +1,14 @@
-// Copyright 2021 Signal Messenger, LLC
+// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
{
- const updateFullScreenClass = (isFullScreen: boolean) => {
+ const updateFullScreenClass = (
+ isFullScreen: boolean,
+ isMaximized: boolean
+ ) => {
document.body.classList.toggle('full-screen', isFullScreen);
+ document.body.classList.toggle('maximized', isMaximized);
};
- updateFullScreenClass(window.isFullScreen());
+ updateFullScreenClass(window.isFullScreen(), window.isMaximized());
window.onFullScreenChange = updateFullScreenClass;
}
diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts
index d9a37ffae..b1dfe82fd 100644
--- a/ts/models/conversations.ts
+++ b/ts/models/conversations.ts
@@ -1417,7 +1417,7 @@ export class ConversationModel extends window.Backbone
messagesAdded({
conversationId,
messages: [{ ...message.attributes }],
- isActive: window.isActive(),
+ isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent,
isNewMessage: true,
});
@@ -1567,7 +1567,7 @@ export class ConversationModel extends window.Backbone
messages: cleaned.map((messageModel: MessageModel) => ({
...messageModel.attributes,
})),
- isActive: window.isActive(),
+ isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent: false,
isNewMessage: false,
});
@@ -1620,7 +1620,7 @@ export class ConversationModel extends window.Backbone
messages: cleaned.map((messageModel: MessageModel) => ({
...messageModel.attributes,
})),
- isActive: window.isActive(),
+ isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent: false,
isNewMessage: false,
});
diff --git a/ts/services/ActiveWindowService.ts b/ts/services/ActiveWindowService.ts
index 93f989ec9..a63420e33 100644
--- a/ts/services/ActiveWindowService.ts
+++ b/ts/services/ActiveWindowService.ts
@@ -25,6 +25,8 @@ export class ActiveWindowService {
private activeCallbacks: Array<() => void> = [];
+ private changeCallbacks: Array<(isActive: boolean) => void> = [];
+
private lastActiveEventAt = -Infinity;
private callActiveCallbacks: () => void;
@@ -73,6 +75,16 @@ export class ActiveWindowService {
);
}
+ registerForChange(callback: (isActive: boolean) => void): void {
+ this.changeCallbacks.push(callback);
+ }
+
+ unregisterForChange(callback: (isActive: boolean) => void): void {
+ this.changeCallbacks = this.changeCallbacks.filter(
+ item => item !== callback
+ );
+ }
+
private onActiveEvent(): void {
this.updateState(() => {
this.lastActiveEventAt = Date.now();
@@ -93,5 +105,11 @@ export class ActiveWindowService {
if (!wasActiveBefore && isActiveNow) {
this.callActiveCallbacks();
}
+
+ if (wasActiveBefore !== isActiveNow) {
+ for (const callback of this.changeCallbacks) {
+ callback(isActiveNow);
+ }
+ }
}
}
diff --git a/ts/services/notifications.ts b/ts/services/notifications.ts
index 04c766193..b7b62fe99 100644
--- a/ts/services/notifications.ts
+++ b/ts/services/notifications.ts
@@ -228,7 +228,7 @@ class NotificationService extends EventEmitter {
}
const { notificationData } = this;
- const isAppFocused = window.isActive();
+ const isAppFocused = window.SignalContext.activeWindowService.isActive();
const userSetting = this.getNotificationSetting();
// This isn't a boolean because TypeScript isn't smart enough to know that, if
diff --git a/ts/set_os_class.ts b/ts/set_os_class.ts
index 297f343e8..cf9f0c3b5 100644
--- a/ts/set_os_class.ts
+++ b/ts/set_os_class.ts
@@ -14,4 +14,8 @@
}
document.body.classList.add(className);
+
+ if (window.SignalContext.OS.hasCustomTitleBar()) {
+ document.body.classList.add('os-has-custom-titlebar');
+ }
}
diff --git a/ts/state/smart/App.tsx b/ts/state/smart/App.tsx
index 49e36078d..47b50e3a6 100644
--- a/ts/state/smart/App.tsx
+++ b/ts/state/smart/App.tsx
@@ -22,7 +22,6 @@ import {
getIsMainWindowMaximized,
getIsMainWindowFullScreen,
getMenuOptions,
- getPlatform,
} from '../selectors/user';
import { shouldShowStoriesView } from '../selectors/stories';
import { getHideMenuBar } from '../selectors/items';
@@ -42,8 +41,7 @@ const mapStateToProps = (state: StateType) => {
isMaximized: getIsMainWindowMaximized(state),
isFullScreen: getIsMainWindowFullScreen(state),
menuOptions: getMenuOptions(state),
- platform: getPlatform(state),
- isWindows11: window.SignalContext.OS.isWindows11(),
+ hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
hideMenuBar: getHideMenuBar(state),
renderCallManager: () => ,
renderCustomizingPreferredReactionsModal: () => (
diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx
index c5eed54ea..2c812e76d 100644
--- a/ts/state/smart/CallManager.tsx
+++ b/ts/state/smart/CallManager.tsx
@@ -53,7 +53,8 @@ async function notifyForCall(
isVideoCall: boolean
): Promise {
const shouldNotify =
- !window.isActive() && window.Events.getCallSystemNotification();
+ !window.SignalContext.activeWindowService.isActive() &&
+ window.Events.getCallSystemNotification();
if (!shouldNotify) {
return;
}
diff --git a/ts/types/RendererConfig.ts b/ts/types/RendererConfig.ts
index 095f2bfed..54e19edb5 100644
--- a/ts/types/RendererConfig.ts
+++ b/ts/types/RendererConfig.ts
@@ -92,6 +92,7 @@ export const rendererConfigSchema = z.object({
// Only used by main window
isMainWindowFullScreen: z.boolean(),
+ isMainWindowMaximized: z.boolean(),
// Only for tests
argv: configOptionalStringSchema,
diff --git a/ts/views/conversation_view.tsx b/ts/views/conversation_view.tsx
index 0df46127a..775268b3c 100644
--- a/ts/views/conversation_view.tsx
+++ b/ts/views/conversation_view.tsx
@@ -450,7 +450,7 @@ export class ConversationView extends window.Backbone.View {
};
const markMessageRead = async (messageId: string) => {
- if (!window.isActive()) {
+ if (!window.SignalContext.activeWindowService.isActive()) {
return;
}
diff --git a/ts/window.d.ts b/ts/window.d.ts
index fba8fe74e..fc4b6362a 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -291,10 +291,10 @@ declare global {
waitForEmptyEventQueue: () => Promise;
getVersion: () => string;
i18n: LocalizerType;
- isActive: () => boolean;
isAfterVersion: (version: string, anotherVersion: string) => boolean;
isBeforeVersion: (version: string, anotherVersion: string) => boolean;
isFullScreen: () => boolean;
+ isMaximized: () => boolean;
initialTheme?: ThemeType;
libphonenumberInstance: {
parse: (number: string) => PhoneNumber;
@@ -303,12 +303,11 @@ declare global {
};
libphonenumberFormat: typeof PhoneNumberFormat;
nodeSetImmediate: typeof setImmediate;
- onFullScreenChange: (fullScreen: boolean) => void;
+ onFullScreenChange: (fullScreen: boolean, maximized: boolean) => void;
platform: string;
preloadedImages: Array;
reduxActions: ReduxActions;
reduxStore: Store;
- registerForActive: (handler: () => void) => void;
restart: () => void;
setImmediate: typeof setImmediate;
showWindow: () => void;
@@ -326,7 +325,6 @@ declare global {
systemTheme: WhatIsThis;
textsecure: typeof textsecure;
titleBarDoubleClick: () => void;
- unregisterForActive: (handler: () => void) => void;
updateTrayIcon: (count: number) => void;
Backbone: typeof Backbone;
CI?: CI;
diff --git a/ts/windows/about/preload.ts b/ts/windows/about/preload.ts
index 8ca4793fc..41b2fdbb3 100644
--- a/ts/windows/about/preload.ts
+++ b/ts/windows/about/preload.ts
@@ -36,8 +36,7 @@ contextBridge.exposeInMainWorld('SignalContext', {
environment: `${environmentText.join(' - ')}${platform}`,
i18n: SignalContext.i18n,
version: SignalContext.getVersion(),
- platform: process.platform,
- isWindows11: SignalContext.OS.isWindows11(),
+ hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
}),
document.getElementById('app')
diff --git a/ts/windows/context.ts b/ts/windows/context.ts
index b0f6f7741..20cc6f730 100644
--- a/ts/windows/context.ts
+++ b/ts/windows/context.ts
@@ -12,6 +12,7 @@ import type { LocaleMessagesType } from '../types/I18N';
import type { NativeThemeType } from '../context/createNativeThemeListener';
import type { SettingType } from '../util/preload';
import type { RendererConfigType } from '../types/RendererConfig';
+import { ActiveWindowService } from '../services/ActiveWindowService';
import { Bytes } from '../context/Bytes';
import { Crypto } from '../context/Crypto';
@@ -28,7 +29,10 @@ import { createSetting } from '../util/preload';
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
import { waitForSettingsChange } from './waitForSettingsChange';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
-import { isWindows, isWindows11, isLinux, isMacOS } from '../OS';
+import { isWindows, isLinux, isMacOS, hasCustomTitleBar } from '../OS';
+
+const activeWindowService = new ActiveWindowService();
+activeWindowService.initialize(window.document, ipcRenderer);
const params = new URLSearchParams(document.location.search);
const configParam = params.get('config');
@@ -58,6 +62,7 @@ export type SignalContextType = {
nativeThemeListener: NativeThemeType;
setIsCallActive: (isCallActive: boolean) => unknown;
+ activeWindowService: typeof activeWindowService;
Settings: {
themeSetting: SettingType;
waitForChange: () => Promise;
@@ -65,9 +70,9 @@ export type SignalContextType = {
OS: {
platform: string;
isWindows: typeof isWindows;
- isWindows11: typeof isWindows11;
isLinux: typeof isLinux;
isMacOS: typeof isMacOS;
+ hasCustomTitleBar: typeof hasCustomTitleBar;
};
config: RendererConfigType;
getAppInstance: () => string | undefined;
@@ -86,6 +91,7 @@ export type SignalContextType = {
};
export const SignalContext: SignalContextType = {
+ activeWindowService,
Settings: {
themeSetting: createSetting('themeSetting', { setter: false }),
waitForChange: waitForSettingsChange,
@@ -93,9 +99,9 @@ export const SignalContext: SignalContextType = {
OS: {
platform: process.platform,
isWindows,
- isWindows11,
isLinux,
isMacOS,
+ hasCustomTitleBar,
},
bytes: new Bytes(),
config,
diff --git a/ts/windows/debuglog/preload.ts b/ts/windows/debuglog/preload.ts
index ace528af0..a395e9834 100644
--- a/ts/windows/debuglog/preload.ts
+++ b/ts/windows/debuglog/preload.ts
@@ -26,8 +26,7 @@ contextBridge.exposeInMainWorld('SignalContext', {
ReactDOM.render(
React.createElement(DebugLogWindow, {
- platform: process.platform,
- isWindows11: SignalContext.OS.isWindows11(),
+ hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
closeWindow: () => SignalContext.executeMenuRole('close'),
downloadLog: (logText: string) =>
diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts
index 636aee433..e28e8a583 100644
--- a/ts/windows/main/phase1-ipc.ts
+++ b/ts/windows/main/phase1-ipc.ts
@@ -254,14 +254,17 @@ window.sendChallengeRequest = request => ipc.send('challenge:request', request);
{
let isFullScreen = Boolean(config.isMainWindowFullScreen);
+ let isMaximized = Boolean(config.isMainWindowMaximized);
window.isFullScreen = () => isFullScreen;
+ window.isMaximized = () => isMaximized;
// This is later overwritten.
window.onFullScreenChange = noop;
- ipc.on('full-screen-change', (_event, isFull) => {
- isFullScreen = Boolean(isFull);
- window.onFullScreenChange(isFullScreen);
+ ipc.on('window:set-window-stats', (_event, stats) => {
+ isFullScreen = Boolean(stats.isFullScreen);
+ isMaximized = Boolean(stats.isMaximized);
+ window.onFullScreenChange(isFullScreen, isMaximized);
});
}
diff --git a/ts/windows/main/phase2-dependencies.ts b/ts/windows/main/phase2-dependencies.ts
index baae91ffa..18936303c 100644
--- a/ts/windows/main/phase2-dependencies.ts
+++ b/ts/windows/main/phase2-dependencies.ts
@@ -1,7 +1,6 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import { ipcRenderer as ipc } from 'electron';
import Backbone from 'backbone';
import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber';
import * as React from 'react';
@@ -12,7 +11,6 @@ import PQueue from 'p-queue';
import { textsecure } from '../../textsecure';
import { imageToBlurHash } from '../../util/imageToBlurHash';
-import { ActiveWindowService } from '../../services/ActiveWindowService';
import * as Attachments from '../attachments';
import { setup } from '../../signal';
import { addSensitivePath } from '../../util/privacy';
@@ -44,14 +42,6 @@ window.imageToBlurHash = imageToBlurHash;
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
window.libphonenumberFormat = PhoneNumberFormat;
-const activeWindowService = new ActiveWindowService();
-activeWindowService.initialize(window.document, ipc);
-window.isActive = activeWindowService.isActive.bind(activeWindowService);
-window.registerForActive =
- activeWindowService.registerForActive.bind(activeWindowService);
-window.unregisterForActive =
- activeWindowService.unregisterForActive.bind(activeWindowService);
-
window.React = React;
window.ReactDOM = ReactDOM;
window.PQueue = PQueue;
diff --git a/ts/windows/settings/preload.ts b/ts/windows/settings/preload.ts
index 481eab411..323d79937 100644
--- a/ts/windows/settings/preload.ts
+++ b/ts/windows/settings/preload.ts
@@ -341,8 +341,7 @@ const renderPreferences = async () => {
i18n: SignalContext.i18n,
- platform: process.platform,
- isWindows11: SignalContext.OS.isWindows11(),
+ hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
executeMenuRole: SignalContext.executeMenuRole,
};