4 Commits
Author SHA1 Message Date
gitops 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.
2026-08-11 08:22:03 +07:00
gitops 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.
2026-08-09 22:52:33 +07:00
gitops 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.
2026-08-09 22:31:40 +07:00
gitops 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.
2026-08-09 22:15:57 +07:00