Commit Graph
10 Commits
Author SHA1 Message Date
gitops e6f8d560ab Re-exec through the login shell, so the session sees /etc/profile
The greeter starts this script with what pam and systemd put in the
environment and nothing else. start-cosmic re-execs itself through $SHELL to
pick up /etc/profile and the user's login files; not doing the same here is
why the two sessions disagreed about the environment.

Measured here, the difference is XDG_DATA_DIRS: empty without this, and with
it the two flatpak exports directories that hold the .desktop files and icons
the launcher reads. PATH is unchanged either way -- /etc/profile only appends
what is missing.

Differs from upstream in two ways. The recursion guard is an exported variable
rather than a positional --in-login-shell flag, because a flag has to survive
being quoted through another shell to be seen on the way back in, and if it
ever failed to, the login would hang instead of failing; an exported variable
survives exec by definition. And it uses bash's own `exec -l` rather than an
extra `bash -c` hop, since this script is already bash.

First in the file, deliberately: the login files may set XDG_CACHE_HOME, which
is where the log goes, so this happens while there is nothing to lose.
Verified the log still rotates exactly once per login.

see: https://github.com/pop-os/cosmic-session/issues/23
2026-08-10 15:24:04 +07:00
gitops f8fec1360b Export the toolkit hints and the dconf profile
The toolkit variables are the generic Wayland session contract, not
anything COSMIC-specific: Firefox on Wayland instead of XWayland, Qt
preferring the Wayland platform plugin with an xcb fallback, and Java AWT
told not to expect a reparenting window manager, without which Swing
windows come up blank or mispositioned. Leaving them out is why Qt and
Java applications behaved differently under HyprCosmic than under the
stock session on the same machine.

They are set with `:-` like the XDG lines above rather than assigned
outright, so a value already chosen in the environment survives.

The Qt platform theme stays pointed at CuteCosmic rather than being
switched to qt5ct the way HyDE would have it. This fork themes GTK,
waybar and rofi and has no Qt pipeline at all, so handing Qt applications
to qt5ct would hand them to a configuration nothing here writes; CuteCosmic
at least follows the COSMIC palette that cosmic.conf does drive. Worth
revisiting if Qt theming ever gets built.

DCONF_PROFILE is guarded, which start-cosmic's is not. Naming a profile
dconf cannot find is not a soft failure: it logs "using null configuration"
and from then on every GSettings read returns schema defaults while writes
go nowhere, so a desktop's worth of settings simply appears blank. Measured
on this machine -- `DCONF_PROFILE=cosmic` dumps 33 keys, a name that does
not resolve dumps zero. So the profile is looked for first and the variable
is only exported once found.

The bare name is used rather than an absolute path. start-cosmic gets its
prefix rewritten in at install time by the Justfile, which pins one prefix;
dconf searches /etc/dconf/profile first and XDG_DATA_DIRS after, so the
bare name finds the packaged profile under /usr/share while still letting
an administrator override it from /etc.

DCONF_PROFILE joins SSH_AUTH_SOCK in the systemd and D-Bus activation
environments, matching what start-cosmic imports.
2026-08-10 15:14:26 +07:00
gitops bdff38edac Start gnome-keyring, so the session has an ssh-agent
PAM starts the daemon at login but brings up only its `control` socket.
Nothing else brought up the rest, so a HyprCosmic session had no agent at
all: SSH_AUTH_SOCK unset, every SSH operation asking for the passphrase
again, and callers of the secrets API finding nothing listening. Under the
stock COSMIC session the same machine works, which makes it look like a
key problem rather than a session-script omission.

`--start` attaches to the daemon that is already running and brings up the
missing components; on this machine that created `pkcs11` and `ssh`
alongside the existing `control`.

Where the socket path comes from is the part worth writing down.
start-cosmic evals the daemon's stdout, but the eval is
`eval "$(... > /dev/null 2>&1)"` -- the redirect is inside the
substitution, so it always evaluates the empty string, and the block works
only because of the socket checks after it. Rather than repair that by
evaluating whatever a daemon writes to stdout, this reads out the single
variable we want and keeps the fixed paths as the fallback. The daemon
also writes chatter to stderr, so the 2>/dev/null is load-bearing for the
parse rather than tidiness.

