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.
This commit is contained in:
2026-08-11 18:15:44 +07:00
parent 0e6dcdc606
commit 111f3ade7b
7 changed files with 169 additions and 31 deletions
+53 -13
View File
@@ -153,26 +153,66 @@ Then log out. `HyprCosmic` appears on the greeter's session menu next to
### 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:
Nothing to do. The session seeds `~/.config` from `/usr/share/hyprcosmic/skel`
at login, copying only what is missing:
```shell
mkdir -p ~/.config/hyprcosmic/waybar
cp config/cosmic.conf config/autostart ~/.config/hyprcosmic/
cp config/waybar/style.css ~/.config/hyprcosmic/waybar/
```
~/.config/hyprcosmic/autostart
~/.config/hyprcosmic/cosmic.conf
~/.config/hyprcosmic/waybar/style.css
~/.config/hyprcosmic/waybar/theme.css
~/.config/rofi/config.rasi
~/.config/rofi/theme.rasi
~/.config/rofi/local.rasi
```
`style.css` is per-user rather than shared for one reason: it `@import`s 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.
An existing file is never touched, not even when the skeleton is newer — the
file wins, one way, and a login is not an invitation to edit your config. Two
things follow: your edits survive every login and upgrade, and deleting a file
is how you ask for the default back. `~/.cache/hyprcosmic/session.log` records
what was seeded.
The fourth file, `~/.config/rofi/config.rasi`, is written by `import-theme
--assets` too, because it names per-machine paths.
`autostart` is the one that matters most, because it is what starts waybar, the
wallpaper daemon and `hyprcosmic-conf watch`. Until the session seeded it, a
machine that had never run HyprCosmic logged in to a bare compositor: running,
holding the display and accepting input, with nothing drawn on the screen and no
binding that opened anything.
`style.css` and `config.rasi` are per-user rather than shared for one reason:
each `@import`s a sibling holding the installed HyDE theme's palette, and a
relative `@import` resolves against the importing file. Those siblings —
`theme.css`, `theme.rasi` and `local.rasi` — arrive empty and are written by
`import-theme --assets`. They ship empty rather than not at all because a
missing `@import` is fatal to both consumers rather than a warning they skip:
GTK fails the entire stylesheet, and rofi reports the error in place of the
launcher.
Working from a git checkout, `just install` still places nothing in a home
directory — under `sudo` the only home directory it could see is root's — but it
does install the skeleton, so logging in seeds the same seven files.
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.
Font for the glyphs the bar and the launcher draw with.
The font is the one thing the packaging cannot do for you on Fedora, which has
no package that provides a Nerd Font at all: `texlive-inconsolata-nerd-font`
installs under `texmf-dist` and kitty's `SymbolsNerdFont` under
`/usr/lib64/kitty`, and fontconfig scans neither. Arch has
`ttf-nerd-fonts-symbols`. Otherwise, install one into your own font directory:
```shell
mkdir -p ~/.local/share/fonts/JetBrainsMonoNerdFont
# unpack JetBrainsMono.zip from github.com/ryanoasis/nerd-fonts/releases there
fc-cache -f
fc-list ":charset=e0b0" family | grep -i nerd # non-empty: the glyphs resolve
```
Query the charset on its own, as above. Adding a family filter —
`":charset=e0b0:family=JetBrainsMono Nerd Font"` — reports nothing even when the
font does cover the codepoint, because the family string is a comma-separated
alias list (`JetBrainsMono Nerd Font,JetBrainsMono NF`). Without the font the
bar still works; every icon is a tofu box.
## Configuration
+29
View File
@@ -0,0 +1,29 @@
/* Per-machine launcher settings for HyprCosmic.
*
* Empty until `hyprcosmic-conf import-theme --assets` writes it. It exists from
* the first session because config.rasi imports it, and a missing @import is an
* error rofi reports in place of the launcher rather than a warning it skips.
*
* It is the last of the four imports in config.rasi, so anything here wins; it
* is also overwritten by the next import run with --overwrite, so keep hand
* edits somewhere else.
*
* Two things belong in this file and nothing else does: values that name a path
* or a package on this particular machine, which neither /usr/share/hyprcosmic
* nor a HyDE theme file can know. Concretely, that is the icon theme and the
* sidebar image:
*
* configuration {
* icon-theme: "Tela-circle-purple", "Adwaita";
* }
*
* dummywall {
* background-image: url("/home/you/.local/share/wallpapers/hyprcosmic/current", height);
* }
*
* Both are shown commented out rather than set, because a default that named
* an icon theme nobody has installed, or a wallpaper path under someone else's
* home, would be worse than no default at all.
*/
* {}
+16
View File
@@ -0,0 +1,16 @@
/* Step 2 of the import chain in config.rasi: the installed HyDE theme's rofi
* colours.
*
* Empty on purpose, for the same reason as waybar's theme.css: a missing
* @import is an error rofi reports in place of the launcher rather than a
* warning it skips, so config.rasi can only import a file that is guaranteed
* to exist. palette.rasi already defines every name rules.rasi reads, so
* defining nothing here leaves a complete theme rather than an unresolved
* reference.
*
* `hyprcosmic-conf import-theme` overwrites this with the imported theme's
* rofi colours. Hand-written overrides belong in local.rasi, which is imported
* after the rules and is left alone unless --overwrite is passed.
*/
* {}
+18
View File
@@ -0,0 +1,18 @@
/* Step 2 of the import chain in style.css: the installed HyDE theme's
* waybar.theme.
*
* This copy is deliberately empty of rules. It is not a stub waiting to be
* written -- an empty file is the correct content when no theme has been
* imported, and it is what makes the other three steps work:
*
* - A missing @import is fatal in GTK, so style.css cannot name a file that
* might not exist. This one always exists, from the moment the session
* first starts.
* - Every colour the rules consume already has a default from palette.css
* and bridge-hyde.css, so a file that defines nothing leaves a complete
* stylesheet rather than an undefined name.
*
* `hyprcosmic-conf import-theme` overwrites this with the waybar.theme of the
* theme being imported. Anything written here by hand is lost at that point,
* which is why the per-user override belongs in style.css instead.
*/
+16 -2
View File
@@ -81,8 +81,22 @@ Requires: rofi-wayland
# unsatisfiable dependency is not.
Recommends: awww
# Nerd Font glyphs are most of what the bar draws.
Recommends: nerd-fonts
# Nerd Font glyphs are most of what the bar and the launcher draw, and Fedora
# has nothing that provides them. This line used to say `nerd-fonts`, which is
# not a package in any Fedora repository -- so it could never be satisfied, and
# the fonts never arrived, silently, because that is what Recommends does when
# it cannot resolve. The two patched fonts Fedora does ship are no help either:
# texlive-inconsolata-nerd-font lands under texmf-dist and kitty's
# SymbolsNerdFont under /usr/lib64/kitty, and fontconfig scans neither path.
#
# So recommend the fallback the configuration actually names. rules.rasi asks
# for "JetBrainsMono Nerd Font, Noto Sans Mono": this is the second of the two.
# It cannot draw a single glyph, but it decides what the text is set in instead
# of leaving that to whatever fontconfig happens to pick. A virtual provide
# rather than a name, because two packages carry it and either will do.
#
# The glyphs themselves have to be installed by hand. See README.md.
Recommends: font(notosansmono)
%description
HyprCosmic is a fork of the COSMIC desktop that takes its configuration in
+36 -15
View File
@@ -97,23 +97,43 @@ SOURCES=(
"config/waybar/generate-config.py" # the generator, and the icon table
)
# Files under config/ that are deliberately NOT installed here, each with the
# thing that does install it. This list is not decoration: the audit below
# refuses to run unless every file under config/ appears in exactly one of the
# three lists, so adding a file forces a decision about where it belongs instead
# of letting it be quietly left out of all of them.
PER_USER=(
"config/autostart" # ~/.config/hyprcosmic/autostart, by hand
"config/cosmic.conf" # ~/.config/hyprcosmic/cosmic.conf, by hand
"config/waybar/style.css" # ~/.config/hyprcosmic/waybar/style.css; @imports a sibling theme.css
"config/rofi/config.rasi" # ~/.config/rofi/config.rasi, by `cosmic-conf import-theme --assets`
# Per-user files. These cannot live in share/hyprcosmic with the rest: style.css
# and config.rasi reach their theme through relative @imports, which resolve
# against the importing file, so the importer has to sit in the same per-user
# directory as the theme it picks up.
#
# They are still installed, as a skeleton. start-hyprcosmic copies anything
# missing out of share/hyprcosmic/skel into $XDG_CONFIG_HOME when a session
# starts, and never overwrites. Destinations below therefore mirror the layout
# under ~/.config exactly -- skel/hyprcosmic/autostart becomes
# ~/.config/hyprcosmic/autostart -- because the seeding is a plain copy that
# reads the layout off this tree rather than a list it keeps in step by hand.
#
# Before this existed the answer was "by hand", which meant a machine that had
# never seen HyprCosmic logged into a bare compositor: no autostart, so no
# waybar and no wallpaper, and no keybindings, on a screen with nothing drawn on
# it. Nothing reported an error, because from the session's point of view
# nothing had gone wrong.
SKEL=(
"config/autostart:share/hyprcosmic/skel/hyprcosmic/autostart:644"
"config/cosmic.conf:share/hyprcosmic/skel/hyprcosmic/cosmic.conf:644"
"config/waybar/style.css:share/hyprcosmic/skel/hyprcosmic/waybar/style.css:644"
"config/waybar/theme.css:share/hyprcosmic/skel/hyprcosmic/waybar/theme.css:644"
"config/rofi/config.rasi:share/hyprcosmic/skel/rofi/config.rasi:644"
"config/rofi/theme.rasi:share/hyprcosmic/skel/rofi/theme.rasi:644"
"config/rofi/local.rasi:share/hyprcosmic/skel/rofi/local.rasi:644"
)
# Refuses to run unless every file under config/ appears in exactly one of
# SHARED, SKEL or SOURCES. This is not decoration: adding a file forces a
# decision about where it belongs instead of letting it be quietly left out of
# all three and never installed.
audit_config_tree() {
local f rel known=" ${PER_USER[*]} ${SOURCES[*]} " unclassified=()
local f rel known=" ${SOURCES[*]} " unclassified=()
# Built with a loop, not `${SHARED[*]%%:*}`: that form strips the suffix
# from the first element only and silently keeps the rest whole.
for f in "${SHARED[@]}"; do known+="${f%%:*} "; done
for f in "${SKEL[@]}"; do known+="${f%%:*} "; done
while IFS= read -r -d '' f; do
rel="${f#"$REPO"/}"
@@ -126,8 +146,8 @@ audit_config_tree() {
# only directories are pruned.
done < <(find "$REPO/config" -name '.?*' -type d -prune -o -type f -print0 | sort -z)
((${#unclassified[@]} == 0)) || die "not listed as shared or per-user: ${unclassified[*]}
Add each to SHARED or PER_USER in $(basename "${BASH_SOURCE[0]}") and say which."
((${#unclassified[@]} == 0)) || die "not listed as shared, skeleton or generator input: ${unclassified[*]}
Add each to SHARED, SKEL or SOURCES in $(basename "${BASH_SOURCE[0]}") and say which."
}
# The prefix is only half honoured, and pretending otherwise would be worse than
@@ -138,11 +158,12 @@ audit_config_tree() {
check_prefix_assumptions() {
[[ "$PREFIX" == /usr ]] && return 0
local hits
hits="$(cd "$REPO" && grep -rl '/usr/share/hyprcosmic' config/ 2>/dev/null | sort | tr '\n' ' ')"
hits="$(cd "$REPO" && grep -rl '/usr/share/hyprcosmic' config/ cosmic-session/data/ 2>/dev/null | sort | tr '\n' ' ')"
[[ -z "$hits" ]] && return 0
warn "PREFIX=$PREFIX, but these name /usr/share/hyprcosmic literally and cannot interpolate it:"
warn " $hits"
warn "they will keep reading /usr/share unless you edit them; rofi will show a parse error if it is empty"
warn "start-hyprcosmic is the exception: set HYPRCOSMIC_SKEL=$PREFIX/share/hyprcosmic/skel to point the seeding at this prefix"
}
# Fail before touching anything rather than half way through. The nearest
@@ -198,7 +219,7 @@ handle() {
audit_config_tree
[[ "$MODE" == install ]] && check_prefix_assumptions
targets=("${SHARED[@]}")
targets=("${SHARED[@]}" "${SKEL[@]}")
if ((WITH_SESSION)); then
if [[ -d "$REPO/cosmic-session/data" ]]; then
targets+=("${SESSION[@]}")