mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 11:06:16 -08:00
feat(shell): startup window mode, --fullscreen switch and F11 toggle (#1514)
Settings > General gains "Window on startup" (normal / maximized / fullscreen), Electron only, mirrored into the main-process config by SETTINGS_UPDATE and applied at the next window creation. `--fullscreen` forces one fullscreen launch (consumed by the first window). F11 toggles OS-level fullscreen through WINDOW:TOGGLE_FULLSCREEN — the exit path on Windows/Linux where the title bar is hidden — and is skipped while the player owns document.fullscreenElement. attachWindowStateEvents tracks native and HTML fullscreen as two flags, since Electron leaves only the HTML state when the window was already natively fullscreen. macOS ignores the constructor `fullscreen` option on a hidden window, so ready-to-show repeats the request after show(). Toggles are decided by an observe-only, event-fed tracker (native-fullscreen-transitions.ts), never against isFullScreen(). Closes #1455 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
078bd07d94
commit
fa9084fca3
57 files changed
+1650
-8
No files matched your search
+87
@@ -92,6 +92,93 @@ describe('WorkspaceKeyboardShortcutsService', () => {
|
||||
input.remove();
|
||||
});
|
||||
|
||||
describe('F11 window fullscreen toggle', () => {
|
||||
const testWindow = window as unknown as {
|
||||
electron?: Record<string, unknown>;
|
||||
};
|
||||
let toggleFullScreenWindow: jest.Mock;
|
||||
|
||||
function pressF11(
|
||||
init: KeyboardEventInit = {},
|
||||
target: EventTarget = document
|
||||
): KeyboardEvent {
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key: 'F11',
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
...init,
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
return event;
|
||||
}
|
||||
|
||||
function setHtmlFullscreenElement(element: Element | null): void {
|
||||
Object.defineProperty(document, 'fullscreenElement', {
|
||||
configurable: true,
|
||||
value: element,
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
toggleFullScreenWindow = jest.fn().mockResolvedValue({
|
||||
isMaximized: false,
|
||||
isFullScreen: true,
|
||||
});
|
||||
testWindow.electron = { toggleFullScreenWindow };
|
||||
setHtmlFullscreenElement(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete testWindow.electron;
|
||||
setHtmlFullscreenElement(null);
|
||||
});
|
||||
|
||||
it('toggles window fullscreen through the bridge and swallows the key', () => {
|
||||
const event = pressF11();
|
||||
|
||||
expect(toggleFullScreenWindow).toHaveBeenCalledTimes(1);
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
expect(dialog.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('works while typing in an input — it is the exit from a fullscreen launch', () => {
|
||||
const input = document.createElement('input');
|
||||
document.body.appendChild(input);
|
||||
|
||||
pressF11({}, input);
|
||||
|
||||
expect(toggleFullScreenWindow).toHaveBeenCalledTimes(1);
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it('leaves the key alone while the player owns HTML fullscreen', () => {
|
||||
setHtmlFullscreenElement(document.createElement('div'));
|
||||
|
||||
const event = pressF11();
|
||||
|
||||
expect(toggleFullScreenWindow).not.toHaveBeenCalled();
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
});
|
||||
|
||||
it('ignores modified and auto-repeated F11 presses', () => {
|
||||
pressF11({ ctrlKey: true });
|
||||
pressF11({ metaKey: true });
|
||||
pressF11({ altKey: true });
|
||||
pressF11({ shiftKey: true });
|
||||
pressF11({ repeat: true });
|
||||
|
||||
expect(toggleFullScreenWindow).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('leaves F11 to the browser without a bridge', () => {
|
||||
delete testWindow.electron;
|
||||
|
||||
const event = pressF11();
|
||||
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not open duplicate dialogs while one is active', () => {
|
||||
service.openShortcutsDialog();
|
||||
service.openShortcutsDialog();
|
||||
|
||||
+39
@@ -78,6 +78,10 @@ export class WorkspaceKeyboardShortcutsService {
|
||||
}
|
||||
|
||||
private handleKeydown(event: KeyboardEvent): void {
|
||||
if (this.handleWindowFullscreenToggle(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isTypingInInput(event) ||
|
||||
!isKeyboardShortcutHelpTrigger(event) ||
|
||||
@@ -90,6 +94,41 @@ export class WorkspaceKeyboardShortcutsService {
|
||||
this.openShortcutsDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* F11 toggles OS-level window fullscreen in the desktop app. It is the
|
||||
* way out of a fullscreen launch on Windows/Linux, where the title bar is
|
||||
* hidden and the window controls hide themselves while fullscreen, so it
|
||||
* is deliberately not gated by `isTypingInInput` — F11 is expected to
|
||||
* work from any focus. While the player owns HTML-element fullscreen
|
||||
* (`document.fullscreenElement`) its own F / Esc are in charge and F11
|
||||
* must not yank OS fullscreen out from under it. Without a bridge (PWA)
|
||||
* the browser keeps its own F11.
|
||||
*/
|
||||
private handleWindowFullscreenToggle(event: KeyboardEvent): boolean {
|
||||
if (
|
||||
event.key !== 'F11' ||
|
||||
event.repeat ||
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.altKey ||
|
||||
event.shiftKey
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bridge = window.electron;
|
||||
if (
|
||||
typeof bridge?.toggleFullScreenWindow !== 'function' ||
|
||||
document.fullscreenElement
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
void bridge.toggleFullScreenWindow().catch(() => undefined);
|
||||
return true;
|
||||
}
|
||||
|
||||
private getShortcutPlatform(): 'mac' | 'other' {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'other';
|
||||
|
||||
Reference in new issue
Block a user