Commit Graph
16 Commits
Author SHA1 Message Date
gitops 75267c5977 Seed ~/.config from the skeleton, so a new machine has a desktop
A machine that had never run HyprCosmic logged in to a bare compositor:
running, holding the display and accepting input, with nothing drawn on
the screen and no binding that opened anything. waybar, the wallpaper and
every keybinding come from ~/.config/hyprcosmic/autostart, and the package
created no such file -- it shipped /usr/share/hyprcosmic and left the home
directory to the reader of a README.

Nothing reported an error, because as far as the session was concerned
nothing had gone wrong, which is what makes it worth handling here rather
than in a note.

Only missing files are copied and an existing one is never touched, not
even when the skeleton is newer: the file wins, one way, and a login is
not an invitation to edit somebody's config. So this is safe on every
start, and deleting a file is how you ask for the default back.

Guarded throughout, like the log above it. A session that cannot seed
should still start.
2026-08-11 18:15:30 +07:00
gitops d65baf688c Start this fork's own binaries, not the distribution's
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.
2026-08-11 14:29:50 +07:00
gitops 73867a5d6a Install as the session, at upstream's paths
Eight files: upstream's six, where upstream puts them, plus this fork's
start-hyprcosmic and hyprcosmic.desktop. Replaces the private libexec
prefix, matching the same change in cosmic-comp.

A machine that installs this gets its cosmic-session from here, so there
is nothing else on disk to provide the systemd target, the mimeapps list
or the dconf profile. Omitting them produced a session that started and
then had no user target for the portals to attach to, which surfaces much
later and much less clearly than a missing file.

Both session entries are installed and both are served by these binaries.
Replacing COSMIC need not mean removing its shell, and keeping
cosmic.desktop costs one file while preserving the one property worth
keeping from the beside-install layout: if the HyDE shell will not start,
there is still something on the greeter's menu that will.

start-hyprcosmic now names /usr/bin for both binaries, still through
overridable variables so the check below it can say which path was
missing and so the session still runs out of a build tree.

The dconf asymmetry is deliberate and now asserted in CI, because it is
easy to get backwards once both scripts are installed: start-cosmic is
sed'd to an absolute profile path, as upstream does; start-hyprcosmic is
not, because it searches /etc/dconf/profile and XDG_DATA_DIRS at runtime
and pinning a build prefix would break an administrator's override.
2026-08-10 18:21:42 +07:00
gitops 8ff1e72d3e test: follow the autostart template off its author's home directory, and let CI compile
The template it parses stopped naming /home/dingo, so the two assertions that
named it had to move with it: the bar is now an `sh -c` invocation rather than a
bare waybar, and the wallpaper path is $HOME. The wallpaper assertion keeps the
surrounding double quotes in the expected string deliberately -- dropping them
is what would split the path at a space in someone's home directory, which is
the failure the test exists to catch.

Adds a ratchet: no line in the shipped autostart may contain "/home/". Both
lines carried an author's home directory right up until the repository was about
to be published, which is a bad moment to find out.

CI is fixed at the same time, because it could never have run these two tests.
They read ../config/autostart through include_str!, so the crate does not
compile its own test profile unless the parent repository is its parent
directory -- and a workflow checkout puts this repository at the workspace root,
alone. Confirmed by building a standalone export of HEAD:

    error: couldn't read `.../standalone/../config/autostart`: No such file
    error: could not compile `cosmic-session` (bin "cosmic-session" test)

So the job now checks the parent out first and this repository over the top of
where its submodule sits, which is the layout the working tree already has. The
coupling is worth keeping: the point of those tests is that the template this
session actually ships still parses to the commands it documents, and a copy
vendored in here to make the build self-contained would be a copy that goes
stale without anyone noticing.

The three dependency-install steps override working-directory back to the
workspace root, since they run before the checkout that creates the directory
the job otherwise defaults to, and a run step whose working-directory does not
exist fails before it runs anything.
2026-08-10 17:13:34 +07:00
gitops 03cd10c0d9 ci: build on three distros, and assert the four dangerous files stay unwritten
Upstream ships no workflows here, so nothing is displaced; the file is named
hyprcosmic.yml anyway, to match the compositor fork where ci.yml is already
upstream's.

A lighter matrix than the compositor's, because this crate has no system
libraries to find -- what actually varies per distribution is the toolchain and
`just`. Rust comes from rustup for the same reason as in cosmic-comp: bookworm
ships rustc 1.63 against a crate that needs 1.93 and edition 2024. `just` comes
from the distribution on Fedora and Arch and from crates.io on Debian, which has
no such package until trixie.

The assertions are the reason this exists. Until recently `just install` wrote
/usr/bin/cosmic-session, cosmic.desktop, the systemd target and the mimeapps
list -- all files the distribution's own cosmic-session package owns. Anyone who
cloned this repository and ran the documented build command replaced their
working COSMIC session with this one, and had nothing left to log into when the
fork failed to start. The negative tests are the important half: it is the files
that must NOT appear that made the old recipe dangerous, and a `test ! -e` is
the only thing that will notice if one of them comes back.

The session scripts are checked off the matrix, on a bare runner. Shell does not
vary by distribution, and doing it there avoids naming the shellcheck package in
three package managers to check the same file three times. Both scripts are
parsed, because a syntax error in either is a black screen at login with nowhere
to print the reason. Only start-hyprcosmic is linted: start-cosmic is upstream's,
this fork no longer installs it, and its existing findings are not ours to
rewrite.
2026-08-10 16:54:38 +07:00
gitops b22fe1687d Install the fork's own three files, not upstream's seven
`just install` still wrote the stock session: /usr/bin/cosmic-session, the
cosmic.desktop entry, the systemd target, the mimeapps list and the dconf
profile. Every one of those is a file the distro's cosmic-session package
already owns, so running it replaced a working COSMIC install with this fork
and left no session to log into if the fork failed to start. What HyprCosmic
has actually been installing, by hand, is three files; this teaches the recipe
that.

The binary moves to $prefix/libexec/hyprcosmic for the reason the compositor
did in Patch D: the stock session has to keep working, because it is what you
use to fix this one. cosmic.desktop is replaced by hyprcosmic.desktop, which
adds a second entry to the greeter's menu rather than overwriting the first.

The four dropped files are not things this fork changes, and a HyprCosmic
session needs stock COSMIC present anyway -- the greeter, the portals and the
settings daemon all come from it -- so reading that package's copies costs
nothing and avoids a packaging conflict over files we have no opinion about.

Upstream's `sed` over DCONF_PROFILE is deliberately not carried across.
start-hyprcosmic searches /etc/dconf/profile and then XDG_DATA_DIRS and exports
the bare profile name only if it finds one; rewriting a prefix into it would
pin one location and defeat an administrator's /etc override. The script
already explains this at the point where it does the search.

Verified by staging into a rootdir: all three files come out byte-identical to
the copies currently installed on this machine, and install-assets.sh --check
still reports all 9 managed files matching.
2026-08-10 16:41:05 +07:00
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