Add the installer, uninstaller, and generated docs

This commit is contained in:
2026-09-20 00:19:38 -04:00
parent 0c422e7345
commit 3e65d176e3
100 changed files with 2585 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
<div align="center">
<h1>Blob</h1>
<p>A self-contained Wayland desktop for Arch Linux.</p>
<img src="https://img.shields.io/badge/Arch_Linux-1793D1?style=for-the-badge&logo=arch-linux&logoColor=white" alt="Arch Linux" />
<img src="https://img.shields.io/badge/Hyprland-00A86B?style=for-the-badge&logo=hyprland&logoColor=white" alt="Hyprland" />
<img src="https://img.shields.io/badge/Quickshell-2A2A2A?style=for-the-badge&logo=qt&logoColor=white" alt="Quickshell" />
</div>
---
This started as a layer of overrides on top of Omarchy and is now its own
desktop. It keeps the look, layout, and base functionality that were worth
keeping, and owns every line that runs. There is no upstream package to track
and nothing to re-apply after somebody else's release.
## What it is
- **[Hyprland](https://hyprland.org/)** as the compositor, configured in Lua.
- **[Quickshell](https://quickshell.org/)** as the desktop: `blob-shell` is one
process hosting the bar, both menus, the panels, notifications, the lock
screen, and the OSD as plugins.
- **A `blob-*` CLI** of 251 commands, dispatched by `blob`.
- **24 themes** with a palette pipeline that retints the terminal, editor,
browser, shell, and lock screen from one `colors.toml`.
## Layout
| Path | Contents |
| --- | --- |
| `bin/` | the `blob-<area>-<verb>` commands |
| `shell/` | the Quickshell desktop |
| `hypr/` | personal Hyprland config, deployed to `~/.config/hypr` |
| `default/` | the shipped defaults the commands and shell resolve: `hypr/` Lua layer, `themed/` templates, `blob/` menu tree |
| `themes/` | colour themes |
| `config/` | shipped app configs |
| `shell.json` | bar layout and idle timings |
| `hooks/` | event hooks, such as retinting on theme change |
| `branding/` | icon, ASCII art, boot splash |
| `wallpapers/` | 103 wallpapers, previewed in [the gallery](wallpaper-gallery/index.md) |
| `session/` | wayland session entry and uwsm environment |
| `docs/` | everything below |
## Docs
| Doc | Covers |
| --- | --- |
| [commands.md](docs/commands.md) | every command, generated from its own metadata |
| [keybinds.md](docs/keybinds.md) | every binding, generated from the Lua |
| [shell.md](docs/shell.md) | shell layout, bar config, plugin model |
| [themes.md](docs/themes.md) | the palette pipeline and how to add a theme |
| [widgets.md](docs/widgets.md) | the ported GTK widgets and where they went |
| [menu.md](docs/menu.md) | the menu tree and what was trimmed |
| [upstream.md](docs/upstream.md) | the fork base, for diffing later |
## Install
`BLOB_PATH` is a symlink to this checkout, so `bin/`, `shell/`, `themes/` and
`default/` are always the working tree and edits are live. Only the handful of
files that must sit under `~/.config` get copied.
```bash
./install.sh # link and deploy
./install.sh --check # report what would change, write nothing
./install.sh --force # overwrite files with local changes
```
Then log out and pick the Blob session.
To back out, `./uninstall.sh` removes the session entry, the config, and the
symlink, restoring any `.bak` the installer made. The checkout, the wallpapers,
and the themes stay where they are.
## Regenerating docs
```bash
blob-docs-commands
blob-docs-keybinds
```
## Keys worth knowing
| Keys | Action |
| --- | --- |
| `Super + Space` | Apps menu |
| `Super + Alt + Space` | Root menu |
| `Super + Ctrl + Q` | Quick settings |
| `Super + Ctrl + M` | System monitor |
| `Super + Alt + W` | Wallpaper picker |
| `Super + Ctrl + L` | Lock |
The rest are in [keybinds.md](docs/keybinds.md), or run `blob menu keybindings`.
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# blob:summary=Regenerate docs/commands.md from each command's own metadata
# blob:hidden=true
set -e
bin_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(dirname "$bin_dir")"
output="$repo_dir/docs/commands.md"
metadata() {
sed -n "s/^# blob:$2=//p" "$1" | head -1
}
area_of() {
local name="${1#blob-}"
printf '%s\n' "${name%%-*}"
}
{
echo "# Commands"
echo
echo "Generated by \`blob-docs-commands\` from the \`# blob:summary=\` line in each"
echo "file. Do not edit by hand."
echo
echo "Commands marked hidden are plumbing other commands call, and are left out"
echo "of the \`blob\` listing."
echo
current_area=""
for file in "$bin_dir"/blob-*; do
[[ -f $file ]] || continue
name="$(basename "$file")"
area="$(area_of "$name")"
if [[ $area != "$current_area" ]]; then
echo
echo "## $area"
echo
echo "| Command | Does | Arguments |"
echo "| --- | --- | --- |"
current_area="$area"
fi
summary="$(metadata "$file" summary)"
args="$(metadata "$file" args)"
[[ -n $summary ]] || summary="-"
[[ -n $args ]] || args="-"
if grep -q '^# blob:hidden=true' "$file"; then
summary="$summary (hidden)"
fi
printf '| `%s` | %s | %s |\n' "$name" "$summary" "${args//|/\\|}"
done
echo
echo "## Totals"
echo
printf '%s commands.\n' "$(ls "$bin_dir"/blob-* | wc -l)"
} > "$output"
echo "Wrote $output"
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# blob:summary=Regenerate docs/keybinds.md from the Hyprland Lua bindings
# blob:hidden=true
set -e
bin_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(dirname "$bin_dir")"
output="$repo_dir/docs/keybinds.md"
# o.bind("KEYS", "Description", ...) and o.bind_toggle("KEYS", "Description", ...)
# are the only two forms the Lua layer uses to declare a binding.
collect() {
local file="$1"
grep -ohE 'o\.bind(_toggle)?\(\s*"[^"]+",\s*"[^"]+"' "$file" 2>/dev/null |
sed -E 's/o\.bind(_toggle)?\(\s*"([^"]+)",\s*"([^"]+)"/\2\t\3/'
}
section() {
local title="$1"
shift
local rows
rows="$(for file in "$@"; do collect "$file"; done | sort -u -t$'\t' -k1,1)"
[[ -n $rows ]] || return 0
echo
echo "## $title"
echo
echo "| Keys | Action |"
echo "| --- | --- |"
printf '%s\n' "$rows" | while IFS=$'\t' read -r keys action; do
printf '| `%s` | %s |\n' "$keys" "$action"
done
}
{
echo "# Keybindings"
echo
echo "Generated by \`blob-docs-keybinds\` from the \`o.bind\` calls in the Hyprland"
echo "Lua files. Do not edit by hand."
echo
echo "Personal overrides in \`hypr/bindings.lua\` win over the defaults, and"
echo "\`hl.unbind\` in that file removes a default outright."
section "Personal" "$repo_dir/hypr/bindings.lua"
section "Applications" "$repo_dir/default/hypr/bindings/applications.lua"
section "Tiling" "$repo_dir/default/hypr/bindings/tiling.lua"
section "Media" "$repo_dir/default/hypr/bindings/media.lua"
section "Clipboard" "$repo_dir/default/hypr/bindings/clipboard.lua"
section "Utilities" "$repo_dir/default/hypr/bindings/utilities.lua"
echo
echo "## Unbound defaults"
echo
grep -ohE 'hl\.unbind\("[^"]+"\)' "$repo_dir/hypr/bindings.lua" 2>/dev/null |
sed -E 's/hl\.unbind\("([^"]+)"\)/- `\1`/' || echo "None."
} > "$output"
echo "Wrote $output"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# blob:summary=Monitor sleep preparation and lock before suspend
# blob:group=system
# blob:hidden=true
consume_sleep_events() {
local line sleep_lock
sleep_lock="$BLOB_PATH/bin/blob-system-sleep"
while IFS= read -r line; do
if [[ $line == *"boolean true"* ]]; then
"$sleep_lock"
return 0
fi
done
}
monitor_sleep_events() {
local monitor_fd monitor_pid status
coproc SLEEP_EVENTS {
exec dbus-monitor --system \
"type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'"
}
monitor_fd=${SLEEP_EVENTS[0]}
monitor_pid=$SLEEP_EVENTS_PID
cleanup_monitor() {
kill "$monitor_pid" 2>/dev/null || true
wait "$monitor_pid" 2>/dev/null || true
}
trap cleanup_monitor EXIT
consume_sleep_events <&"$monitor_fd"
status=$?
cleanup_monitor
trap - EXIT
return "$status"
}
case ${1:-} in
--consume)
consume_sleep_events
exit 0
;;
--inhibited)
monitor_sleep_events
exit 0
;;
esac
sleep_monitor="$BLOB_PATH/bin/blob-system-sleep-monitor"
exec systemd-inhibit \
--what=sleep \
--mode=delay \
--who=Blob \
--why="Lock screen before suspend" \
"$sleep_monitor" --inhibited
+272
View File
@@ -0,0 +1,272 @@
#? Config file for btop v.1.4.6
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
color_theme = "current"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = true
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
truecolor = true
#* Set to true to force tty mode regardless if a real tty has been detected or not.
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
force_tty = false
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = true
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = true
#* Use terminal synchronized output sequences to reduce flickering on supported terminals.
terminal_sync = true
#* Default symbols to use for graph creation, "braille", "block" or "tty".
#* "braille" offers the highest resolution but might not be included in all fonts.
#* "block" has half the resolution of braille but uses more common characters.
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
graph_symbol = "braille"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_cpu = "default"
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
graph_symbol_gpu = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_mem = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "cpu mem net proc"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False.
proc_reversed = false
#* Show processes as a tree.
proc_tree = false
#* Use the cpu graph colors in the process list.
proc_colors = true
#* Use a darkening gradient in the process list.
proc_gradient = true
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core = false
#* Show process memory as bytes instead of percent.
proc_mem_bytes = true
#* Show cpu graph for each process.
proc_cpu_graphs = true
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
proc_info_smaps = false
#* Show proc box on left side of screen instead of right.
proc_left = false
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = false
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = false
#* Should cpu and memory usage display be preserved for dead processes when paused.
keep_dead_proc_usage = false
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_upper = "Auto"
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_lower = "Auto"
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
show_gpu_info = "Auto"
#* Toggles if the lower CPU graph should be inverted.
cpu_invert_lower = true
#* Set to True to completely disable the lower CPU graph.
cpu_single_graph = false
#* Show cpu box at bottom of screen instead of top.
cpu_bottom = false
#* Shows the system uptime in the CPU box.
show_uptime = true
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
show_cpu_watts = true
#* Show cpu temperature.
check_temp = true
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
cpu_sensor = "Auto"
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
show_coretemp = true
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
#* Example: "4:0 5:1 6:3"
cpu_core_map = ""
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
temp_scale = "celsius"
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
base_10_sizes = false
#* Show CPU frequency.
show_cpu_freq = true
#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average".
freq_mode = "first"
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
clock_format = "%X"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update = true
#* Custom cpu model name, empty string to disable.
custom_cpu_name = ""
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user"
disks_filter = ""
#* Show graphs instead of meters for memory values.
mem_graphs = true
#* Show mem box below net box instead of above.
mem_below_net = false
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = true
#* If swap memory should be shown in memory box.
show_swap = true
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = true
#* If mem box should be split to also show disks info.
show_disks = true
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
only_physical = true
#* Read disks list from /etc/fstab. This also disables only_physical.
use_fstab = true
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
zfs_hide_datasets = false
#* Set to true to show available disk space for privileged users.
disk_free_priv = false
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
show_io_stat = true
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
io_mode = false
#* Set to True to show combined read/write io graphs in io mode.
io_graph_combined = false
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
#* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = ""
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100
net_upload = 100
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = true
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
net_sync = true
#* Starts with the Network Interface specified here.
net_iface = ""
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
base_10_bitrate = "Auto"
#* Show battery stats in top right if battery is present.
show_battery = true
#* Which battery to use if multiple are present. "Auto" for auto detection.
selected_battery = "Auto"
#* Show power stats of battery next to charge indicator.
show_battery_watts = true
#* Set loglevel for "~/.local/state/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level = "WARNING"
#* Automatically save current settings to config file on exit.
save_config_on_exit = true
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = true
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
rsmi_measure_pcie_speeds = true
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = true
#* Set which GPU vendors to show. Available values are "nvidia amd intel"
shown_gpus = "nvidia amd intel"
#* Custom gpu0 model name, empty string to disable.
custom_gpu_name0 = ""
#* Custom gpu1 model name, empty string to disable.
custom_gpu_name1 = ""
#* Custom gpu2 model name, empty string to disable.
custom_gpu_name2 = ""
#* Custom gpu3 model name, empty string to disable.
custom_gpu_name3 = ""
#* Custom gpu4 model name, empty string to disable.
custom_gpu_name4 = ""
#* Custom gpu5 model name, empty string to disable.
custom_gpu_name5 = ""
+26
View File
@@ -0,0 +1,26 @@
[main]
include=~/.local/state/blob/current/theme/foot.ini
term=xterm-256color
font=JetBrainsMono Nerd Font:size=9
pad=14x14
initial-window-mode=windowed
workers=0
[scrollback]
lines=10000
multiplier=7.0
[cursor]
style=block
blink=no
[key-bindings]
clipboard-copy=Control+Insert Control+Shift+c XF86Copy
primary-paste=none
clipboard-paste=Shift+Insert Control+Shift+v XF86Paste
[text-bindings]
# Send Shift+Return as CSI-u so TUIs can distinguish it from Return.
\x1b[13;2u=Shift+Return
# Send Alt+Shift+Return as CSI-u so tmux can match M-S-Enter.
\x1b[13;4u=Mod1+Shift+Return
+28
View File
@@ -0,0 +1,28 @@
# See https://git-scm.com/docs/git-config
[alias]
co = checkout
br = branch
ci = commit
st = status
[init]
defaultBranch = master
[pull]
rebase = true # Rebase (instead of merge) on pull
[push]
autoSetupRemote = true # Automatically set upstream branch on push
[diff]
algorithm = histogram # Clearer diffs on moved/edited lines
colorMoved = plain # Highlight moved blocks in diffs
mnemonicPrefix = true # More intuitive refs in diff output
[commit]
verbose = true # Include diff comment in commit message template
[column]
ui = auto # Output in columns when possible
[branch]
sort = -committerdate # Sort branches by most recent commit first
[tag]
sort = -version:refname # Sort version numbers as you would expect
[rerere]
enabled = true # Record and reuse conflict resolutions
autoupdate = true # Apply stored conflict resolutions automatically
+2
View File
@@ -0,0 +1,2 @@
-- Extra autostart processes.
-- o.launch_on_start("my-service")
+29
View File
@@ -0,0 +1,29 @@
-- Keep only your personal keybinding overrides here. Add new bindings or
-- unbind defaults before replacing them.
-- See current bindings and descriptions:
-- blob menu keybindings --print
-- To disable every Blob default binding, set this in
-- ~/.config/hypr/hyprland.lua before require("default.hypr.blob"), then add
-- only the bindings you want below:
-- blob_default_bindings = false
-- To disable all preinstalled app/webapp bindings, set:
-- blob_preinstalled_bindings = false
-- Add a new binding.
-- o.bind("SUPER + SHIFT + R", "SSH", "alacritty -e ssh your-server")
-- Change an existing binding by unbinding it first, then binding the key again.
-- This example changes SUPER+SPACE from the launcher to the Blob root menu.
-- hl.unbind("SUPER + SPACE")
-- o.bind("SUPER + SPACE", "Blob menu", "blob-menu toggle root")
-- Disable a default binding without replacing it.
-- hl.unbind("SUPER + SHIFT + B")
-- Logitech MX Keys examples:
-- o.bind("SUPER + SHIFT + S", nil, "blob-capture-screenshot")
-- o.bind("SUPER + H", nil, "voxtype record toggle")
-- o.bind("SUPER + PERIOD", nil, "blob-shell shell toggle blob.emojis")
+29
View File
@@ -0,0 +1,29 @@
-- Learn how to configure Hyprland: https://wiki.hypr.land/Configuring/Start/
-- Blob's bootstrap keeps path setup out of this user config.
dofile((os.getenv("BLOB_PATH") or "$HOME/.local/share/blob") .. "/default/hypr/bootstrap.lua")
-- Disable all Blob default bindings. Add your own in hypr/bindings.lua.
-- blob_default_bindings = false
--
-- Or disable only bindings for Blob's preinstalled apps/web apps while
-- keeping core window-manager bindings:
-- blob_preinstalled_bindings = false
-- Load Blob defaults.
require("default.hypr.blob")
-- Put your personal overrides in these files. They're loaded after Blob's
-- defaults so package updates can improve the defaults without rewriting your
-- ~/.config/hypr files.
require("hypr.monitors")
require("hypr.input")
require("hypr.bindings")
require("hypr.looknfeel")
require("hypr.autostart")
-- Toggle config flags dynamically.
require("default.hypr.toggles")
-- Add any other personal Hyprland configuration below.
-- o.window("qemu", { workspace = "5" })
+14
View File
@@ -0,0 +1,14 @@
# Makes hyprsunset do nothing to the screen by default
# Without this, the default applies some tint to the monitor
profile {
time = 07:00
identity = true
}
# To enable auto switch to nightlight, add to your .config/hypr/autostart.lua:
# o.launch_on_start("hyprsunset")
# and use the following:
# profile {
# time = 20:00
# temperature = 4000
# }
+57
View File
@@ -0,0 +1,57 @@
-- Keep only your personal input overrides here. Uncommented settings below
-- replace Blob's defaults.
-- Keyboard layout and options.
-- See https://wiki.hypr.land/Configuring/Basics/Variables/#input
-- hl.config({
-- input = {
-- -- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt.
-- kb_layout = "us,dk,eu",
-- kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
--
-- -- Use a specific keyboard variant if needed (e.g. intl for international keyboards).
-- kb_variant = "intl",
--
-- -- Change speed of keyboard repeat.
-- repeat_rate = 40,
-- repeat_delay = 250,
--
-- -- Start with numlock on by default.
-- numlock_by_default = true,
--
-- -- Increase sensitivity for mouse/trackpad (default: 0).
-- sensitivity = 0.35,
--
-- -- Turn off mouse acceleration (default: adaptive).
-- accel_profile = "flat",
--
-- touchpad = {
-- -- Use natural (inverse) scrolling.
-- natural_scroll = true,
--
-- -- Use two-finger clicks for right-click instead of lower-right corner.
-- clickfinger_behavior = true,
--
-- -- Control the speed of your scrolling.
-- scroll_factor = 0.4,
--
-- -- Enable the touchpad while typing.
-- disable_while_typing = false,
--
-- -- Left-click-and-drag with three fingers.
-- drag_3fg = 1,
-- },
-- },
-- })
-- App-specific touchpad scroll speeds.
-- o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 })
-- o.window("com.mitchellh.ghostty", { scroll_touchpad = 0.2 })
-- Enable touchpad gestures for changing workspaces.
-- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Gestures/
-- hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
-- Enable touchpad gestures for moving focus (helpful on scrolling layout).
-- hl.gesture({ fingers = 3, direction = "left", action = function() hl.dispatch(hl.dsp.focus({ direction = "l" })) end })
-- hl.gesture({ fingers = 3, direction = "right", action = function() hl.dispatch(hl.dsp.focus({ direction = "r" })) end })
+50
View File
@@ -0,0 +1,50 @@
-- Change the default Blob look'n'feel.
-- https://wiki.hypr.land/Configuring/Basics/Variables/#general
-- hl.config({
-- general = {
-- -- No gaps between windows or borders.
-- gaps_in = 0,
-- gaps_out = 0,
-- border_size = 0,
--
-- -- Change to niri-like side-scrolling layout.
-- layout = "scrolling",
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#decoration
-- hl.config({
-- decoration = {
-- -- Use round window corners.
-- rounding = 8,
--
-- -- Dim unfocused windows (0.0 = no dim, 1.0 = fully dimmed).
-- dim_inactive = true,
-- dim_strength = 0.15,
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#animations
-- hl.config({
-- animations = {
-- -- Disable all animations.
-- enabled = false,
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#layout
-- hl.config({
-- layout = {
-- -- Avoid overly wide single-window layouts on wide screens.
-- single_window_aspect_ratio = { 1, 1 },
-- },
-- })
-- https://wiki.hypr.land/Configuring/Layouts/Scrolling-Layout/
-- hl.config({
-- scrolling = {
-- -- See only one column per screen instead of two.
-- column_width = 0.97,
-- },
-- })
+14
View File
@@ -0,0 +1,14 @@
-- See https://wiki.hypr.land/Configuring/Basics/Monitors/
-- List current monitors and supported resolutions with: hyprctl monitors all
local blob_gdk_scale = 2
local blob_monitor_scale = "auto"
hl.env("GDK_SCALE", tostring(blob_gdk_scale))
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = blob_monitor_scale })
-- Configure a specific monitor.
-- hl.monitor({ output = "DP-2", mode = "2560x1440@144", position = "0x0", scale = 1 })
-- Portrait/rotated secondary monitor (transform: 1 = 90°, 3 = 270°).
-- hl.monitor({ output = "DP-2", mode = "preferred", position = "auto", scale = 1, transform = 1 })
+4
View File
@@ -0,0 +1,4 @@
screencopy {
allow_token_by_default = true
custom_picker_binary = hyprland-preview-share-picker
}
+16
View File
@@ -0,0 +1,16 @@
[binds]
# Print the current image file
<Ctrl+p> = exec lp "$imv_current_file"
# Trash the current image and quit the viewer (recoverable from Trash)
<Ctrl+x> = exec gio trash -- "$imv_current_file"; quit
# Trash the current image and move to the next one
<Ctrl+Shift+X> = exec gio trash -- "$imv_current_file"; close
# Rotate the currently open image by 90 degrees
<Ctrl+r> = exec mogrify -rotate 90 "$imv_current_file"
# Edit the current image in Tensaku and quit the viewer
<Ctrl+e> = exec tensaku-edit "$imv_current_file" & ; quit
View File
+32
View File
@@ -0,0 +1,32 @@
add_newline = true
command_timeout = 200
format = "[$directory$git_branch$git_status]($style)$character"
[character]
error_symbol = "[✗](bold cyan)"
success_symbol = "[](bold cyan)"
[directory]
truncation_length = 2
truncation_symbol = "…/"
repo_root_style = "bold cyan"
repo_root_format = "[$repo_root]($repo_root_style)[$path]($style)[$read_only]($read_only_style) "
[git_branch]
format = "[$branch]($style) "
style = "italic cyan"
[git_status]
format = '[$all_status]($style)'
style = "cyan"
ahead = "⇡${count} "
diverged = "⇕⇡${ahead_count}⇣${behind_count} "
behind = "⇣${count} "
conflicted = " "
up_to_date = " "
untracked = "? "
modified = " "
stashed = ""
staged = ""
renamed = ""
deleted = ""
+40
View File
@@ -0,0 +1,40 @@
# Host config for the Blob speaker tuning.
#
# This exists so the tuning gets its own PipeWire client rather than sharing
# PipeWire's stock filter-chain.conf. That config merges every fragment in
# ~/.config/pipewire/filter-chain.conf.d/, so hosting the tuning there would load
# any unrelated filter a user keeps in that directory -- duplicating filters
# already hosted elsewhere, and stopping them all when the tuning is switched off.
#
# Installed as ~/.config/pipewire/blob-speaker-tuning.conf with the tuning
# graph merged from blob-speaker-tuning.conf.d/, and run with
# pipewire -c blob-speaker-tuning.conf
#
# The contents are the minimum a filter-hosting client needs, taken from
# /usr/share/pipewire/filter-chain.conf.
context.properties = {
log.level = 0
}
context.spa-libs = {
audio.convert.* = audioconvert/libspa-audioconvert
support.* = support/libspa-support
}
context.modules = [
# Boost the audio thread priority.
{ name = libpipewire-module-rt
args = { }
flags = [ ifexists nofail ]
}
# The native communication protocol.
{ name = libpipewire-module-protocol-native }
# Lets this process provide nodes to PipeWire.
{ name = libpipewire-module-client-node }
# Wraps nodes in an adapter with a converter and resampler.
{ name = libpipewire-module-adapter }
]
@@ -0,0 +1,140 @@
# Dell XPS 14 / XPS 16 (2026) speaker tuning.
#
# Biquad chain fitted to the measured response of the xps-audio-linux EasyEffects
# profile under a dense pink-weighted multitone of 104 bin-aligned tones,
# followed by a lookahead limiter. Measures 1.24 dB RMS against that reference
# (0.97 dB weighted over the fit's own error metric).
#
# Fitted and measured on the XPS 14 (SKU 0DB9); the XPS 16 (0DBA) is covered on
# report that the same profile suits it. See tuning.conf.
#
# Q below 200 Hz is capped at 1.8 on purpose. A closer magnitude fit is possible
# with high-Q sections, but the reference produces its narrow bass features by
# convolution, and reproducing them with high-Q biquads swung group delay 31 ms
# across 63-80 Hz, which smears bass transients. The cap costs 0.33 dB and
# halves the swing.
#
# This is a plain filter-chain sink rather than a WirePlumber smart filter. A
# smart filter is the better shape -- it would leave the real device as the
# default output instead of adding a second one -- but on PipeWire 1.6.8 /
# WirePlumber 0.5.15 this graph loads and links correctly as a smart filter and
# then passes audio through unprocessed: its controls are present and
# mpv -> filter -> sink links are made, yet the filter's input monitor and the
# speaker sink's monitor measure identically. Revisit when that is understood.
#
# Channels are wired explicitly because the limiter is a stereo plugin; a mono
# graph is duplicated per channel and would limit each side independently,
# shifting the stereo image on bass transients.
context.modules = [
{ name = libpipewire-module-filter-chain
args = {
node.description = "Laptop Speakers"
media.name = "Laptop Speakers"
filter.graph = {
nodes = [
{ type = builtin name = s0_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s1_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s2_l label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
{ type = builtin name = s3_l label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
{ type = builtin name = s4_l label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
{ type = builtin name = s5_l label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
{ type = builtin name = s6_l label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
{ type = builtin name = s7_l label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
{ type = builtin name = s8_l label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
{ type = builtin name = s9_l label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
{ type = builtin name = s10_l label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
{ type = builtin name = s11_l label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
{ type = builtin name = s12_l label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
{ type = builtin name = s0_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s1_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
{ type = builtin name = s2_r label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
{ type = builtin name = s3_r label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
{ type = builtin name = s4_r label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
{ type = builtin name = s5_r label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
{ type = builtin name = s6_r label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
{ type = builtin name = s7_r label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
{ type = builtin name = s8_r label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
{ type = builtin name = s9_r label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
{ type = builtin name = s10_r label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
{ type = builtin name = s11_r label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
{ type = builtin name = s12_r label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
{ type = lv2
name = limiter
plugin = "http://lsp-plug.in/plugins/lv2/limiter_stereo"
control = {
# Both default to enabled: "alr" regulates level toward the
# threshold and "boost" normalises the threshold up to full
# scale. A fixed tuning must switch them off or its tone drifts
# with programme level.
"alr" = 0
"boost" = 0
"g_in" = 0.5456
"th" = 0.891
}
}
]
links = [
{ output = "s0_l:Out" input = "s1_l:In" }
{ output = "s1_l:Out" input = "s2_l:In" }
{ output = "s2_l:Out" input = "s3_l:In" }
{ output = "s3_l:Out" input = "s4_l:In" }
{ output = "s4_l:Out" input = "s5_l:In" }
{ output = "s5_l:Out" input = "s6_l:In" }
{ output = "s6_l:Out" input = "s7_l:In" }
{ output = "s7_l:Out" input = "s8_l:In" }
{ output = "s8_l:Out" input = "s9_l:In" }
{ output = "s9_l:Out" input = "s10_l:In" }
{ output = "s10_l:Out" input = "s11_l:In" }
{ output = "s11_l:Out" input = "s12_l:In" }
{ output = "s12_l:Out" input = "limiter:in_l" }
{ output = "s0_r:Out" input = "s1_r:In" }
{ output = "s1_r:Out" input = "s2_r:In" }
{ output = "s2_r:Out" input = "s3_r:In" }
{ output = "s3_r:Out" input = "s4_r:In" }
{ output = "s4_r:Out" input = "s5_r:In" }
{ output = "s5_r:Out" input = "s6_r:In" }
{ output = "s6_r:Out" input = "s7_r:In" }
{ output = "s7_r:Out" input = "s8_r:In" }
{ output = "s8_r:Out" input = "s9_r:In" }
{ output = "s9_r:Out" input = "s10_r:In" }
{ output = "s10_r:Out" input = "s11_r:In" }
{ output = "s11_r:Out" input = "s12_r:In" }
{ output = "s12_r:Out" input = "limiter:in_r" }
]
inputs = [ "s0_l:In" "s0_r:In" ]
outputs = [ "limiter:out_l" "limiter:out_r" ]
}
audio.channels = 2
audio.position = [ FL FR ]
capture.props = {
node.name = "blob_speaker_tuning"
media.class = Audio/Sink
}
playback.props = {
node.name = "blob_speaker_tuning_output"
node.passive = true
target.object = "@SPEAKER_SINK@"
# This stream is the filter's output and is a movable sink input like any
# other, so anything that reroutes "all streams" to a newly selected
# output would drag the processing along with it -- onto headphones, or
# into the tuning's own sink, which is a cycle. Pin it.
node.dont-move = true
# If the speaker sink is not present yet -- the tuning host can start
# before the device is discovered -- WirePlumber would otherwise link this
# output to whatever default exists, quietly tuning the wrong device while
# the tuning sink still looks healthy. Wait for the named target instead.
# Both are needed: without linger, WirePlumber destroys the node rather
# than waiting (see its scripts/linking/find-defined-target.lua).
node.dont-fallback = true
node.linger = true
}
}
}
]
@@ -0,0 +1,39 @@
## Dell XPS 14 / XPS 16 (2026) internal speakers.
##
## Thirteen biquads and a lookahead limiter, applied as a PipeWire filter-chain
## in front of the internal speaker sink. The stock Linux path already loads
## Dell's Cirrus smart-amplifier firmware; this adds the perceptual voicing the
## Windows Waves layer provides and Linux does not.
description="Dell XPS 14/16 (2026) speakers"
## Matched on the DMI product SKU, which is what Dell keys the Cirrus speaker
## firmware on -- 10280db9 for the XPS 14 and 10280dba for the XPS 16 -- so it
## identifies the speaker hardware itself rather than a marketing name. Compared as
## whole values, so this cannot widen to the rest of the XPS line.
##
## 0DB9 XPS 14 -- measured here, see below
## 0DBA XPS 16 -- included on report that this profile suits it, not measured
match_sku=("0DB9" "0DBA")
## Unescaped dots: this is passed to awk as a string, where a backslash escape
## would be consumed before the regex sees it.
sink_pattern='^alsa_output.*sof_sdw.*HiFi__Speaker__sink$'
## Provenance. Derived by measuring the response of the xps-clone EasyEffects
## profile from https://github.com/spencerbull/xps-audio-linux (MIT) and fitting
## a biquad chain to it. No upstream asset is redistributed: the convolution
## impulse response is not carried, so this tuning has no binary blob and is
## sample-rate agnostic.
derived_from="xps-audio-linux xps-clone (MIT, spencerbull)"
validated_by="dhh"
validated_on="2026-07-24"
## The measurements below were taken on the XPS 14 (0DB9). The XPS 16 (0DBA) is
## covered on report rather than measurement; re-measure there before treating
## these figures as describing it.
validated_hardware="XPS 14 DA14260 (0DB9)"
## Measured against that reference under a dense pink-weighted multitone of 104
## bin-aligned tones. See docs/AUDIO-TUNING.md for how to reproduce these.
magnitude_rms_db="1.24"
bass_group_delay_swing_ms="13.2"
limiter_headroom_db="1.6" ## worst-case peak on a hot master vs -1 dBFS
dynamic_range_delta_lu="0.1"
+7
View File
@@ -0,0 +1,7 @@
[main]
font=JetBrainsMono Nerd Font:size=18
pad=0x0
[colors-dark]
background=000000
foreground=ffffff
+2
View File
@@ -0,0 +1,2 @@
[Manager]
DefaultTimeoutStopSec=5s
+65
View File
@@ -0,0 +1,65 @@
#!/bin/bash
set -e
# Use the Vfio to Integrated trick to turn off NVIDIA dgpu when in integrated mode
# without needing to restart the computer. This is needed because computers like the Asus G14
# will wake after suspend in Hybrid mode, even if the system was in Integrated mode before
# suspending.
restore_marker=/run/blob-force-igpu-integrated
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
[[ -x /usr/bin/supergfxctl ]] || exit 0
switch_mode() {
local expected="$1" current
if ! /usr/bin/timeout --kill-after=1s 3s /usr/bin/supergfxctl -m "$expected"; then
echo "Could not request the GPU transition to $expected mode" >&2
return 1
fi
for _ in {1..10}; do
if current=$(/usr/bin/timeout --kill-after=1s 2s /usr/bin/supergfxctl -g 2>/dev/null) &&
[[ $current == "$expected" ]]; then
return 0
fi
sleep 1
done
echo "Could not confirm the GPU transition to $expected mode" >&2
return 1
}
case "$1" in
pre)
# Remember the mode this sleep cycle started in. supergfxctl persists the
# temporary hibernate switch to Vfio, so post must not consult that mutable
# value when deciding whether to restore Integrated mode.
if [[ -L $restore_marker ]]; then
exit 1
elif [[ ! -f $restore_marker ]]; then
/usr/bin/grep -Eq '"mode"[[:space:]]*:[[:space:]]*"Integrated"' /etc/supergfxd.conf 2>/dev/null || exit 0
/usr/bin/install -m 0600 -o root -g root -T /dev/null "$restore_marker"
fi
# Before hibernating, switch to Vfio so the nvidia driver is detached from the dGPU.
# Without this, hibernate resume fails because the nvidia driver can't freeze a
# powered-off dGPU (returns -EIO), which aborts the entire resume.
if [[ $sleep_action == "hibernate" ]]; then
switch_mode Vfio
fi
;;
post)
[[ -f $restore_marker && ! -L $restore_marker ]] || exit 0
# small delay so the device is fully re-enumerated
sleep 4
# force-bind dGPU to vfio (fully detached from nvidia)
switch_mode Vfio
# then go back to Integrated, which powers it off again
switch_mode Integrated
/usr/bin/rm -f -- "$restore_marker"
;;
esac
@@ -0,0 +1,20 @@
#!/bin/bash
# Turn off keyboard backlight before hibernate to prevent hang on power-off.
# The ASUS keyboard controller can block S4 shutdown if LEDs are active.
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
if [[ $1 == "pre" && $sleep_action == "hibernate" ]]; then
device=""
for candidate in /sys/class/leds/*kbd_backlight*; do
if [[ -e "$candidate" ]]; then
device="$(basename "$candidate")"
break
fi
done
if [[ -n "$device" ]]; then
brightnessctl -d "$device" set 0 >/dev/null 2>&1
fi
fi
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Lazy-unmount gvfsd-fuse filesystems before suspend/hibernate to prevent the
# kernel's process freeze from timing out. FUSE daemons (like gvfsd-fuse from
# Nautilus) can block in uninterruptible sleep during freeze, causing suspend
# to silently fail. After wake, restart gvfs so the FUSE mount is restored.
if [[ $1 == "pre" ]]; then
while IFS=' ' read -r _ mountpoint fstype _; do
if [[ $fstype == fuse.gvfsd-fuse ]]; then
mountpoint=$(printf '%b' "$mountpoint")
fusermount3 -uz "$mountpoint" 2>/dev/null || fusermount -uz "$mountpoint" 2>/dev/null || true
fi
done < /proc/mounts
fi
if [[ $1 == "post" ]]; then
# Run in background — user.slice is still frozen at this point, so a
# synchronous restart would block the thaw for up to 90 seconds.
(
sleep 5
for uid_dir in /run/user/*; do
uid=$(basename "$uid_dir")
if [[ -S $uid_dir/bus ]]; then
sudo -u "#$uid" env \
DBUS_SESSION_BUS_ADDRESS="unix:path=$uid_dir/bus" \
XDG_RUNTIME_DIR="$uid_dir" \
systemctl --user restart gvfs-daemon.service 2>/dev/null || true
fi
done
) &
fi
@@ -0,0 +1,3 @@
[Service]
ExecStart=
ExecStart=/usr/bin/updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots
@@ -0,0 +1,6 @@
[Service]
# Delay startup to avoid race condition with display manager initialization
# when booting in Integrated mode. Without this delay, the system can freeze
# on boot because supergfxd tries to disable the dGPU while the display
# subsystem is still initializing.
ExecStartPre=/bin/sleep 5
@@ -0,0 +1,16 @@
# Make user apps the only thing systemd-oomd is allowed to kill.
#
# Hyprland runs in session.slice, as wayland-wm@hyprland.desktop.service, while
# everything launched through uwsm-app lands in app.slice/app-*.scope. Marking
# only app.slice as a kill candidate means the compositor is structurally
# ineligible: oomd takes the browser or terminal that caused the pressure, and
# the session survives to show the notification about it. Setting this on
# user@.service instead would put the compositor back in the candidate pool.
#
# Swap kill is a backstop for the slower shape of the same problem, where swap
# fills before pressure spikes. It uses the global SwapUsedLimit (90%) from
# /etc/systemd/oomd.conf.d/10-blob.conf, which also carries the pressure
# thresholds.
[Slice]
ManagedOOMMemoryPressure=kill
ManagedOOMSwap=kill
@@ -0,0 +1,19 @@
[Unit]
Description=Announce process crashes and offer an AI diagnosis
# Needs the session bus to notify, and uwsm-app to open the diagnosis terminal.
# Both are up only after graphical-session.target.
After=graphical-session.target
PartOf=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
# Set by blob-toggle-crash-capture. Checked here so a disabled watcher stays
# disabled across logins without the unit having to be disabled.
ConditionPathExists=!%h/.local/state/blob/toggles/crash-capture-off
[Service]
Type=simple
ExecStart=/usr/bin/blob-crash-watch
Restart=always
RestartSec=5
[Install]
WantedBy=graphical-session.target
+31
View File
@@ -0,0 +1,31 @@
[Unit]
Description=Fcitx5 input method (XCompose sequences)
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
# Wayland clients.
#
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
# imports into the user manager before it reaches graphical-session.target.
After=graphical-session.target
# The wayland connection dies with the compositor, so follow the session rather
# than linger against a socket that is gone.
PartOf=graphical-session.target
# After= is ordering only -- it does not stop anything from starting this unit
# while the target is inactive. An `blob update` over SSH has a live user
# manager (pam_systemd) and no graphical session, and a fcitx5 started there
# comes up with no WAYLAND_DISPLAY and no way to reach any client. Worse, it
# stays active, so the later graphical-session.target activation won't pull in a
# working one -- Wants= does not restart what is already running. Skip the start
# instead; the unit stays enabled and starts for real at graphical login.
ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
# notificationitem duplicates the tray entry blob already renders itself.
ExecStart=/usr/bin/fcitx5 --disable notificationitem
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
# its bus name, and a clean exit still leaves the user with no input method.
Restart=always
RestartSec=2
[Install]
WantedBy=graphical-session.target
@@ -0,0 +1,19 @@
[Unit]
Description=Lock Blob before suspend
# The monitor calls into the running Blob shell. Wait until UWSM has imported
# BLOB_PATH and WAYLAND_DISPLAY, but keep the default target ordering so the
# monitor starts before graphical-session.target is reached.
After=dbus.socket wayland-session-waitenv.service
Requires=dbus.socket
PartOf=graphical-session.target
ConditionEnvironment=BLOB_PATH
ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
ExecStart=/usr/bin/blob-system-sleep-monitor
Restart=always
RestartSec=2
[Install]
WantedBy=graphical-session.target
@@ -0,0 +1,32 @@
[Unit]
Description=Blob speaker tuning filter-chain
Documentation=https://github.com/basecamp/omarchy/blob/master/docs/AUDIO-TUNING.md
# WirePlumber does the linking, so starting before it is up risks the output being
# linked before the speaker device has been discovered.
After=pipewire.service wireplumber.service
Requires=pipewire.service
Wants=wireplumber.service
# Restart with the audio daemon, since the filter-chain loses its connection when
# PipeWire goes away.
PartOf=pipewire.service
[Service]
Type=simple
# Hosts the tuning as a PipeWire *client* rather than loading it into the daemon
# from pipewire.conf.d, which is only read at daemon startup. That is what lets
# the tuning be switched on and off without restarting pipewire-pulse -- a
# restart drops every PulseAudio client's connection, and applications that do
# not reconnect (Spotify) have to be restarted by hand.
#
# It also contains failure: a malformed tuning breaks only this service, where a
# bad drop-in in the daemon's own config stops PipeWire from starting at all.
#
# The config name is deliberately not PipeWire's stock filter-chain.conf, which
# merges every fragment in ~/.config/pipewire/filter-chain.conf.d/ and would make
# this service host unrelated user filters too.
ExecStart=/usr/bin/pipewire -c blob-speaker-tuning.conf
Restart=on-failure
RestartSec=2
[Install]
WantedBy=graphical-session.target
+23
View File
@@ -0,0 +1,23 @@
[Unit]
Description=Bluetooth pairing agent (auto-accept)
Documentation=man:bt-agent(1)
ConditionPathIsDirectory=/sys/class/bluetooth
# bluez must be reachable on the system bus before we can register.
After=dbus.socket
Requires=dbus.socket
[Service]
Type=simple
# If bluetoothd is unavailable (for example in VMs or machines without a
# usable adapter), skip cleanly instead of entering a restart loop.
ExecCondition=/usr/bin/systemctl is-active --quiet bluetooth.service
# NoInputNoOutput auto-accepts pair requests. Safe because the adapter
# is only `pairable: true` when the user explicitly opens the blob
# bluetoothPanel and starts scanning; outside that window bluez refuses
# inbound pair attempts at a lower layer.
ExecStart=/usr/bin/bt-agent -c NoInputNoOutput
Restart=on-failure
RestartSec=2
[Install]
WantedBy=graphical-session.target
@@ -0,0 +1,2 @@
[Service]
TimeoutStopSec=5s
+559
View File
@@ -0,0 +1,559 @@
# Commands
Generated by `blob-docs-commands` from the `# blob:summary=` line in each
file. Do not edit by hand.
Commands marked hidden are plumbing other commands call, and are left out
of the `blob` listing.
## audio
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-audio-availability` | Print PulseAudio sink availability for the shell | - |
| `blob-audio-input-mute` | Toggle microphone mute. Drives the hardware mic-mute LED on laptops that expose one. | - |
| `blob-audio-input-set` | Set the default audio input and move active streams | <node-id> <source-name> |
| `blob-audio-restart` | Restart audio services and recover stuck USB audio devices. | - |
| `blob-audio-sink` | Print the sink whose volume and mute a given output really uses | [sink-name] |
| `blob-audio-sink-set` | Set the default audio output and move active streams | <node-id> <sink-name> |
| `blob-audio-sink-switch` | Switch between audio outputs while preserving the mute status | - |
| `blob-audio-source-switch` | Cycle to the next media source and transfer playback when the current source is playing | [next\|previous] |
| `blob-audio-tuning` | Manage the speaker tuning for this laptop | <on\|off\|status\|match\|fronted-sink> [--force] |
| `blob-audio-volume` | Adjust output volume and show the Blob OSD | <raise\|lower\|mute-toggle\|+N\|-N> |
## bar
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-bar` | Configure the bar and its widget layout | use <id> \| reset \| defaults \| position <top\|bottom\|left\|right> \| transparent <true\|false\|toggle> \| put <id> [placement] \| move <id> [placement] \| set <id> <key> <value> [--json] [placement] |
| `blob-bar-color` | Choose a legible transparent bar text color (hidden) | - |
## battery
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-battery-low` | Send the low battery warning notification and run battery-low hooks. (hidden) | <percentage> |
| `blob-battery-status` | Returns a formatted battery status string with percentage and power draw/charge. | [--shell] |
## bg
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-bg-cache` | Cache background switcher thumbnails for the current theme | - |
| `blob-bg-current` | Show current background | - |
| `blob-bg-install` | Open the current theme's user background folder | - |
| `blob-bg-next` | Cycle to the next background for the current theme | - |
| `blob-bg-set` | Set the current background image | <path-to-image> |
| `blob-bg-switcher` | Open the Blob background switcher | - |
## bluetooth
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-bluetooth-device` | Control a Bluetooth device | [pair\|connect\|disconnect\|forget] <address> |
| `blob-bluetooth-power` | Turn Bluetooth on or off, remembered across reboots | <on\|off\|toggle\|is-on> |
| `blob-bluetooth-restart` | Unblock and restart the bluetooth service. | - |
## boot
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-boot` | - | - |
## branding
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-branding-about` | Edit, set, or reset About branding | <image\|text\|reset> |
| `blob-branding-screensaver` | Edit, set, or reset screensaver branding | <image\|text\|reset> |
## brightness
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-brightness-ddc` | Show or adjust DDC/CI display brightness for a Hyprland monitor. | <monitor> [+N%\|N%-\|N%] |
| `blob-brightness-display` | Show or adjust brightness on the focused display. | [--no-osd] [--monitor name] [+N%\|N%-\|N%\|off\|on] |
| `blob-brightness-display-apple` | Show or adjust Apple Studio Display and Apple XDR Display brightness using asdcontrol. | [--no-osd] [+N%\|N%-\|N%] |
| `blob-brightness-keyboard` | Adjust keyboard backlight brightness using available steps. | [--no-osd] <up\|down\|cycle\|off\|restore> |
| `blob-brightness-keyboard-mute` | Set the mic-mute indicator LED on laptops that expose a platform::micmute LED node. | <on\|off> |
## capture
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-capture-qr` | Decode a QR code from a screenshot region | - |
| `blob-capture-record` | Start or stop screen recording | [--fullscreen] [--with-desktop-audio] [--with-microphone-audio] [--with-webcam] [--webcam-device=<device>] [--webcam-size=<small\|medium\|large>] [--resolution=<size>] [--stop-recording] |
| `blob-capture-record-webcam` | Pick a webcam and start a screen recording with it | - |
| `blob-capture-region` | Pick a screen region over frozen screen content (hidden) | [region\|windows\|smart\|fullscreen] [--keep-freeze] [--match-monitor] \| --take-fullscreen \| --take-window \| --select-window <next\|prev\|left\|right\|up\|down> |
| `blob-capture-screenshot` | Take a screenshot | [smart\|region\|windows\|fullscreen] [slurp\|copy\|save] [--editor=<name>] |
| `blob-capture-text` | Extract text from a screenshot region with OCR | - |
| `blob-capture-webcam-list` | List webcam devices that support video capture (hidden) | - |
| `blob-capture-webcam-resize` | Resize the active webcam recording overlay | <smaller\|larger\|reset\|small\|medium\|large> |
## clipboard
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-clipboard-file` | Copy a file to the clipboard and paste it (hidden) | [--copy-only] <mime-type> <path> |
| `blob-clipboard-open` | Open a clipboard history entry (hidden) | --history-index <index> |
| `blob-clipboard-text` | Copy text to the clipboard and type or paste it (hidden) | [--shift-insert] [--copy-only] [--history-index <index>\|<text>] |
## cmd
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-cmd-cwd` | Print the current working directory of the active terminal window (hidden) | - |
| `blob-cmd-missing` | Check whether any required commands are missing | - |
| `blob-cmd-present` | Check whether all required commands are available | - |
## crash
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-crash-watch` | Watch for process crashes and offer an AI diagnosis (hidden) | - |
## default
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-default-browser` | Set the default browser for Blob and XDG handlers | [chromium\|chrome\|brave\|brave-origin\|edge\|firefox\|zen] |
| `blob-default-editor` | Set the default editor used by blob-launch-editor | [code\|cursor\|zed\|sublime_text\|helix\|vim\|emacs\|nvim] |
| `blob-default-terminal` | Set the default terminal used by xdg-terminal-exec | [alacritty\|foot\|ghostty\|kitty] |
## disk
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-disk-speedtest` | Measure live disk read and write speed | [target-dir] |
## display
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-display-size` | Scale text everywhere — blob shell, GTK apps, and terminals | [size\|reset] |
| `blob-display-state` | Print monitor panel state for the shell | - |
## docs
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-docs-commands` | Regenerate docs/commands.md from each command's own metadata (hidden) | - |
| `blob-docs-keybinds` | Regenerate docs/keybinds.md from the Hyprland Lua bindings (hidden) | - |
## drive
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-drive-info` | Print drive information such as size, model, and mount details | <drive> |
| `blob-drive-password` | Set a new encryption password for a drive selected. | - |
| `blob-drive-select` | Select a drive from a list with info that includes space and brand. Used by blob-drive-password. | - |
## file
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-file-select` | Pick files with the desktop file chooser | [--title <title>] [--multiple] [--directory] [--extensions "<ext ext...>"] |
## font
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-font-current` | Show current monospace font | - |
| `blob-font-list` | List available monospace fonts | - |
| `blob-font-set` | Set the system monospace font | <font-name> |
## git
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-git-url-check` | Check that a git URL names a repository, not a transport helper (hidden) | <git-url> |
## hibernation
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-hibernation-available` | Check if hibernation is supported | - |
## hook
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-hook` | Run a named hook from ~/.config/blob/hooks/<name> and ~/.config/blob/hooks/<name>.d/. | [name] [args...] |
## hw
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-hw-clamshell` | Returns true when clamshell mode is active (hidden) | - |
| `blob-hw-dell-xps-haptic-touchpad` | Match Dell XPS systems with the Synaptics haptic touchpad. | - |
| `blob-hw-display` | Print the most likely display backlight device. | - |
| `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-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-closed` | Returns true when the laptop lid is closed (hidden) | - |
| `blob-hw-match` | Match against the computer's DMI product name or product family (case-insensitive). | <pattern> |
| `blob-hw-nvidia` | Detect whether the computer has an NVIDIA GPU. | - |
| `blob-hw-nvidia-gsp` | Detect whether the computer has an NVIDIA GPU with GSP firmware (Turing or newer). | - |
| `blob-hw-nvidia-without-gsp` | Detect whether the computer has an NVIDIA GPU without GSP firmware (Maxwell/Pascal/Volta). | - |
| `blob-hw-touchpad` | Print the detected Hyprland touchpad or trackpad device name | - |
| `blob-hw-touchscreen` | Print the detected Hyprland touchscreen or tablet device name | - |
| `blob-hw-webcam` | Check whether a webcam is available | - |
## hypr
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-hypr-focus` | Focus a Hyprland window by application identity | <app-name> |
| `blob-hypr-monitor-clamshell` | Apply clamshell display state to Hyprland monitors (hidden) | - |
| `blob-hypr-monitor-external` | Returns true when Hyprland has an active external monitor (hidden) | - |
| `blob-hypr-monitor-focused` | Print the name of the currently focused Hyprland monitor. | - |
| `blob-hypr-monitor-focused-apple` | Return success if the focused or named Hyprland monitor is an Apple display. | [monitor] |
| `blob-hypr-monitor-internal` | Enable, disable, toggle, or recover the internal laptop display | <on\|off\|toggle\|recover> |
| `blob-hypr-monitor-laptop` | Print the name of the built-in laptop display, including disabled outputs. | - |
| `blob-hypr-monitor-mirror` | Enable, disable, toggle, or recover mirroring the internal display to an external monitor | <on\|off\|toggle\|recover> |
| `blob-hypr-monitor-modeless` | Returns true when Hyprland has an enabled monitor with no mode (hidden) | - |
| `blob-hypr-monitor-scaling` | Show, set, or adjust focused Hyprland monitor scaling | [up\|down\|SCALE] |
| `blob-hypr-monitor-watch` | Watch Hyprland monitor events and recover monitor toggles when a monitor is removed | - |
| `blob-hypr-reload-guard` | Pause or resume Hyprland config auto-reload around package transactions. (hidden) | - |
| `blob-hypr-restart` | Reload hyprland configuration (used by the Blob theme switching). | - |
| `blob-hypr-session-locked` | Returns true when the compositor holds a session lock (hidden) | - |
| `blob-hypr-toggle` | Toggle permanent Hyprland flags by copying them into a directory that's sourced entirely. | <flag-name> [on\|off\|toggle] |
| `blob-hypr-toggle-disabled` | Check if a Hyprland toggle is currently disabled (missing). | <flag-name> |
| `blob-hypr-toggle-enabled` | Check if a Hyprland toggle is currently enabled. | <flag-name> |
| `blob-hypr-window-close-all` | Close all open windows | - |
| `blob-hypr-window-gaps-toggle` | Toggles the window gaps globally between no gaps and the default. | - |
| `blob-hypr-window-pop` | Toggle to pop-out a tile to stay fixed on a display basis. | [width height x y] |
| `blob-hypr-window-single-square-aspect-toggle` | Toggle single-window square aspect ratio. | - |
| `blob-hypr-window-tiled-fullscreen-toggle` | Toggle tiled fullscreen for the focused Hyprland window | - |
| `blob-hypr-window-transparency-toggle` | Toggles transparency for the currently focused window. | - |
| `blob-hypr-window-width` | Save or restore the focused Hyprland window width | <save\|restore> |
| `blob-hypr-workspace-layout-toggle` | Toggle the layout on the current active workspace between dwindle and scrolling | - |
## install
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-install-font` | Install a Nerd Font package and switch the system to it | <display-name> <package> <family> |
| `blob-install-launch` | Install a packaged app and launch it once it finishes | <display-name> <packages> <desktop-id> |
## launch
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-launch-about` | Launch the fastfetch TUI that gives information about the current system. | - |
| `blob-launch-browser` | Launch the default browser as determined by xdg-settings. | [url] |
| `blob-launch-config-editor` | Open a config file in the user's editor and surface a toast | <path> |
| `blob-launch-docker-tui` | Open the Docker TUI (lazydocker) with access to the Docker daemon (hidden) | - |
| `blob-launch-editor` | Launch the default editor selected via Blob defaults. | [--inline] <path> |
## launcher
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-launcher-remove` | Remove or uninstall the selected launcher entry (hidden) | <desktop-id> <name> |
## launch
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-launch-floating` | Launch a floating terminal with the Blob presentation wrapper | <command> |
| `blob-launch-or-focus` | Launch an app or focus an existing window matching a pattern | <window-pattern> <launch-command> |
| `blob-launch-or-focus-tui` | Launch a TUI or focus an existing terminal window for it | [--app-id=<app-id>] <command> [args...] |
| `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-tui` | Launch a TUI command in the default terminal with Blob styling | [--app-id=<app-id>] <command> [args...] |
## menu
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-menu` | Control the Blob menu (toggle / summon / close / refresh) | [toggle\|summon\|close\|refresh\|ping] [route] |
| `blob-menu-emoji` | Launch emojis | - |
| `blob-menu-emoji-insert` | Insert an emoji into the focused application (hidden) | <emoji> |
| `blob-menu-file` | Pick a file from a menu | label paths formats [menu args...] |
| `blob-menu-images` | Open a generic image selector menu | [--selected <image>] [--print-name] [--show-labels] [--filterable] [--lazy-thumbnails] [--preload] [--cache-only] <image-dir>... |
| `blob-menu-keybindings` | Display Hyprland keybindings defined in your configuration using an interactive search menu. | - |
| `blob-menu-plugin` | Pick a shell plugin to enable, disable, clone, or remove | <enable\|disable\|clone\|remove> |
| `blob-menu-select` | Pick one option from a menu | prompt [option...] [-- menu args...] |
| `blob-menu-share` | Share clipboard, files, or folders with LocalSend | <clipboard\|file\|folder> [path...] |
| `blob-menu-timezone` | Select and set the system timezone | - |
## network
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-network-band` | Show or pin the Wi-Fi band for the active connection | [auto\|2.4\|5\|6] |
| `blob-network-dns` | Show or configure the system DNS provider | [Cloudflare\|Google\|DHCP\|Custom] |
| `blob-network-iwd` | - | - |
| `blob-network-password` | Print the active Wi-Fi connection's password | <interface> |
| `blob-network-qr` | Generate a Wi-Fi QR matrix for the shell | [--meta] [interface] |
| `blob-network-restart` | Unblock and restart the Wi-Fi service. | - |
| `blob-network-speedtest` | Measure live internet speed for one direction | [down\|up] |
| `blob-network-status` | Print active network status for the shell | [--verbose] |
## notification
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-notification-battery` | Show the current battery status notification | - |
| `blob-notification-time` | Show the current time and date notification | - |
| `blob-notification-wait` | Wait for the desktop notification server to accept notifications (hidden) | [timeout-seconds] |
| `blob-notification-weather` | Toggle the current weather panel | - |
## notify
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-notify-dismiss` | Dismiss a notification by summary substring. Used by the first-run notifications to dismiss them after clicking for action. | <summary> |
| `blob-notify-send` | Send an Blob desktop notification | [--app-name <app-name>] [-g <glyph>] [-u <low\|normal\|critical>] [-i <icon>] [-t <ms>] [-r <id>] [-p] [--image <path-or-uri>] <headline> [description] [--exec <program> [args...]] |
## osd
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-osd` | Show the Blob Quickshell on-screen display | [-i\|--icon <icon>] [-m\|--message <text>] [-p\|--progress <0-100>] [-d\|--duration <ms>] |
## pkg
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-pkg-add` | Install Arch packages if they are missing | <packages...> |
| `blob-pkg-aur` | Returns true if the AUR is up and available. | - |
| `blob-pkg-aur-install` | Show a fuzzy-finder TUI for picking new AUR packages to install. | - |
| `blob-pkg-drop` | Remove all the named packages from the system if they're installed (otherwise ignore). | <packages...> |
| `blob-pkg-install` | Show a fuzzy-finder TUI for picking new Arch and OPR packages to install. | - |
| `blob-pkg-missing` | Returns true if any of the named packages are missing from the system (or false if they're all there). | <packages...> |
| `blob-pkg-present` | Returns true if all of the named packages are installed on the system (or false if any of them are missing). | <packages...> |
| `blob-pkg-remove` | Show a fuzzy-finder TUI for picking packages installed on the system to be removed. | - |
## plugin
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-plugin-add` | Add a shell plugin from git | [git-url] [--enable] [--yes] |
| `blob-plugin-catalog` | Emit every first-party and user plugin manifest as JSON (hidden) | - |
| `blob-plugin-clone` | Clone a built-in Blob shell plugin into your own config | <source-id> [--edit] |
| `blob-plugin-enable` | Enable a shell plugin | <id> [placement] |
| `blob-plugin-list` | List discovered shell plugins | [--json] |
| `blob-plugin-remove` | Remove an installed shell plugin | [id] [--yes] |
| `blob-plugin-validate` | Validate a plugin folder against the Blob plugin manifest schema | <plugin-folder> |
## plymouth
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-plymouth-set` | Set the Plymouth boot theme colors and logo | <background-hex> <text-hex> <path-to-logo.png> |
## power
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-power-list` | Returns a list of all the available power profiles on the system. | [--active-state] |
## powerprofiles
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-powerprofiles-init` | Set the correct power profile on boot based on current AC/battery state. | - |
## power
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-power-set` | Set and remember the power profile for AC or battery use | [autodetect\|ac\|battery] [power-saver\|balanced\|performance] |
## refresh
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-refresh-config` | Copy a shipped user config from $BLOB_PATH/config into ~/.config (backs up your version). | <config-path> |
| `blob-refresh-hyprland` | Overwrite all the user Hyprland Lua configs in ~/.config/hypr with the Blob defaults. | - |
| `blob-refresh-hyprsunset` | Overwrite the user config for hyprsunset with the Blob default and restart the service. | - |
| `blob-refresh-plymouth` | Overwrite the user config for the Plymouth drive decryption and boot sequence with the Blob default and rebuild it. | - |
| `blob-refresh-shell` | Reset shell.json to Blob defaults | - |
## reminder
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-reminder` | Set and show lightweight desktop notification reminders | [-i\|--interactive] \| <minutes> [message] \| show [-j\|--json] \| clear |
## remove
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-remove-security-fido2` | Remove FIDO2 authentication from sudo and polkit | - |
| `blob-remove-security-sudoless-docker` | Disable sudoless Docker by removing your user from the docker group | - |
## restart
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-restart-app` | Restart an application by killing it and relaunching via uwsm. | <application-name> [application-args...] |
| `blob-restart-btop` | Reload btop configuration (used by the Blob theme switching). | - |
| `blob-restart-gum` | Export the current theme's gum styling into the environment (hidden) | - |
| `blob-restart-hyprsunset` | Restart the hyprsunset service (used for blue light filtering/night light). | - |
| `blob-restart-terminal` | Reload supported terminal emulators after config changes | - |
| `blob-restart-trackpad` | Reset the trackpad by unbinding and rebinding its driver. | - |
| `blob-restart-xcompose` | Restart the XCompose input method service (fcitx5) to apply new compose key settings. | - |
## screensaver
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-screensaver` | Run the Blob screensaver using random effects from TTE. | - |
## secret
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-secret` | - | - |
## setup
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-setup-direct-boot` | Add or remove an EFI boot entry for the Blob UKI, allowing the system to boot directly | - |
| `blob-setup-security-fido2` | Set up FIDO2 authentication for sudo and polkit | - |
| `blob-setup-security-fingerprint` | Set up fingerprint authentication for sudo, polkit, and lock screen | - |
| `blob-setup-security-sshd` | Set up the OpenSSH server, open the firewall, and authorize an SSH key | [--key=<public-key>] |
| `blob-setup-security-sudoless-docker` | Enable sudoless Docker by adding your user to the docker group (root-equivalent!) | - |
## shell
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-shell` | Send an IPC call to the running Blob shell | [-q] <target> <method> [args...] |
| `blob-shell-config` | Shared helpers for editing ~/.config/blob/shell.json (source this, don't run it). (hidden) | - |
| `blob-shell-restart` | Restart the Blob shell | - |
## show
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-show-done` | Display a "Done!" message and wait for user to press any key. | - |
| `blob-show-logo` | Display the Blob logo in the terminal using green color. | - |
## state
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-state` | Manage persistent state files for Blob toggles and settings. (hidden) | <set\|clear> <state-name-or-pattern> |
## sudo
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-sudo-docker` | Succeed when Docker needs sudo, fail when it can be used directly (hidden) | [--configured] |
| `blob-sudo-keepalive` | Prompt for sudo once and keep the credential alive in the background. | - |
| `blob-sudo-passwordless` | Toggle passwordless sudo for the current user. | [MINUTES] |
## system
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-system-lid` | Lock and reconcile displays when the laptop lid closes (hidden) | - |
| `blob-system-lock` | Lock the computer and turn off the display | - |
| `blob-system-logout` | Log out after closing application windows | - |
| `blob-system-reboot` | Reboot after closing application windows | - |
| `blob-system-shutdown` | Shut down after closing application windows | - |
| `blob-system-sleep` | Lock before suspend and wait for the session lock to become secure (hidden) | - |
| `blob-system-sleep-monitor` | Monitor sleep preparation and lock before suspend (hidden) | - |
| `blob-system-stats` | Print CPU and memory stats for the shell | [--bar-widget] |
| `blob-system-wake` | Wake displays and restore brightness after idle | - |
## tablet
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-tablet-follow` | - | - |
## theme
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-theme-browser` | Apply the current theme color to Chromium, Chrome, Edge, and Brave (hidden) | - |
| `blob-theme-browser-policy` | Write the current theme color into the browser policy directories (hidden) | <rrggbb> |
| `blob-theme-color` | Resolve semantic colors from an Blob theme colors.toml (hidden) | [--file <colors.toml>] (--all \| --raw \| <key> [fallback]) |
| `blob-theme-contrast` | - | - |
| `blob-theme-current` | Show current theme | - |
| `blob-theme-dir` | Print the directory holding a theme, preferring a user-installed copy | <theme-name> |
| `blob-theme-dynamic` | Apply the pywal-generated theme built from the current wallpaper | - |
| `blob-theme-extras` | List the user-installed themes that came from a git clone | - |
| `blob-theme-foot` | Apply current Blob theme colors to running Foot terminals (hidden) | - |
| `blob-theme-import` | Generate a theme's colors.toml from its alacritty.toml palette (hidden) | <theme-dir> |
| `blob-theme-install` | Install a theme from a git repository | [git-repo-url] |
| `blob-theme-list` | List available themes | - |
| `blob-theme-menu` | Pick a color theme from the local and bundled sets | [--mode\|--print] |
| `blob-theme-osc` | Print OSC sequences for an Blob color theme (hidden) | - |
| `blob-theme-refresh` | Refresh the current theme from its templates. | - |
| `blob-theme-remove` | Remove a user-installed theme | [theme-name] |
| `blob-theme-set` | Apply an Blob theme | <theme-name> |
| `blob-theme-share` | Fetch a shared theme by link or id and apply it as blob-dynamic | <share-link-or-id> |
| `blob-theme-switcher` | Open the Blob theme switcher | - |
| `blob-theme-templates` | Generate themed config files from Blob templates (hidden) | - |
| `blob-theme-update` | Update user-installed git themes | - |
## toggle
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-toggle` | Toggle Blob features between enabled and disabled | <flag-name> [toggle\|on\|off] |
| `blob-toggle-bar` | Toggle bar visibility without killing the Blob shell | [toggle\|on\|off] |
| `blob-toggle-crash-capture` | Toggle crash capture notifications | - |
| `blob-toggle-enabled` | Check if a toggle is enabled (flag file exists) | <flag-name> |
| `blob-toggle-glass` | - | - |
| `blob-toggle-hybrid-gpu` | Toggle dedicated vs integrated GPU mode via supergfxd (for hybrid gpu laptops, like Asus G14). | - |
| `blob-toggle-idle` | Toggle idle behavior so the system either idles normally or stays awake | [toggle\|stay-awake\|allow-idle\|status] |
| `blob-toggle-input` | Enable, disable, or toggle a Hyprland input device (hidden) | <touchpad\|touchscreen> [on\|off\|toggle] |
| `blob-toggle-nightlight` | Toggle nightlight screen temperature | [--status] |
| `blob-toggle-screensaver` | Toggle screensaver availability | - |
| `blob-toggle-silencing` | Toggle notification do-not-disturb mode | - |
| `blob-toggle-tablet` | - | - |
| `blob-toggle-touchpad` | Enable, disable, or toggle the touchpad | [on\|off\|toggle] |
| `blob-toggle-touchscreen` | Enable, disable, or toggle the touch functionality of the screen | [on\|off\|toggle] |
## transcode
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-transcode` | Transcode pictures and videos for sharing | [--path path] [input] [format] [resolution] |
| `blob-transcode-ascii` | Transcode an image into ASCII/Unicode art text | <input-image.svg\|png> <output-path> [--width <columns>] [--height <rows>] [--mode <braille\|block>] [--threshold <percent>] [--invert] |
## tui
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-tui-install` | Create a desktop launcher for a terminal UI app | [name command window-style icon-url-or-name] |
| `blob-tui-remove` | Remove a terminal UI desktop launcher | [name] |
| `blob-tui-remove-all` | Remove all TUIs installed via blob-tui-install. | - |
## update
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-update` | Update system and AUR packages | [--no-confirm] |
| `blob-update-available` | Print the number of pending package updates (hidden) | - |
| `blob-update-firmware` | Update system firmware using fwupd. Ensures the fwupd EFI binary is installed | - |
| `blob-update-time` | Restart system time synchronization | - |
## wallpaper
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-wallpaper-set` | Set a wallpaper from ~/wallpapers and recolor the desktop from it | [--menu\|<file>] |
## weather
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-weather-card` | Print a one-line weather summary for the quick settings card (hidden) | - |
| `blob-weather-icon` | Returns a weather condition icon, adjusted for live sunrise and sunset. | - |
| `blob-weather-location` | Show or set the location used for weather reports | - |
| `blob-weather-status` | Returns a formatted weather status string with temperature and wind speed. | - |
## Totals
252 commands.
+202
View File
@@ -0,0 +1,202 @@
# Keybindings
Generated by `blob-docs-keybinds` from the `o.bind` calls in the Hyprland
Lua files. Do not edit by hand.
Personal overrides in `hypr/bindings.lua` win over the defaults, and
`hl.unbind` in that file removes a default outright.
## Personal
| Keys | Action |
| --- | --- |
| `SUPER + ALT + SPACE` | Blob menu |
| `SUPER + ALT + W` | Wallpaper picker |
| `SUPER + CTRL + M` | System monitor |
| `SUPER + CTRL + Q` | Quick settings |
| `SUPER + SPACE` | Apps menu |
## Applications
| Keys | Action |
| --- | --- |
| `SUPER + ALT + SHIFT + F` | File manager (cwd) |
| `SUPER + RETURN` | Terminal |
| `SUPER + SHIFT + ALT + B` | Browser (private) |
| `SUPER + SHIFT + B` | Browser |
| `SUPER + SHIFT + D` | Docker |
| `SUPER + SHIFT + F` | File manager |
| `SUPER + SHIFT + G` | Signal |
| `SUPER + SHIFT + M` | Music |
| `SUPER + SHIFT + N` | Editor |
| `SUPER + SHIFT + O` | Obsidian |
| `SUPER + SHIFT + RETURN` | Browser |
## Tiling
| Keys | Action |
| --- | --- |
| `ALT + SHIFT + TAB` | Focus on previous window |
| `ALT + TAB` | Focus on next window |
| `CTRL + ALT + DELETE` | Close all windows |
| `CTRL + ALT + SHIFT + TAB` | Focus on previous monitor |
| `CTRL + ALT + TAB` | Focus on next monitor |
| `SUPER + ALT + code:20` | Expand window left a little |
| `SUPER + ALT + code:21` | Shrink window left a little |
| `SUPER + ALT + DOWN` | Move window to group on bottom |
| `SUPER + ALT + F` | Full width |
| `SUPER + ALT + G` | Move active window out of group |
| `SUPER + ALT + Home` | Save window width |
| `SUPER + ALT + LEFT` | Move window to group on left |
| `SUPER + ALT + mouse_down` | Next window in group |
| `SUPER + ALT + mouse_up` | Previous window in group |
| `SUPER + ALT + RIGHT` | Move window to group on right |
| `SUPER + ALT + S` | Move window to scratchpad |
| `SUPER + ALT + SHIFT + TAB` | Previous window in group |
| `SUPER + ALT + SLASH` | Monitor scaling down |
| `SUPER + ALT + TAB` | Next window in group |
| `SUPER + ALT + UP` | Move window to group on top |
| `SUPER + code:20` | Expand window left |
| `SUPER + code:21` | Shrink window left |
| `SUPER + CTRL + code:20` | Expand window left a lot |
| `SUPER + CTRL + code:21` | Shrink window left a lot |
| `SUPER + CTRL + F` | Tiled full screen |
| `SUPER + CTRL + LEFT` | Move grouped window focus left |
| `SUPER + CTRL + RIGHT` | Move grouped window focus right |
| `SUPER + CTRL + SHIFT + code:20` | Shrink window up a lot |
| `SUPER + CTRL + SHIFT + code:21` | Expand window down a lot |
| `SUPER + CTRL + TAB` | Former workspace |
| `SUPER + DOWN` | Focus on below window |
| `SUPER + F` | Full screen |
| `SUPER + G` | Toggle window grouping |
| `SUPER + Home` | Restore window width |
| `SUPER + J` | Toggle window split |
| `SUPER + L` | Toggle workspace layout |
| `SUPER + LEFT` | Focus on left window |
| `SUPER + mouse:272` | Move window |
| `SUPER + mouse:273` | Resize window |
| `SUPER + mouse_down` | Scroll active workspace forward |
| `SUPER + mouse_up` | Scroll active workspace backward |
| `SUPER + O` | Pop window out (float & pin) |
| `SUPER + P` | Pseudo window |
| `SUPER + RIGHT` | Focus on right window |
| `SUPER + S` | Toggle scratchpad |
| `SUPER + SHIFT + ALT + code:20` | Shrink window up a little |
| `SUPER + SHIFT + ALT + code:21` | Expand window down a little |
| `SUPER + SHIFT + ALT + DOWN` | Move workspace to down monitor |
| `SUPER + SHIFT + ALT + LEFT` | Move workspace to left monitor |
| `SUPER + SHIFT + ALT + RIGHT` | Move workspace to right monitor |
| `SUPER + SHIFT + ALT + UP` | Move workspace to up monitor |
| `SUPER + SHIFT + code:20` | Shrink window up |
| `SUPER + SHIFT + code:21` | Expand window down |
| `SUPER + SHIFT + DOWN` | Swap window down |
| `SUPER + SHIFT + LEFT` | Swap window to the left |
| `SUPER + SHIFT + RIGHT` | Swap window to the right |
| `SUPER + SHIFT + TAB` | Previous workspace |
| `SUPER + SHIFT + UP` | Swap window up |
| `SUPER + SLASH` | Monitor scaling up |
| `SUPER + T` | Toggle window floating/tiling |
| `SUPER + TAB` | Next workspace |
| `SUPER + UP` | Focus on above window |
| `SUPER + W` | Close window |
## Media
| Keys | Action |
| --- | --- |
| `ALT + SHIFT + XF86AudioPlay` | Previous track |
| `ALT + XF86AudioLowerVolume` | Volume down precise |
| `ALT + XF86AudioPlay` | Next track |
| `ALT + XF86AudioRaiseVolume` | Volume up precise |
| `ALT + XF86MonBrightnessDown` | Brightness down precise |
| `ALT + XF86MonBrightnessUp` | Brightness up precise |
| `SHIFT + XF86AudioMute` | Switch audio output |
| `SHIFT + XF86AudioPause` | Switch media source |
| `SHIFT + XF86AudioPlay` | Switch media source |
| `SHIFT + XF86MonBrightnessDown` | Brightness minimum |
| `SHIFT + XF86MonBrightnessUp` | Brightness maximum |
| `XF86AudioLowerVolume` | Volume down |
| `XF86AudioMicMute` | Mute microphone |
| `XF86AudioMute` | Mute |
| `XF86AudioNext` | Next track |
| `XF86AudioPause` | Pause |
| `XF86AudioPlay` | Play |
| `XF86AudioPrev` | Previous track |
| `XF86AudioRaiseVolume` | Volume up |
| `XF86Eject` | Eject media |
| `XF86KbdBrightnessDown` | Keyboard brightness down |
| `XF86KbdBrightnessUp` | Keyboard brightness up |
| `XF86KbdLightOnOff` | Keyboard backlight cycle |
| `XF86MonBrightnessDown` | Brightness down |
| `XF86MonBrightnessUp` | Brightness up |
| `XF86TouchpadOff` | Disable touchpad |
| `XF86TouchpadOn` | Enable touchpad |
| `XF86TouchpadToggle` | Toggle touchpad |
## Clipboard
| Keys | Action |
| --- | --- |
| `SUPER + C` | Universal copy |
| `SUPER + CTRL + V` | Clipboard manager |
| `SUPER + V` | Universal paste |
| `SUPER + X` | Universal cut |
## Utilities
| Keys | Action |
| --- | --- |
| `ALT + PRINT` | Screenrecording |
| `PRINT` | Screenshot |
| `SUPER + ALT + code:34` | Make webcam overlay smaller |
| `SUPER + ALT + code:35` | Make webcam overlay larger |
| `SUPER + ALT + comma` | Invoke last notification |
| `SUPER + ALT + SPACE` | Apps menu |
| `SUPER + BACKSPACE` | Toggle window transparency |
| `SUPER + comma` | Dismiss last notification |
| `SUPER + CTRL + A` | Audio |
| `SUPER + CTRL + ALT + B` | Show battery remaining |
| `SUPER + CTRL + ALT + D` | Calendar |
| `SUPER + CTRL + ALT + Delete` | Toggle laptop display mirroring |
| `SUPER + CTRL + ALT + R` | Show reminders |
| `SUPER + CTRL + ALT + T` | Show time |
| `SUPER + CTRL + ALT + W` | Toggle weather |
| `SUPER + CTRL + ALT + Z` | Reset zoom |
| `SUPER + CTRL + B` | Bluetooth |
| `SUPER + CTRL + BACKSPACE` | Toggle single-window square aspect |
| `SUPER + CTRL + C` | Capture menu |
| `SUPER + CTRL + comma` | Toggle silencing notifications |
| `SUPER + CTRL + D` | Display |
| `SUPER + CTRL + Delete` | Toggle laptop display |
| `SUPER + CTRL + E` | Emojis |
| `SUPER + CTRL + H` | Hardware menu |
| `SUPER + CTRL + I` | Toggle locking on idle |
| `SUPER + CTRL + L` | Lock system |
| `SUPER + CTRL + N` | Toggle nightlight |
| `SUPER + CTRL + O` | Toggle menu |
| `SUPER + CTRL + P` | Power |
| `SUPER + CTRL + PERIOD` | Transcode |
| `SUPER + CTRL + PRINT` | Extract text (OCR) from screenshot |
| `SUPER + CTRL + R` | Set reminder |
| `SUPER + CTRL + S` | Share |
| `SUPER + CTRL + SPACE` | Background switcher |
| `SUPER + CTRL + T` | Activity |
| `SUPER + CTRL + W` | Network |
| `SUPER + CTRL + Z` | Zoom in |
| `SUPER + ESCAPE` | System menu |
| `SUPER + K` | Keybindings |
| `SUPER + PRINT` | Color picker |
| `SUPER + SHIFT + ALT + comma` | Open notification history |
| `SUPER + SHIFT + BACKSPACE` | Toggle window gaps |
| `SUPER + SHIFT + code:201` | Blob menu |
| `SUPER + SHIFT + comma` | Dismiss all notifications |
| `SUPER + SHIFT + CTRL + R` | Clear reminders |
| `SUPER + SHIFT + CTRL + SPACE` | Theme menu |
| `SUPER + SHIFT + SPACE` | Toggle top bar |
| `SUPER + SPACE` | Blob menu |
| `XF86PowerOff` | Power menu |
## Unbound defaults
- `SUPER + SPACE`
- `SUPER + ALT + SPACE`
+91
View File
@@ -0,0 +1,91 @@
# The shell
`blob-shell` is one long-running Quickshell process hosting the bar, panels,
notifications, lock screen, OSD, and both menus. Everything runs inside it as a
plugin, so summoning a panel is an IPC call into a process that is already up.
## Layout
```
shell/
shell.qml entry point
Commons/ Style, Color, Border, Util
Ui/ shared components
services/ plugin registry, bar widget registry, app library
plugins/
bar/ the bar and its widgets
menu/ both menus
notifications/ daemon, popups, DND, history
notification-center/
lock/ polkit/ osd/ background/ clipboard/ emojis/
image-picker/ reminders/
panels/ audio bluetooth clock disk-speedtest monitor network
power quick-settings speedtest sysmon weather wifiqr
services/ battery idle media nightlight
```
First-party plugins are discovered under `shell/plugins` at a depth of two or
three and are enabled without being listed anywhere. Third-party plugins live in
`~/.config/blob/plugins/<id>/` and are enabled through `shell.json`.
## Config
`shell.json` is the whole bar config. It hot-reloads on save, and dragging a bar
widget rewrites `~/.config/blob/shell.json` directly, so after rearranging by
hand copy it back:
```
cp ~/.config/blob/shell.json shell.json
```
`./install.sh --check` reports the drift.
Defaults live at `config/blob/shell.json`. A valid user file replaces them
entirely rather than merging.
## Bar layout
| Section | Widgets |
| --- | --- |
| left | `blob.menu`, `blob.workspaces` |
| center | `blob.clock` (anchored), `blob.system-update` |
| right | `blob.tray`, `blob.bluetooth`, `blob.network`, `blob.audio`, `blob.monitor`, `blob.power`, `blob.notifications` |
`blob.clock` and `blob.notifications` are `type: "command"` modules, which is how
they carry their own click actions: the clock opens quick settings on left click,
the shell's calendar on middle, and the timezone picker on right; the bell opens
the notification centre on left and toggles silencing on right.
The centre module named by `centerAnchor` cannot be dragged out of the centre.
Every other widget still reorders.
## Differences from upstream
This shell was forked from Omarchy 4.0.4 and is not tracked against it. The
changes beyond renaming:
- `agents`, `tailscale`, `dropbox`, and `dev-gallery` plugins removed, about
7,000 lines.
- The `Dictation` bar indicator removed with voice typing.
- Persistent workspaces 1-9 instead of 1-5.
- Menu cards 440 wide instead of 300, with the Blob icon on the bar button.
- The lock screen carries `branding/screensaver.txt` and an AGS-style password
field: square corners, a 2px border on the blue slot at half alpha going to
solid accent once typing starts, and urgent on a failed attempt.
- `quick-settings`, `sysmon`, and `notification-center` are new, ported from the
GTK widgets. See [widgets.md](widgets.md).
None of these are clones or overrides. They are edits to first-party source, so
there is no clone machinery and nothing to re-apply after an upstream release.
The fork base is recorded in [upstream.md](upstream.md).
## Debugging
```
journalctl --user -t blob-shell -f
blob-shell-restart
```
A QML error in a `service` plugin means that service never loads. For the lock
that leaves the machine unlockable rather than locked open, so test it with
`blob-shell lock lock` before trusting a change to it.
+68
View File
@@ -0,0 +1,68 @@
# Themes
A theme is a directory under `themes/` holding at minimum a `colors.toml`.
`blob-theme-set <name>` stages it, renders every template against it, and
retints the running desktop.
## What a theme can ship
| File | Used by |
| --- | --- |
| `colors.toml` | required; the palette everything else derives from |
| `backgrounds/` | the background switcher |
| `shell.lock.toml` | lock screen surface tokens |
| `neovim.lua` | the editor colorscheme |
| `icons.theme` | GTK icon theme name |
| `preview.png`, `unlock.png`, `preview-unlock.png` | theme and lock previews |
## colors.toml
Named keys are preferred: `background`, `foreground`, `accent`, `muted`,
`selection`, the eight base colors, and their `bright_` variants. The pywal
`color0`..`color15` form also works and is resolved through the same cascade:
`color0` is the background, `color4` is blue, `color5` is magenta.
That cascade lives in `blob-theme-color`, and `Commons/Color.qml` mirrors it so
the shell and the CLI never disagree about what a slot means.
## Templates
`default/themed/*.tpl` are rendered per theme into
`~/.local/state/blob/current/theme/`. Nine ship: foot, kitty, btop, chromium,
hyprland, the screenshare picker, neovim, `shell.toml`, and zen.
An app picks its colors up in one of two ways. Most include the generated file
directly, so nothing has to run:
```
include ~/.local/state/blob/current/theme/kitty.conf
```
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`:
`blob-theme-foot`, `blob-theme-browser`, `blob-restart-terminal`,
`blob-restart-btop`, `blob-hypr-restart`.
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.
## The three ways to set a theme
| Command | Does |
| --- | --- |
| `blob theme menu` | pick from the bundled and local sets |
| `blob theme set <name>` | apply one by name |
| `blob wallpaper set <image>` | extract a palette from a wallpaper with pywal, fix flat palettes with `blob-theme-contrast`, and apply it as `blob-dynamic` |
| `blob theme share <link>` | fetch a shared palette and apply it as `blob-dynamic` |
Aether is also installed and does the same job with a GUI. To have its palettes
land where these do, add Blob as a custom app in Aether with a template writing
`colors.toml` into `~/.local/state/blob/current/theme/` and a post-apply hook
calling `blob-theme-refresh`.
## Bundled themes
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.
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# This hook is called with the current battery percentage when the low battery
# notification is sent. To put it into use, remove .sample from the name.
SOUND_FILE="/usr/share/sounds/freedesktop/stereo/dialog-warning.oga"
if blob-cmd-present mpv && [[ -f $SOUND_FILE ]]; then
mpv --no-video "$SOUND_FILE" >/dev/null 2>&1
fi
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called with the snake-cased name of the font that has just been set.
# To put it into use, remove .sample from the name.
# Example: Show the name of the theme that was just set.
# notify-send -u low "New font" "Your new font is $1"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called after an Blob system update has been performed.
# To put it into use, remove .sample from the name.
# Example: Show notification after the system has been updated.
# notify-send -u low "Update Performed" "Your system is now up to date"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called with the snake-cased name of the theme that has just been set.
# To put it into use, remove .sample from the name.
# Example: Show the name of the theme that was just set.
# notify-send -u low "New theme" "Your new theme is $1"
Executable
+196
View File
@@ -0,0 +1,196 @@
#!/bin/bash
set -e
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
blob_path="$HOME/.local/share/blob"
config_dir="$HOME/.config"
state_dir="$HOME/.local/state/blob"
session_dir="/usr/share/wayland-sessions"
font_dir="$HOME/.local/share/fonts"
force=false
check=false
changes=0
usage() {
cat <<USAGE
Usage: ./install.sh [OPTIONS]
--check Report what would change, write nothing
--force Overwrite files that have local changes
--help Show this message
USAGE
exit 0
}
while (( $# > 0 )); do
case "$1" in
--force) force=true; shift ;;
--check) check=true; shift ;;
--help) usage ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
say() {
printf '%s\n' "$1"
}
note_change() {
changes=$((changes + 1))
}
# 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.
link_blob_path() {
local current=""
[[ -L $blob_path ]] && current="$(readlink -f "$blob_path")"
if [[ $current == "$repo_dir" ]]; then
say "ok BLOB_PATH -> $repo_dir"
return
fi
if [[ -e $blob_path && ! -L $blob_path ]]; then
say "WARN $blob_path exists and is not a symlink; move it aside first"
note_change
return
fi
note_change
if [[ $check == true ]]; then
say "would link BLOB_PATH -> $repo_dir"
return
fi
ln -sfn "$repo_dir" "$blob_path"
say "link BLOB_PATH -> $repo_dir"
}
# Copy a file only when the destination is missing or matches what we last
# wrote. A destination that differs is a local edit, reported and kept unless
# --force says otherwise.
install_file() {
local source="$1" dest="$2" label="$3"
if [[ ! -e $source ]]; then
say "SKIP $label (missing in repo)"
return
fi
if [[ -e $dest ]] && cmp -s "$source" "$dest"; then
say "ok $label"
return
fi
note_change
if [[ -e $dest ]] && [[ $force == false ]]; then
say "DIFF $label (local changes kept; --force to overwrite)"
return
fi
if [[ $check == true ]]; then
say "would write $label"
return
fi
mkdir -p "$(dirname "$dest")"
[[ -e $dest ]] && cp "$dest" "$dest.bak"
cp "$source" "$dest"
say "write $label"
}
install_tree() {
local source="$1" dest="$2" label="$3"
if [[ ! -d $source ]]; then
say "SKIP $label (missing in repo)"
return
fi
local relative
while IFS= read -r relative; do
install_file "$source/$relative" "$dest/$relative" "$label/$relative"
done < <(cd "$source" && find . -type f -printf '%P\n' | sort)
}
install_session_entry() {
local source="$repo_dir/session/blob.desktop"
local dest="$session_dir/blob.desktop"
if [[ -e $dest ]] && cmp -s "$source" "$dest"; then
say "ok wayland session entry"
return
fi
note_change
if [[ $check == true ]]; then
say "would install wayland session entry (needs sudo)"
return
fi
sudo install -Dm644 "$source" "$dest"
say "write wayland session entry"
}
say "Blob installer"
say "repo: $repo_dir"
if [[ $check == true ]]; then
say "mode: check (nothing will be written)"
fi
say ""
link_blob_path
if [[ $check == false ]]; then
mkdir -p "$state_dir"/{toggles/hypr,indicators,notifications}
mkdir -p "$config_dir/blob"/{hooks,extensions,plugins,themed,themes}
mkdir -p "$HOME/wallpapers"
fi
install_tree "$repo_dir/hypr" "$config_dir/hypr" "hypr"
# config/ is the shipped-defaults directory. Everything in it is reachable as
# $BLOB_PATH/config/... so `blob-refresh-config` can reset a file, and most of it
# is also the right thing to deploy on a fresh install. Two subdirectories are
# reference-only and must not be copied into ~/.config:
#
# blob/ the shell defaults; copying them over ~/.config/blob/shell.json would
# replace the user's bar layout with the stock one
# hypr/ the stock Hyprland config; the personal hypr/ below is authoritative
# and is installed to the same place
for entry in "$repo_dir"/config/*; do
entry_name="$(basename "$entry")"
[[ $entry_name == blob || $entry_name == hypr ]] && continue
if [[ -d $entry ]]; then
install_tree "$entry" "$config_dir/$entry_name" "config/$entry_name"
else
install_file "$entry" "$config_dir/$entry_name" "config/$entry_name"
fi
done
install_tree "$repo_dir/hooks" "$config_dir/blob/hooks" "hooks"
install_tree "$repo_dir/branding" "$config_dir/blob/branding" "branding"
install_file "$repo_dir/shell.json" "$config_dir/blob/shell.json" "shell.json"
install_file "$repo_dir/session/uwsm/default" "$config_dir/uwsm/default" "uwsm/default"
install_file "$repo_dir/session/uwsm/env.d/10-blob" "$config_dir/uwsm/env.d/10-blob" "uwsm/env.d/10-blob"
install_file "$repo_dir/fonts/omarchy.ttf" "$font_dir/omarchy.ttf" "fonts/omarchy.ttf"
install_session_entry
zen_profile="$(find "$config_dir/zen" -maxdepth 1 -type d -name '*.Default (release)*' 2>/dev/null | head -1)"
if [[ -n $zen_profile ]]; then
install_file "$repo_dir/zen/userChrome.css" "$zen_profile/chrome/userChrome.css" "zen/userChrome.css"
else
say "SKIP zen/userChrome.css (no Zen profile found)"
fi
say ""
if [[ $check == true ]]; then
say "$changes item(s) would change."
(( changes == 0 )) || exit 1
else
say "$changes item(s) changed."
say ""
say "Next: log out and pick the Blob session, then run 'blob theme set <name>'."
fi
Executable
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
set -e
blob_path="$HOME/.local/share/blob"
config_dir="$HOME/.config"
session_entry="/usr/share/wayland-sessions/blob.desktop"
keep_state=false
assume_yes=false
usage() {
cat <<USAGE
Usage: ./uninstall.sh [OPTIONS]
Removes what install.sh wrote. The checkout itself is never touched.
--keep-state Leave ~/.local/state/blob in place
--yes Do not prompt
--help Show this message
USAGE
exit 0
}
while (( $# > 0 )); do
case "$1" in
--keep-state) keep_state=true; shift ;;
--yes) assume_yes=true; shift ;;
--help) usage ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
say() {
printf '%s\n' "$1"
}
restore_or_remove() {
local path="$1" label="$2"
if [[ -e $path.bak ]]; then
mv "$path.bak" "$path"
say "restore $label (from .bak)"
return
fi
if [[ -e $path ]]; then
rm -f "$path"
say "remove $label"
fi
}
say "This removes the Blob session, its config, and the BLOB_PATH symlink."
say "Your checkout, wallpapers, and themes stay where they are."
if [[ $keep_state == false ]]; then
say "It also removes ~/.local/state/blob (active theme, toggles, notifications)."
fi
say ""
if [[ $assume_yes == false ]]; then
read -rp "Continue? [y/N] " answer
[[ $answer == [yY] ]] || exit 0
fi
if [[ -L $blob_path ]]; then
rm -f "$blob_path"
say "remove BLOB_PATH symlink"
fi
for relative in blob/shell.json uwsm/default uwsm/env.d/10-blob; do
restore_or_remove "$config_dir/$relative" "$relative"
done
if [[ -d $config_dir/blob ]]; then
rm -rf "$config_dir/blob"
say "remove ~/.config/blob"
fi
for lua in hyprland.lua monitors.lua input.lua looknfeel.lua bindings.lua autostart.lua hyprsunset.conf xdph.conf; do
restore_or_remove "$config_dir/hypr/$lua" "hypr/$lua"
done
if [[ $keep_state == false && -d $HOME/.local/state/blob ]]; then
rm -rf "$HOME/.local/state/blob"
say "remove ~/.local/state/blob"
fi
if [[ -e $session_entry ]]; then
sudo rm -f "$session_entry"
say "remove wayland session entry"
fi
say ""
say "Done. Pick another session at the login screen before rebooting."