Compare commits

...
4 Commits
Author SHA1 Message Date
gitops 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.
2026-08-12 06:55:40 +07:00
gitops 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.
2026-08-11 19:11:47 +07:00
gitops 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.
2026-08-11 18:25:24 +07:00
gitops 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.
2026-08-11 18:15:44 +07:00
9 changed files with 301 additions and 47 deletions
+72 -13
View File
@@ -153,26 +153,85 @@ 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.
The cost of that is real and worth stating: an upgrade that improves a shipped
default will not reach a file you already have. If a release changes something
you want — the `autostart` line that sets the wallpaper did change once — take
it deliberately, after reading what you would lose:
```shell
diff -u ~/.config/hyprcosmic/autostart /usr/share/hyprcosmic/skel/hyprcosmic/autostart
cp /usr/share/hyprcosmic/skel/hyprcosmic/autostart ~/.config/hyprcosmic/
```
`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 wallpaper is `awww`'s job, and the image it draws is
`~/.local/share/wallpapers/hyprcosmic/current` — a symlink `import-theme
--assets` maintains. Before you have imported a theme there is no such link, so
`hyprcosmic-wallpaper` falls back to whatever the distribution ships,
`/usr/share/backgrounds/cosmic` first; the Fedora package recommends
`cosmic-wallpapers` so there is something there. With no `awww` installed it
says so on the session log and leaves the background alone, rather than waiting
for a daemon that is never coming.
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
+14 -16
View File
@@ -60,28 +60,26 @@ awww-daemon
# get a blank screen below the bar with no error anywhere: the failure is that
# nobody asked for a wallpaper, so nothing reports one missing.
#
# `sh -c` rather than a bare `awww img`, for one reason: awww-daemon above has
# only just been forked and is not listening yet, so an immediate `awww img`
# loses a race and fails silently. The loop waits for the daemon to answer
# before setting the image.
# It sets ~/.local/share/wallpapers/hyprcosmic/current, a symlink
# `hyprcosmic-conf import-theme --assets` maintains beside the wallpapers it
# copies. The link is used rather than a real path so that this and rofi's
# local.rasi -- which shows the same image in the launcher's sidebar -- cannot
# drift apart, and so that importing a different theme does not leave either
# pointing at a file that no longer exists.
#
# This does not weaken the no-shell rule in the header. That rule exists so a
# file naming programs cannot be escalated into arbitrary execution; naming
# `sh` explicitly is just naming a program, and anyone able to write this file
# could already name any binary on the system.
#
# `current` is a symlink `cosmic-conf import-theme --assets` maintains beside
# the wallpapers it copies, pointing at one of them. It is named here rather
# than a real file so that this line and rofi's local.rasi -- which shows the
# same image in the launcher's sidebar -- cannot drift apart, and so that
# importing a different theme does not leave this pointing at a path that no
# longer exists.
# Until a theme has been imported there is no such link, so on a machine that
# has just installed the package it falls back to whatever the distribution
# ships, /usr/share/backgrounds/cosmic first. A first login gets a desktop.
#
# Change the wallpaper by repointing the link, not by editing this file:
#
# ln -sfn ~/".local/share/wallpapers/hyprcosmic/<theme>/<image>" \
# ~/.local/share/wallpapers/hyprcosmic/current
sh -c 'until awww query >/dev/null 2>&1; do sleep 0.2; done; exec awww img "$HOME/.local/share/wallpapers/hyprcosmic/current"'
#
# A program rather than the `sh -c` this used to be, because waiting for the
# daemon needs a bound: awww is a Recommends in a COPR, and the old unbounded
# `until awww query` loop spun for the whole session on a machine without it.
hyprcosmic-wallpaper
# A terminal, unconditionally, as the way back in.
#
+81
View File
@@ -0,0 +1,81 @@
#!/bin/sh
# Set the desktop wallpaper once the wallpaper daemon is listening.
#
# The hyprcosmic profile does not start cosmic-bg, so nothing else draws a
# background: without this there is a black screen below the bar, and no error
# anywhere, because the failure is that nobody asked for a wallpaper.
#
# This was three shell fragments on one autostart line. It became a program for
# two reasons, both of which cost a first login its desktop:
#
# - `current` is a symlink `import-theme --assets` maintains, so it does not
# exist until a theme has been imported. Naming it directly meant a machine
# that had just installed the package set no wallpaper at all.
# - The wait for the daemon was `until awww query; do sleep 0.2; done`, with
# nothing to stop it. awww is a Recommends and lives in a COPR, so on a
# machine that never enabled that repository the loop spun at 5 Hz for the
# length of the session.
#
# Both are now bounded and both say so on the session log.
set -eu
link="${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers/hyprcosmic/current"
# Searched in order when the link is not there yet. The first is cosmic-wallpapers,
# which the Fedora package recommends for exactly this; the other two are where
# freedesktop and KDE put theirs, so a machine with neither still has a chance.
fallbacks='/usr/share/backgrounds/cosmic /usr/share/backgrounds /usr/share/wallpapers'
if ! command -v awww >/dev/null 2>&1; then
echo "hyprcosmic-wallpaper: awww is not installed; no wallpaper set" >&2
exit 0
fi
# awww-daemon is started immediately above this in autostart and has only just
# been forked, so an immediate `awww img` loses the race and fails silently.
# Ten seconds is far longer than the daemon has ever taken and short enough that
# a daemon which is never coming does not leave a process spinning all session.
tries=0
until awww query >/dev/null 2>&1; do
tries=$((tries + 1))
if [ "$tries" -ge 50 ]; then
echo "hyprcosmic-wallpaper: awww-daemon did not answer in 10s; no wallpaper set" >&2
exit 0
fi
sleep 0.2
done
# -e rather than -L, deliberately: it follows the link, so a `current` left
# dangling by a deleted theme falls through to the defaults instead of being
# handed to awww as a path that is not there.
img=''
if [ -e "$link" ]; then
img=$link
else
for dir in $fallbacks; do
[ -d "$dir" ] || continue
for candidate in "$dir"/*.jpg "$dir"/*.jpeg "$dir"/*.png; do
# The glob is literal when nothing matches, which is why
# this tests the file rather than trusting the expansion.
[ -f "$candidate" ] || continue
img=$candidate
break
done
# An if, not `[ -n "$img" ] && break`: under set -e a trailing
# false at the end of the loop body would take the script with it.
if [ -n "$img" ]; then
break
fi
done
if [ -n "$img" ]; then
echo "hyprcosmic-wallpaper: no $link yet, falling back to $img" >&2
fi
fi
if [ -z "$img" ]; then
echo "hyprcosmic-wallpaper: no wallpaper found in $link or $fallbacks" >&2
exit 0
fi
exec awww img "$img"
+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.
*/
+33 -2
View File
@@ -81,8 +81,30 @@ Requires: rofi-wayland
# unsatisfiable dependency is not.
Recommends: awww
# Nerd Font glyphs are most of what the bar draws.
Recommends: nerd-fonts
# Something for the wallpaper daemon to draw before a theme has been imported.
# hyprcosmic-wallpaper looks here first when ~/.local/share/wallpapers has no
# `current` link yet, which on a machine that has just installed this package is
# always. Nothing in the dependency chain pulls it in otherwise -- neither
# cosmic-session nor cosmic-comp requires it -- and without it a first login is
# a bar on a black screen.
Recommends: cosmic-wallpapers
# 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
@@ -131,6 +153,15 @@ cp -a "%{stagedir}/." "%{buildroot}/"
%files -f %{filelist}
%changelog
* Wed Aug 12 2026 dingo <[email protected]> - 0.1.1-1
- Seed ~/.config from /usr/share/hyprcosmic/skel at every session start, so a
machine that has never seen HyprCosmic logs into a configured desktop rather
than a bare compositor. Existing files are never overwritten.
- Recommend font(notosansmono) in place of nerd-fonts, which is not a package
in any Fedora repository and so could never have been installed.
- Recommend cosmic-wallpapers, and add hyprcosmic-wallpaper, which falls back
to it when no theme has been imported yet.
* Mon Aug 10 2026 dingo <[email protected]> - 0.1.0-1
- First package of the fork: hyprcosmic-comp, hyprcosmic-session and
hyprcosmic-conf installed beside the distribution's COSMIC, with a HyDE shell.
+37 -15
View File
@@ -78,6 +78,7 @@ SHARED=(
"config/bin/hyprcosmic-powermenu:bin/hyprcosmic-powermenu:755"
"config/bin/hyprcosmic-fan:bin/hyprcosmic-fan:755"
"config/bin/hyprcosmic-keybinds:bin/hyprcosmic-keybinds:755"
"config/bin/hyprcosmic-wallpaper:bin/hyprcosmic-wallpaper:755"
)
# The session entry point. Kept apart from SHARED because it is versioned in the
@@ -97,23 +98,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 +147,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 +159,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 +220,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[@]}")