diff --git a/README.md b/README.md index ace978f..d3ae581 100644 --- a/README.md +++ b/README.md @@ -74,24 +74,26 @@ It runs these steps, in this order, and every one of them is idempotent: | Step | What it does | | --- | --- | -| 1. `BLOB_PATH` | symlinks `~/.local/share/blob` to this checkout | -| 2. Directories | creates the 19 directories the shell and scripts expect | +| 1. Directories | creates the 19 directories the shell and scripts expect, before anything writes into them | +| 2. `BLOB_PATH` | symlinks `~/.local/share/blob` to this checkout | | 3. Packages | installs what is missing from `packages/`, bootstrapping `yay` first on a machine that has no AUR helper | | 4. Config | copies `hypr/`, the themed app configs, `blob/shell.json`, the uwsm environment, and the icon font into `~/.config` | -| 5. User units | installs the session services into `~/.config/systemd/user` and enables them | -| 6. systemd drop-ins | writes the shutdown timeouts, oomd thresholds, plocate prune paths, and sleep hooks under `/etc` | -| 7. System services | enables NetworkManager, resolved, bluetooth, cups, avahi, docker, power-profiles-daemon, and oomd, and masks `NetworkManager-wait-online` | -| 8. Login screen | installs the session entry and the SDDM config, enables `sddm.service`, and sets the default target to `graphical.target` | -| 9. Launch | starts the login screen, so the desktop comes up without a reboot | +| 5. First theme | links `~/wallpapers` to the checkout, applies `flats` headless, and picks a background, so the first login is not a blank desktop | +| 6. User units | installs the session services into `~/.config/systemd/user` and enables them | +| 7. systemd drop-ins | writes the shutdown timeouts, oomd thresholds, plocate prune paths, and sleep hooks under `/etc` | +| 8. System services | enables NetworkManager, resolved, bluetooth, cups, avahi, docker, power-profiles-daemon, and oomd, and masks `NetworkManager-wait-online` | +| 9. Login screen | installs the session entry and the SDDM config, enables `sddm.service`, and sets the default target to `graphical.target` | +| 10. Launch | starts the login screen, so the desktop comes up without a reboot | -Step 8 is the one a plain Arch install cannot do without: SDDM's stock Wayland +Step 9 is the one a plain Arch install cannot do without: SDDM's stock Wayland greeter runs on `weston`, which Blob does not install, so the greeter is pointed -at Hyprland instead. It also reports any leftover drop-in that autologs into a -session that is not installed, which is what a black screen with no login prompt -usually is. [install.md](docs/install.md) covers each step in full. +at Hyprland with a Lua config, the only format Hyprland still reads. It also +reports any leftover drop-in that autologs into a session that is not installed, +which is the other thing a black screen with no login prompt usually is. +[install.md](docs/install.md) covers each step in full. To back out, `./uninstall.sh` removes the session entry, the config, the user -services, and the symlink, restoring any `.bak` the installer made. The +services, and the symlinks, restoring any `.bak` the installer made. The checkout, the wallpapers, and the themes stay where they are. ## Regenerating docs diff --git a/default/sddm/hyprland-greeter.conf b/default/sddm/hyprland-greeter.conf deleted file mode 100644 index e24cdac..0000000 --- a/default/sddm/hyprland-greeter.conf +++ /dev/null @@ -1,9 +0,0 @@ -misc { - disable_hyprland_logo = true - disable_splash_rendering = true - force_default_wallpaper = 0 -} - -animations { - enabled = false -} diff --git a/default/sddm/hyprland-greeter.lua b/default/sddm/hyprland-greeter.lua new file mode 100644 index 0000000..5b14e02 --- /dev/null +++ b/default/sddm/hyprland-greeter.lua @@ -0,0 +1,13 @@ +-- Hyprland config for the SDDM Wayland greeter. SDDM starts the greeter itself +-- once the compositor is up, so nothing here launches anything. +hl.config({ + misc = { + disable_hyprland_logo = true, + disable_splash_rendering = true, + force_default_wallpaper = 0, + }, + + animations = { + enabled = false, + }, +}) diff --git a/default/sddm/zz-blob.conf b/default/sddm/zz-blob.conf index 4c7d7d4..590a6a5 100644 --- a/default/sddm/zz-blob.conf +++ b/default/sddm/zz-blob.conf @@ -2,7 +2,7 @@ DisplayServer=wayland [Wayland] -CompositorCommand=start-hyprland -- --config /etc/sddm/hyprland-greeter.conf +CompositorCommand=start-hyprland -- --config /etc/sddm/hyprland-greeter.lua [Theme] Current= diff --git a/docs/install.md b/docs/install.md index 8f7606b..a23c455 100644 --- a/docs/install.md +++ b/docs/install.md @@ -15,42 +15,55 @@ with `--check`. | `install/steps/packages.sh` | repo and AUR packages, and the yay bootstrap | | `install/steps/config.sh` | the `BLOB_PATH` symlink and everything under `~/.config` | | `install/steps/services.sh` | system services, systemd drop-ins, user units | +| `install/steps/theme.sh` | the wallpaper link, the first theme, the first background | | `install/steps/login.sh` | SDDM, the session entry, and the first launch | ## Steps -1. **`BLOB_PATH`** is symlinked to the checkout at `~/.local/share/blob`. Nothing - under `bin/`, `shell/`, `themes/` or `default/` is ever copied. -2. **Directories** are created, including `~/.config/systemd/user/app.slice.d` - and `~/wallpapers`. +1. **Directories** are created first, because nothing below can write into a + directory that is not there: `~/.config/blob`, `~/.config/systemd/user/app.slice.d`, + `~/.local/state/blob`, `~/.local/share` and `~/.cache/blob`. A fresh Arch + install has none of them. +2. **`BLOB_PATH`** is symlinked to the checkout at `~/.local/share/blob`. Nothing + under `bin/`, `shell/`, `themes/` or `default/` is ever copied. `ln` does not + create the parent of a link, so this step makes `~/.local/share` itself rather + than assuming step 1 ran. 3. **Packages** come from `packages/blob.packages` and `packages/blob-aur.packages`. A machine with no AUR helper gets `yay-bin` built once with `makepkg`. See [packages.md](packages.md). 4. **Config** is copied into `~/.config`: `hypr/`, the themed app configs, `blob/shell.json`, the uwsm environment, and the icon font. -5. **User units** land in `~/.config/systemd/user` and are enabled, not started: +5. **The first theme** is applied. `~/wallpapers` is symlinked to the checkout's + `wallpapers/`, `flats` is applied with `BLOB_THEME_HEADLESS=1` (no shell or + session bus exists yet), and the background symlink is pointed at the first + image in `~/wallpapers`. Without this the first login has no palette and no + wallpaper, which draws as a black desktop. All three steps are skipped when a + theme, a background or a wallpaper directory of your own is already there. +6. **User units** land in `~/.config/systemd/user` and are enabled, not started: they are all `WantedBy=graphical-session.target`, so they come up with the session. `blob-speaker-tuning.service` is left out, because `blob-audio-tuning` installs it only on machines with a tuning profile. -6. **systemd drop-ins** are written under `/etc`: the shutdown timeouts, the +7. **systemd drop-ins** are written under `/etc`: the shutdown timeouts, the oomd pressure thresholds, the plocate prune paths, and the sleep hooks. -7. **System services** are enabled: NetworkManager, resolved, bluetooth, cups, +8. **System services** are enabled: NetworkManager, resolved, bluetooth, cups, avahi, docker, power-profiles-daemon, and oomd. `NetworkManager-wait-online.service` is masked so a slow DHCP lease cannot hold up the login screen. -8. **The login screen** is SDDM. Three pieces have to be in place: +9. **The login screen** is SDDM. Three pieces have to be in place: | File | Why | | --- | --- | | `/usr/local/share/wayland-sessions/blob.desktop` | the session SDDM offers | | `/etc/sddm.conf.d/zz-blob.conf` | Wayland greeter, run on Hyprland, stock theme, remember the last session | - | `/etc/sddm/hyprland-greeter.conf` | the greeter's own tiny Hyprland config | + | `/etc/sddm/hyprland-greeter.lua` | the greeter's own tiny Hyprland config | The greeter config matters more than it looks: SDDM's stock Wayland greeter runs on `weston`, which Blob does not install, so a default SDDM would fail - to draw anything. `sddm.service` is enabled and the default systemd target is - set to `graphical.target`, which an Arch install without a display manager - does not have. + to draw anything. That greeter config has to be Lua: Hyprland dropped the old + hyprlang format, and a config it cannot parse leaves the greeter with no + compositor and the screen black. `sddm.service` is enabled and the default + systemd target is set to `graphical.target`, which an Arch install without a + display manager does not have. The `zz-` prefix is load-bearing. SDDM reads `/etc/sddm.conf.d` in alphabetical order and the last file wins, so a leftover `10-wayland.conf`, @@ -70,10 +83,10 @@ with `--check`. `--autologin` writes `zzz-blob-autologin.conf`, the only file that sorts after `zz-blob.conf`. -9. **Launch.** When the installer was run from a console and nothing graphical - is running, it starts `sddm.service` so the desktop appears without a reboot. - `--no-launch` prints the command instead. `--autologin` writes - `/etc/sddm.conf.d/99-blob-autologin.conf` for the current user. +10. **Launch.** When the installer was run from a console and nothing graphical + is running, it starts `sddm.service` so the desktop appears without a reboot. + `--no-launch` prints the command instead. `--autologin` writes + `/etc/sddm.conf.d/zzz-blob-autologin.conf` for the current user. ## Flags @@ -88,7 +101,7 @@ with `--check`. ## Uninstall `./uninstall.sh` removes the config, the user units, the `/etc` drop-ins, the -session entry and the symlink, restoring any `.bak` the installer left. -`--keep-session` keeps the session entry and the SDDM config, `--keep-state` -keeps `~/.local/state/blob`. `sddm.service` is left enabled either way, so the +session entry, the wallpaper link and the symlink, restoring any `.bak` the +installer left. `--keep-session` keeps the session entry and the SDDM config, +`--keep-state` keeps `~/.local/state/blob`. `sddm.service` is left enabled either way, so the machine still reaches a login screen. diff --git a/docs/themes.md b/docs/themes.md index 10299dd..1f5d1f3 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -66,3 +66,8 @@ Twenty-two came across from upstream, plus `flats` and `pitch-dark`. Their `backgrounds/` directories are empty on purpose: the upstream images were Omarchy branding, and `~/wallpapers` holds 103 of your own. A theme with no background of its own leaves the current wallpaper alone. + +Because of that, a machine with no wallpaper yet would get none from the theme +either. The installer covers the gap: it symlinks `~/wallpapers` to the +checkout, applies `flats`, and points the background symlink at the first image +it finds. See [install.md](install.md). diff --git a/install.sh b/install.sh index b8fd5ef..5f3c152 100755 --- a/install.sh +++ b/install.sh @@ -48,6 +48,7 @@ source "$repo_dir/install/steps/directories.sh" source "$repo_dir/install/steps/packages.sh" source "$repo_dir/install/steps/config.sh" source "$repo_dir/install/steps/services.sh" +source "$repo_dir/install/steps/theme.sh" source "$repo_dir/install/steps/login.sh" say "Blob installer" @@ -57,8 +58,8 @@ if [[ $check == true ]]; then fi say "" -link_blob_path create_directories +link_blob_path install_packages install_shipped_config @@ -66,6 +67,10 @@ install_user_units install_systemd_dropins install_zen_config +link_wallpapers +install_default_theme +install_default_background + enable_system_services mask_system_services enable_user_units @@ -82,7 +87,7 @@ if [[ $check == true ]]; then (( changes == 0 )) || exit 1 else say "$changes item(s) changed." - say "Set a theme with 'blob theme set ' once the session is up." + say "Change the theme with 'blob theme set ' once the session is up." say "" launch_desktop fi diff --git a/install/steps/config.sh b/install/steps/config.sh index 1969151..4fdfbf4 100644 --- a/install/steps/config.sh +++ b/install/steps/config.sh @@ -1,6 +1,10 @@ # BLOB_PATH is a symlink to this checkout rather than a copy, so bin/, shell/, # themes/ and default/ are always the working tree. Every path the shell # resolves as $BLOB_PATH/... therefore needs no install step at all. +# +# ~/.local/share does not exist on a fresh Arch install, and ln does not create +# the parent of its link, so make it here rather than relying on another step +# having run first. link_blob_path() { local current="" [[ -L $blob_path ]] && current="$(readlink -f "$blob_path")" @@ -22,6 +26,7 @@ link_blob_path() { return 0 fi + mkdir -p "$(dirname "$blob_path")" ln -sfn "$repo_dir" "$blob_path" say "link BLOB_PATH -> $repo_dir" } diff --git a/install/steps/directories.sh b/install/steps/directories.sh index 0a393a1..274cc67 100644 --- a/install/steps/directories.sh +++ b/install/steps/directories.sh @@ -17,7 +17,7 @@ directories=( "$HOME/.local/share/fonts" "$HOME/.local/share/applications" "$HOME/.local/share/icons/hicolor/256x256/apps" - "$HOME/wallpapers" + "$HOME/.cache/blob" ) create_directories() { diff --git a/install/steps/login.sh b/install/steps/login.sh index c1989c9..513a305 100644 --- a/install/steps/login.sh +++ b/install/steps/login.sh @@ -1,7 +1,7 @@ session_entry_dir=/usr/local/share/wayland-sessions session_entry_search_dirs=(/usr/local/share/wayland-sessions /usr/share/wayland-sessions) greeter_config_dir=/etc/sddm.conf.d -greeter_compositor_config=/etc/sddm/hyprland-greeter.conf +greeter_compositor_config=/etc/sddm/hyprland-greeter.lua # SDDM reads /etc/sddm.conf.d in alphabetical order and the last file wins, so # Blob's drop-ins are named to sort after anything a distribution, an installer @@ -10,10 +10,12 @@ blob_greeter_conf="$greeter_config_dir/zz-blob.conf" blob_autologin_conf="$greeter_config_dir/zzz-blob-autologin.conf" # SDDM's stock Wayland greeter runs on weston, which Blob does not install. -# Point it at Hyprland with a greeter-only config instead. +# Point it at Hyprland with a greeter-only config instead. That config is Lua: +# Hyprland dropped the old hyprlang format, and a config it cannot parse leaves +# the greeter with no compositor and the screen black. install_login_config() { - install_root_file "$repo_dir/default/sddm/hyprland-greeter.conf" \ - "$greeter_compositor_config" "sddm/hyprland-greeter.conf" + install_root_file "$repo_dir/default/sddm/hyprland-greeter.lua" \ + "$greeter_compositor_config" "sddm/hyprland-greeter.lua" install_root_file "$repo_dir/default/sddm/zz-blob.conf" \ "$blob_greeter_conf" "sddm.conf.d/zz-blob.conf" } diff --git a/install/steps/theme.sh b/install/steps/theme.sh new file mode 100644 index 0000000..e15d3c4 --- /dev/null +++ b/install/steps/theme.sh @@ -0,0 +1,92 @@ +default_theme=flats +wallpaper_dir="$HOME/wallpapers" +current_state_dir="$HOME/.local/state/blob/current" + +# ~/wallpapers is what the wallpaper picker and `blob wallpaper set` read, and +# the checkout already carries the collection. Link it for the same reason +# BLOB_PATH is a link: one copy, and edits in the checkout are live. +link_wallpapers() { + local source="$repo_dir/wallpapers" + local current="" + + [[ -L $wallpaper_dir ]] && current="$(readlink -f "$wallpaper_dir")" + + if [[ $current == "$source" ]]; then + say "ok wallpapers" + return 0 + fi + + if [[ -d $wallpaper_dir && ! -L $wallpaper_dir ]] && [[ -n "$(ls -A "$wallpaper_dir")" ]]; then + say "ok wallpapers (own directory kept)" + return 0 + fi + + note_change + if [[ $check == true ]]; then + say "would link $wallpaper_dir -> $source" + return 0 + fi + + [[ -d $wallpaper_dir && ! -L $wallpaper_dir ]] && rmdir "$wallpaper_dir" + ln -sfn "$source" "$wallpaper_dir" + say "link wallpapers -> $source" +} + +# Without a theme there is no palette, no themed app config, and no background +# for the shell to draw, so the first login lands on a black screen that looks +# like a failed session. Applied headless: no shell and no session bus exist +# while the installer runs. +install_default_theme() { + if [[ -s $current_state_dir/theme.name ]]; then + say "ok theme ($(cat "$current_state_dir/theme.name"))" + return 0 + fi + + note_change + if [[ $check == true ]]; then + say "would apply the $default_theme theme" + return 0 + fi + + if BLOB_PATH="$blob_path" BLOB_THEME_HEADLESS=1 PATH="$repo_dir/bin:$PATH" \ + blob-theme-set "$default_theme" >/dev/null; then + say "theme $default_theme" + else + say "WARN could not apply the $default_theme theme" + fi +} + +# Only a theme that ships its own backgrounds/ sets this link, and most ship +# none: they are meant to leave the current wallpaper alone. On a first install +# there is no current wallpaper, so pick one. +install_default_background() { + local link="$current_state_dir/background" + local search="$wallpaper_dir" + local first + + if [[ -e $link ]]; then + say "ok background" + return 0 + fi + + # --check writes nothing, so the wallpaper link is not there yet to look in. + [[ -d $search ]] || search="$repo_dir/wallpapers" + + first="$(find -L "$search" -maxdepth 1 -type f \ + \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) \ + 2>/dev/null | sort | head -1)" + + if [[ -z $first ]]; then + say "SKIP background (no image in $search)" + return 0 + fi + + note_change + if [[ $check == true ]]; then + say "would set the background to $(basename "$first")" + return 0 + fi + + ln -nsf "$first" "$link" + say "bg $(basename "$first")" +} diff --git a/uninstall.sh b/uninstall.sh index c2b5e97..0098ca6 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -92,6 +92,11 @@ if [[ -L $blob_path ]]; then say "remove BLOB_PATH symlink" fi +if [[ -L $HOME/wallpapers ]]; then + rm -f "$HOME/wallpapers" + say "remove ~/wallpapers symlink" +fi + for relative in blob/shell.json uwsm/default uwsm/env.d/10-blob; do restore_or_remove "$config_dir/$relative" "$relative" done