Linux display backend detector wayland x11
Skill kjuhwa/skills-hub/skills/launcher/linux-display-backend-detector-wayland-x11
Detect whether the current session is Wayland or X11, auto-route to XWayland for global hotkey support, and handle compositor-specific overrides (e.g. Niri which has no XWayland). Exports an Electron args array ready for exec.From its SKILL.md
npx -y skills add kjuhwa/skills-hub --skill linux-display-backend-detector-wayland-x11Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
6.7 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Linux Display Backend Detector (Wayland / X11)
When to use
Use this pattern when writing a launcher script for an Electron application on Linux that:
- Must run on both X11 and Wayland hosts.
- Uses global hotkeys (which require X11/XWayland; native Wayland Electron does not yet support global hotkeys universally).
- Must handle compositor edge cases — specifically compositors without XWayland (like Niri), which must use native Wayland even when global hotkeys are desired.
- Should allow a user override (
MYAPP_USE_WAYLAND=1) to opt into native Wayland when they knowingly accept the hotkey limitation.
Pattern
The detector runs three checks in order:
- Session type — check
$WAYLAND_DISPLAYand$DISPLAYenv vars. - Compositor override — if Wayland, check
$XDG_CURRENT_DESKTOPand compositor-specific socket env vars for compositors with no XWayland. - User override — check
$MYAPP_USE_WAYLANDto allow forcing native Wayland mode.
Then assemble an electron_args array with the appropriate --ozone-platform flag.
Package type affects --no-sandbox
- AppImage: always needs
--no-sandbox(FUSE constraints prevent the setuid sandbox). - deb/rpm/nix: need
--no-sandboxonly on Wayland (Electron sandbox detection is broken for some Wayland setups). - X11: never needs
--no-sandboxfor security reasons.
Minimal example
#!/usr/bin/env bash
# Sourced as a library; call detect_display_backend then build_electron_args
# Sets: is_wayland (bool string), use_x11_on_wayland (bool string)
detect_display_backend() {
is_wayland=false
[[ -n "${WAYLAND_DISPLAY:-}" ]] && is_wayland=true
use_x11_on_wayland=true
[[ "${MYAPP_USE_WAYLAND:-}" == '1' ]] && use_x11_on_wayland=false
# Compositors with no XWayland: force native Wayland
if [[ $is_wayland == true && $use_x11_on_wayland == true ]]; then
local desktop="${XDG_CURRENT_DESKTOP:-}"
desktop="${desktop,,}" # lowercase
# Niri compositor example: check socket env var AND desktop name
# XDG_CURRENT_DESKTOP can be colon-separated ("niri:GNOME")
if [[ -n "${NIRI_SOCKET:-}" || "$desktop" == *niri* ]]; then
log_message "No-XWayland compositor detected — forcing native Wayland"
use_x11_on_wayland=false
fi
fi
}
# Sets: electron_args array
# $1 = "appimage" | "deb" | "nix"
build_electron_args() {
local package_type="${1:-deb}"
electron_args=()
# AppImage always needs --no-sandbox (FUSE)
[[ $package_type == 'appimage' ]] && electron_args+=('--no-sandbox')
# Disable custom title bar (better Linux WM integration)
electron_args+=('--disable-features=CustomTitlebar')
if [[ $is_wayland != true ]]; then
# Pure X11 session — no extra flags needed
return
fi
# Wayland: deb/nix also need --no-sandbox
[[ $package_type == 'deb' || $package_type == 'nix' ]] \
&& electron_args+=('--no-sandbox')
if [[ $use_x11_on_wayland == true ]]; then
# Default: XWayland mode for global hotkey support
electron_args+=('--ozone-platform=x11')
else
# Native Wayland mode (user opt-in or compositor forces it)
electron_args+=('--enable-features=UseOzonePlatform,WaylandWindowDecorations')
electron_args+=('--ozone-platform=wayland')
electron_args+=('--enable-wayland-ime')
electron_args+=('--wayland-text-input-version=3')
fi
}
# Usage in launcher:
setup_logging
detect_display_backend
build_electron_args 'deb' # or 'appimage' or 'nix'
electron_args+=("$app_path") # app path must be last
exec "$electron_exec" "${electron_args[@]}" "$@"
Why this works
Global hotkeys require X11
Electron's global shortcut API (globalShortcut.register) relies on X11's XGrabKey mechanism on Linux. Native Wayland does not expose an equivalent API that Electron currently uses. Running the Electron app in XWayland mode (--ozone-platform=x11) even in a Wayland session preserves hotkey functionality because the app interacts with the X server through XWayland.
XDG_CURRENT_DESKTOP can be colon-separated
Some compositors set XDG_CURRENT_DESKTOP=niri:GNOME or similar compound values. Glob matching with *niri* handles this correctly whereas a plain == equality check would fail. Always lowercase and glob-match.
Compositor-specific socket env vars are more reliable than desktop name
$NIRI_SOCKET (or similar compositor-managed env vars) is set by the compositor itself and is not affected by user customization of XDG_CURRENT_DESKTOP. Checking it first, then falling back to the desktop name, gives two independent signals.
--no-sandbox placement
The --no-sandbox flag must come before the app path in the Electron args array. Chromium parses flags in order; anything after the app path is treated as app arguments, not Chromium flags.
IME flags for Wayland
--enable-wayland-ime and --wayland-text-input-version=3 are required for IBus and Fcitx5 to work in native Wayland mode. They are harmless no-ops in XWayland mode but should only be added in native Wayland mode to avoid confusion.
Pitfalls
- Do not check
$XDG_SESSION_TYPE— it is less reliable than$WAYLAND_DISPLAY. Some Wayland sessions do not setXDG_SESSION_TYPE=wayland(especially in containers or when launched viastartx/startwaylandscripts). - Do not run the display check from a TTY — if both
$DISPLAYand$WAYLAND_DISPLAYare unset, exit with a user-friendly error rather than trying to launch a headless Electron app (which will crash with a cryptic error). electron_argsmust be a proper Bash array — not a space-delimited string. Paths with spaces in$app_pathwill break if you use string concatenation.- AppImage FUSE and
--no-sandbox— AppImages use FUSE to mount themselves. FUSE mounts do not support the setuid bit required by the Chromium sandbox. Always add--no-sandboxfor AppImage launchers regardless of session type.
Source reference
scripts/launcher-common.sh — functions detect_display_backend and build_electron_args
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most data backend skills give in ~1.5k tokens
Counted across 229 of the 229 authors here whose files we hold, read 2026-08-07
- Separate business logic into service layersin 22 of 229, across 15 files
- Retry failures with exponential backoffin 21 of 229, across 14 files
- Select only needed database columnsin 20 of 229, across 13 files
- Abstract data access into repository classesin 19 of 229, across 12 files
- Use centralized error handlersin 17 of 229, across 10 files
- Use AsNoTracking for read-only queriesin 16 of 229, across 4 files
- Use async/await for all I/O operationsin 16 of 229, across 5 files
- Implement structured loggingin 15 of 229, across 4 files
- Use dependency injection for all servicesin 14 of 229, across 2 files
- Use resource-based URLs for REST APIsin 13 of 229, across 7 files
- Invalidate cache after data changesin 13 of 229, across 9 files
- Use a dependency injection containerin 12 of 229, across 4 files
Said here and by no other author read
- check session type using environment variables
- check for compositors without xwayland support
- check for user override to force native wayland
- assemble an electron arguments array
- add the no sandbox flag for appimage packages
- add the no sandbox flag on wayland for deb packages
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.