start-cosmic's rule is kept intact: set the correct socket or set none at
all, never a wrong one. A plain file sitting where the socket belongs is
rejected by the `-S` test, and a machine with no keyring directory skips
the block entirely.

SSH_AUTH_SOCK then joins the systemd and D-Bus activation environments for
the same reason XDG_CURRENT_DESKTOP did: a user unit or an activated app
looks there, not in this script's environment. `import-environment` ignores
an unset name and exits 0, so a machine without a keyring still logs in.
2026-08-10 15:09:07 +07:00
gitops 22e894330e Hand the session's identity to systemd --user, so portals work
Pressing PrtScr said "COSMIC screenshot crashed". cosmic-screenshot was
aborting on an unwrap, because org.freedesktop.portal.Screenshot had no
implementation behind it.

start-hyprcosmic exported XDG_CURRENT_DESKTOP=COSMIC, and the compositor had
it. But an export only reaches processes started by this script, and the
portal is not one of them: xdg-desktop-portal is launched by `systemd --user`,
which is older than the session and carries its own environment block. Its
copy had XDG_SESSION_TYPE and nothing else.

xdg-desktop-portal chooses a backend by matching the `UseIn=` line of
/usr/share/xdg-desktop-portal/portals/*.portal against XDG_CURRENT_DESKTOP.
Unset, `UseIn=COSMIC` never matched, no backend loaded, and the Screenshot
interface was never exported. Nothing anywhere logged a missing portal. The
crash was the only symptom, and it pointed at the screenshot tool.

`systemctl --user import-environment` fixes it, which is what start-cosmic has
always done and this script had not. Ordering works out: the portal unit is
`After=` and `Requisite=graphical-session.target`, which cosmic-session brings
up later, so it cannot start ahead of the import.

dbus-update-activation-environment as well, for any backend whose .service
file has no `SystemdService=` line -- but only when a bus already exists,
since in the dbus-run-session branch the bus is created after this point.
Everything is `||:`. Losing a portal is bad; refusing to log in is worse.

Verified by clearing all three variables out of the user manager, running the
script's environment block, restarting xdg-desktop-portal, and watching the
Screenshot interface come back and cosmic-screenshot exit 0 with a real PNG.
2026-08-10 12:58:28 +07:00
gitops 6400f64791 Point the autostart test at the wallpaper symlink
config/autostart now sets the wallpaper through the `current` symlink that
`cosmic-conf import-theme --assets` maintains, rather than naming a file
inside the theme directory. The test still asserted the old path and failed.

The hazard it was written for is gone with it: the path no longer contains a
space, because it no longer contains a theme name. That case is still covered
on synthetic input by quotes_hold_arguments_containing_spaces_together, so
this one now asserts what actually matters here -- that the script arrives as
a single argument, that it names the link rather than one of the copies beside
it, and that it waits for the daemon before setting an image.

11 profile tests pass.
2026-08-10 11:48:50 +07:00
gitops c67cb01f2d Backport the session log block from the installed copy
/usr/bin/start-hyprcosmic gained this during the blank-screen debugging and
the versioned copy never did, so the next install from this repository would
have silently removed it. Found by diffing the two while writing an installer;
the installer's --check exists so that the next one is not found by hunch.

What it does, unchanged from the copy that has been running: redirect the
session's own output to ~/.cache/hyprcosmic/session.log. A session that fails
drops you at the greeter taking its stderr with it, and by then the journal
cannot be read from inside a session that no longer exists.

A plain redirect rather than tee into a process substitution -- tee would be a
child the session writes through, so its death would deliver SIGPIPE, and
nothing about diagnostics should be able to kill the desktop. Every step is
guarded, so a failure here starts the session with no log rather than refusing
to log in. The previous log is kept as .1 so a second attempt does not erase
the evidence from the first.
2026-08-10 11:48:50 +07:00
gitops 04b61b49df Test that the shipped autostart parses to what it documents
The template is mostly prose, and `#` starts a comment wherever a word would
start -- so a comment marker landing in the wrong column drops a command
silently rather than failing. Parsing the real file in a test means editing
the explanatory text around a command cannot quietly disable it.

Tests only; no behaviour change, so the installed session binary is unaffected.
2026-08-10 09:49:43 +07:00
gitops 1c3e3c3e53 Give profile extras real argv, and point the session entry at the fork
Two defects in the previous commit, both of which would have failed silently
at the greeter rather than loudly at build time.

`start_component` passes its whole string to `Process::with_executable`, which
treats it as the program name. An autostart line like `waybar -c <path>` was
therefore looked up as a binary literally called "waybar -c <path>", and the
bar would simply never appear. The unit test asserting that "rofi -show drun"
parses as one entry implied arguments worked; they did not. Extras are now
split into argv and spawned through a `start_process` helper, which is
upstream's `start_component` body with args threaded through --
`start_component` keeps its signature so no upstream call site changes.

The splitter honours quoting, because config paths contain spaces, and treats
`#` as a comment only where a word would start, so `--color=#1a1b26` survives.
It expands nothing else: no variables, globs or command substitution, so a
file that names programs cannot be escalated into running arbitrary shell.

`start-hyprcosmic` also defaulted HYPRCOSMIC_SESSION_BIN to
/usr/bin/cosmic-session -- the stock binary, which has no profile module. The
session would have come up as ordinary COSMIC with the gating apparently doing
nothing. It now points at /usr/libexec/hyprcosmic, so the fork installs
alongside the packaged COSMIC instead of over it, a dnf update cannot clobber
it, and the stock entry stays as the escape hatch. It fails loudly with a
pointer back to that entry if the binary is missing.

Verified: 9 unit tests, including a regression test for the argv defect and
one asserting nothing is expanded; `cargo build -j1` clean.
2026-08-10 08:35:57 +07:00
gitops 80958c24da Add session profiles so HyprCosmic can replace the COSMIC shell
Upstream hardcodes the set of cosmic-* components a session launches.
HyprCosmic needs cosmic-comp for window management but waybar and rofi in
place of cosmic-panel and cosmic-launcher, so `profile` gates that set.

The default profile is upstream behaviour exactly, and it is what an
unconfigured build gets; the alternate set is opt-in through
HYPRCOSMIC_PROFILE, which only the new session entry sets. So installing
this fork cannot change how the stock session boots, and a broken
HyprCosmic config is always one logout away from being escaped.

Gating lives at the single definition of `start_component` rather than at
its call sites, keeping the diff against upstream small enough to rebase
against a fast-moving tree. cosmic-panel and cosmic-notifications are the
exception: they are started as a coupled pair sharing notification fds and
cannot go through that path, so they are gated where they are spawned
(hence the re-indentation in the diff).

cosmic-greeter is deliberately never disabled - a display manager is the
easiest thing to lock yourself out of. cosmic-osd and cosmic-idle stay too,
since HyDE's equivalents are separate programs a user may not have and
neither competes with waybar for layer-shell space.

Verified: 5 unit tests over profile selection and extras parsing; `cargo
check -j1` and `cargo build -j1` clean.

NOT verified: the gating has never been observed at runtime. Two attempts
to run this session nested inside the live desktop logged the developer out
instead, so runtime confirmation is deferred to logging into the
hyprcosmic.desktop entry from the greeter. Two hazards found along the way
and worth recording: name-matching (pkill/pgrep) cannot distinguish this
fork's processes from the live session's, and a nested cosmic-session steals
the well-known D-Bus name com.system76.CosmicSession from the running
session unless it is given a private bus.
2026-08-10 08:24:25 +07:00
d 78d361446d Fork of pop-os/cosmic-session @ upstream 2026-08-10 08:00:21 +07:00