Commit Graph
6 Commits
Author SHA1 Message Date
gitops 620941e18b install-assets: say which recipe installs the binaries, now that one does
The header said the binaries were out of scope because `cargo` installs them.
That was true when they were built by hand in three separate checkouts. The
top-level justfile now installs all three, so the sentence pointed at the wrong
thing -- someone looking for where cosmic-conf lands would have gone to cargo
and found nothing. The scope is unchanged; only the reason was stale.
2026-08-10 17:37:31 +07:00
gitops 08028e6f2c Add a power menu, so a session can be left without rebooting
The hyprcosmic profile disables cosmic-panel, and COSMIC's power applet lives
in that panel. Nothing replaced it, so the session had no logout, reboot or
shutdown anywhere in it: the only way out was `systemctl reboot` typed into a
terminal, which also meant every login-time change cost a reboot to test.

hyprcosmic-powermenu is a rofi menu offering lock, suspend, log out, reboot and
shut down. It is reached two ways -- $mainMod SHIFT E and a button at the right
end of waybar -- and both run the same script, so a click cannot bypass the
confirmation a keypress gets.

Logging out calls com.system76.CosmicSession.Exit, which is the method the
panel applet used and the only one that stops the session's clients in order
rather than pulling the compositor out from under them. Reboot and poweroff go
straight to systemd; polkit already authorises both for an active local session
without a prompt, verified with pkcheck, so no pkexec is involved. Lock uses
loginctl, which cosmic-greeter is listening for.

Confirmation is asked only for the three that end the session. Lock and suspend
undo themselves with a keypress, so a prompt there is pure friction; the other
three throw away everything unsaved and are one keystroke away at all times.
"No" is listed first so it is the row already selected.

The menu entries are plain words rather than Nerd Font glyphs. The bar icon is
the only glyph involved, and it comes through generate-config.py, whose whole
purpose is that no Private Use Area character is ever typed by hand -- U+F011,
confirmed present in the installed JetBrainsMono Nerd Font.

rules.css names every module id explicitly, so #custom-power had to be added
there too or the button would have rendered with no pill behind it.

The script sits under config/bin/ rather than a new top-level directory so that
install-assets.sh's audit still covers it: that check refuses to run unless
every file under config/ is classified, which is what stops a new file from
being silently left uninstalled.
2026-08-10 15:35:45 +07:00
gitops b8b5ce546d Check the IPC from outside, and write down the input bug we did not solve
tools/verify-hypr-ipc.py exercises the fork's Hyprland IPC the way a client
reaches it, which the compositor's own log cannot show you. It checks the
socket names, that the five read commands answer JSON, that `bogus`,
`dispatch exec rofi`, `dispatch killactive`, `dispatch workspace +1` and
`dispatch workspace 0` are all refused, and that a real switch returns `ok`.
It moves the focused workspace, so it returns to the one you started on.

It is a validated negative test, not a hopeful one: run against the old
compositor before the socket rename was installed, it failed on the names and
exited 1.

docs/unreproducible-dead-input-2026-08-10.md records the session that came up
with no keyboard or pointer at all, and did not come back after a reboot. It
is closed deliberately rather than fixed, and most of its value is the list of
things it is not -- the fork's patches, `seats.for_device()` returning None,
the modifier-only Super binding, a shortcuts-config race, and two scary log
lines that stock COSMIC prints too. The one suspicious fact is that it was the
fourth compositor start on that boot. If it recurs, there is a list of what to
collect before rebooting destroys it.

The design spec said `.socket` and `.socket2`; corrected to the names clients
actually open.

cosmic.conf's bare-Super binding gets a comment saying why the key field is
empty, since an empty field in a `bind` line reads like a typo. COSMIC
supports modifier-only bindings and Hyprland's `bind` cannot express one.
2026-08-10 12:59:04 +07:00
gitops b89e4be10a Generate the waybar config, and fill the bar out
The bar had six modules and one dead click: pulseaudio's on-click ran
pavucontrol, which is not installed. It now opens `cosmic-settings sound`,
with middle-click as a mute toggle. Added: mpris, bluetooth, temperature,
idle_inhibitor, privacy, a swaync notification button, and power-profiles-
daemon -- and HyDE's pill styling, so each module is its own rounded chip
rather than text in a row.

No backlight module. This machine's panel has no sysfs backlight interface, so
it would render as a permanent error.

The rest of this is about the icons, which have now been got wrong enough
times to deserve a mechanism.

Private Use Area characters do not survive being typed. Writing the previous
version I put a comment at the top of the file saying every glyph was an
escape, then typed literal glyphs into the same file; the codepoint dump found
`format-bluetooth` had picked up a stray U+F293 and another field had two
glyphs where I had written one. Nothing errors -- waybar is perfectly happy to
render a label that is one space.

