Compare commits

...
5 Commits
18 changed files with 253 additions and 9 deletions
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# blob:summary=Detect whether a DRM device with an accelerated driver is present.
drm_path="${BLOB_DRM_PATH:-/sys/class/drm}"
accelerated_drivers=(
amdgpu
i915
nouveau
nvidia
nvidia-drm
radeon
xe
)
shopt -s nullglob
for card in "$drm_path"/card[0-9]*; do
[[ -L $card/device/driver ]] || continue
driver=$(basename "$(readlink -f "$card/device/driver")")
for accelerated in "${accelerated_drivers[@]}"; do
[[ $driver == "$accelerated" ]] && exit 0
done
done
exit 1
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# blob:summary=Launch the default terminal
# blob:args=[command [args...]]
if blob-cmd-present xdg-terminal-exec; then
exec setsid uwsm-app -- xdg-terminal-exec "$@"
fi
for terminal in foot kitty; do
blob-cmd-present "$terminal" && exec setsid uwsm-app -- "$terminal" "$@"
done
blob-notify-send "No terminal installed" "Install xdg-terminal-exec, foot or kitty."
exit 1
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# blob:summary=Apply the current Blob theme's light or dark preference to GTK apps
# blob:hidden=true
blob-cmd-present gsettings || exit 0
colors_toml=$HOME/.local/state/blob/current/theme/colors.toml
[[ -f $colors_toml ]] || exit 0
mode=$(blob-theme-color --file "$colors_toml" mode)
if [[ $mode == "light" ]]; then
color_scheme="prefer-light"
gtk_theme="Adwaita"
else
color_scheme="prefer-dark"
gtk_theme="Adwaita-dark"
fi
gsettings set org.gnome.desktop.interface color-scheme "$color_scheme" 2>/dev/null || true
gsettings set org.gnome.desktop.interface gtk-theme "$gtk_theme" 2>/dev/null || true
+1
View File
@@ -321,6 +321,7 @@ post_theme_commands=(
blob-restart-btop blob-restart-btop
blob-theme-foot blob-theme-foot
blob-theme-browser blob-theme-browser
blob-theme-gtk
) )
if [[ $THEME_HEADLESS != "1" ]]; then if [[ $THEME_HEADLESS != "1" ]]; then
+1
View File
@@ -37,6 +37,7 @@ hl.env("PATH", table.concat(kept, ":"))
-- Hardware-specific environment. -- Hardware-specific environment.
require("default.hypr.nvidia") require("default.hypr.nvidia")
require("default.hypr.renderer")
hl.config({ hl.config({
xwayland = { xwayland = {
+14
View File
@@ -0,0 +1,14 @@
local paths = require("default.hypr.paths")
local accelerated = paths.blob_path .. "/bin/blob-hw-gpu-accelerated"
if not o.shell_succeeds(o.shell_quote(accelerated)) then
hl.env("LIBGL_ALWAYS_SOFTWARE", "1")
hl.env("WLR_NO_HARDWARE_CURSORS", "1")
hl.config({
cursor = {
no_hardware_cursors = true,
},
})
end
+25
View File
@@ -1,6 +1,31 @@
-- Hyprland config for the SDDM Wayland greeter. SDDM starts the greeter itself -- Hyprland config for the SDDM Wayland greeter. SDDM starts the greeter itself
-- once the compositor is up, so nothing here launches anything. -- once the compositor is up, so nothing here launches anything.
-- The greeter runs as SDDM's own user, with no BLOB_PATH and no read access to
-- the checkout, so it calls the copy install.sh puts in /usr/local/bin rather
-- than loading the Lua modules the session uses. A detector that is not there
-- yet reads as no acceleration, which is the fallback that always draws.
local function gpu_is_accelerated()
local pipe = io.popen("/usr/local/bin/blob-hw-gpu-accelerated >/dev/null 2>&1 && echo OK")
if not pipe then
return false
end
local output = pipe:read("*a") or ""
pipe:close()
return output:match("OK") ~= nil
end
if not gpu_is_accelerated() then
hl.env("LIBGL_ALWAYS_SOFTWARE", "1")
hl.env("WLR_NO_HARDWARE_CURSORS", "1")
end
hl.config({ hl.config({
cursor = {
no_hardware_cursors = true,
},
misc = { misc = {
disable_hyprland_logo = true, disable_hyprland_logo = true,
disable_splash_rendering = true, disable_splash_rendering = true,
+41
View File
@@ -0,0 +1,41 @@
@define-color window_bg_color {{ background }};
@define-color window_fg_color {{ foreground }};
@define-color view_bg_color {{ background }};
@define-color view_fg_color {{ foreground }};
@define-color headerbar_bg_color {{ lighter_background }};
@define-color headerbar_fg_color {{ foreground }};
@define-color headerbar_border_color {{ dark_background }};
@define-color headerbar_backdrop_color {{ background }};
@define-color sidebar_bg_color {{ lighter_background }};
@define-color sidebar_fg_color {{ foreground }};
@define-color sidebar_backdrop_color {{ background }};
@define-color sidebar_border_color {{ dark_background }};
@define-color secondary_sidebar_bg_color {{ lighter_background }};
@define-color secondary_sidebar_fg_color {{ foreground }};
@define-color secondary_sidebar_backdrop_color {{ background }};
@define-color secondary_sidebar_border_color {{ dark_background }};
@define-color card_bg_color {{ lighter_background }};
@define-color card_fg_color {{ foreground }};
@define-color dialog_bg_color {{ background }};
@define-color dialog_fg_color {{ foreground }};
@define-color popover_bg_color {{ lighter_background }};
@define-color popover_fg_color {{ foreground }};
@define-color accent_bg_color {{ accent }};
@define-color accent_fg_color {{ background }};
@define-color accent_color {{ accent }};
@define-color destructive_bg_color {{ red }};
@define-color destructive_fg_color {{ background }};
@define-color destructive_color {{ red }};
@define-color success_color {{ green }};
@define-color warning_color {{ yellow }};
@define-color error_color {{ red }};
@define-color theme_bg_color {{ background }};
@define-color theme_fg_color {{ foreground }};
@define-color theme_base_color {{ background }};
@define-color theme_text_color {{ foreground }};
@define-color theme_selected_bg_color {{ accent }};
@define-color theme_selected_fg_color {{ background }};
@define-color insensitive_bg_color {{ dark_background }};
@define-color insensitive_fg_color {{ muted }};
@define-color borders {{ dark_background }};
+2
View File
@@ -206,6 +206,7 @@ of the `blob` listing.
| `blob-hw-display` | Print the most likely display backlight device. | - | | `blob-hw-display` | Print the most likely display backlight device. | - |
| `blob-hw-external` | Returns true when an external monitor is physically connected. | - | | `blob-hw-external` | Returns true when an external monitor is physically connected. | - |
| `blob-hw-fingerprint` | Returns true when a fingerprint reader is present (hidden) | - | | `blob-hw-fingerprint` | Returns true when a fingerprint reader is present (hidden) | - |
| `blob-hw-gpu-accelerated` | Detect whether a DRM device with an accelerated driver is present. | - |
| `blob-hw-hybrid-gpu` | Detect whether the system has an active hybrid GPU configuration | - | | `blob-hw-hybrid-gpu` | Detect whether the system has an active hybrid GPU configuration | - |
| `blob-hw-laptop` | Returns true when running on a laptop (has a lid or laptop chassis). | - | | `blob-hw-laptop` | Returns true when running on a laptop (has a lid or laptop chassis). | - |
| `blob-hw-laptop-closed` | Returns true when the laptop lid is closed (hidden) | - | | `blob-hw-laptop-closed` | Returns true when the laptop lid is closed (hidden) | - |
@@ -295,6 +296,7 @@ of the `blob` listing.
| `blob-launch-or-focus-webapp` | Launch or focus on a given web app identified by the window-pattern. | <window-pattern> <url-and-flags...> | | `blob-launch-or-focus-webapp` | Launch or focus on a given web app identified by the window-pattern. | <window-pattern> <url-and-flags...> |
| `blob-launch-screensaver` | Launch the Blob screensaver in the default terminal on the system with the correct font configuration. | - | | `blob-launch-screensaver` | Launch the Blob screensaver in the default terminal on the system with the correct font configuration. | - |
| `blob-launch-shell` | Launch the Blob shell with its log kept in the journal (hidden) | - | | `blob-launch-shell` | Launch the Blob shell with its log kept in the journal (hidden) | - |
| `blob-launch-terminal` | Launch the default terminal | [command [args...]] |
| `blob-launch-tui` | Launch a TUI command in the default terminal with Blob styling | [--app-id=<app-id>] <command> [args...] | | `blob-launch-tui` | Launch a TUI command in the default terminal with Blob styling | [--app-id=<app-id>] <command> [args...] |
| `blob-launch-webapp` | Launch a URL as a web app in the default supported browser | <url> | | `blob-launch-webapp` | Launch a URL as a web app in the default supported browser | <url> |
+51 -1
View File
@@ -86,10 +86,14 @@ with `--check`.
| `/usr/local/share/wayland-sessions/blob.desktop` | the session SDDM offers | | `/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.conf.d/zz-blob.conf` | Wayland greeter, run on Hyprland, stock theme, remember the last session |
| `/etc/sddm/hyprland-greeter.lua` | the greeter's own tiny Hyprland config | | `/etc/sddm/hyprland-greeter.lua` | the greeter's own tiny Hyprland config |
| `/usr/local/bin/blob-hw-gpu-accelerated` | the renderer check that config runs, where SDDM's user can read it |
The greeter config matters more than it looks: SDDM's stock Wayland greeter 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 runs on `weston`, which Blob does not install, so a default SDDM would fail
to draw anything. That greeter config has to be Lua: Hyprland dropped the old to draw anything. The installer checks that the `CompositorCommand` named in
`zz-blob.conf` is actually installed and warns when it is not, because SDDM
with a compositor command it cannot run starts, fails, and leaves the machine
sitting at a text console with no explanation. That greeter config has to be Lua: Hyprland dropped the old
hyprlang format, and a config it cannot parse leaves the greeter with no 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 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 systemd target is set to `graphical.target`, which an Arch install without a
@@ -131,6 +135,52 @@ with `--check`.
| `--autologin` | skip the SDDM prompt for this user | | `--autologin` | skip the SDDM prompt for this user |
| `--no-launch` | leave the login screen for the next reboot | | `--no-launch` | leave the login screen for the next reboot |
## Choosing a renderer
Hyprland renders through Mesa, and on an adapter Mesa has no driver for it exits
with `failed to create dri2 screen` instead of drawing. The greeter is Hyprland
too, so that is a black screen at boot and a second one after login, which is
how a machine with no real GPU behind its display arrives.
`blob-hw-gpu-accelerated` decides which way to go. It reads the kernel driver of
each `/sys/class/drm/card*` from sysfs, not from `lspci`, which reads PCI config
space and resumes runtime-suspended GPUs. A card driven by `amdgpu`, `i915`,
`nouveau`, `nvidia`, `nvidia-drm`, `radeon` or `xe` is a real integrated or
dedicated GPU and hardware rendering is left alone. Everything else - `vmwgfx`,
`vboxvideo`, `qxl`, `bochs-drm`, `cirrus`, `simpledrm`, `vkms`, `virtio_gpu` -
is virtual or framebuffer-only, and two settings are applied:
| Setting | Effect |
| --- | --- |
| `LIBGL_ALWAYS_SOFTWARE=1` | Mesa uses llvmpipe, which renders on the CPU |
| `WLR_NO_HARDWARE_CURSORS=1`, `cursor:no_hardware_cursors` | the cursor is drawn by the compositor, since there is no plane to put it on |
llvmpipe is slow and it draws. A machine with a real GPU never touches it.
Two places read the detector, because they run as different users:
| Where | How |
| --- | --- |
| the session | `default/hypr/renderer.lua`, required from `envs.lua` beside `nvidia.lua`, through `$BLOB_PATH/bin` |
| the greeter | `default/sddm/hyprland-greeter.lua`, through `/usr/local/bin/blob-hw-gpu-accelerated` |
The greeter runs as SDDM's own user, with no `BLOB_PATH` and no read access to
your home directory, so `install.sh` copies the detector to `/usr/local/bin`.
A detector that is not there yet reads as no acceleration, which is the fallback
that always draws.
To add a driver to the accelerated list, edit `accelerated_drivers` in
`bin/blob-hw-gpu-accelerated` and rerun `./install.sh` so the greeter's copy
follows. `virtio_gpu` is deliberately not on it: QEMU only accelerates it when
the host enables virgl, and Mesa fails the same way when it does not.
To see the difference by hand, from a console:
```bash
Hyprland # fails with the dri2 error
LIBGL_ALWAYS_SOFTWARE=1 Hyprland # starts
```
## Uninstall ## Uninstall
`./uninstall.sh` removes the config, the user units, the `/etc` drop-ins, the `./uninstall.sh` removes the config, the user units, the `/etc` drop-ins, the
+18 -5
View File
@@ -30,12 +30,12 @@ the shell and the CLI never disagree about what a slot means.
Each rendered file is picked up differently: `foot.ini` and `kitty.conf` by an Each rendered file is picked up differently: `foot.ini` and `kitty.conf` by an
`include` in the shipped terminal config, `hyprland.lua` through Hyprland's Lua `include` in the shipped terminal config, `hyprland.lua` through Hyprland's Lua
module path, `shell.toml` and `chromium.theme` read straight from the state module path, `shell.toml` and `chromium.theme` read straight from the state
directory, and `btop.theme` and `neovim.lua` through symlinks the installer directory, and `btop.theme`, `neovim.lua` and `gtk.css` through symlinks the
makes into those apps' own config trees. installer makes into those apps' own config trees.
`default/themed/*.tpl` are rendered per theme into `default/themed/*.tpl` are rendered per theme into
`~/.local/state/blob/current/theme/`. Nine ship: foot, kitty, btop, chromium, `~/.local/state/blob/current/theme/`. Ten ship: foot, kitty, btop, chromium,
hyprland, the screenshare picker, neovim, `shell.toml`, and zen. hyprland, the screenshare picker, neovim, `shell.toml`, zen, and `gtk.css`.
An app picks its colors up in one of two ways. Most include the generated file An app picks its colors up in one of two ways. Most include the generated file
directly, so nothing has to run: directly, so nothing has to run:
@@ -47,7 +47,20 @@ include ~/.local/state/blob/current/theme/kitty.conf
Zen does the same through a CSS import in `userChrome.css`. The rest are pushed Zen does the same through a CSS import in `userChrome.css`. The rest are pushed
by a short applier listed in `post_theme_commands` inside `blob-theme-set`: by a short applier listed in `post_theme_commands` inside `blob-theme-set`:
`blob-theme-foot`, `blob-theme-browser`, `blob-restart-terminal`, `blob-theme-foot`, `blob-theme-browser`, `blob-restart-terminal`,
`blob-restart-btop`, `blob-hypr-restart`. `blob-restart-btop`, `blob-hypr-restart`, `blob-theme-gtk`.
## GTK apps
Nautilus and the other GTK apps read two things. The palette comes from
`gtk.css`, linked into both `~/.config/gtk-3.0/gtk.css` and
`~/.config/gtk-4.0/gtk.css`, which redefines the libadwaita named colors
(`window_bg_color`, `headerbar_bg_color`, `accent_bg_color` and the rest) so a
stock Adwaita app is drawn in the theme's own colors. The light or dark
preference comes from `blob-theme-gtk`, which reads the theme's `mode` and sets
`org.gnome.desktop.interface color-scheme` and `gtk-theme` through `gsettings`.
The preference applies to running apps immediately; the CSS is read at startup,
so an app that was already open keeps its old colors until it is restarted.
Adding a template for another app means dropping a `.tpl` in `default/themed/` Adding a template for another app means dropping a `.tpl` in `default/themed/`
and, if it cannot include a file, adding an applier to that list. and, if it cannot include a file, adding an applier to that list.
+1
View File
@@ -81,6 +81,7 @@ install_login_config
install_session_entry install_session_entry
install_autologin install_autologin
enable_display_manager enable_display_manager
report_greeter_compositor
report_login_conflicts report_login_conflicts
report_desktop_essentials report_desktop_essentials
+19
View File
@@ -8,6 +8,7 @@ greeter_compositor_config=/etc/sddm/hyprland-greeter.lua
# ISO, or a previous desktop left behind. # ISO, or a previous desktop left behind.
blob_greeter_conf="$greeter_config_dir/zz-blob.conf" blob_greeter_conf="$greeter_config_dir/zz-blob.conf"
blob_autologin_conf="$greeter_config_dir/zzz-blob-autologin.conf" blob_autologin_conf="$greeter_config_dir/zzz-blob-autologin.conf"
greeter_gpu_detector=/usr/local/bin/blob-hw-gpu-accelerated
# SDDM's stock Wayland greeter runs on weston, which Blob does not install. # SDDM's stock Wayland greeter runs on weston, which Blob does not install.
# Point it at Hyprland with a greeter-only config instead. That config is Lua: # Point it at Hyprland with a greeter-only config instead. That config is Lua:
@@ -18,6 +19,8 @@ install_login_config() {
"$greeter_compositor_config" "sddm/hyprland-greeter.lua" "$greeter_compositor_config" "sddm/hyprland-greeter.lua"
install_root_file "$repo_dir/default/sddm/zz-blob.conf" \ install_root_file "$repo_dir/default/sddm/zz-blob.conf" \
"$blob_greeter_conf" "sddm.conf.d/zz-blob.conf" "$blob_greeter_conf" "sddm.conf.d/zz-blob.conf"
install_root_file "$repo_dir/bin/blob-hw-gpu-accelerated" \
"$greeter_gpu_detector" "blob-hw-gpu-accelerated" 0755
} }
install_session_entry() { install_session_entry() {
@@ -54,6 +57,22 @@ setting_value() {
sed -n "s/^[[:space:]]*$key[[:space:]]*=[[:space:]]*//p" "$file" | tail -1 sed -n "s/^[[:space:]]*$key[[:space:]]*=[[:space:]]*//p" "$file" | tail -1
} }
# SDDM runs CompositorCommand to get a Wayland greeter on screen. When that
# binary is not installed SDDM fails with no greeter at all, which reads as a
# machine that boots to a console for no reason, so name it here instead.
report_greeter_compositor() {
local command_line binary
command_line="$(setting_value "$repo_dir/default/sddm/zz-blob.conf" CompositorCommand)"
binary="${command_line%% *}"
[[ -n $binary ]] || return 0
command -v "$binary" &>/dev/null && return 0
say "WARN the greeter's compositor command '$binary' is not installed"
say " SDDM cannot draw a greeter without it; it is set in $blob_greeter_conf"
}
# zz-blob.conf blanks Theme and Autologin, so a leftover drop-in cannot point the # zz-blob.conf blanks Theme and Autologin, so a leftover drop-in cannot point the
# greeter at a theme that was uninstalled or autolog into a session that no # greeter at a theme that was uninstalled or autolog into a session that no
# longer exists. Either one leaves SDDM on a black screen it never falls back # longer exists. Either one leaves SDDM on a black screen it never falls back
+2
View File
@@ -131,6 +131,8 @@ link_theme_file() {
# creating the tree would be inventing a layout the user has not chosen. # creating the tree would be inventing a layout the user has not chosen.
link_app_themes() { link_app_themes() {
link_theme_file btop.theme "$config_dir/btop/themes/current.theme" "btop theme" link_theme_file btop.theme "$config_dir/btop/themes/current.theme" "btop theme"
link_theme_file gtk.css "$config_dir/gtk-3.0/gtk.css" "gtk-3.0 theme"
link_theme_file gtk.css "$config_dir/gtk-4.0/gtk.css" "gtk-4.0 theme"
if [[ -d $config_dir/nvim/lua/plugins ]]; then if [[ -d $config_dir/nvim/lua/plugins ]]; then
link_theme_file neovim.lua "$config_dir/nvim/lua/plugins/theme.lua" "neovim theme" link_theme_file neovim.lua "$config_dir/nvim/lua/plugins/theme.lua" "neovim theme"
+1
View File
@@ -6,6 +6,7 @@
# Compositor and shell # Compositor and shell
hyprland hyprland
mesa
quickshell quickshell
uwsm uwsm
sddm sddm
+6 -2
View File
@@ -39,8 +39,12 @@ BarWidget {
Process { Process {
id: updateProc id: updateProc
command: ["blob-update-available"] command: ["blob-update-available"]
onExited: function(exitCode) { stdout: StdioCollector {
root.updateAvailable = exitCode === 0 waitForEnd: true
onStreamFinished: {
const pending = parseInt(String(text).trim(), 10)
root.updateAvailable = !isNaN(pending) && pending > 0
}
} }
} }
@@ -20,7 +20,7 @@ Item {
Process { Process {
id: listProc id: listProc
command: ["bash", "-c", command: ["bash", "-c",
'find "$1" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" ' + 'find -L "$1" -maxdepth 1 -type f \\( -iname "*.jpg" -o -iname "*.jpeg" ' +
'-o -iname "*.png" -o -iname "*.webp" -o -iname "*.gif" \\) | sort', '-o -iname "*.png" -o -iname "*.webp" -o -iname "*.gif" \\) | sort',
"--", root.directory] "--", root.directory]
stdout: StdioCollector { stdout: StdioCollector {
+5
View File
@@ -8,6 +8,7 @@ session_entry="/usr/local/share/wayland-sessions/blob.desktop"
root_files=( root_files=(
/etc/profile.d/blob.sh /etc/profile.d/blob.sh
/usr/local/bin/blob-hw-gpu-accelerated
/etc/sddm.conf.d/zz-blob.conf /etc/sddm.conf.d/zz-blob.conf
/etc/sddm.conf.d/zzz-blob-autologin.conf /etc/sddm.conf.d/zzz-blob-autologin.conf
/etc/sddm/hyprland-greeter.conf /etc/sddm/hyprland-greeter.conf
@@ -111,6 +112,10 @@ for lua in hyprland.lua monitors.lua input.lua looknfeel.lua bindings.lua autost
restore_or_remove "$config_dir/hypr/$lua" "hypr/$lua" restore_or_remove "$config_dir/hypr/$lua" "hypr/$lua"
done done
for gtk in gtk-3.0 gtk-4.0; do
[[ -L $config_dir/$gtk/gtk.css ]] && restore_or_remove "$config_dir/$gtk/gtk.css" "$gtk/gtk.css"
done
if [[ $keep_state == false && -d $HOME/.local/state/blob ]]; then if [[ $keep_state == false && -d $HOME/.local/state/blob ]]; then
rm -rf "$HOME/.local/state/blob" rm -rf "$HOME/.local/state/blob"
say "remove ~/.local/state/blob" say "remove ~/.local/state/blob"