The bar showed which windows existed but not which one had the keyboard,
and nothing at all about the GPU, the fans, the disk, or a unit that had
failed at boot. Six modules, two of which needed a script because waybar
has no module that fits.
hyprland/window, and no compositor change
Reading the active window needs `j/activewindow` on startup and
`activewindow>>` on .socket2.sock, and hypr_ipc already serves both. A
retitle of the focused window propagates too, which is the case that
usually needs a separate `windowtitle` event: this fork diffs a snapshot
on a 150 ms tick rather than emitting from call sites, so src/hypr_ipc/
sync.rs sees a (class, title) change and cannot miss it. There is already
a test for exactly that -- title_change_alone_emits_activewindow.
The rewrite rules trim the app suffix from titles written for a title bar
rather than a 60-column slot. All five now use the same [-<em dash>]
class. Vivaldi had a bare hyphen because it was written from the live
title -- j/clients reports "Inbox - ... - Gmail - Vivaldi" -- and the
others got the class in a later pass, which left the one rule that would
break if Vivaldi ever switched separators. COSMIC's own applications use
an em dash and not a hyphen, so a rule written with a hyphen matches
nothing there.
custom/fan, because neither of the obvious options can name this sensor
waybar has no fan module. Its temperature module cannot be borrowed: it
divides by 1000 to turn millidegrees into degrees, which renders 2700 rpm
as 2 C. Nor can hwmon-path-abs point at the fans. That option names a
parent directory and takes the one hwmonN inside it, which works for
k10temp and amdgpu because each has exactly one, but asus-nb-wmi has two:
hwmon9 name=asus fan1_input, fan2_input
hwmon10 name=asus_custom_fan_curve the curve's set points
Which of the two waybar picked would be down to readdir order. So the
script resolves by content instead -- any hwmon with a fan*_input -- which
also makes it work on hardware that is not this laptop. It prints nothing
where there is no readable fan, and waybar draws an empty custom module as
nothing, so a VM loses the item rather than showing a dead 0 rpm.
hyprcosmic-keybinds, and why it does not read cosmic.conf
The obvious implementation reads cosmic.conf and would be wrong. This
machine's cosmic.conf declares six bindings. The session answers to 122.
The other 116 are COSMIC's defaults, which the fork does not restate
because it has no reason to -- Super+Q closes a window whether or not
anybody wrote it down. A reference showing six entries would not look
broken, it would look complete, while missing every window, workspace and
media key on the machine.
So it reads the three RON files the compositor reads: Shortcuts/v1/
defaults for the built-ins, the user's Shortcuts/v1/custom for what
cosmic-conf projected out of cosmic.conf, and Shortcuts/v1/system_actions
to turn System(Screenshot) into cosmic-screenshot. Without the third, a
third of the list would name internal actions rather than the programs
they launch, which is the half of the question that was being asked.
Parsing RON with sed deserves a defence. The principled home for this is a
`cosmic-conf binds` subcommand, which already has the schema and the bind
grammar -- and is a compiled change and a full package build. The files
are one binding per line and nothing downstream consumes this, so the cost
of being wrong is a mangled row in a help window, not a broken keybinding.
The script refuses to show a list it could not parse rather than showing a
short one.
The binding for it was chosen with the script: Super+K and Super+I are
both focus actions in COSMIC's defaults, and Super+Shift+/ is free.
install-assets: prune dot-directories from the audit
audit_config_tree refuses to run unless every file under config/ is
classified, and it started failing on six gitignored .omc/ state files
that tooling had dropped into config/waybar/. Failing on those trains you
to ignore the one message that catches a genuinely unclassified asset.
Dot files are still walked; only directories are pruned.
Verified before committing
The generated config parses as JSON with all 23 modules defined and none
defined but unplaced. The three stylesheets concatenate and parse clean
through GTK's own CssProvider, which is the parser waybar uses, and every
@colour they name is defined. Both scripts pass a syntax check, and
hyprcosmic-keybinds was run with rofi stubbed -- nothing appeared on the
desktop -- producing 118 rows, which is 116 defaults plus 6 ours less the
4 that override. System() actions resolved to real commands, chords sorted
Super first, and the modifier-only Super binding survived, which a parser
requiring a key field would have dropped.
Every sensor was read before being wired to a module rather than after:
amdgpu edge 55 C, cpu_fan 2700 and gpu_fan 2500 rpm, zero failed units in
both scopes, / at 3 percent.
Not verified: how any of it looks. Rendering needs waybar restarted
against a session that is not at the lock screen.
HyprCosmic
COSMIC's compositor, driven the way Hyprland is configured, wearing a HyDE shell.
It is a fork of cosmic-epoch, the meta-repository that names every COSMIC component and builds the desktop out of them. Two of its 29 submodules point at forks; the other 27 are System76's, unchanged. So this is not a re-implementation of COSMIC and not a theme pack sitting beside it — it is COSMIC, built from source, with a different shell on top and a different way of telling it what to do.
Three things distinguish a HyprCosmic session from a COSMIC one:
- Hyprland's configuration idiom. A single
~/.config/hyprcosmic/cosmic.confwithgeneral { }blocks,bind =lines and$variablesis compiled into COSMIC's config tree. The file wins: what it names, it owns. - HyDE's shell. waybar instead of cosmic-panel, rofi instead of
cosmic-launcher,
awwwinstead of cosmic-bg. HyDE themes are imported directly, palette and wallpapers and all. - It replaces COSMIC rather than sitting next to it. The binaries install as
/usr/bin/cosmic-compand/usr/bin/cosmic-session, the paths a cosmic-comp and a cosmic-session go to, and the packages conflict with the distribution's accordingly. Both session entries are installed, so the greeter still offers a stock COSMIC shell for the day the HyDE one does not start — now served by these binaries rather than by a second copy on disk.
Repository layout
Everything in cosmic-epoch, plus:
| Path | What it is |
|---|---|
cosmic-comp/ |
submodule → outbackdingo/hyprcosmic-comp |
cosmic-session/ |
submodule → outbackdingo/hyprcosmic-session |
cosmic-conf/ |
the config compiler and HyDE theme importer. A crate in this repository, not a submodule |
config/ |
the shipped cosmic.conf, autostart, waybar and rofi assets, and the power menu |
tools/install-assets.sh |
installs the parts of config/ that live outside $HOME, and --checks them for drift |
docs/ |
the design spec, a debugging guide, and one written-up bug that is still open |
The other 27 submodules stay on pop-os. Nothing about them needs to change,
and pinning them to copies nobody maintains would be a promise to keep 27 forks
current.
What the two forks change
cosmic-comp — four patches, each independent:
zwlr_foreign_toplevel_management_v1, which is the protocol waybar's window list and rofi's window mode read. Without it the taskbar is empty.- A Hyprland-compatible IPC socket (
.socket.sockand the.socket2.sockevent stream) under the names Hyprland clients actually open, so HyDE's scripts and waybar'shyprland/*modules work unmodified. The write surface is deliberately small:dispatch execanddispatch killactiveare rejected, because this is the surface any process that can open the socket gets. - New windows open beside the focused window rather than inside it.
- The install goes to
/usr/bin/cosmic-comp, at upstream's paths and alongside upstream's two.rondefaults files, which are carried unmodified.
cosmic-session — profiles. HYPRCOSMIC_PROFILE=hyprcosmic (set by
hyprcosmic.desktop) skips cosmic-panel, cosmic-launcher, cosmic-app-library,
cosmic-workspaces, cosmic-bg and cosmic-files-applet, then starts whatever
~/.config/hyprcosmic/autostart names. cosmic-greeter is deliberately not
skippable — a display manager is the easiest thing to lock yourself out of. The
fork installs three files where upstream installs seven; the four it drops are
owned by the distribution's own cosmic-session package and writing them would
make the two conflict.
Building
git clone --recurse-submodules https://github.com/outbackdingo/hyprcosmic
cd hyprcosmic
just build
Build dependencies are COSMIC's — see upstream's list,
which is long and distribution-specific. rustup is recommended over the
distribution's rustc: cosmic-comp is edition 2024 and pins Rust 1.93 in its
rust-toolchain.toml, which is newer than several stable distributions ship —
Debian bookworm's rustc is 1.63. just is likewise absent before Debian
trixie; cargo install just --locked covers it.
Installing
The easiest route is a package. Every tag builds one for Fedora, Arch and Debian
and attaches it to a draft release; workflow_dispatch on Packages builds
them at any other time and leaves them as run artifacts.
sudo dnf install ./hyprcosmic-*.rpm # Fedora
sudo pacman -U ./hyprcosmic-*.pkg.tar.zst # Arch
sudo dpkg -i ./hyprcosmic_*_amd64.deb # Debian
Expect this to fail the first time, and read what it says when it does. These
packages provide /usr/bin/cosmic-comp and /usr/bin/cosmic-session, so they
conflict with the distribution's cosmic-comp and cosmic-session and your
package manager will refuse until those are removed. That refusal is the design:
installing HyprCosmic replaces the machine's desktop, and it should take a
deliberate dnf remove cosmic-comp cosmic-session to say so rather than a
resolver deciding on your behalf. Both session entries survive the swap, so the
greeter still offers a stock COSMIC shell afterwards.
Building it yourself instead:
sudo just install '' /usr
The two positional arguments are rootdir (a staging root, for packaging) and
prefix. Use /usr, not the /usr/local default. Several files name
/usr/share/hyprcosmic as a literal because they have no way to interpolate a
prefix — a rofi .rasi has no variables, hyprcosmic.desktop has no way to
expand one into Exec=, and autostart is deliberately not a shell.
install-assets.sh prints the exact list when you use another prefix.
To stage instead of install:
just install /tmp/stage /usr
This installs all of COSMIC — the 27 unmodified components as well — plus
cosmic-conf at $prefix/bin/cosmic-conf, the shared waybar and rofi assets
under $prefix/share/hyprcosmic/, and hyprcosmic-powermenu.
install depends on build, which is upstream's arrangement and means sudo just install compiles as root. That is inherited, not chosen; if you would
rather not, build into a staging root as your own user and copy it into place.
Then log out. HyprCosmic appears on the greeter's session menu next to
COSMIC; both work.
Per-user setup
just install places nothing in a home directory — under sudo the only home
directory it could see is root's. Four files are yours to place:
mkdir -p ~/.config/hyprcosmic/waybar
cp config/cosmic.conf config/autostart ~/.config/hyprcosmic/
cp config/waybar/style.css ~/.config/hyprcosmic/waybar/
style.css is per-user rather than shared for one reason: it @imports a
sibling theme.css holding the installed HyDE theme's palette, and a relative
@import resolves against the importing file. That sibling is written by
import-theme --assets, so the bar is unstyled until you have imported a theme.
The fourth file, ~/.config/rofi/config.rasi, is written by import-theme --assets too, because it names per-machine paths.
Runtime dependencies of the shell itself are not COSMIC's and are not built
here: waybar, rofi (wayland build), awww (formerly swww), and a Nerd
Font for the bar's glyphs.
Configuration
~/.config/hyprcosmic/cosmic.conf, in Hyprland's idiom, compiled into
cosmic-config by:
cosmic-conf apply # once
cosmic-conf apply --diff # show what would change, write nothing
cosmic-conf watch # recompile on every edit, for the whole session
watch is the first line of the shipped autostart, which is what makes "the
file wins" true at login and not only when you last ran apply by hand:
whatever COSMIC's settings UI stored since then is overwritten before the
desktop settles. A malformed edit is reported to the session log and the last
good configuration stays in place, so a typo cannot leave you at a broken
desktop.
The rule is one-way and deliberate. Keys this file names are overwritten from
it on every login; keys it does not name are left entirely alone, so
cosmic-settings remains the right place to change anything the file is silent
about. There is no write-back — the GUI never edits cosmic.conf.
bind lines go to the Shortcuts custom key, which cosmic-comp merges over
defaults, so the system defaults file is never touched and reverting is a
matter of deleting the lines and re-applying. Hyprland spellings and COSMIC
spellings are both accepted for the same setting (input:follow_mouse and
general:focus_follows_cursor), and the last assignment wins. Where a Hyprland
value has no COSMIC equivalent — follow_mouse = 2 and 3, which separate
pointer focus from keyboard focus — it is rejected with an explanation rather
than quietly rounded.
What the shipped file sets up, since the components those keys used to reach are no longer running:
| Binding | Does |
|---|---|
Super (tap), Super+/, Super+A |
rofi -show drun |
Super+W |
rofi -show window, in place of the workspace overview |
Super+Return |
cosmic-term (Super+T still works — cosmic-comp handles that one itself) |
Super+Shift+E |
hyprcosmic-powermenu: lock, suspend, log out, reboot, shut down |
The power menu is there because cosmic-panel hosts COSMIC's power applet, and
without the panel a session had no way out short of systemctl reboot from a
terminal. The same script backs waybar's power button, so the two cannot drift
apart, and it confirms before anything that ends the session.
See config/cosmic.conf; it is commented at length and is
the reference for what is supported.
Theming
cosmic-conf import-theme ~/.config/hyde/themes/'Tokyo Night'/hypr.theme \
--out ~/.config/hyprcosmic/theme.conf --report --assets
This translates a HyDE theme into conf keys, and with --assets also installs
the wallpapers, GTK and icon themes, and the waybar/rofi/kitty theme files that
sit beside hypr.theme. --report prints everything that did not translate
cleanly, which is the honest half of the output.
theme.conf is written as a separate file and sourced from cosmic.conf
rather than pasted into it. That keeps re-importing from touching your
keybindings, and anything you want to override can simply be repeated later in
cosmic.conf, since the last assignment to a key wins. The source line ships
commented out — a source naming a file that does not exist is a hard error,
and no theme is imported on a fresh install. Uncomment it once you have run the
command above; import-theme says so as well.
Change the wallpaper by repointing the current symlink that --assets
maintains, not by editing autostart:
ln -sfn ~/".local/share/wallpapers/hyprcosmic/<theme>/<image>" \
~/.local/share/wallpapers/hyprcosmic/current
Continuous integration
Two workflows, on purpose:
.github/workflows/ci.ymlis upstream's, unmodified. It builds the entire desktop on Arch viajust sysext, which is exactly the check a meta-repo wants and is not made less useful by forking..github/workflows/hyprcosmic.ymlcovers what upstream's does not:cosmic-confbuilt, tested and clippy-clean on Fedora, Debian and Arch; a check that the shippedcosmic.confstill parses and resolves against the current schema; thatconfig/waybar/config.jsoncis still in step with the generator that produces it; that the template and generator stay pure ASCII; and aninstall-assets.shround trip into a staging root, verified with--check.
The two forks carry a hyprcosmic.yml of the same shape, each building on
Fedora, Debian and Arch and asserting that its install landed at upstream's
paths — and that nothing landed in the private /usr/libexec/hyprcosmic/ this
fork used to use, which is the assertion that would otherwise rot quietly.
packages.yml in this repository builds installable packages for the same three
distributions: an RPM, a .pkg.tar.zst and a .deb, each compiled inside a
container of the distribution it targets so the sonames it records are the ones
the installing machine will have. It runs on tags and on demand, not on every
push — three full desktop builds is hours of runner time. Tags additionally open
a draft release with the packages attached; drafts rather than published,
because installing one of these replaces the machine's desktop.
The waybar generator deserves its own note. config.jsonc is generated from
config.jsonc.in and a codepoint table in generate-config.py, and is never
hand-edited: Nerd Font glyphs live in the Private Use Area, where they are
destroyed by being retyped and indistinguishable from each other in a diff. CI
regenerates the file and fails if it moves.
Known gaps
- The
/usr/share/hyprcosmicliterals described under Installing. docs/unreproducible-dead-input-2026-08-10.mdrecords a session that came up without input and has not been reproduced since. It is written down rather than closed.
Trademark
COSMIC is a System76 trademark. This fork is not affiliated with or endorsed by System76. See TRADEMARK.md, which is upstream's policy and applies here.
Upstream
For COSMIC itself — the component list, packaging status, translations, and how to install it on your distribution rather than building it — see pop-os/cosmic-epoch.