So the config is generated, not written. config.jsonc.in is pure ASCII with
@@TOKEN@@ placeholders, generate-config.py holds the name-to-codepoint table
and emits `\uXXXX` escapes, and it asserts its own output `.isascii()` before
writing. A hand-typed glyph now cannot reach the file. The delimiter is
doubled because single `@NAME@` collided with wpctl's `@DEFAULT_AUDIO_SINK@`,
which the generator caught as an unknown token rather than mangling.

Every codepoint was checked against the installed font with `fc-list
":charset=..."`. HyDE's own values do not all survive that: its muted glyph
U+FA80 is an old Material Design Icons codepoint that Nerd Fonts v3 moved, so
copying upstream verbatim would have shipped tofu. Three were replaced.

The font stack named "FontAwesome 6 Free" first, which resolves to the Regular
face and carries a fraction of the icon set -- the icons that did appear were
coming from accidental per-character fontconfig fallback. JetBrainsMono Nerd
Font goes first now.

One CSS note, because the failure mode is not obvious: GTK has no `:empty`
pseudo-class, and an unknown pseudo-class does not skip the rule, it rejects
the whole stylesheet and waybar exits 1. `#tray:empty` took the bar down.

install-assets.sh grows a third category. The template and the generator live
under config/ but must not be installed -- a file full of placeholders sitting
next to the real config is a coin toss for whoever opens one first -- and the
audit refuses to run until every file is classified, which is exactly what it
is for.
2026-08-10 12:58:48 +07:00
gitops bae7c5b0ff Install the system-side assets from a script, not by hand
Everything outside $HOME was placed with `sudo install` while the desktop was
being built, which left two problems.

A fresh machine has none of it, and the failure is loud in the worst way:
config.rasi imports /usr/share/hyprcosmic/rofi/{palette,rules}.rasi by absolute
path, and a missing @import is an error rofi renders *in place of the
launcher*, not a warning it skips. Miss those two files and Super+A shows a
parse error.

And hand-installed files drift. Writing this found that /usr/bin/start-
hyprcosmic had silently gained a session-logging block during the blank-screen
debugging that never made it back to the copy under version control -- found
by diffing the two on a hunch, which is not a strategy. `--check` compares
every managed file against its source and exits non-zero on any difference.

PER_USER is not documentation. The script refuses to run unless every file
under config/ is listed as either shared or per-user, so adding one forces a
decision about which it is instead of letting it be quietly left out of both.

PREFIX is only half honoured and the script says so rather than pretending
otherwise: rofi's .rasi has no variables and the autostart file is explicitly
not a shell, so both name /usr/share/hyprcosmic literally. The warning finds
them by grep rather than from a hardcoded list, so it cannot go stale -- it
already turned up waybar/style.css, which I had not thought of.

No internal sudo. It probes the nearest existing ancestor of each destination
up front and dies with the exact command to re-run, rather than escalating on
its own or failing half way through.

The session entry point comes from the cosmic-session fork, which is a
separate checkout and may be absent; it is skipped with a note when it is, or
by --no-session. Binaries are out of scope: they are build outputs, so
comparing them byte-for-byte would only ever report a rebuild.

Verified all four modes against a DESTDIR staging tree and the live /usr:
drift and missing files are detected and exit 1, a re-install repairs them, an
unclassified file under config/ is refused, a bad argument is refused, modes
land as 644 and 755, and `--check` against /usr now reports all 8 files
matching.
2026-08-10 11:48:36 +07:00
gitops 8d21c0d084 Add a safe harness for nested session tests; ignore the cosmic-session fork
Running a second cosmic-session on the development machine turns out to be
genuinely dangerous, and twice it logged the developer out mid-session and
destroyed open work. Two distinct causes, both encoded here as guards:

  - Name-based process selection cannot distinguish the fork, the system
    install, or a stand-in binary; they all answer to `cosmic-session`. The
    second logout came from `pgrep -x cosmic-session | head -1` inside the
    test written to demonstrate that name matching is unsafe, because `head
    -1` favours the oldest match, which is always the live desktop. The
    harness therefore never selects a process by name: it spawns under
    setsid and signals `-$PGID`, with the group ID taken from `$!`.

  - A nested cosmic-session takes the well-known D-Bus name
    com.system76.CosmicSession away from the running session on a shared bus
    ("Connection `:1.3` lost name ..." in the journal), destabilising the
    outer desktop before anything is killed. The harness always runs under
    dbus-run-session. Nesting cosmic-comp alone does not need this.

It also refuses to start without WAYLAND_DISPLAY, since the winit backend
would otherwise fall back to DRM and seize the real display, and it reaps
IPC socket directories whose owning PID is gone.

Verified: shellcheck-clean syntax; the no-WAYLAND_DISPLAY guard fires; an
audit confirms every `kill` targets the script's own process group and no
code path matches a process by name. NOT verified: the harness has never
been run against the real binaries. Session-level runtime testing is now
deferred to a VM or to logging into hyprcosmic.desktop directly, rather than
nesting inside the developer's live desktop.

cosmic-session joins cosmic-comp in .gitignore; both are forks that become
submodules under the topology in the design spec.
2026-08-10 08:25:47 +07:00