mirror of
https://github.com/outbackdingo/hyprcosmic.git
synced 2026-08-25 14:53:21 +00:00
master
296
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c80566768c |
Record 0.1.1 in the changelog
Three fixes, all of which decide whether a fresh install comes up at all: the skeleton that seeds ~/.config, a font recommendation that can actually resolve, and a wallpaper for a machine with no theme yet.v0.1.1 |
||
|
|
06f974f09f |
Say how to take a newer default, since seeding will not
Seeding never overwrites, which is the right contract and also means an upgrade that improves a shipped default never reaches anyone who already has that file. Both machines this was tested on keep the old autostart line that waits for awww in an unbounded loop, and nothing will ever replace it for them. That is a trade worth making and not one worth hiding, so document the way out: diff against the skeleton, then copy if you want it. |
||
|
|
e006e95e55 |
Draw a wallpaper on a machine that has no theme yet
Seeding the config gets the bar and the keybindings up, and then leaves
you looking at a black screen, because the autostart line that sets the
wallpaper named ~/.local/share/wallpapers/hyprcosmic/current directly.
That is a symlink import-theme --assets maintains, so it does not exist
until a theme has been imported -- which on a machine that has just
installed the package it has not.
The same line waited for the daemon with an unbounded loop:
until awww query >/dev/null 2>&1; do sleep 0.2; done
awww is a Recommends and lives in a COPR, so on a machine that never
enabled that repository nothing ever answered and the loop spun at 5 Hz
for the length of the session.
Both now live in hyprcosmic-wallpaper, which falls back to the
distribution's own images when there is no link yet, gives up on the
daemon after ten seconds, and exits 0 on every path so autostart is never
left supervising a process that cannot finish. Fedora recommends
cosmic-wallpapers so the fallback has something to find; nothing in the
dependency chain pulled it in before.
Verified against all five paths: no awww at all, a daemon that never
answers, a fresh machine with no link, a link pointing at a real file,
and a link left dangling by a deleted theme.
|
||
|
|
111f3ade7b |
Ship the per-user config, and recommend a font that exists
Two things anyone installing v0.1.0 would have hit. The per-user files were listed in install-assets.sh as installed 'by hand', which meant a clean machine logged in to a bare compositor with nothing drawn on it and no binding that opened anything. They are now installed as a skeleton under share/hyprcosmic/skel, laid out exactly as they sit under ~/.config, and the session copies what is missing at login. The audit still forces every file under config/ into exactly one list, so this could not be quietly skipped again. theme.css, theme.rasi and local.rasi are new and deliberately empty. They are the relative @imports of style.css and config.rasi, and a missing @import is fatal to both consumers rather than a warning they skip: GTK fails the whole stylesheet, and rofi reports the error in place of the launcher. Every colour they could define already has a default from palette.css and palette.rasi, so empty is the correct content until import-theme writes them. style.css's own header has promised 'a sibling file we create at install time' since it was written; now there is one. Recommends: nerd-fonts named a package that is in no Fedora repository, so it could never be satisfied and the font never arrived -- silently, which is what Recommends does when it cannot resolve. Fedora has no Nerd Font at all: texlive-inconsolata-nerd-font lands under texmf-dist and kitty's SymbolsNerdFont under /usr/lib64/kitty, and fontconfig scans neither. Recommend font(notosansmono) instead, the fallback rules.rasi already names, and say in the README how to install the glyphs by hand. |
||
|
|
0e6dcdc606 | Say why the release is a draft, now that it installs beside COSMIC v0.1.0 | ||
|
|
28042b9879 |
Install beside COSMIC instead of replacing it
The packages could not be installed. Claiming the cosmic-* names put 62 files in conflict with 25 distribution packages -- something dnf's depsolve never reports, because it shows up only in rpm's transaction check -- and the only way to satisfy that was Conflicts against all 25, which erases cosmic-greeter. On a stock Fedora COSMIC that is the display manager, so the machine comes back to a text console with no way to log in and no session to fall back to. So the fork ships what it actually forks. The three changed binaries take hyprcosmic-comp, hyprcosmic-session and hyprcosmic-conf, the workflow reduces the staged tree to those plus this fork's own assets, and cosmic-settings, the portal, the OSD and the rest come from the distribution at the version it tested them at. No Conflicts, no Provides, nothing erased, and the stock COSMIC entry stays on the greeter's menu to fall back to. Debian is the exception and keeps the whole desktop, because COSMIC is not packaged there in any suite: there is nothing to depend on and nothing to install beside. Its stock session entry is dropped instead, since start-cosmic execs the cosmic-session this rename takes away. |
||
|
|
5bb057cb71 |
fedora: version the compat Provides at COSMIC's version, not at ours
`Provides: cosmic-comp = %{version}-%{release}` reads as the obvious thing to
write and is wrong in a way that only shows up at the next boot.
cosmic-greeter requires `cosmic-comp >= 1.5.0`. A Provides of 0.1.0 does not
satisfy that, so dnf's resolution of `install hyprcosmic` is to remove
cosmic-greeter -- which on a stock Fedora COSMIC is the display manager. The
transaction succeeds, nothing warns, and the machine comes back to a text
console.
Verified against the installed set on a Fedora 44 COSMIC machine, where the
same transaction removed cosmic-greeter (`>= 1.5.0`) and cosmic-config-fedora
(`>= 1.0.0~alpha.3`) while leaving cosmic-workspaces alone. Workspaces depends
on a bare `cosmic-comp` with no version, so it is the control: the Provides
mechanism works, only the comparison was failing.
Arch and Debian are not affected and are left alone. Arch's cosmic-greeter
depends on a bare `cosmic-comp` with no constraint, so its provides at $pkgver
resolves; Debian has no cosmic-comp package at all, so its Conflicts and
Provides are precautionary and have nothing to compare against.
Separately, and not a packaging matter: cosmic-session is listed in
/etc/dnf/protected.d/fedora-cosmic.conf on a Fedora COSMIC install. Protected
packages are locked in the solver, so the transaction is unsolvable and
--allowerasing has no effect on it. Installing over a stock COSMIC needs
`--setopt=protected_packages=` on the command line. That is the distribution
declining to let its desktop be removed by accident, which is reasonable, and
overriding it is the operator's call to make explicitly.
|
||
|
|
43507504f4 |
cosmic-conf: windowrule, and the rules it refuses
windowrule = workspace name:web, class:^(vivaldi|firefox)$
windowrule = workspace 1, class:^(kitty)$, title:^(dev)$
Pairs with the workspace lines from the previous commit: those make a
named workspace exist, this sends an application to it. `windowrulev2` is
accepted as the same key, since that is the spelling configs in the wild
are written with.
Hyprland's windowrule has around forty actions. One of them is
implemented, because one of them has a COSMIC counterpart, and the other
thirty-nine fail with an explanation rather than parsing into nothing. A
rule that looks right and silently does not fire is the worst outcome
available -- the window opens in the wrong place and there is nothing to
read that says why. So `float` points at the tiling exceptions in
cosmic-settings, which is where floating actually lives; the rest say that
only `workspace` exists here. Matchers that ask about window state --
floating:, fullscreen:, onworkspace: -- say that matching happens once, as
the window opens, so only what the window arrives with can be tested.
Three things the parser has to get right
`+1` is not workspace 1. u32::from_str accepts a leading sign, so
Hyprland's relative target would have parsed as an absolute one and sent
the window somewhere the rule never asked for. The digits are checked
before parsing rather than the result checked after.
`silent` is stripped from the end, not parsed as one word among several,
because a workspace name may contain spaces. `workspace = 2, name:web and
mail` is a legal declaration, so `workspace name:web and mail` has to be a
legal rule.
A rule with neither class nor title is refused. Both are regular
expressions and an empty one matches everything, so the rule that looks
like it does nothing would in fact send the entire session to one
workspace.
Order is the semantics
Rules are emitted in the order they were written and not deduplicated: two
rules can differ only in their title and both be wanted, and the
compositor takes the first that matches, so a file that reads top to
bottom has to be written top to bottom.
The regex dependency
Expressions are compiled here so a broken one is a diagnostic against the
line that wrote it, with the crate's own message pointing at the offending
character, rather than a warning in the compositor log nobody reads.
Pinned to the same major cosmic-comp matches with, so what compiles here
compiles there. Cargo.lock carries the five new entries at the versions
cosmic-comp already resolved.
Verified
24 unit tests in the new module and 6 in resolve, covering the sign trap,
the name with spaces, both spellings of the key, the order of the emitted
list, variable expansion, and that each refused action and matcher carries
its explanation. Not verified here: nothing is compiled on this machine.
|
||
|
|
49d5f8b20e |
cosmic-conf: a fixed set of workspaces, with no compositor change
COSMIC's workspaces are dynamic and there is no setting that turns that
off. ensure_last_empty keeps exactly one trailing empty workspace and
collects every other empty one the moment its last window closes, so
"workspace 4 is the browser" is only true while the browser is open.
The primitive that fixes it already exists. can_auto_remove is
is_empty() && !has_activation_token() && !pinned
and pinned_workspaces is an existing persisted CosmicCompConfig key that
Workspaces::add_output drains into the first output to appear. So this is
a projection and nothing else: no cosmic-comp patch, no new config key,
and the fast cosmic-conf CI job covers all of it rather than a 2.5 hour
package build per distro.
workspace = 1, name:term
workspace = 2, name:web
workspace = 3, name:code
Three things about the restore path shaped the module.
Restore is positional. PinnedWorkspace has no index field -- the order of
the Vec becomes the order of the workspaces -- so `workspace = 4` cannot
emit one entry. It emits four, with 1..3 unnamed, or the browser
workspace comes back as workspace 1. That is why there is a cap: without
one, `workspace = 1000` is a typo that silently creates a thousand
workspaces rather than a diagnostic.
The dynamic workspace survives. Pinned workspaces are pushed into an
empty WorkspaceSet and ensure_last_empty then appends the usual empty
one, so declaring four gives 1-4 always present and a fifth appearing
when you use it. That is Hyprland's behaviour and not a compromise
either way.
It lands at the next login. Workspaces::new reads the key once at
compositor start and there is no reload path for it, while cosmic-conf
watch is started from the autostart file after COSMIC's own components.
Every other key in this file is live, so the one that is not is worth
saying out loud in cosmic.conf rather than leaving to be discovered.
Ids are generated rather than random
random_workspace_id is format!("{:x}", rand(0..2<<24)), at most seven hex
digits. Ours are FNV-1a of the index with the high nibble forced on, so
always eight -- a collision with a compositor-generated id is impossible
by construction rather than unlikely. FNV is written out rather than
reaching for DefaultHasher, which is explicitly not stable across Rust
releases: the id is what ties a window's saved workspace to the
workspace it reappears on, so it changing under a toolchain bump is a
bug that would be very hard to attribute.
monitor: is refused, not ignored
Hyprland's monitor: is the parameter a user reaches for first and the one
COSMIC cannot honour. output_matches compares the EDID before the name,
so an OutputMatch with edid: None is rejected outright against any output
that reports one, and only falls through to the name when neither side
has an EDID. Every real panel reports one, so a name-only match would
work on a VM and nowhere else. cosmic.conf cannot supply an EDID -- it is
a manufacturer triple, product id, serial and manufacture date read off
the wire by the DRM backend.
So the parameter fails with that explanation, the same way follow_mouse =
2 does. Accepting it and quietly doing nothing was the third option and
the worst of the three. The emitted OutputMatch is empty, which is not a
placeholder: an unmatchable match is how a workspace says it has no
output preference, which is the only thing this file can truthfully say.
tiling: defaults to general:autotile
A PinnedWorkspace carries its own tiling_enabled. Defaulting it to false
would have meant that turning workspaces on quietly turned tiling off for
someone whose whole reason for editing the file was autotile = true, so
resolve reads the folded autotile write and passes it through.
CI
The assets job syntax-checked two of the four shell scripts;
hyprcosmic-fan and hyprcosmic-keybinds were added in
|
||
|
|
fc8c2a727d |
waybar: six more modules, and a keyboard reference the session had none of
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.
|
||
|
|
5f521529a8 |
packages: fix the three packaging failures, none of which were the build
Run 31394740824 compiled all 27 components on all three distributions --
two and a half hours each -- staged them, passed the completeness
assertions, and then failed in the packaging step on every one. Three
separate causes, none of them the desktop.
desktop-file-validate rejects DesktopNames (fedora, arch)
hyprcosmic.desktop: error: file contains key "DesktopNames" in group
"Desktop Entry", but keys extending the format should start with "X-"
DesktopNames is the key a display manager reads to set
XDG_CURRENT_DESKTOP, so a session entry needs it, but the Desktop Entry
Specification registers keys for application launchers and the validator
has no entry for it. This is not something the fork introduced:
cosmic.desktop is upstream cosmic-session's file, unchanged apart from the
Exec path, and the copy already installed on this machine as
cosmic-session-1.5.0-1.fc44 fails the identical check. Dropping the key
would satisfy the validator and break the session.
So the validation moves out of the three packaging recipes into one step
against the staged tree, and checks what desktop-file-validate would not
have caught anyway: that Exec is absolute and names an executable this
package actually installs. A wrong Exec puts an entry on the greeter's
menu that dies silently when it is chosen, which is the failure that
matters here; a key the validator has not heard of is not.
The check was run against a staged tree built from the real desktop files
before committing: it passes clean, and it rejects an Exec naming a binary
that is not in the tree, an Exec that exists but is not executable, a
relative Exec, a missing DesktopNames, a missing Name, a missing [Desktop
Entry] header and Type=Link, each with a message saying which file and
what.
SIGPIPE under pipefail (debian, and arch next)
dpkg-deb: error: tar subprocess was killed by signal (Broken pipe)
The Debian package built: 217 MB, correct control, Depends computed by
dpkg-shlibdeps across 17 libraries. It failed on `dpkg-deb --contents
dist/*.deb | head -20` in the verify step -- head closes the pipe after
twenty lines, dpkg-deb dies of SIGPIPE, and Actions runs these steps with
pipefail. Both now write to a file and head the file. The Arch verify step
had the same line waiting for it and is fixed in the same way.
bogus date in %changelog (fedora)
10 August 2026 is a Monday.
Also: the PKGBUILD still pointed at tools/make-packages.sh, which was
deleted when packaging moved to Actions. It now names the two commands
that stage a tree and build from it by hand.
|
||
|
|
7df465ef26 |
justfile: build the portal with just, because it has no Makefile
Run 31383454158, arch job. It compiled all 26 other components over two
hours and eighteen minutes and then failed on the last line of `build`:
/usr/sbin/make -C xdg-desktop-portal-cosmic all
make: *** No rule to make target 'all'. Stop.
xdg-desktop-portal-cosmic has no Makefile. It has a justfile, with `build`
and `install` recipes, and the submodule is clean at the commit this fork
pins.
This is upstream's, not something introduced here. The line dates from
2022 (
|
||
|
|
a397334242 |
packages: derive the build dependencies instead of assembling them by hand
Run 31383454158, debian job, eighteen minutes in:
error: failed to run custom build command for `dav1d-sys v0.8.3`
Package 'dav1d', required by 'virtual:world', not found
dav1d is pulled in by cosmic-bg, and nothing in the obvious set of
compositor dependencies names it. A hand-assembled list will keep having
that shape of hole, and each one costs a full run to find.
So the lists are now the union of Build-Depends across all 27 upstream
components' debian/control files, collected mechanically. That adds
libdav1d-dev, libglib2.0-dev, libspa-0.2-dev, libegl1-mesa-dev,
imagemagick, intltool and fonts-open-sans, none of which were present
before and all of which upstream declares.
The Fedora and Arch translations were verified against `dnf repoquery` and
archlinux.org's package API rather than guessed, because a name that does
not exist fails the whole step and costs another run to discover: it is
libdav1d-devel on Fedora and not dav1d-devel, and ttf-opensans on Arch and
not otf-opensans. Both mistakes were in the first draft of this commit.
|
||
|
|
4bedb04f77 |
packages: name the basic tools Fedora's image does not ship, and drop rust-cache
Run 31383454158, fedora job, first line of the build:
justfile:3: make := `which make`
sh: line 1: which: command not found
Fedora's container image is deliberately minimal and has neither `which`
-- deprecated there in favour of `command -v` -- nor make. Arch's
base-devel and Debian's build-essential include both, which is why this
broke on exactly one of the three.
Rather than fix that one symptom and rediscover the next, the recipes were
read for what they actually shell out to: 360 cargo, 163 install, 114 tar,
26 which, 18 find, 11 sed, 4 xargs, 2 make. findutils and tar are also
absent from Fedora's image, so both would have failed in turn, one run at
a time. All of them are now named, along with gzip, sed and diffutils.
Debian gains `file`, used by the deb step to pick ELF binaries out of the
staged tree for dpkg-shlibdeps. It is not part of Debian's essential set.
rust-cache is removed rather than repaired. It runs `cargo metadata` at the
workspace root to decide what to cache, and this root is a meta-repository
with no Cargo.toml -- 29 submodules, each its own crate -- so it reported
"could not find Cargo.toml" and cached nothing. Listing all 29 workspaces
would fix the error and buy a worse problem: their target directories come
to roughly 17 GB against a 10 GB per-repository limit, so the three jobs
would evict each other every run and pay the upload time for it. A cold
build is the honest cost of a workflow that runs on tags and on demand.
|
||
|
|
e65a9722d9 |
packages: give rustup a default, and run steps under bash
Both failures on run 31383204572, all three distributions, in the same
step, before anything was compiled.
--default-toolchain none was copied from the two fork workflows, where it
is correct: their checkout root is the crate, so rust-toolchain.toml sits
in it and rustup reads the pin. This repository's root has no pin -- it is
in cosmic-comp/, one level down, and the other 27 components have none at
all -- so `none` left no default and the first cargo invocation at the
root failed with "could not choose a version of cargo to run".
stable does not weaken the pin. rustup applies a directory-local
rust-toolchain.toml on entering that directory and installs it on demand,
so cosmic-comp still gets the 1.93 it asks for.
The same logs showed `shell: sh -e {0}`, which is the container default
and is dash on Debian. Two later steps use brace expansion, which dash
lacks and bash-as-sh disables, so the RPM build would have created a
directory literally named rpmbuild/{BUILD,RPMS,...} and failed further
along. Declared bash for the job rather than rewriting around a constraint
none of the three images impose.
Worth recording why this was not caught before pushing: the run blocks
were syntax-checked with bash, which is not what was going to run them.
Checking with dash would not have caught it either -- brace expansion
failing is runtime behaviour, not a parse error. Declaring the shell is
the fix; there is no static check that substitutes for it.
|
||
|
|
d2b091fa8b |
Build installable packages in CI, and replace COSMIC rather than sit beside it
Bumps both forks to the commits that install at upstream's paths, and adds the packaging that follows from it. packaging/ holds one definition per distribution -- an RPM spec, a PKGBUILD and a Debian control template -- and each wraps a tree that `just install` has already staged rather than compiling again inside the packaging tool. Building 27 Rust components a second time to produce bytes that already exist costs hours and creates a way for the packaged desktop and the built one to drift apart. One package per distribution, not one per component. Fedora splits COSMIC into 27 packages, which is right for a distribution tracking upstream. This is a fork that replaces the desktop as a unit: there is no supported combination in which you take the HyprCosmic cosmic-comp and the distribution's cosmic-session, and one package says so accurately. All three declare a conflict with the distribution's cosmic-comp and cosmic-session, and stop rather than resolve it. Obsoletes would let a routine install quietly remove the desktop the machine is currently running; removing COSMIC stays a decision a person makes. .github/workflows/packages.yml builds all three, each inside a container of the distribution it targets, because nothing here is statically linked and a package built elsewhere records sonames the installing machine will not have. It runs on tags and on demand rather than on every push -- three full desktop builds is hours of runner time for artifacts nobody downloads -- and a tag opens a draft release, not a published one. The RPM file list is generated from the staged tree, and claims a directory only when no package already owns it. A naive list would have the package own /usr, /usr/bin and /usr/share, which belong to `filesystem`. README follows the same reframe: the install goes to /usr/bin, the package route is documented first, and the conflict is explained where a reader meets it rather than left to be discovered. |
||
|
|
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. |
||
|
|
63610d57df |
ci: give this workflow a name of its own, so it stops cancelling upstream's
Both files said `name: CI`. The concurrency group is built from
${{ github.workflow }}, which is that name, so the two workflows shared a group
and cancelled each other on every push -- whichever started second would be the
only one that ever reported a result. The compositor and session forks already
say HyprCosmic for the same reason; this one was missed when it was renamed.
|
||
|
|
84a5eac79f |
Write the README for the fork, not for what it was forked from
The tree still carried cosmic-epoch's README, which describes how to install
COSMIC from a distribution's packages -- accurate, and about a different thing
than the repository it is now sitting in. Anyone arriving here needs to know
what changed, which two submodules are ours, how to build it, and what to do on
first login, and none of that was written down anywhere outside commit messages
and the comments in config/cosmic.conf.
Deliberately not a copy of upstream's. The dependency list, the packaging notes,
the per-distribution install instructions and the translation links are all
still correct and all still upstream's, so this links to them rather than
forking a second copy to go stale. What it says instead is the delta: the four
cosmic-comp patches, the session profile and what it stops starting, the
one-way config model, the theme import, the four per-user files `just install`
deliberately does not place, and what the two CI workflows each cover.
Two things in it are warnings rather than instructions, and are there because
leaving them out would make the document a nicer read and a worse one:
- prefix must be /usr in practice, and the reason is that some files name
/usr/share/hyprcosmic literally because they have no way to interpolate a
prefix. Written as its own paragraph rather than a footnote.
- `install` depends on `build`, so `sudo just install` compiles as root. That
is upstream's arrangement, inherited rather than chosen, and says so.
Every command, path and claim in it was checked against the tree: the CLI help,
the profile's disabled list, the socket names, the power menu's five entries,
the Shortcuts merge, and the theme path HyDE's own generated config records.
|
||
|
|
31ce3a51c4 |
Build and install cosmic-conf and the shared assets from the top-level justfile
`just build` built 26 upstream components and our two forks. `just install` installed the same set. Neither touched cosmic-conf or anything under config/, which meant the recipe that is supposed to produce a desktop produced one with no config compiler, no bar layout, no rofi theme and no power menu -- a HyprCosmic session that comes up to a blank screen and cannot be logged out of. So three lines, in the places the existing recipes already establish: build cargo build --release --manifest-path cosmic-conf/Cargo.toml install the binary to $prefix/bin, then tools/install-assets.sh clean cosmic-conf/target cargo directly rather than `just cosmic-conf/build-release`, because cosmic-conf is a crate in this repository rather than a submodule and has no Justfile of its own to delegate to. install-assets.sh runs last and with --no-session. Last because it is the only step whose output is worth reading: several of the files it places name /usr/share/hyprcosmic as a literal -- a .rasi has no variables and the autostart file is deliberately not a shell -- so a prefix other than /usr installs them where nothing will look, and the script says which ones. --no-session because cosmic-session/install has already placed start-hyprcosmic and hyprcosmic.desktop by then, and installing them twice would leave it unclear which recipe owns them. Verified by running the two new install lines verbatim into a staging root: eight files, the cosmic-conf binary and the seven shared assets, and no session entry point among them. |
||
|
|
c6150755d2 |
Fetch the two forks over HTTPS, and stop cloning 29 submodules to lint CSS
Two consequences of becoming the meta-repo, neither of which the merge itself could have caught. The submodule URLs were written as [email protected]: because that is how they are pushed from here. That is a working copy's business, not the repository's: upstream names all 29 over HTTPS, a clone with no key configured is the normal case, and CI has no key at all. The URLs are now HTTPS; each submodule's own origin stays SSH, which is where it belongs. The assets job asked for `submodules: recursive` back when this repository had two of them. It now names 29, and 27 are the rest of COSMIC -- several gigabytes fetched per run to check that some CSS is in step with its generator. It fetches the one it needs instead. That one is cosmic-session, because install-assets.sh also places the session entry point that lives in that fork. The script skips those two files with a warning when the checkout is absent rather than failing, so a fetch that silently stopped working would quietly reduce what the job covers instead of turning it red. Hence the `test -f` after it. |
||
|
|
c0ae1350f3 |
Merge pop-os/cosmic-epoch: become the meta-repo, with two submodules swapped
HyprCosmic started as a repository beside COSMIC holding a config compiler and a
pile of theme assets, with the two modified components hanging off it. That had
the relationship backwards. COSMIC already has a meta-repo whose whole job is to
name every component and build the desktop from them, so the honest shape for a
fork is to be that meta-repo with the components we changed pointing at our
copies -- not a separate tree that assumes the rest of COSMIC arrived some other
way.
So this merges cosmic-epoch in and repoints exactly two of its 29 submodules:
cosmic-comp -> outbackdingo/hyprcosmic-comp
cosmic-session -> outbackdingo/hyprcosmic-session
The other 27 stay on pop-os. Nothing about them needs to change, and pinning
them to copies we do not maintain would be a promise to keep 27 forks current.
`just build` and `just install` now build and install the whole desktop with our
compositor and session in it, which is what "fork COSMIC" ought to mean. The two
forks still install into /usr/libexec/hyprcosmic rather than over /usr/bin, so a
system that already has COSMIC from its distribution keeps that session on the
greeter's menu alongside this one.
Conflict resolutions worth stating:
.github/workflows/ci.yml upstream's kept as-is. It builds the entire desktop
on Arch through `just sysext`, which is precisely
the check a meta-repo wants and is not made less
useful by forking. Our per-distribution workflow
moved to hyprcosmic.yml beside it, the same way it
did in the compositor fork.
.gitmodules upstream's 29 entries, then two URLs rewritten.
cosmic-comp, cosmic-session
ours. Git reports "no merge base" because they are
unrelated to the pop-os commits recorded here,
which is expected: they are different repositories,
not newer commits of the same one.
.gitignore both sides, plus a note that the two forks are
deliberately no longer ignored.
|
||
|
|
98e75ecdc8 |
Track the two forks as submodules instead of ignoring them
They were in .gitignore with a note saying they would become submodules once they had somewhere to live. They do now, so they are, and ignoring them would from here on only hide which commit of each one this tree is built against. The paths stay cosmic-comp/ and cosmic-session/, which is where cosmic-epoch puts them, so this repository can be merged with the upstream meta-repo rather than laid out beside it. |
||
|
|
6f9696e791 |
config: stop baking one author's home directory into the autostart template
Two lines named /home/dingo outright: waybar's stylesheet and the wallpaper the `current` symlink points at. This file is installed verbatim into every user's config directory, so both worked for exactly one person -- and failed quietly for everyone else, because a waybar whose stylesheet cannot be read still starts and a wallpaper that was never set looks the same as one that failed to load. Nobody would have got an error message; they would have got an unstyled bar over a black screen. The wallpaper line was already a shell invocation, so it only needed $HOME. The bar was not, and this file is deliberately not a shell -- `~` and `$HOME` on a bare command line here are literal text, which is what stops a file that names programs from being escalated into arbitrary execution. So it now goes through `sh -c`, on the same terms the wallpaper line already established: naming `sh` is naming a program, and anyone who can write this file could already name any binary on the system. `exec` keeps the process tree flat, which matters because cosmic-session supervises these -- without it the shell would be what gets supervised, and a waybar that died would never be restarted. Verified that the expansion produces the same two paths that were hardcoded, and that both exist. |
||
|
|
3c1a092e51 |
ci: build cosmic-conf per distro, and check the assets against their generators
Two jobs, shaped differently on purpose. cosmic-conf gets the same three-distribution container matrix the forks use, plus one check the unit tests cannot make: it resolves the cosmic.conf this repository actually ships. A schema change that invalidated the shipped config would pass all 143 tests in the crate and still break every user on their first login. The binary is invoked directly rather than through `cargo run`, because the step redirects HOME and cargo keys its registry cache on it -- `cargo run` would re-download every dependency into a directory the cache action does not know about. The assets job is where the rules that were previously only remembered become enforced. config.jsonc is regenerated and must not move, which is what stops it being hand-edited; the template and the generator are held to pure ASCII, which is what stops a Nerd Font glyph being pasted somewhere it will be silently destroyed by the next person who retypes it. install-assets.sh is then run into a staging root and asked to verify its own work, which also exercises the audit that refuses to install at all while any file under config/ is unclassified. The ASCII check is written as an `if` rather than `! grep`, because grep exits 1 for "no match" and 2 for "no such file" -- negating it would turn a vanished file into a pass, and the check would quietly stop checking anything. |
||
|
|
4ba11d1bb5 |
Accept Hyprland's input:follow_mouse, and turn focus-follows-mouse on
Focus follows mouse was already there and already off. cosmic-comp has supported it for as long as this fork has existed -- focus_follows_cursor and focus_follows_cursor_delay, both live-watched -- and the schema already exposed them under general. What was missing was the Hyprland spelling: there was no input section at all, so a config written the way a Hyprland user would write it named a setting that did not exist. So this is an alias, not a new setting. input.follow_mouse and input.follow_mouse_delay resolve to exactly the same cosmic-config keys as general.focus_follows_cursor and its delay, and both spellings stay. Setting both in one file is not an error; the last assignment wins, which is the rule the rest of the file already follows. A test pins the two pairs to the same targets, because a rebase that renames a target key would otherwise leave one spelling working and the other quietly dead. follow_mouse needs its own type. Hyprland writes it as a number and COSMIC stores a bool, so Ty::FollowMouse maps 0 to false and 1 to true -- the same trick Ty::Mode already plays for dark/light. Hyprland's 2 and 3 split pointer focus from keyboard focus, which cosmic-comp cannot express: it has one focus and either moves it or does not. They are rejected with a diagnostic that says why, rather than rounded up to 1, because silently handing click-to-focus to someone who asked for the opposite is worse than telling them the mode does not exist here. Anything else gets the ordinary "expected 0 or 1" error. There is no autoraise key because autoraise is not a separate feature. raise_with_children runs inside update_active, which is what the focus-follows-cursor timer ends up calling, so a floating window under the pointer comes to the front as part of being focused. Tiled windows do not overlap, so raising one is a no-op. The delay is left at COSMIC's 250ms rather than shortened. It is what stops focus from skating across every window the pointer crosses on its way somewhere else, and that failure is more irritating than the wait. Installing the binary before editing the config is the required order, not a preference: resolution is transactional, so the old binary meeting an unknown input section would refuse to write the whole file, not just that block. |
||
|
|
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. |
||
|
|
178dfbec1a |
Expose preserve_split, and turn it on
`general.preserve_split` maps straight to the compositor key Patch C adds, so opening a third window gives three panes in a row instead of quadrants. Every other key under `general` exists upstream; this one is the fork's own field on `CosmicCompConfig`. If a rebase ever loses that field the compositor would ignore the key without saying anything, so the target is pinned in a test of its own rather than left to the generic registry checks. |
||
|
|
563fdc7330 |
Turn autotile on, which is what makes placement automatic
COSMIC ships autotile off, so every new window opened floating at whatever size the application asked for, on top of whatever you were looking at. The key already existed in cosmic-conf's registry and had simply never been set. Gaps come with it. They are invisible until windows tile -- with nothing being laid out, nothing has a gap -- so setting one without the other is half a change. gaps_out is doubled so the screen edge reads as margin rather than as one more seam. autotile_behavior is deliberately not a conf key. It defaults to Global, which retiles workspaces that already exist rather than only arming new ones, and that is the value worth having; anyone who wants PerWorkspace can set it in cosmic-settings without this file overwriting them. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|
||
|
|
316fb843c3 |
Autostart: set the wallpaper through the current symlink
The line named a wallpaper inside the theme directory directly, so importing a different theme left it pointing at a path that no longer existed -- a blank screen at the next login, with nothing in the log to say why, because nobody asked for a wallpaper and so nothing reported one missing. It now names ~/.local/share/wallpapers/hyprcosmic/current, the symlink `import-theme --assets` maintains. rofi's local.rasi shows the same image in the launcher sidebar and names the same link, so the two cannot drift. Changing the wallpaper is now `ln -sfn`, not an edit to this file, which the comment says so that the next person does not undo the indirection. The path stays literal. This file is not a shell -- deliberately, so that a file naming programs cannot be escalated into arbitrary execution -- so `~` and `$HOME` would be passed through as text. |
||
|
|
362f324755 |
import-theme: write rofi's entry point and per-machine overrides
The four-layer rofi chain needs config.rasi and local.rasi to exist, and neither has an upstream file to copy: a HyDE theme has no equivalent of either. They were hand-written for this machine, which meant a second machine got a launcher that reported a missing @import instead of opening. config.rasi is `include_str!`d from config/rofi/ rather than kept as a string literal, so it stays a real .rasi file -- highlightable, diffable, editable without a rebuild to see the result -- and there is one copy of it rather than two that can disagree. local.rasi is composed per machine from two things the repo cannot know: the theme's $ICON_THEME, and a wallpaper path. Each half is omitted entirely when there is nothing to say, because an empty `icon-theme:` list is something rofi would honour. $ICON_THEME comes back as a field on Import rather than being re-parsed out of the conf text that the same function just rendered. The wallpaper needed a stable name. local.rasi cannot hardcode a filename without going stale at the next theme import, so plan_wallpapers now also maintains ~/.local/share/wallpapers/hyprcosmic/current as a symlink to one of the copies it made. HyDE has this problem too and solves it the same way, with ~/.cache/hyde/wall.thmb. The launcher sidebar and the autostart's `awww img` line both name the link, so they cannot drift apart. Which wallpaper it points at is the first in *sorted* order. read_dir returns whatever the filesystem feels like, and an arbitrary choice is fine where an unrepeatable one is not: re-running the import would otherwise change the wallpaper at random. The link is repointed even when every wallpaper was skipped as already installed -- the copies are theme-specific and unchanged, but the link is global and has to follow the theme just imported. Repointing has to handle a *dangling* link, which is exactly what a previous import leaves behind once its theme directory is gone: symlink(2) fails with EEXIST rather than replacing, and Path::exists follows the link, so it answers false for the one case that needs removing. symlink_metadata asks about the link itself. Values reaching a generated config come from a theme directory that may have been downloaded from anywhere, so quote_rasi_string strips quotes, backslashes and control characters -- .rasi has no escape syntax worth relying on. Four existing tests counted actions and broke once every plan carried two more. Fixed with a theme_assets() filter rather than by bumping the numbers, so what they are actually asserting stays visible and the next generated file does not break them again. 125 unit, 5 bin and 4 integration tests pass; clippy --all-targets and `cargo fmt --check` are clean. A real import of Tokyo Night installed all 13 paths, and `cosmic-conf apply --diff` reports no changes afterwards. |
||
|
|
1d3252aabd |
Give rofi a theme, in four layers
`rofi -show drun` came up unstyled. Three separate reasons, all of them invisible: `import-theme` wrote ~/.config/rofi/theme.rasi, but rofi only ever auto-loads config.rasi and nothing imported theme.rasi; theme.rasi holds a HyDE palette and nothing else, no widget geometry at all; and no layout was shipped anywhere for it to colour. The layering mirrors what config/waybar already does, for the same reason -- a theme supplies colours, and the layout has to survive being handed a theme that defines only some of them: 1. palette.rasi defaults, under the exact names HyDE themes use 2. theme.rasi the installed theme's rofi.theme, written by cosmic-conf 3. rules.rasi geometry and layout, with no colour literals at all 4. local.rasi per-machine paths: sidebar wallpaper, icon theme Later imports win, so a theme recolours the launcher without rules.rasi knowing a theme exists, and a machine points at its own wallpaper without either of them knowing the path. Unlike waybar there is no bridge step: a HyDE rofi.theme defines the names rules.rasi already references. 1 and 3 are shared and go under /usr/share/hyprcosmic/rofi. 2 and 4 must be per-user, and config.rasi with them: it imports those two relatively, and rofi resolves a relative @import against the importing file's directory. rules.rasi is HyDE's style_1 with three deliberate departures, documented in its header. HyDE computes the border width, radius and font in rofilaunch.sh before invoking rofi; there is no launcher script here -- the keybinding runs `rofi -show drun` bare -- so those are baked in. The sidebar image moves to local.rasi because HyDE's ~/.cache/hyde/wall.thmb does not exist outside HyDE. And dummywall gets `background-color: @main-bg` rather than transparent, so a machine with no wallpaper set shows a panel instead of a hole. The display-* labels are Nerd Font glyphs copied byte-for-byte out of style_1.rasi. They are Private Use Area codepoints and do not survive being retyped; verified as U+F303, U+F120, U+F07B, U+F2D0, each followed by a thin space, and confirmed present in JetBrainsMono Nerd Font via `fc-list :charset=`. Verified with `rofi -dump-theme` and `-dump-config`: both exit 0 with empty stderr, and the merged dump shows the Tokyo Night theme's main-bg beating the palette default. |
||
|
|
50e6c1948c |
Autostart cosmic-conf watch
Without it "the file wins" only held at the moment someone last ran `apply` by hand. Now cosmic.conf is compiled at login and recompiled on every edit to it or to anything it sources, so whatever COSMIC's settings UI has stored since the last login is overwritten before the desktop settles. First in the file for that reason. The bar does not read cosmic-config, so the ordering is for the compositor's benefit rather than waybar's. No `--config`: the default path is derived from XDG_CONFIG_HOME inside the process, so unlike the waybar line this needs no hardcoded home directory -- the one part of this file that is not portable as written. A malformed edit stays non-fatal. It goes to the session log and the last good configuration remains in place, so a typo cannot strand you at a broken desktop; fix the file and the next save applies. Requires cosmic-conf on PATH, which it now is (/usr/bin/cosmic-conf, release build). `waybar` and `awww-daemon` are already named bare here, so the profile resolves argv[0] through PATH. |
||
|
|
9933ff2415 |
Expose the watch subcommand
`watch::watch` has been written, tested and unreachable from the CLI since it landed. It now has a command: `cosmic-conf watch [--config <path>]`, sharing `--config` with `apply` and refusing `--diff`, which means nothing for a daemon whose whole job is to notice a change and write it. Exposing it made an existing wart user-visible: a single bad save reported itself three or four times. One write arrives as several inotify events -- modify, close_write, and a rename when the editor writes atomically -- and they do not all land inside one 250ms debounce window, so each produced its own compile and its own copy of the same diagnostic. Consecutive identical errors are now printed once, reset on any successful compile so the same error after a good one is still news. Verified against an isolated XDG_CONFIG_HOME, driving a real daemon rather than calling `compile` directly, since none of this is reachable from the unit tests: applies at startup, recompiles on edit, notices edits to sourced files, picks up a `source` line added at runtime, survives a malformed edit with the last good value intact, reports it exactly once, does not suppress a *different* error, and resumes after a fix. Ten checks, all passing. |
||
|
|
cd99893b33 |
Fix every clippy lint, including one real panic
Mostly mechanical -- writeln! for format strings ending in a newline, sort_by_key, slice::from_ref, &Path over &PathBuf, a stray &mut in a test. Two were worth more than the lint that found them. `parse_color` sliced `hex[i..i + 2]` after checking `hex.len()`. Both are byte counts, so a multi-byte character inside `rgb(...)` split a char boundary and panicked: `rgb(€abc)` is six bytes and aborted the compiler with "end byte index 2 is not a char boundary". A typo in a config file must produce a diagnostic, not a crash. Clippy did not see this -- it flagged the duplicated `rgb(`/`rgba(` arms as foldable into `?`, and folding them is what put the two length assumptions next to each other where the mismatch was visible. Now guarded by is_ascii, with a test that panics without the guard. `plan_verbatim` tripped too_many_arguments at 8. Three of them were the `&mut Vec<Action>`, `&mut Vec<Note>` and `&mut Vec<AssetError>` threaded through both plan helpers -- a Plan under construction, so `Draft` now names it and `finish()` owns the errors-are-fatal rule that was previously inline. 118 unit tests plus 4 integration tests pass; `cargo fmt --check` and `cargo clippy --all-targets` are both clean. |
||
|
|
a9400d0550 |
rustfmt, no behaviour change
rustfmt and clippy were installed all along; I had wrongly recorded them as missing and never ran either. This is the mechanical half -- files rustfmt reformatted and nothing else touched, committed separately so the real fixes in the next commit are readable. |
||
|
|
272ec0c5d2 |
Refuse arguments apply does not understand
`cosmic-conf apply --diff-only ~/.config/hyprcosmic/cosmic.conf` did exactly the wrong thing twice over: the misspelt flag was ignored, so it wrote instead of diffing, and the path was ignored too, so it wrote to whatever the *default* config compiles to. It reported success either way. Both arguments are now errors with exit 2. An argument parser that silently skips the unknown is a bad fit for a command whose job is to overwrite settings; the surface here is six flags, so the check is fifteen lines rather than a dependency. |
||
|
|
a32596216a |
import-theme: say when the file it wrote is inert
Keeping the imported theme in its own file, sourced from cosmic.conf, is what stops a re-import from clobbering the keybindings. But a sourced file only does anything if something sources it, and until now `import-theme --out` reported "Wrote ..." whether or not anything did -- which looks like success while the desktop stays exactly as it was. It now checks the sibling cosmic.conf and prints the line to add when the file is unreachable. The match is by filename and deliberately loose: it is looking for evidence the user already knows about the file, not parsing the config. The shipped template carries that `source` line commented out rather than live, because `source` naming a file that does not exist is a hard error, and a fresh checkout has no theme.conf yet. Copying the template and running `apply` has to work before any theme is imported. |
||
|
|
e46514cbab |
Install HyDE themes end to end
Three things stood between `assets.rs` and a themed desktop. `import-theme` never called it. The module was written, tested and unreachable; `--assets` now wires it up, with `--source`, `--overwrite` and `--dry-run`, and finds the theme repo's Source/ directory by searching upward rather than assuming HyDE's exact nesting depth. The archive guard rejected every real icon theme. Refusing any `..` in a link target is right for an entry path but wrong for a symlink: icon themes are built out of relative links into sibling directories, and Tela ships thousands of `../devices/network-wireless.svg`. What matters is whether the target resolves inside the destination, which `stays_within_root` now decides lexically -- no canonicalize, since the tree does not exist at plan time and following real links during validation would be a TOCTOU window. Absolute targets and links that climb past the root are still refused; the existing escape tests still pass. `apply` silently ignored `source`. It parsed and resolved inline while `watch` went through `compile`, and `flatten` drops `Item::Source` -- so an include that worked under `watch` vanished under `apply`. `apply` now uses `compile` too. This matters immediately: the generated theme lives in its own theme.conf, sourced from cosmic.conf, so re-importing a theme cannot clobber the keybindings. The waybar stylesheet claimed a theme could be dropped in ahead of it to recolour the bar. It could not -- HyDE names its colours main-bg/wb-act-bg and the rules referenced bar-bg/accent. Split into palette + theme + bridge + rules, imported in that order, so the claim is now true. Verified by loading the result through GTK's own CSS parser: with Tokyo Night installed main-bg resolves to #24283b and wb-act-bg to #bb9af7; with an empty theme.css the defaults stand. Both parse without error. Two deliberate departures, both commented where they are made: the theme's near-transparent bar-bg is composited at 0.85 because cosmic-comp has no blur to put behind it, and theme.css is copied next to style.css rather than imported from HyDE's own path, because a missing @import is fatal in GTK and would break the bar on any machine without a theme. |
||
|
|
10c5cc73d4 |
config: start the wallpaper daemon, under its current name
The hyprcosmic profile does not start cosmic-bg, so until now nothing was drawing a background at all. HyDE calls this swww, and it is packaged for Fedora after all -- the alebastr/sway-extras COPR carries it. Upstream renamed the project to awww at 0.12 and the package Obsoletes swww < 0.12.0, so `dnf install swww` lands awww-0.12.1. /usr/bin/swww still exists as a shim, but it prints a deprecation warning on every invocation and its own help says it will be removed in a future update, so the autostart line uses the real name. |
||
|
|
19927bc00b |
cosmic-conf: translate Hyprland bind lines into COSMIC shortcuts
`bind = SUPER, D, exec, rofi -show drun` is the most recognisable line in a hyprland.conf, and the hyprcosmic profile makes it necessary rather than just idiomatic: with cosmic-launcher and cosmic-app-library not running, COSMIC's stock Super, Super+/ and Super+A bindings point at nothing. Binds are the one repeatable key in the language -- many lines fold into a single map instead of the last one winning -- so they bypass the schema, which is built around one conf key naming one value. They land in the Shortcuts `custom` key, which cosmic-comp merges over `defaults`, so the system file is untouched and reverting means deleting the lines and re-applying. Actions are rendered as RON text rather than modelled as an enum: COSMIC's Action has forty-odd variants, this crate deliberately does not link the cosmic crates, and the mapping table only ever needs a handful. Dispatchers without a genuine equivalent are refused rather than approximated, since a keybinding that silently does the wrong thing is worse than one that fails to compile. Verified the emitted file deserializes into cosmic-settings-config's own `Shortcuts` type: five bindings, keysyms XK_a/XK_slash/XK_Return, Spawn actions. |
||
|
|
214a77ea18 |
Add the waybar config and autostart file the hyprcosmic profile needs
The profile disables cosmic-panel, so without these a HyprCosmic session is a
compositor with no bar, no clock and no tray. Waybar's shipped default is not
a substitute: /etc/xdg/waybar/config.jsonc is built entirely from sway/*
modules and renders nothing under cosmic-comp.
Module choices are pinned to what cosmic-comp actually advertises on the
Wayland registry, checked against a live session rather than assumed:
ext/workspaces ext_workspace_manager_v1, present in stock cosmic-comp.
wlr/taskbar zwlr_foreign_toplevel_management_v1, absent from stock
cosmic-comp and supplied by this fork's Patch A. Under a
stock compositor the module stays empty and the bar
otherwise works, so the config is not fork-only.
tray cosmic-panel normally hosts the StatusNotifierWatcher via
cosmic-applet-status-area; with the panel disabled waybar
hosts it instead.
The stylesheet keeps every colour behind an @define-color so that a HyDE
theme's waybar.theme -- which is only a list of such declarations, the bespoke
CSS being HyDE's own rather than any theme's -- can recolour the bar by being
sourced ahead of it.
Configs install to /usr/share/hyprcosmic/waybar so the autostart file needs no
per-user paths, which matters because the profile parser is deliberately not a
shell and cannot expand ~ or $HOME.
Verified: config.jsonc parses as JSONC and every compositor module placed on
the bar has a matching config block.
|
||
|
|
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.
|
||
|
|
e56ffe8465 |
cosmic-conf: watch + theme asset installation
watch: source-include expansion by textual splicing so diagnostic spans stay correct across merged files; debounced inotify; a bad edit prints diagnostics and keeps watching rather than killing the daemon. assets: plan/apply split mirroring emit.rs. Tarball entries and symlink/ hardlink targets are validated before extraction, sharing one routine between plan and apply so the check cannot drift. Added tests/archive_escape.rs as independent verification of that boundary. The tar crate refuses to build hostile archives through its safe API, so the fixtures write GNU header name/linkname bytes directly — the same thing a malicious archiver does. Asserts on the filesystem afterwards rather than on returned errors, and covers symlink indirection, where neither entry path contains '..' yet a later write still escapes. 99 tests. |
||
|
|
1d1909baaf |
cosmic-conf: HyDE theme importer
import-theme translates a HyDE hypr.theme into cosmic.conf, reusing the Phase 1 parser — which is the payoff for choosing Hyprland-style syntax. Nothing is dropped silently. Every source key either lands in the output or carries a Note explaining why not, classified as NoEquivalent, NeedsCompositorPatch, DifferentProgram or Lossy. Gradient borders contribute their first stop as the accent and say so. Handles real-world quirks found in actual theme files: HyDE's |> destination header (no '=', would otherwise be a parse error), colon-keys like shadow:enabled, and nested blur blocks. Tests run against the verbatim Catppuccin-Mocha theme, and assert that the generated conf both parses and resolves against the registry. Verified end-to-end on Tokyo-Night, a theme absent from the tests: import -> apply produced 8 correct cosmic-config files with 20 settings reported. 64 tests. |
||
|
|
dee24ac30a |
cosmic-conf: emit + CLI
emit writes RON directly rather than linking libcosmic. Spike 2 showed cosmic-config is a filesystem KV store whose notify watcher keys off file paths (lib.rs:377), so an atomic write is observed identically to the typed API — for the cost of ron instead of the whole libcosmic graph. Two-stage: plan renders without touching disk, apply writes. Composites are read-modify-write against verified upstream defaults — gaps (0,8) at theme.rs:939, CornerRadii at corner.rs:20-31 — so setting one field never drops its siblings. Unmodelled composites error rather than write blind. 52 tests. CLI verified end-to-end: apply, --diff (writes nothing), idempotent rerun, partial update preserving siblings, and multi-diagnostic failure with exit 1 and zero writes. |
||
|
|
09f90f65de |
cosmic-conf: parser, schema registry, resolve with projection folding
Spike results corrected the spec: ThemeBuilder.gaps is (outer, inner) at
cosmic-theme/src/model/theme.rs:895, lives under CosmicTheme.{Dark,Light}.Builder
rather than CosmicTk, and fans out to two components. Entry therefore carries
targets: &[Target].
34 tests, including the folding property that keeps gaps_out from clobbering
gaps_in. Bare #rrggbb colours rejected: # begins a comment, as in Hyprland.
|