mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
fix(desktop): leave full screen before hiding on macOS close (#3284)
This commit is contained in:
1 parent
46a356cc40
commit
e7859fefb1
3 files changed
+158
-2
No files matched your search
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {EventEmitter} from 'node:events';
|
||||||
|
import {readFileSync} from 'node:fs';
|
||||||
|
import {createRequire} from 'node:module';
|
||||||
|
import {describe, test} from 'node:test';
|
||||||
|
import {fileURLToPath} from 'node:url';
|
||||||
|
import vm from 'node:vm';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
|
const sourcePath = fileURLToPath(new URL('./MacFullScreenHide.ts', import.meta.url));
|
||||||
|
const transformedSource = esbuild.transformSync(readFileSync(sourcePath, 'utf8'), {
|
||||||
|
loader: 'ts',
|
||||||
|
format: 'cjs',
|
||||||
|
platform: 'node',
|
||||||
|
target: 'node20',
|
||||||
|
}).code;
|
||||||
|
|
||||||
|
function loadMacFullScreenHide() {
|
||||||
|
const module = {exports: {}};
|
||||||
|
const context = vm.createContext({module, exports: module.exports, process: {platform: 'darwin'}});
|
||||||
|
vm.runInContext(transformedSource, context, {filename: sourcePath});
|
||||||
|
return module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeWindow extends EventEmitter {
|
||||||
|
fullScreen;
|
||||||
|
visible = true;
|
||||||
|
destroyed = false;
|
||||||
|
calls = [];
|
||||||
|
|
||||||
|
constructor(fullScreen) {
|
||||||
|
super();
|
||||||
|
this.fullScreen = fullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDestroyed() {
|
||||||
|
return this.destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
isFullScreen() {
|
||||||
|
return this.fullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFullScreen(flag) {
|
||||||
|
this.calls.push(`setFullScreen(${flag})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.calls.push('hide');
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
finishLeavingFullScreen() {
|
||||||
|
this.fullScreen = false;
|
||||||
|
this.emit('leave-full-screen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MacFullScreenHide', () => {
|
||||||
|
test('hides a windowed window at once', () => {
|
||||||
|
const {hideWindowLeavingFullScreen} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(false);
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
assert.deepEqual(window.calls, ['hide']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('leaves full screen on macOS and hides only after the transition ends', () => {
|
||||||
|
const {hideWindowLeavingFullScreen} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(true);
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
assert.deepEqual(window.calls, ['setFullScreen(false)']);
|
||||||
|
assert.equal(window.visible, true);
|
||||||
|
window.finishLeavingFullScreen();
|
||||||
|
assert.deepEqual(window.calls, ['setFullScreen(false)', 'hide']);
|
||||||
|
assert.equal(window.visible, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a second close during the transition does not queue another exit', () => {
|
||||||
|
const {hideWindowLeavingFullScreen} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(true);
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
window.finishLeavingFullScreen();
|
||||||
|
assert.deepEqual(window.calls, ['setFullScreen(false)', 'hide']);
|
||||||
|
assert.equal(window.listenerCount('leave-full-screen'), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('showing the window during the transition cancels the pending hide', () => {
|
||||||
|
const {hideWindowLeavingFullScreen, cancelPendingFullScreenHide} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(true);
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
cancelPendingFullScreenHide(window);
|
||||||
|
window.finishLeavingFullScreen();
|
||||||
|
assert.deepEqual(window.calls, ['setFullScreen(false)']);
|
||||||
|
assert.equal(window.visible, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not touch a window destroyed before the transition ends', () => {
|
||||||
|
const {hideWindowLeavingFullScreen} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(true);
|
||||||
|
hideWindowLeavingFullScreen(window, 'darwin');
|
||||||
|
window.destroyed = true;
|
||||||
|
window.finishLeavingFullScreen();
|
||||||
|
assert.deepEqual(window.calls, ['setFullScreen(false)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides directly on other platforms', () => {
|
||||||
|
const {hideWindowLeavingFullScreen} = loadMacFullScreenHide();
|
||||||
|
const window = new FakeWindow(true);
|
||||||
|
hideWindowLeavingFullScreen(window, 'win32');
|
||||||
|
assert.deepEqual(window.calls, ['hide']);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
export interface FullScreenHideableWindow {
|
||||||
|
isDestroyed(): boolean;
|
||||||
|
isFullScreen(): boolean;
|
||||||
|
setFullScreen(flag: boolean): void;
|
||||||
|
hide(): void;
|
||||||
|
once(event: 'leave-full-screen', listener: () => void): unknown;
|
||||||
|
removeListener(event: 'leave-full-screen', listener: () => void): unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingHides = new WeakMap<FullScreenHideableWindow, () => void>();
|
||||||
|
|
||||||
|
export function hideWindowLeavingFullScreen(
|
||||||
|
window: FullScreenHideableWindow,
|
||||||
|
platform: NodeJS.Platform = process.platform,
|
||||||
|
): void {
|
||||||
|
if (platform !== 'darwin' || !window.isFullScreen()) {
|
||||||
|
window.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pendingHides.has(window)) return;
|
||||||
|
const hide = () => {
|
||||||
|
pendingHides.delete(window);
|
||||||
|
if (!window.isDestroyed()) window.hide();
|
||||||
|
};
|
||||||
|
pendingHides.set(window, hide);
|
||||||
|
window.once('leave-full-screen', hide);
|
||||||
|
window.setFullScreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelPendingFullScreenHide(window: FullScreenHideableWindow): void {
|
||||||
|
const hide = pendingHides.get(window);
|
||||||
|
if (!hide) return;
|
||||||
|
pendingHides.delete(window);
|
||||||
|
window.removeListener('leave-full-screen', hide);
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import {isNativeGatewayAvailable} from '@electron/main/GatewaySocketNativeBounda
|
|||||||
import {shouldDisableV8CodeCache} from '@electron/main/LaunchOptions';
|
import {shouldDisableV8CodeCache} from '@electron/main/LaunchOptions';
|
||||||
import {getDesktopLocalAppAuthorization} from '@electron/main/LocalAppProtocolAuthorization';
|
import {getDesktopLocalAppAuthorization} from '@electron/main/LocalAppProtocolAuthorization';
|
||||||
import {isLocalAppRendererDocumentURL, isLocalAppURL} from '@electron/main/LocalAppURL';
|
import {isLocalAppRendererDocumentURL, isLocalAppURL} from '@electron/main/LocalAppURL';
|
||||||
|
import {cancelPendingFullScreenHide, hideWindowLeavingFullScreen} from '@electron/main/MacFullScreenHide';
|
||||||
import {t} from '@electron/main/MainI18n';
|
import {t} from '@electron/main/MainI18n';
|
||||||
import {MainWindowRevealGate, MainWindowRevealReason} from '@electron/main/MainWindowReveal';
|
import {MainWindowRevealGate, MainWindowRevealReason} from '@electron/main/MainWindowReveal';
|
||||||
import {signalMainWindowCreated, signalMainWindowReady} from '@electron/main/ModuleBootHandoff';
|
import {signalMainWindowCreated, signalMainWindowReady} from '@electron/main/ModuleBootHandoff';
|
||||||
@@ -874,7 +875,7 @@ export function createWindow(options: CreateWindowOptions = {}): BrowserWindow {
|
|||||||
? 'Window close hid the app. Use Quit to terminate the process'
|
? 'Window close hid the app. Use Quit to terminate the process'
|
||||||
: 'Window close hid the app to the tray. Use Quit to terminate the process',
|
: 'Window close hid the app to the tray. Use Quit to terminate the process',
|
||||||
);
|
);
|
||||||
mainWindow?.hide();
|
if (mainWindow) hideWindowLeavingFullScreen(mainWindow);
|
||||||
refreshDesktopTrayMenu();
|
refreshDesktopTrayMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1140,6 +1141,7 @@ export function showWindow(): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
|
cancelPendingFullScreenHide(mainWindow);
|
||||||
if (mainWindow.isMinimized()) {
|
if (mainWindow.isMinimized()) {
|
||||||
mainWindow.restore();
|
mainWindow.restore();
|
||||||
}
|
}
|
||||||
@@ -1180,7 +1182,7 @@ export function showWindow(): void {
|
|||||||
|
|
||||||
export function hideWindow(): void {
|
export function hideWindow(): void {
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.hide();
|
hideWindowLeavingFullScreen(mainWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user