mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -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
@@ -298,6 +298,96 @@ handlers in `apps/electron-backend/src/app/events/window.events.ts`):
|
||||
stuck the maximize/restore glyph on the wrong icon. Regression coverage:
|
||||
`app-window-state.spec.ts` and `window-controls.e2e.ts`.
|
||||
|
||||
The pushed `isFullScreen` is the OR of two flags tracked apart: native
|
||||
(OS-level, fed by `enter/leave-full-screen`) and HTML-element (fed by
|
||||
the `*-html-*` pair). Electron remembers when the window was already
|
||||
natively fullscreen before the player entered HTML fullscreen and then
|
||||
leaves ONLY the HTML state on exit — no `leave-full-screen` fires and the
|
||||
window stays fullscreen — so a single flag cleared by
|
||||
`leave-html-full-screen` would un-hide the controls over a window that
|
||||
is still fullscreen. A fullscreen launch (below) or F11 followed by the
|
||||
player's `F` → `Esc` makes that path routine on Windows/Linux.
|
||||
3. `WINDOW:TOGGLE_FULLSCREEN` toggles OS-level fullscreen (`setFullScreen`)
|
||||
and, like the maximize toggle, reports the requested state and leaves
|
||||
the `WINDOW:STATE_CHANGED` push authoritative. Because the transition is
|
||||
asynchronous — `isFullScreen()` reports the old value until it lands,
|
||||
and on Windows even while the matching event fires — a toggle must
|
||||
never be decided against the getter. Every native fullscreen request
|
||||
goes through the tracker in
|
||||
`apps/electron-backend/src/app/services/native-fullscreen-transitions.ts`
|
||||
(the F11 toggle AND the startup fallback below). Like the state pushes
|
||||
above it keeps its own per-window fullscreen state, seeded from the
|
||||
getter once at window creation (`trackNativeFullScreen` in
|
||||
`initMainWindow`, while no transition can be in flight) and fed
|
||||
afterwards only by the `enter`/`leave-full-screen` events, which also
|
||||
cover transitions the app did not request. A toggle is decided against
|
||||
the pending target while a transition is in flight, else against that
|
||||
tracked state: two quick presses are an enter-then-exit, not two enters,
|
||||
and F11 during the startup animation leaves fullscreen instead of asking
|
||||
for it again. The tracker observes and never issues a request on its
|
||||
own. The pending record is the LATEST requested target: an event landing
|
||||
on it clears it, an event landing on the other state (an earlier request
|
||||
of a burst landed first; ours is still queued) leaves it in place so the
|
||||
next press still follows the user's latest intent, and a record older
|
||||
than `FULLSCREEN_TRANSITION_TIMEOUT_MS` (2 s) is ignored. Events cannot
|
||||
say which request they belong to, so any automatic "repeat the target"
|
||||
on a mismatch is indistinguishable from reversing the user's own
|
||||
green-button/Ctrl+Cmd+F action and is deliberately not done: should a
|
||||
platform ever drop a queued request (Electron queues them on macOS and
|
||||
applies them synchronously elsewhere), the record expires, the event-fed
|
||||
state takes over and the next press corrects the window. The renderer
|
||||
binds it to
|
||||
**F11** in `WorkspaceKeyboardShortcutsService` (deliberately not gated
|
||||
by `isTypingInInput` — it must work from any focus, because it is the
|
||||
only exit from a fullscreen launch on Windows/Linux, where the title bar
|
||||
is hidden and the controls hide themselves) and skips the key while
|
||||
`document.fullscreenElement` is set, since the player's own `F` / `Esc`
|
||||
own HTML fullscreen and F11 must not yank OS fullscreen out from under
|
||||
it. Without a bridge (PWA) F11 is left to the browser.
|
||||
|
||||
Startup window mode (`Settings.startupWindowMode`, issue #1455):
|
||||
|
||||
1. `normal` (default) / `maximized` / `fullscreen`, chosen in Settings →
|
||||
General ("Window on startup"). Electron only — the select renders only
|
||||
when `RuntimeCapabilitiesService.supportsStartupWindowMode` sees both
|
||||
`updateSettings` and `toggleFullScreenWindow` on the bridge, so the mode
|
||||
is never offered without its F11 exit.
|
||||
2. Settings live in the renderer's IndexedDB, which the main process cannot
|
||||
read at window creation, so the `SETTINGS_UPDATE` handler mirrors the
|
||||
value into electron-conf (`STARTUP_WINDOW_MODE`, the same pattern as the
|
||||
frame-copy flag) and `initMainWindow` reads it synchronously. A change
|
||||
therefore applies on the next launch. Both sides normalize through
|
||||
`normalizeStartupWindowMode`, so junk never reaches the config file or
|
||||
the window options.
|
||||
3. `fullscreen` is the `BrowserWindow` constructor option: on Windows/Linux
|
||||
the window is created hidden and enters fullscreen before its first
|
||||
paint. macOS ignores the option while the window is hidden (an NSWindow
|
||||
only toggles fullscreen once it is on screen), so `ready-to-show` repeats
|
||||
the request with `setFullScreen(true)` right after `show()` wherever
|
||||
`isFullScreen()` is still false — never unconditionally, or the
|
||||
platforms that honoured the option would animate a second toggle. The
|
||||
saved bounds stay spread into the options — they are the normal bounds
|
||||
the window returns to, and the close handler keeps persisting
|
||||
`getNormalBounds()`. `maximized` calls `maximize()` inside
|
||||
`ready-to-show` right before `show()`, never earlier: `maximize()` on a
|
||||
hidden window shows it, and a blank window would flash.
|
||||
4. `iptvnator --fullscreen` (read via `app.commandLine.hasSwitch`, so it can
|
||||
sit anywhere in argv; the playlist-path extractor already skips every
|
||||
`-`-prefixed argument) forces `fullscreen` for that launch only and is
|
||||
never persisted. Resolution lives in
|
||||
`apps/electron-backend/src/app/services/startup-window-mode.ts`. The
|
||||
switch is consumed by the first window (`launchFullscreenSwitchConsumed`
|
||||
in `App`): on macOS the process outlives its last window and the Dock
|
||||
re-creates it through the same `initMainWindow`, which must then follow
|
||||
the stored setting only. A second-instance launch carrying the switch is
|
||||
ignored — the window already exists.
|
||||
5. Deliberately not offered: kiosk mode (removes the exit path) and
|
||||
"remember last state" (bounds persistence stores normal bounds only; an
|
||||
explicit choice is clearer). Regression coverage: `app.spec.ts`
|
||||
("startup window mode"), `settings.events.spec.ts`,
|
||||
`window.events.spec.ts`, and the startup-window-mode cases in
|
||||
`settings.e2e.ts`.
|
||||
|
||||
Layout integration:
|
||||
|
||||
1. `document.body` gets a `frameless-platform` class (set in
|
||||
|
||||
Reference in new issue
Block a user