Titlebar fixes
This commit is contained in:
parent
f273333046
commit
f92be05b15
41 changed files with 225 additions and 154 deletions
|
@ -9,6 +9,9 @@
|
|||
type="text/css"
|
||||
/>
|
||||
<script>
|
||||
// eslint-disable-next-line
|
||||
const noop = () => {};
|
||||
|
||||
window.SignalWindow = window.SignalWindow || {};
|
||||
window.SignalWindow.log = {
|
||||
fatal: console.error.bind(console),
|
||||
|
@ -19,19 +22,27 @@
|
|||
trace: console.trace.bind(console),
|
||||
};
|
||||
window.SignalContext = {
|
||||
activeWindowService: {
|
||||
isActive: () => true;
|
||||
registerForActive: noop,
|
||||
unregisterForActive: noop,
|
||||
registerForChange: noop,
|
||||
unregisterForChange: noop,
|
||||
},
|
||||
|
||||
nativeThemeListener: {
|
||||
getSystemValue: async () => 'light',
|
||||
subscribe: () => {},
|
||||
unsubscribe: () => {},
|
||||
subscribe: noop,
|
||||
unsubscribe: noop,
|
||||
},
|
||||
Settings: {
|
||||
themeSetting: {
|
||||
getValue: async () => 'light',
|
||||
},
|
||||
waitForChange: () => {},
|
||||
waitForChange: noop,
|
||||
},
|
||||
OS: {
|
||||
isWindows11: () => false,
|
||||
hasCustomTitleBar: () => false,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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
|
||||
|
|
28
app/main.ts
28
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<TitleBarOverlayOptions | false> {
|
||||
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 {
|
||||
|
|
|
@ -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 (
|
||||
<TitleBarContainer
|
||||
iconSrc="../../images/icon_32.png"
|
||||
platform={platform}
|
||||
isWindows11={isWindows11}
|
||||
hasCustomTitleBar={hasCustomTitleBar}
|
||||
theme={theme}
|
||||
executeMenuRole={executeMenuRole}
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ const ColdRoot = () => (
|
|||
<I18n messages={localeMessages} locale={SignalContext.config.locale}>
|
||||
<App
|
||||
executeMenuRole={SignalContext.executeMenuRole}
|
||||
platform={SignalContext.OS.platform}
|
||||
isWindows11={SignalContext.OS.isWindows11()}
|
||||
hasCustomTitleBar={SignalContext.OS.hasCustomTitleBar()}
|
||||
/>
|
||||
</I18n>
|
||||
</Router>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
8
ts/OS.ts
8
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);
|
||||
|
|
|
@ -1835,7 +1835,9 @@ export async function startApp(): Promise<void> {
|
|||
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<void> {
|
|||
});
|
||||
|
||||
// Maybe refresh remote configuration when we become active
|
||||
window.registerForActive(async () => {
|
||||
activeWindowService.registerForActive(async () => {
|
||||
strictAssert(server !== undefined, 'WebAPI not ready');
|
||||
|
||||
try {
|
||||
|
|
|
@ -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 (
|
||||
<TitleBarContainer
|
||||
platform={platform}
|
||||
isWindows11={isWindows11}
|
||||
hasCustomTitleBar={hasCustomTitleBar}
|
||||
theme={theme}
|
||||
executeMenuRole={executeMenuRole}
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -26,8 +26,7 @@ const createProps = (): PropsType => ({
|
|||
return 'https://picsum.photos/1800/900';
|
||||
},
|
||||
executeMenuRole: action('executeMenuRole'),
|
||||
platform: 'win32',
|
||||
isWindows11: false,
|
||||
hasCustomTitleBar: true,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
@ -31,8 +31,7 @@ export type PropsType = {
|
|||
i18n: LocalizerType;
|
||||
fetchLogs: () => Promise<string>;
|
||||
uploadLogs: (logs: string) => Promise<string>;
|
||||
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>(LoadState.NotStarted);
|
||||
|
@ -147,8 +145,7 @@ export const DebugLogWindow = ({
|
|||
|
||||
return (
|
||||
<TitleBarContainer
|
||||
platform={platform}
|
||||
isWindows11={isWindows11}
|
||||
hasCustomTitleBar={hasCustomTitleBar}
|
||||
theme={theme}
|
||||
executeMenuRole={executeMenuRole}
|
||||
>
|
||||
|
@ -191,8 +188,7 @@ export const DebugLogWindow = ({
|
|||
|
||||
return (
|
||||
<TitleBarContainer
|
||||
platform={platform}
|
||||
isWindows11={isWindows11}
|
||||
hasCustomTitleBar={hasCustomTitleBar}
|
||||
theme={theme}
|
||||
executeMenuRole={executeMenuRole}
|
||||
>
|
||||
|
|
|
@ -158,8 +158,7 @@ const createProps = (): PropsType => ({
|
|||
i18n,
|
||||
|
||||
executeMenuRole: action('executeMenuRole'),
|
||||
platform: 'win32',
|
||||
isWindows11: false,
|
||||
hasCustomTitleBar: true,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
@ -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 (
|
||||
<TitleBarContainer
|
||||
platform={platform}
|
||||
isWindows11={isWindows11}
|
||||
hasCustomTitleBar={hasCustomTitleBar}
|
||||
theme={theme}
|
||||
executeMenuRole={executeMenuRole}
|
||||
>
|
||||
|
|
|
@ -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 (
|
||||
<div className="TitleBarContainer">
|
||||
<TitleBar
|
||||
<div
|
||||
className={classNames(
|
||||
'TitleBarContainer__title',
|
||||
|
||||
// Add a pixel of padding on non-maximized Windows 11 titlebar.
|
||||
isWindows11 && !isMaximized
|
||||
? 'TitleBarContainer__title--extra-padding'
|
||||
: null
|
||||
'TitleBarContainer',
|
||||
isWindowActive ? 'TitleBarContainer--active' : null
|
||||
)}
|
||||
platform={platform}
|
||||
>
|
||||
<div className="TitleBarContainer__padding" />
|
||||
<div className="TitleBarContainer__content">{children}</div>
|
||||
|
||||
<TitleBar
|
||||
className="TitleBarContainer__title"
|
||||
platform="win32"
|
||||
iconSrc={iconSrc}
|
||||
theme={titleBarTheme}
|
||||
maximized={isMaximized}
|
||||
|
@ -254,8 +256,6 @@ export const TitleBarContainer = (props: PropsType): JSX.Element => {
|
|||
onDoubleClick={titleBarDoubleClick}
|
||||
hideControls
|
||||
/>
|
||||
|
||||
<div className="TitleBarContainer__content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -36,11 +36,6 @@ export default {
|
|||
// eslint-disable-next-line
|
||||
const noop = () => {};
|
||||
|
||||
Object.assign(window, {
|
||||
registerForActive: noop,
|
||||
unregisterForActive: noop,
|
||||
});
|
||||
|
||||
const items: Record<string, TimelineItemType> = {
|
||||
'id-1': {
|
||||
type: 'message',
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
23
ts/hooks/useIsWindowActive.ts
Normal file
23
ts/hooks/useIsWindowActive.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,4 +14,8 @@
|
|||
}
|
||||
|
||||
document.body.classList.add(className);
|
||||
|
||||
if (window.SignalContext.OS.hasCustomTitleBar()) {
|
||||
document.body.classList.add('os-has-custom-titlebar');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: () => <SmartCallManager />,
|
||||
renderCustomizingPreferredReactionsModal: () => (
|
||||
|
|
|
@ -53,7 +53,8 @@ async function notifyForCall(
|
|||
isVideoCall: boolean
|
||||
): Promise<void> {
|
||||
const shouldNotify =
|
||||
!window.isActive() && window.Events.getCallSystemNotification();
|
||||
!window.SignalContext.activeWindowService.isActive() &&
|
||||
window.Events.getCallSystemNotification();
|
||||
if (!shouldNotify) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -450,7 +450,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
};
|
||||
|
||||
const markMessageRead = async (messageId: string) => {
|
||||
if (!window.isActive()) {
|
||||
if (!window.SignalContext.activeWindowService.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
6
ts/window.d.ts
vendored
6
ts/window.d.ts
vendored
|
@ -291,10 +291,10 @@ declare global {
|
|||
waitForEmptyEventQueue: () => Promise<void>;
|
||||
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<WhatIsThis>;
|
||||
reduxActions: ReduxActions;
|
||||
reduxStore: Store<StateType>;
|
||||
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;
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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<IPCEventsValuesType['themeSetting']>;
|
||||
waitForChange: () => Promise<void>;
|
||||
|
@ -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,
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -341,8 +341,7 @@ const renderPreferences = async () => {
|
|||
|
||||
i18n: SignalContext.i18n,
|
||||
|
||||
platform: process.platform,
|
||||
isWindows11: SignalContext.OS.isWindows11(),
|
||||
hasCustomTitleBar: SignalContext.OS.hasCustomTitleBar(),
|
||||
executeMenuRole: SignalContext.executeMenuRole,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue