Skip to content

Instantly share code, notes, and snippets.

@felipealfonsog
Created January 25, 2026 10:37
Show Gist options
  • Select an option

  • Save felipealfonsog/1f0ba48bdbcbf585f1356bf040b069fb to your computer and use it in GitHub Desktop.

Select an option

Save felipealfonsog/1f0ba48bdbcbf585f1356bf040b069fb to your computer and use it in GitHub Desktop.
A minimal, manual healthcheck and stabilization tool for Chromium/Electron Wayland issues on Arch KDE.
#!/usr/bin/env bash
set -euo pipefail
# wayland-ozone-doctor - v 2
# Manual healthcheck + repair for Chromium/Electron Wayland resize/compositor issues (Arch/KDE).
# Commands:
# - check : short headless Wayland probe (colors + spinner)
# - fix : apply max-stability configs (force X11) + write per-app flags files (best-effort)
# - fix-wrap : create per-app wrappers in ~/.local/bin (strongest per-app enforcement)
# - install : self-install to ~/.local/bin/wayland-ozone-doctor
# - status : show detected apps + config presence
# - readme : operating guide / FAQ (English)
VERSION="1.1.0"
# ----------------------------
# UI helpers (colors + spinner)
# ----------------------------
if [[ -t 1 ]]; then
RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; BLUE=$'\033[34m'
BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m'
else
RED=""; GREEN=""; YELLOW=""; BLUE=""; BOLD=""; DIM=""; RESET=""
fi
spinner() {
local pid="$1"
local msg="$2"
local spin='|/-\'
local i=0
printf "%s%s%s " "${BLUE}${BOLD}" "$msg" "${RESET}"
while kill -0 "$pid" 2>/dev/null; do
printf "\b%s" "${spin:i++%${#spin}:1}"
sleep 0.08
done
printf "\b"
}
ok() { printf "%s✔%s %s\n" "${GREEN}${BOLD}" "${RESET}" "$1"; }
warn() { printf "%s!%s %s\n" "${YELLOW}${BOLD}" "${RESET}" "$1"; }
bad() { printf "%s✘%s %s\n" "${RED}${BOLD}" "${RESET}" "$1"; }
info() { printf "%s%s%s\n" "${DIM}" "$1" "${RESET}"; }
die() { bad "$1"; exit "${2:-1}"; }
# ----------------------------
# Paths / Config
# ----------------------------
SELF_PATH="$(readlink -f "${0}")"
INSTALL_DIR="${HOME}/.local/bin"
INSTALL_PATH="${INSTALL_DIR}/wayland-ozone-doctor"
ENV_DIR="${HOME}/.config/environment.d"
ENV_FILE="${ENV_DIR}/99-stable-x11.conf"
TIMEOUT_S="${TIMEOUT_S:-6}"
LOG_LINES="${LOG_LINES:-10}"
# Error patterns that strongly correlate with "Wayland path still unhealthy"
BAD_RE='GLib: g_main_context_pop_thread_default|SharedImageManager::ProduceSkia|wl_display|Wayland.*(error|failed)|ozone.*wayland.*(error|failed)|XDG_RUNTIME_DIR.*(invalid|not set)'
# Candidates for engine-layer probe
CANDIDATES=( "chromium" "google-chrome-stable" )
# Flags: stable posture for your exact issue
FLAGS_X11_STABLE=$'--ozone-platform=x11\n--disable-gpu\n'
# "Known" per-app flags filenames (best effort)
declare -A APP_FLAGS_FILES=(
["google-chrome-stable"]="${HOME}/.config/chrome-flags.conf"
["chromium"]="${HOME}/.config/chromium-flags.conf"
["electron"]="${HOME}/.config/electron-flags.conf"
["code"]="${HOME}/.config/code-flags.conf"
["codium"]="${HOME}/.config/codium-flags.conf"
)
# Common Electron apps (best-effort detection)
# Add/remove as you wish; wrappers are created only if the binary exists.
ELECTRON_APPS=(
slack
discord
signal-desktop
obsidian
teams
telegram-desktop
spotify
code
codium
)
# ----------------------------
# Helpers
# ----------------------------
have_cmd() { command -v "$1" >/dev/null 2>&1; }
write_file_atomic() {
local path="$1"
local content="$2"
local tmp
tmp="$(mktemp)"
printf "%s" "$content" >"$tmp"
mkdir -p "$(dirname "$path")"
if [[ -f "$path" ]]; then
cp -a "$path" "${path}.bak.$(date +%F_%H%M%S)" || true
fi
mv -f "$tmp" "$path"
}
detect_apps() {
# Prints detected relevant apps (one per line)
local found=()
for k in "${!APP_FLAGS_FILES[@]}"; do
if [[ "$k" == "electron" ]]; then
continue
fi
if have_cmd "$k"; then
found+=("$k")
fi
done
# If any known Electron app exists, include logical target "electron" for electron-flags.conf.
local electronish=0
for x in "${ELECTRON_APPS[@]}"; do
if have_cmd "$x"; then
electronish=1
break
fi
done
if [[ "$electronish" -eq 1 ]]; then
found+=("electron")
fi
printf "%s\n" "${found[@]:-}"
}
real_path_for_cmd() {
local c="$1"
# prefer /usr/bin if present; fallback to PATH resolution
if [[ -x "/usr/bin/$c" ]]; then
printf "%s" "/usr/bin/$c"
else
command -v "$c" 2>/dev/null || true
fi
}
# ----------------------------
# README / FAQ (built in)
# ----------------------------
show_readme() {
cat <<'EOF'
Wayland Ozone Doctor (Arch/KDE) — Operating Guide
=================================================
What this is
------------
A minimal, manual healthcheck + mitigation tool for Chromium/Electron Wayland issues
(e.g., auto-resizing windows, broken settings UI, compositor/rendering glitches).
It provides:
- check : short headless Wayland probe for Chromium engines (cheap smoke test)
- fix : maximum-stability posture (force X11) via environment.d + per-app flags files
- fix-wrap : strongest per-app enforcement via wrappers in ~/.local/bin
- status : operational visibility (what is installed / configured)
Why this exists
---------------
Wayland + compositor + GPU + Chromium/Electron can misbehave in rolling environments.
This tool prioritizes deterministic stability. You decide when/if to experiment with Wayland again.
Recommended workflow
--------------------
1) Apply stable fix (once):
wayland-ozone-doctor fix
Then LOG OUT fully and log back in (required).
2) If some Electron apps still misbehave depending on how they are launched:
wayland-ozone-doctor fix-wrap
This creates wrappers in ~/.local/bin that force X11 for those apps.
3) When you want to reassess Wayland viability:
wayland-ozone-doctor check
If "clean", you may experiment later (one app at a time).
What "fix" changes
------------------
- Creates/updates: ~/.config/environment.d/99-stable-x11.conf
Forces X11 for Chromium/Electron/GTK/Qt:
OZONE_PLATFORM=x11
ELECTRON_OZONE_PLATFORM_HINT=x11
GDK_BACKEND=x11
QT_QPA_PLATFORM=xcb
- Writes per-app flags files when relevant:
~/.config/chrome-flags.conf
~/.config/chromium-flags.conf
~/.config/electron-flags.conf
~/.config/code-flags.conf
~/.config/codium-flags.conf
What "fix-wrap" changes
-----------------------
- Creates wrappers in ~/.local/bin for common Electron apps (only if installed).
- Wrappers force the same X11 environment and exec the real binary in /usr/bin/...
Operational notes
-----------------
- environment.d changes require full logout/login.
- Wrappers work when PATH prioritizes ~/.local/bin before /usr/bin.
- If a .desktop explicitly calls /usr/bin/app, wrappers may not be used. In that case,
clone and patch the desktop entry in ~/.local/share/applications (optional).
Limitations
-----------
- There is no universal, deterministic "Wayland is fixed" signal for every Electron app.
- 'check' validates the engine layer (Chromium/Chrome). Electron usually follows, not guaranteed.
EOF
}
# ----------------------------
# check: headless probe (Wayland)
# ----------------------------
probe_cmd() {
local cmd="$1"
local log
log="$(mktemp)"
(
# Override global X11-forcing env vars only for this probe:
# we WANT to test Wayland viability while keeping real apps stable on X11.
env -u OZONE_PLATFORM -u ELECTRON_OZONE_PLATFORM_HINT -u GDK_BACKEND -u QT_QPA_PLATFORM \
OZONE_PLATFORM=wayland \
ELECTRON_OZONE_PLATFORM_HINT=wayland \
GDK_BACKEND=wayland \
QT_QPA_PLATFORM=wayland \
timeout "${TIMEOUT_S}" \
"$cmd" \
--headless=new \
--disable-gpu \
--no-first-run \
--no-default-browser-check \
--disable-background-networking \
--disable-sync \
--disable-breakpad \
--disable-features=Translate,MediaRouter \
--enable-logging=stderr --v=0 \
about:blank \
>"$log" 2>&1 || true
) &
local pid=$!
spinner "$pid" "Probing $cmd (Wayland headless, ${TIMEOUT_S}s)…"
wait "$pid" 2>/dev/null || true
printf "\n"
if grep -Eqi "$BAD_RE" "$log"; then
bad "$cmd: Wayland probe shows known conflict signals."
info "Last ${LOG_LINES} log lines:"
tail -n "${LOG_LINES}" "$log" | sed 's/^/ /'
rm -f "$log"
return 1
fi
ok "$cmd: probe looks clean (no strong conflict signals)."
rm -f "$log"
return 0
}
cmd_check() {
printf "%sWayland / Ozone Healthcheck%s (v%s)\n" "${BOLD}" "${RESET}" "$VERSION"
printf "%sSession:%s %s\n" "${BOLD}" "${RESET}" "${XDG_SESSION_TYPE:-unknown}"
printf "%sDesktop:%s %s\n\n" "${BOLD}" "${RESET}" "${XDG_CURRENT_DESKTOP:-unknown}"
if [[ "${XDG_SESSION_TYPE:-}" != "wayland" ]]; then
warn "Not a Wayland session. This check is only meaningful on Wayland."
info "Operational: nothing to probe. If Chromium/Electron misbehave, it's not Wayland-specific."
return 0
fi
local found_any=0 pass_any=0 fail_any=0
for c in "${CANDIDATES[@]}"; do
if have_cmd "$c"; then
found_any=1
if probe_cmd "$c"; then
pass_any=$((pass_any+1))
else
fail_any=$((fail_any+1))
fi
printf "\n"
fi
done
if [[ "$found_any" -eq 0 ]]; then
die "No chromium-based engine found (chromium / google-chrome-stable not in PATH)." 1
fi
printf "%sFinal operational verdict%s\n" "${BOLD}" "${RESET}"
if [[ "$fail_any" -gt 0 ]]; then
bad "Wayland still appears risky for Chromium/Electron on this machine today."
info "Operational: keep your stable posture (X11-forced env + flags + wrappers if needed)."
return 2
fi
ok "Wayland looks healthy for Chromium engines (based on headless probe)."
info "Operational: you may test Wayland later, one app at a time (controlled experiment)."
warn "Limitation: this validates the engine layer; Electron usually follows but is not guaranteed."
return 0
}
# ----------------------------
# fix: apply stable configs
# ----------------------------
apply_env_fix() {
local content
content=$'# Force X11 for Chromium / Chrome / Electron (maximum stability)\n\nOZONE_PLATFORM=x11\nELECTRON_OZONE_PLATFORM_HINT=x11\n\n# GTK apps (avoid Wayland resize bugs)\nGDK_BACKEND=x11\n\n# Qt / hybrid apps\nQT_QPA_PLATFORM=xcb\n'
write_file_atomic "$ENV_FILE" "$content"
ok "Applied environment fix: $ENV_FILE"
}
apply_flags_fix_for_app() {
local app="$1"
local flags_file="${APP_FLAGS_FILES[$app]:-}"
[[ -z "$flags_file" ]] && return 0
write_file_atomic "$flags_file" "$FLAGS_X11_STABLE"
ok "Wrote flags: $flags_file (for $app)"
}
cmd_fix() {
printf "%sApply maximum-stability fix (no auto)%s\n" "${BOLD}" "${RESET}"
printf "%sThis forces X11 for Chromium/Electron stacks.%s\n\n" "${DIM}" "${RESET}"
apply_env_fix
local detected
detected="$(detect_apps || true)"
if [[ -z "$detected" ]]; then
warn "No known affected apps detected in PATH. (Still applied global env fix.)"
else
ok "Detected relevant targets:"
while IFS= read -r a; do
[[ -z "$a" ]] && continue
info " - $a"
done <<<"$detected"
printf "\n"
# Apply per-app flags best-effort
while IFS= read -r a; do
[[ -z "$a" ]] && continue
apply_flags_fix_for_app "$a"
done <<<"$detected"
fi
printf "\n"
warn "Operational next step:"
info " You MUST log out completely and log back in for environment.d changes to take effect."
info " Restarting apps is not enough."
return 0
}
# ----------------------------
# fix-wrap: create per-app wrappers (strong enforcement)
# ----------------------------
create_wrapper() {
local app="$1"
local real
real="$(real_path_for_cmd "$app")"
[[ -z "$real" ]] && return 0
local wrapper="${HOME}/.local/bin/${app}"
# Avoid wrapping ourselves or already-local wrapper
if [[ "$real" == "$wrapper" ]]; then
return 0
fi
local content
content="#!/usr/bin/env bash
set -euo pipefail
# Wrapper generated by wayland-ozone-doctor (forces X11 for stability)
export OZONE_PLATFORM=x11
export ELECTRON_OZONE_PLATFORM_HINT=x11
export GDK_BACKEND=x11
export QT_QPA_PLATFORM=xcb
exec \"$real\" \"\$@\"
"
write_file_atomic "$wrapper" "$content"
chmod +x "$wrapper"
ok "Wrapper installed: $wrapper -> $real"
}
cmd_fix_wrap() {
printf "%sCreate per-app wrappers (strong enforcement)%s\n" "${BOLD}" "${RESET}"
printf "%sThis creates wrappers in ~/.local/bin for common Electron apps detected on your system.%s\n\n" "${DIM}" "${RESET}"
if ! echo "${PATH:-}" | tr ':' '\n' | grep -qx "${HOME}/.local/bin"; then
warn "~/.local/bin is not in PATH. Wrappers won't take effect until it is."
info "Add to your shell rc: export PATH=\"\$HOME/.local/bin:\$PATH\""
fi
local any=0
for a in "${ELECTRON_APPS[@]}"; do
if have_cmd "$a"; then
any=1
create_wrapper "$a"
fi
done
if [[ "$any" -eq 0 ]]; then
warn "No known Electron apps detected from the built-in list."
info "Operational: tell me your exact app commands (e.g., 'slack', 'discord', 'obsidian'), and I’ll add them."
fi
printf "\n"
warn "Operational note:"
info " Wrappers take effect when launching apps from terminal or launchers that respect PATH ordering."
info " If a .desktop entry calls /usr/bin/app explicitly, wrappers may be bypassed."
info " If that happens, we can patch the desktop entry in ~/.local/share/applications (optional)."
return 0
}
# ----------------------------
# status
# ----------------------------
cmd_status() {
printf "%sStatus%s\n" "${BOLD}" "${RESET}"
printf "%sInstalled binary:%s %s\n" "${BOLD}" "${RESET}" "$(have_cmd wayland-ozone-doctor && echo "yes ($(command -v wayland-ozone-doctor))" || echo "no")"
printf "%sEnv fix file:%s %s\n" "${BOLD}" "${RESET}" "$([[ -f "$ENV_FILE" ]] && echo "present ($ENV_FILE)" || echo "missing")"
printf "%sSession:%s %s\n" "${BOLD}" "${RESET}" "${XDG_SESSION_TYPE:-unknown}"
printf "%sDesktop:%s %s\n\n" "${BOLD}" "${RESET}" "${XDG_CURRENT_DESKTOP:-unknown}"
ok "Per-app flags (best-effort):"
local detected
detected="$(detect_apps || true)"
if [[ -z "$detected" ]]; then
info " (no relevant targets detected)"
else
while IFS= read -r a; do
[[ -z "$a" ]] && continue
local f="${APP_FLAGS_FILES[$a]:-}"
if [[ -n "$f" ]]; then
if [[ -f "$f" ]]; then
info " - $a : flags present -> $f"
else
info " - $a : flags missing -> $f"
fi
else
info " - $a"
fi
done <<<"$detected"
fi
printf "\n"
ok "Wrappers in ~/.local/bin (detected from common Electron list):"
local any=0
for a in "${ELECTRON_APPS[@]}"; do
if [[ -x "${HOME}/.local/bin/${a}" ]]; then
any=1
info " - ${a} -> $(head -n 1 "${HOME}/.local/bin/${a}" >/dev/null 2>&1; echo "~/.local/bin/${a}")"
fi
done
if [[ "$any" -eq 0 ]]; then
info " (none)"
fi
return 0
}
# ----------------------------
# install: self-install
# ----------------------------
cmd_install() {
mkdir -p "$INSTALL_DIR"
cp -f "$SELF_PATH" "$INSTALL_PATH"
chmod +x "$INSTALL_PATH"
ok "Installed: $INSTALL_PATH"
if ! echo "${PATH:-}" | tr ':' '\n' | grep -qx "${HOME}/.local/bin"; then
warn "~/.local/bin is not in PATH (current shell)."
info "Operational: add this to your shell rc (e.g., ~/.bashrc):"
info " export PATH=\"\$HOME/.local/bin:\$PATH\""
else
ok "~/.local/bin is in PATH."
fi
printf "\n"
ok "Try:"
info " wayland-ozone-doctor status"
info " wayland-ozone-doctor fix"
info " wayland-ozone-doctor fix-wrap"
info " wayland-ozone-doctor check"
return 0
}
# ----------------------------
# help
# ----------------------------
show_help() {
cat <<EOF
wayland-ozone-doctor (v${VERSION})
Usage:
$0 install # self-install to ~/.local/bin/wayland-ozone-doctor
$0 fix # apply maximum-stability (force X11) configs + flags (best-effort)
$0 fix-wrap # create per-app wrappers in ~/.local/bin (strongest per-app enforcement)
$0 check # lightweight headless Wayland probe (engine-layer)
$0 status # show detected apps + config presence
$0 readme # operating guide / FAQ (English)
EOF
}
# ----------------------------
# Main
# ----------------------------
cmd="${1:-help}"
case "$cmd" in
install) cmd_install ;;
fix) cmd_fix ;;
fix-wrap) cmd_fix_wrap ;;
check) cmd_check ;;
status) cmd_status ;;
readme) show_readme ;;
help|-h|--help) show_help ;;
*) show_help; die "Unknown command: $cmd" 2 ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment