Desktop Window Management#
Element Desktop (Electron) manages its main window through apps/desktop/src/electron-main.ts, which handles the full window lifecycle: creation, show/hide state, close behavior, single-instance enforcement, and crash recovery. Three behaviors are most commonly encountered in practice: tray icon toggling, Linux/Wayland taskbar binding, and GPU-crash blank windows.
Tray Icon and Window Toggle#
The tray is implemented in apps/desktop/src/tray.ts. Key behaviors:
- Creation: The tray icon is created at startup only when
minimizeToTrayis enabled in the user store . Tray is skipped entirely on macOS . - Toggle logic: Clicking the tray icon calls
toggleWin(). It hides the window if the window is visible, not minimized, and focused; otherwise it restores/shows and focuses it. - Close → minimize-to-tray: When a tray is active (or on macOS),
closeevents are intercepted — the window is hidden instead of destroyed .global.appQuittingis the escape hatch: set totrueviabefore-quit/before-quit-for-update, it lets the actual quit proceed . - Dynamic icon: The tray icon tracks the page favicon (
page-favicon-updated) and updates accordingly. On Windows the PNG is first converted to.ico. - Windows GUID: On Windows, the tray is constructed with a stable GUID derived from the cert subject name and user-data path, so Windows can persist user tray preferences across app restarts .
Wayland Window Binding / Taskbar Attachment (Linux)#
On Linux, GNOME and other desktops use StartupWMClass in the .desktop file to bind a running window to its launcher icon. Element Desktop ships StartupWMClass=Element (capital E), but the Electron process reports a lowercase WM_CLASS:
WM_CLASS(STRING) = "element", "element"
This case mismatch prevents GNOME Shell (Wayland or X11) from matching the window to the pinned launcher, causing a second generic-icon instance to appear in the dock instead of focusing the existing one . The stable package (element-desktop 1.12.22/1.12.23) still ships the incorrect value .
Root cause: electron-builder historically set StartupWMClass from productName ("Element") rather than the actual Electron window class. This is a known upstream bug (electron-userland/electron-builder#4974), addressed in electron-builder v26.15.0 (PR #9832). The Element repo is on electron-builder 26.15.3 but the generated .desktop file still needs verification . The Linux build config lives in apps/desktop/electron-builder.ts L138–143.
User workaround (takes effect without reboot):
- Create
~/.local/share/applications/element-desktop.desktopoverriding the system file. - Change
StartupWMClass=Element→StartupWMClass=element. - Restart the GNOME Shell session .
For system-wide fixes, editing /usr/share/applications/element-desktop.desktop and running sudo update-desktop-database also works .
Note on stable vs. nightly: The real WM_CLASS for stable appears to be element; for nightly it appears to be element-nightly. Proposals to fix via StartupWMClass: variant.name (element-desktop) may not produce the correct value — see PR #33635 and issue #33472 for the open discussion .
GPU Rendering Failures / Blank Windows#
GPU process crashes produce a permanent white/blank window — the user cannot recover without killing the app. This is an upstream Electron/Chromium issue (corrupted GPU cache or driver incompatibility) that cannot be fixed in-repo .
Automatic recovery (renderer-recovery.ts): setupRendererRecovery(win) is called on the main window at startup . It listens for render-process-gone and unresponsive events and automatically reloads the renderer. Key parameters :
- Cap: at most 3 reloads within a 60-second rolling window — after that a dialog is shown instead.
- Recoverable crash reasons:
crashed,oom,launch-failed,integrity-failure. - Excluded reasons:
clean-exit,killed,abnormal-exit,memory-eviction— these are intentional or ambiguous and are not reloaded . - The
recoverIfCrashed()method is also called onactivate(macOS dock click) andsecond-instanceevents so a hidden crashed window is healed before being shown .
Manual workarounds for persistent blank windows:
- Disable GPU acceleration at runtime: launch with
--disable-gpuflag . - Disable hardware acceleration persistently: set
disableHardwareAcceleration: truein the user store; the main process checks this at startup and callsapp.disableHardwareAcceleration()before any window is created . - Delete the GPU cache directory (
<userData>/GPUCache) and restart.
Tracked in: element-web#22656 (GPU error / blank window) and element-web#32222 (renderer crash auto-recovery).
Key Files#
| File | Purpose |
|---|---|
apps/desktop/src/electron-main.ts | Window creation, lifecycle events, single-instance lock, hardware acceleration flag |
apps/desktop/src/tray.ts | Tray icon creation, toggle logic, dynamic icon updates |
apps/desktop/src/renderer-recovery.ts | Auto-recovery from GPU/renderer crashes with crash-loop protection |
apps/desktop/electron-builder.ts | Build config including Linux .desktop file generation |