mirror of
https://github.com/outbackdingo/hyprcosmic-session.git
synced 2026-08-25 14:53:23 +00:00
start-hyprcosmic now execs /usr/bin/hyprcosmic-session with /usr/bin/hyprcosmic-comp as argv[1], leaving the cosmic-* paths to the distribution's packages. Taking the cosmic-* names meant erasing the distribution's compositor and session to install this fork, which on Fedora also erases cosmic-greeter -- the display manager. cosmic-session already takes the compositor as argv[1], so pairing the two here needs no source change.
312 lines
15 KiB
Bash
Executable File
312 lines
15 KiB
Bash
Executable File
#!/usr/bin/bash
|
|
#
|
|
# Launch a HyprCosmic session: cosmic-comp for window management, HyDE's shell
|
|
# on top.
|
|
#
|
|
# Deliberately separate from /usr/bin/start-cosmic rather than patching it.
|
|
# The stock COSMIC session entry keeps working untouched, so a broken
|
|
# HyprCosmic config is always one logout away from being escaped — which
|
|
# matters when the thing you are replacing is your desktop.
|
|
|
|
set -e
|
|
|
|
# Re-exec through the user's login shell, before anything else runs.
|
|
#
|
|
# The greeter starts this script with a minimal environment: what pam and
|
|
# systemd put there, and nothing from /etc/profile or the user's own login
|
|
# files. start-cosmic re-execs itself through $SHELL to pick those up, and not
|
|
# doing the same here is why the two sessions disagree about the environment.
|
|
# see: https://github.com/pop-os/cosmic-session/issues/23
|
|
#
|
|
# The measurable difference on this machine is XDG_DATA_DIRS. Without this pass
|
|
# it arrives empty; /etc/profile.d/flatpak.sh is what appends the two flatpak
|
|
# exports directories, and those are where flatpak applications keep the
|
|
# .desktop files and icons the launcher looks for. PATH is untouched either way
|
|
# -- /etc/profile only appends what is missing -- so this adds and does not
|
|
# rearrange.
|
|
#
|
|
# First in the file, deliberately. The login files are allowed to set
|
|
# XDG_CACHE_HOME, which is where the log below decides to go, so this has to
|
|
# happen while there is still nothing to lose. The cost is one extra process
|
|
# and, measured here, about 35ms.
|
|
#
|
|
# The guard is an exported variable rather than start-cosmic's positional
|
|
# `--in-login-shell`. A flag has to survive being quoted through another shell
|
|
# to be seen on the way back in; if it ever failed to, the script would re-exec
|
|
# itself forever and the login would hang instead of failing. An exported
|
|
# variable survives exec by definition, so the recursion cannot happen.
|
|
#
|
|
# `exec -l` is bash's own flag and this script is bash, so start-cosmic's extra
|
|
# `bash -c` hop buys nothing. The inner `exec` means the login shell replaces
|
|
# itself with the session rather than sitting there as its parent for the rest
|
|
# of the login.
|
|
#
|
|
# Whatever goes wrong here is unlogged, because the log does not exist yet --
|
|
# hence checking that the shell is real and runnable first rather than finding
|
|
# out from a session that dies silently.
|
|
if [[ -z "${HYPRCOSMIC_LOGIN_SHELL:-}" && -n "${SHELL:-}" && -x "${SHELL}" ]]; then
|
|
case "${SHELL##*/}" in
|
|
nologin | false)
|
|
# An account with no usable login shell still gets a desktop.
|
|
;;
|
|
*)
|
|
export HYPRCOSMIC_LOGIN_SHELL=1
|
|
exec -l "${SHELL}" -c "exec $(printf '%q' "$0")"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# The binaries, under their own names. HyprCosmic installs beside COSMIC rather
|
|
# than over it: these are this fork's builds, and /usr/bin/cosmic-session and
|
|
# /usr/bin/cosmic-comp are left to the distribution's packages.
|
|
#
|
|
# That is what keeps the stock session on the greeter's menu and working. The
|
|
# alternative -- taking the cosmic-* names -- means erasing the distribution's
|
|
# compositor and session to install this, which on Fedora also means erasing
|
|
# cosmic-greeter, which is the display manager. A fork that can only be tried by
|
|
# removing the desktop it forked is a fork nobody can back out of.
|
|
#
|
|
# cosmic-session takes the compositor as argv[1], so the pairing is arranged
|
|
# here and needs no patch: see the exec at the end of this file.
|
|
#
|
|
# Still named in variables rather than called bare. Two reasons: the check below
|
|
# can then say which path was missing, and both stay overridable from the
|
|
# environment, which is how the session is run out of a build tree without
|
|
# installing it.
|
|
HYPRCOSMIC_SESSION_BIN="${HYPRCOSMIC_SESSION_BIN:-/usr/bin/hyprcosmic-session}"
|
|
HYPRCOSMIC_COMP_BIN="${HYPRCOSMIC_COMP_BIN:-/usr/bin/hyprcosmic-comp}"
|
|
|
|
if [[ ! -x "$HYPRCOSMIC_SESSION_BIN" ]]; then
|
|
echo "start-hyprcosmic: $HYPRCOSMIC_SESSION_BIN is missing or not executable" >&2
|
|
echo "start-hyprcosmic: log out and choose the stock COSMIC session" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Capture this session's output to a file that outlives it.
|
|
#
|
|
# A session that fails drops you straight back to the greeter, taking its
|
|
# stderr with it -- and by then you cannot read the journal from inside a
|
|
# session that no longer exists. A plain file in the cache directory is still
|
|
# there afterwards, whatever happened.
|
|
#
|
|
# A plain redirect, not `tee` into a process substitution: tee would be a child
|
|
# process the session writes through, so if it ever died the session would take
|
|
# a SIGPIPE. Nothing about diagnostics should be able to kill the desktop.
|
|
#
|
|
# Every step is guarded. If any of it fails the session starts anyway with no
|
|
# log -- losing diagnostics is an inconvenience, refusing to log in is not.
|
|
#
|
|
# The previous log is kept as .1, so a second attempt does not erase the
|
|
# evidence from the first.
|
|
HYPRCOSMIC_LOG="${XDG_CACHE_HOME:-$HOME/.cache}/hyprcosmic/session.log"
|
|
if mkdir -p "$(dirname "$HYPRCOSMIC_LOG")" 2>/dev/null; then
|
|
if [[ -f "$HYPRCOSMIC_LOG" ]]; then
|
|
mv -f "$HYPRCOSMIC_LOG" "${HYPRCOSMIC_LOG}.1" 2>/dev/null || true
|
|
fi
|
|
if : >>"$HYPRCOSMIC_LOG" 2>/dev/null; then
|
|
exec >>"$HYPRCOSMIC_LOG" 2>&1
|
|
echo "=== hyprcosmic session starting $(date -Is) ==="
|
|
echo " session bin: $HYPRCOSMIC_SESSION_BIN"
|
|
echo " comp bin: $HYPRCOSMIC_COMP_BIN"
|
|
echo " autostart: ${XDG_CONFIG_HOME:-$HOME/.config}/hyprcosmic/autostart"
|
|
fi
|
|
fi
|
|
|
|
# Selects the component set in cosmic-session's profile module: cosmic-panel,
|
|
# cosmic-launcher, cosmic-app-library, cosmic-workspaces, cosmic-bg and
|
|
# cosmic-files-applet are skipped; waybar and friends come from
|
|
# ~/.config/hyprcosmic/autostart.
|
|
export HYPRCOSMIC_PROFILE=hyprcosmic
|
|
|
|
# Keep COSMIC's identity: portals, cosmic-settings and any COSMIC app still
|
|
# key off this, and the compositor underneath really is COSMIC.
|
|
export XDG_CURRENT_DESKTOP="${XDG_CURRENT_DESKTOP:-COSMIC}"
|
|
export XDG_SESSION_DESKTOP="${XDG_SESSION_DESKTOP:-hyprcosmic}"
|
|
export XDG_SESSION_TYPE="${XDG_SESSION_TYPE:-wayland}"
|
|
|
|
# Toolkit hints, same set start-cosmic exports.
|
|
#
|
|
# These are what make non-GTK applications behave under a Wayland session at
|
|
# all: Firefox on Wayland rather than XWayland, Qt preferring the Wayland
|
|
# platform plugin and falling back to xcb, and Java AWT not expecting a
|
|
# reparenting window manager (without it, Swing windows come up blank or
|
|
# mispositioned). Nothing here is COSMIC-specific -- it is the generic Wayland
|
|
# session contract, and leaving it out is why Qt and Java apps looked different
|
|
# under HyprCosmic than under the stock session.
|
|
#
|
|
# `:-` rather than a plain assignment, matching the XDG lines above: if
|
|
# something in the environment already made a deliberate choice, it keeps it.
|
|
export _JAVA_AWT_WM_NONREPARENTING="${_JAVA_AWT_WM_NONREPARENTING:-1}"
|
|
export GDK_BACKEND="${GDK_BACKEND:-wayland,x11}"
|
|
export MOZ_ENABLE_WAYLAND="${MOZ_ENABLE_WAYLAND:-1}"
|
|
export QT_QPA_PLATFORM="${QT_QPA_PLATFORM:-wayland;xcb}"
|
|
export QT_AUTO_SCREEN_SCALE_FACTOR="${QT_AUTO_SCREEN_SCALE_FACTOR:-1}"
|
|
export QT_ENABLE_HIGHDPI_SCALING="${QT_ENABLE_HIGHDPI_SCALING:-1}"
|
|
|
|
# Qt platform theme: CuteCosmic if it is installed, qt6ct/qt5ct if not.
|
|
#
|
|
# Kept pointing at COSMIC's theme rather than HyDE's, because this fork themes
|
|
# GTK/waybar/rofi and has no Qt pipeline: switching to qt5ct here would hand Qt
|
|
# apps to a configuration nothing in this project writes. CuteCosmic at least
|
|
# follows the COSMIC palette that cosmic.conf does drive.
|
|
if [[ -z "${QT_QPA_PLATFORMTHEME:-}" ]]; then
|
|
export QT_QPA_PLATFORMTHEME=cosmic
|
|
for qt_plugin_path in /usr/lib{*,/*}/qt6/plugins; do
|
|
if [[ -f "${qt_plugin_path}/platformthemes/libcutecosmictheme.so" ]]; then
|
|
export QT_QPA_PLATFORMTHEME=cosmic
|
|
break
|
|
elif [[ -f "${qt_plugin_path}/platformthemes/libqt6ct.so" ]] ||
|
|
[[ -f "${qt_plugin_path}/platformthemes/libqt5ct.so" ]]; then
|
|
# Keep looking; CuteCosmic later in the glob still wins.
|
|
# "qt5ct" is the value both qt5ct and qt6ct answer to.
|
|
export QT_QPA_PLATFORMTHEME=qt5ct
|
|
fi
|
|
done
|
|
unset qt_plugin_path
|
|
fi
|
|
|
|
# dconf profile, which is where COSMIC's own settings live.
|
|
#
|
|
# The profile file -- `user-db:cosmic` layered above the usual user and system
|
|
# databases -- is shipped by the cosmic-session package, under
|
|
# /usr/share/dconf/profile/cosmic on Fedora.
|
|
#
|
|
# Naming a profile that cannot be found is not a soft failure. dconf logs
|
|
# "using null configuration" and then every GSettings read falls back to schema
|
|
# defaults while writes go nowhere: a whole desktop's settings, apparently
|
|
# blank. Measured here -- a missing profile dumps zero keys where the default
|
|
# profile dumps a full set -- which is why this is guarded and start-cosmic's
|
|
# unconditional export is not copied.
|
|
#
|
|
# The bare name is used rather than an absolute path. start-cosmic has its
|
|
# prefix rewritten in at install time by the Justfile, but dconf searches
|
|
# /etc/dconf/profile first and XDG_DATA_DIRS after, so the bare name keeps an
|
|
# administrator's /etc override working instead of pinning one prefix.
|
|
dconf_profile_found=""
|
|
IFS=: read -r -a xdg_data_dirs <<<"${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" || :
|
|
for dir in /etc "${xdg_data_dirs[@]}"; do
|
|
if [[ -f "${dir}/dconf/profile/cosmic" ]]; then
|
|
dconf_profile_found=1
|
|
break
|
|
fi
|
|
done
|
|
if [[ -n "${dconf_profile_found}" ]]; then
|
|
export DCONF_PROFILE=cosmic
|
|
else
|
|
echo "start-hyprcosmic: no dconf profile 'cosmic' found; leaving DCONF_PROFILE unset" >&2
|
|
fi
|
|
unset dconf_profile_found xdg_data_dirs dir
|
|
|
|
# gnome-keyring, and the SSH agent it provides.
|
|
#
|
|
# PAM starts the daemon at login but brings up only its `control` socket, so
|
|
# without this a HyprCosmic session has no ssh-agent at all: SSH_AUTH_SOCK is
|
|
# unset, anything over SSH asks for the passphrase every time, and callers of
|
|
# the secrets API find nothing listening. `--start` attaches to the daemon that
|
|
# is already running and brings up whichever components are missing.
|
|
#
|
|
# The daemon writes `NAME=value` to stdout and chatter like
|
|
# "discover_other_daemon: 1" to stderr, so the 2>/dev/null is load-bearing for
|
|
# the parse, not just for tidiness.
|
|
#
|
|
# start-cosmic evals that stdout, but its eval is
|
|
# `eval "$(... > /dev/null 2>&1)"` -- the redirect is *inside* the substitution,
|
|
# so the eval always runs on an empty string and the block works only because
|
|
# of the socket checks that follow it. Rather than repair that by evaluating
|
|
# whatever a daemon chooses to write to stdout, read out the one variable we
|
|
# actually want.
|
|
if [[ -d "/run/user/$(id -u)/keyring" ]]; then
|
|
keyring_ssh_sock=""
|
|
if command -v gnome-keyring-daemon >/dev/null 2>&1; then
|
|
keyring_ssh_sock="$(
|
|
gnome-keyring-daemon --start --components=pkcs11,secrets,ssh 2>/dev/null |
|
|
sed -n 's/^SSH_AUTH_SOCK=//p' | tail -n1
|
|
)"
|
|
else
|
|
echo "start-hyprcosmic: gnome-keyring-daemon not found in PATH" >&2
|
|
fi
|
|
|
|
# start-cosmic's rule, kept: set the correct socket or set none at all,
|
|
# never a wrong one. The daemon's own answer goes first because it is
|
|
# authoritative about where it just put the socket; the two fixed paths are
|
|
# the fallback for when it told us nothing.
|
|
for candidate in \
|
|
"${keyring_ssh_sock}" \
|
|
"/run/user/$(id -u)/gcr/ssh" \
|
|
"/run/user/$(id -u)/keyring/ssh"
|
|
do
|
|
if [[ -n "${candidate}" && -S "${candidate}" ]]; then
|
|
export SSH_AUTH_SOCK="${candidate}"
|
|
break
|
|
fi
|
|
done
|
|
unset keyring_ssh_sock candidate
|
|
fi
|
|
|
|
# Same hygiene as start-cosmic: a failed unit left by a previous graphical
|
|
# session will otherwise block this one from starting.
|
|
if command -v systemctl >/dev/null; then
|
|
for unit in $(systemctl --user --no-legend --state=failed --plain list-units | cut -f1 -d' '); do
|
|
partof="$(systemctl --user show -p PartOf --value "$unit")"
|
|
for target in cosmic-session.target graphical-session.target; do
|
|
if [ "$partof" = "$target" ]; then
|
|
systemctl --user reset-failed "$unit"
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
|
|
# Hand the session's identity to `systemd --user`, which is what actually
|
|
# launches the portal.
|
|
#
|
|
# Exporting XDG_CURRENT_DESKTOP above only reaches processes started *by
|
|
# this script*. The user manager is older than the session and has its own
|
|
# environment block, so anything it activates -- xdg-desktop-portal, and
|
|
# through it xdg-desktop-portal-cosmic, which is `SystemdService=` in its
|
|
# D-Bus service file -- never saw the variable.
|
|
#
|
|
# xdg-desktop-portal picks a backend by matching the `UseIn=` line of
|
|
# /usr/share/xdg-desktop-portal/portals/*.portal against
|
|
# XDG_CURRENT_DESKTOP. With the variable unset, `UseIn=COSMIC` did not
|
|
# match, no backend loaded, and org.freedesktop.portal.Screenshot was never
|
|
# exported -- so pressing PrtScr made cosmic-screenshot unwrap an error and
|
|
# abort with "COSMIC screenshot crashed". Nothing logged a missing portal;
|
|
# the only symptom was the crash.
|
|
#
|
|
# Ordering is fine: xdg-desktop-portal.service is `After=` and
|
|
# `Requisite=graphical-session.target`, which cosmic-session brings up
|
|
# later, so the portal cannot start before this import.
|
|
#
|
|
# SSH_AUTH_SOCK rides along for the same reason: a user unit or a
|
|
# systemd-activated app that wants the agent has to find it in the manager's
|
|
# environment, not in this script's. It is skipped harmlessly when the
|
|
# keyring block above declined to set it.
|
|
#
|
|
# `||:` throughout -- losing a portal is bad, refusing to log in is worse.
|
|
systemctl --user import-environment \
|
|
XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE \
|
|
DCONF_PROFILE SSH_AUTH_SOCK ||:
|
|
fi
|
|
|
|
# Same variables again for dbus-daemon's own activation environment, which
|
|
# covers any backend whose .service file has no `SystemdService=` line.
|
|
#
|
|
# Only when a bus already exists. In the dbus-run-session branch below the bus
|
|
# is created *after* this point, so calling it here would talk to the wrong bus
|
|
# or none at all. That branch is the fallback path anyway; a normal graphical
|
|
# login arrives with DBUS_SESSION_BUS_ADDRESS already set.
|
|
if [[ -n "${DBUS_SESSION_BUS_ADDRESS}" ]] && command -v dbus-update-activation-environment >/dev/null; then
|
|
dbus-update-activation-environment \
|
|
XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE \
|
|
DCONF_PROFILE SSH_AUTH_SOCK ||:
|
|
fi
|
|
|
|
# cosmic-session takes the compositor to launch as its first argument
|
|
# (main.rs: `args.next().unwrap_or_else(|| String::from("cosmic-comp"))`),
|
|
# which is how a forked cosmic-comp gets used without replacing the system one.
|
|
if [[ -z "${DBUS_SESSION_BUS_ADDRESS}" ]]; then
|
|
exec /usr/bin/dbus-run-session -- "${HYPRCOSMIC_SESSION_BIN}" "${HYPRCOSMIC_COMP_BIN}"
|
|
else
|
|
exec "${HYPRCOSMIC_SESSION_BIN}" "${HYPRCOSMIC_COMP_BIN}"
|
|
fi
|