diff --git a/README.md b/README.md index e69de29..3fdbe96 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,92 @@ +
+

Blob

+

A self-contained Wayland desktop for Arch Linux.

+ + Arch Linux + Hyprland + Quickshell +
+ +--- + +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--` 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`. diff --git a/bin/blob-docs-commands b/bin/blob-docs-commands new file mode 100755 index 0000000..b8b8a68 --- /dev/null +++ b/bin/blob-docs-commands @@ -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" diff --git a/bin/blob-docs-keybinds b/bin/blob-docs-keybinds new file mode 100755 index 0000000..7587426 --- /dev/null +++ b/bin/blob-docs-keybinds @@ -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" diff --git a/bin/blob-system-sleep-monitor b/bin/blob-system-sleep-monitor new file mode 100755 index 0000000..44010dd --- /dev/null +++ b/bin/blob-system-sleep-monitor @@ -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 diff --git a/config/btop/btop.conf b/config/btop/btop.conf new file mode 100644 index 0000000..b53509d --- /dev/null +++ b/config/btop/btop.conf @@ -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 = "" diff --git a/config/foot/foot.ini b/config/foot/foot.ini new file mode 100644 index 0000000..3b4ec60 --- /dev/null +++ b/config/foot/foot.ini @@ -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 diff --git a/config/git/config b/config/git/config new file mode 100644 index 0000000..0f8e979 --- /dev/null +++ b/config/git/config @@ -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 diff --git a/config/hypr/autostart.lua b/config/hypr/autostart.lua new file mode 100644 index 0000000..de1a69a --- /dev/null +++ b/config/hypr/autostart.lua @@ -0,0 +1,2 @@ +-- Extra autostart processes. +-- o.launch_on_start("my-service") diff --git a/config/hypr/bindings.lua b/config/hypr/bindings.lua new file mode 100644 index 0000000..8bcc011 --- /dev/null +++ b/config/hypr/bindings.lua @@ -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") diff --git a/config/hypr/hyprland.lua b/config/hypr/hyprland.lua new file mode 100644 index 0000000..cdb6935 --- /dev/null +++ b/config/hypr/hyprland.lua @@ -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" }) diff --git a/config/hypr/hyprsunset.conf b/config/hypr/hyprsunset.conf new file mode 100644 index 0000000..a3ba208 --- /dev/null +++ b/config/hypr/hyprsunset.conf @@ -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 +# } diff --git a/config/hypr/input.lua b/config/hypr/input.lua new file mode 100644 index 0000000..e956fae --- /dev/null +++ b/config/hypr/input.lua @@ -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 }) diff --git a/config/hypr/looknfeel.lua b/config/hypr/looknfeel.lua new file mode 100644 index 0000000..18a5f38 --- /dev/null +++ b/config/hypr/looknfeel.lua @@ -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, +-- }, +-- }) diff --git a/config/hypr/monitors.lua b/config/hypr/monitors.lua new file mode 100644 index 0000000..420fd51 --- /dev/null +++ b/config/hypr/monitors.lua @@ -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 }) diff --git a/config/hypr/xdph.conf b/config/hypr/xdph.conf new file mode 100644 index 0000000..63b66be --- /dev/null +++ b/config/hypr/xdph.conf @@ -0,0 +1,4 @@ +screencopy { + allow_token_by_default = true + custom_picker_binary = hyprland-preview-share-picker +} diff --git a/config/imv/config b/config/imv/config new file mode 100644 index 0000000..f96aa6a --- /dev/null +++ b/config/imv/config @@ -0,0 +1,16 @@ +[binds] + +# Print the current image file + = exec lp "$imv_current_file" + +# Trash the current image and quit the viewer (recoverable from Trash) + = exec gio trash -- "$imv_current_file"; quit + +# Trash the current image and move to the next one + = exec gio trash -- "$imv_current_file"; close + +# Rotate the currently open image by 90 degrees + = exec mogrify -rotate 90 "$imv_current_file" + +# Edit the current image in Tensaku and quit the viewer + = exec tensaku-edit "$imv_current_file" & ; quit diff --git a/config/lazygit/config.yml b/config/lazygit/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/config/starship.toml b/config/starship.toml new file mode 100644 index 0000000..36f15d1 --- /dev/null +++ b/config/starship.toml @@ -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 = "" diff --git a/default/audio/filter-chain-host.conf b/default/audio/filter-chain-host.conf new file mode 100644 index 0000000..a1f2752 --- /dev/null +++ b/default/audio/filter-chain-host.conf @@ -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 } +] diff --git a/default/audio/tunings/dell-xps-2026/filter-chain.conf b/default/audio/tunings/dell-xps-2026/filter-chain.conf new file mode 100644 index 0000000..4ef787d --- /dev/null +++ b/default/audio/tunings/dell-xps-2026/filter-chain.conf @@ -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 + } + } + } +] diff --git a/default/audio/tunings/dell-xps-2026/tuning.conf b/default/audio/tunings/dell-xps-2026/tuning.conf new file mode 100644 index 0000000..e7bb633 --- /dev/null +++ b/default/audio/tunings/dell-xps-2026/tuning.conf @@ -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" diff --git a/menu/menu.jsonc b/default/blob/blob-menu.jsonc similarity index 100% rename from menu/menu.jsonc rename to default/blob/blob-menu.jsonc diff --git a/default/foot/screensaver.ini b/default/foot/screensaver.ini new file mode 100644 index 0000000..7130d06 --- /dev/null +++ b/default/foot/screensaver.ini @@ -0,0 +1,7 @@ +[main] +font=JetBrainsMono Nerd Font:size=18 +pad=0x0 + +[colors-dark] +background=000000 +foreground=ffffff diff --git a/hypr/default/apps.lua b/default/hypr/apps.lua similarity index 100% rename from hypr/default/apps.lua rename to default/hypr/apps.lua diff --git a/hypr/default/apps/1password.lua b/default/hypr/apps/1password.lua similarity index 100% rename from hypr/default/apps/1password.lua rename to default/hypr/apps/1password.lua diff --git a/hypr/default/apps/battlenet.lua b/default/hypr/apps/battlenet.lua similarity index 100% rename from hypr/default/apps/battlenet.lua rename to default/hypr/apps/battlenet.lua diff --git a/hypr/default/apps/bitwarden.lua b/default/hypr/apps/bitwarden.lua similarity index 100% rename from hypr/default/apps/bitwarden.lua rename to default/hypr/apps/bitwarden.lua diff --git a/hypr/default/apps/browser.lua b/default/hypr/apps/browser.lua similarity index 100% rename from hypr/default/apps/browser.lua rename to default/hypr/apps/browser.lua diff --git a/hypr/default/apps/davinci-resolve.lua b/default/hypr/apps/davinci-resolve.lua similarity index 100% rename from hypr/default/apps/davinci-resolve.lua rename to default/hypr/apps/davinci-resolve.lua diff --git a/hypr/default/apps/geforce.lua b/default/hypr/apps/geforce.lua similarity index 100% rename from hypr/default/apps/geforce.lua rename to default/hypr/apps/geforce.lua diff --git a/hypr/default/apps/hermes.lua b/default/hypr/apps/hermes.lua similarity index 100% rename from hypr/default/apps/hermes.lua rename to default/hypr/apps/hermes.lua diff --git a/hypr/default/apps/jetbrains.lua b/default/hypr/apps/jetbrains.lua similarity index 100% rename from hypr/default/apps/jetbrains.lua rename to default/hypr/apps/jetbrains.lua diff --git a/hypr/default/apps/localsend.lua b/default/hypr/apps/localsend.lua similarity index 100% rename from hypr/default/apps/localsend.lua rename to default/hypr/apps/localsend.lua diff --git a/hypr/default/apps/moonlight.lua b/default/hypr/apps/moonlight.lua similarity index 100% rename from hypr/default/apps/moonlight.lua rename to default/hypr/apps/moonlight.lua diff --git a/hypr/default/apps/omarchy-shell.lua b/default/hypr/apps/omarchy-shell.lua similarity index 100% rename from hypr/default/apps/omarchy-shell.lua rename to default/hypr/apps/omarchy-shell.lua diff --git a/hypr/default/apps/pip.lua b/default/hypr/apps/pip.lua similarity index 100% rename from hypr/default/apps/pip.lua rename to default/hypr/apps/pip.lua diff --git a/hypr/default/apps/qemu.lua b/default/hypr/apps/qemu.lua similarity index 100% rename from hypr/default/apps/qemu.lua rename to default/hypr/apps/qemu.lua diff --git a/hypr/default/apps/retroarch.lua b/default/hypr/apps/retroarch.lua similarity index 100% rename from hypr/default/apps/retroarch.lua rename to default/hypr/apps/retroarch.lua diff --git a/hypr/default/apps/screenshot-selection.lua b/default/hypr/apps/screenshot-selection.lua similarity index 100% rename from hypr/default/apps/screenshot-selection.lua rename to default/hypr/apps/screenshot-selection.lua diff --git a/hypr/default/apps/steam.lua b/default/hypr/apps/steam.lua similarity index 100% rename from hypr/default/apps/steam.lua rename to default/hypr/apps/steam.lua diff --git a/hypr/default/apps/system.lua b/default/hypr/apps/system.lua similarity index 100% rename from hypr/default/apps/system.lua rename to default/hypr/apps/system.lua diff --git a/hypr/default/apps/telegram.lua b/default/hypr/apps/telegram.lua similarity index 100% rename from hypr/default/apps/telegram.lua rename to default/hypr/apps/telegram.lua diff --git a/hypr/default/apps/terminals.lua b/default/hypr/apps/terminals.lua similarity index 100% rename from hypr/default/apps/terminals.lua rename to default/hypr/apps/terminals.lua diff --git a/hypr/default/apps/webcam-overlay.lua b/default/hypr/apps/webcam-overlay.lua similarity index 100% rename from hypr/default/apps/webcam-overlay.lua rename to default/hypr/apps/webcam-overlay.lua diff --git a/hypr/default/autostart.lua b/default/hypr/autostart.lua similarity index 100% rename from hypr/default/autostart.lua rename to default/hypr/autostart.lua diff --git a/hypr/default/bindings.lua b/default/hypr/bindings.lua similarity index 100% rename from hypr/default/bindings.lua rename to default/hypr/bindings.lua diff --git a/hypr/default/bindings/applications.lua b/default/hypr/bindings/applications.lua similarity index 100% rename from hypr/default/bindings/applications.lua rename to default/hypr/bindings/applications.lua diff --git a/hypr/default/bindings/clipboard.lua b/default/hypr/bindings/clipboard.lua similarity index 100% rename from hypr/default/bindings/clipboard.lua rename to default/hypr/bindings/clipboard.lua diff --git a/hypr/default/bindings/media.lua b/default/hypr/bindings/media.lua similarity index 100% rename from hypr/default/bindings/media.lua rename to default/hypr/bindings/media.lua diff --git a/hypr/default/bindings/tiling.lua b/default/hypr/bindings/tiling.lua similarity index 100% rename from hypr/default/bindings/tiling.lua rename to default/hypr/bindings/tiling.lua diff --git a/hypr/default/bindings/utilities.lua b/default/hypr/bindings/utilities.lua similarity index 100% rename from hypr/default/bindings/utilities.lua rename to default/hypr/bindings/utilities.lua diff --git a/hypr/default/blob.lua b/default/hypr/blob.lua similarity index 100% rename from hypr/default/blob.lua rename to default/hypr/blob.lua diff --git a/hypr/default/bootstrap.lua b/default/hypr/bootstrap.lua similarity index 100% rename from hypr/default/bootstrap.lua rename to default/hypr/bootstrap.lua diff --git a/hypr/default/disabled-input-device.lua b/default/hypr/disabled-input-device.lua similarity index 100% rename from hypr/default/disabled-input-device.lua rename to default/hypr/disabled-input-device.lua diff --git a/hypr/default/envs.lua b/default/hypr/envs.lua similarity index 100% rename from hypr/default/envs.lua rename to default/hypr/envs.lua diff --git a/hypr/default/helpers.lua b/default/hypr/helpers.lua similarity index 100% rename from hypr/default/helpers.lua rename to default/hypr/helpers.lua diff --git a/hypr/default/input.lua b/default/hypr/input.lua similarity index 100% rename from hypr/default/input.lua rename to default/hypr/input.lua diff --git a/hypr/default/looknfeel.lua b/default/hypr/looknfeel.lua similarity index 100% rename from hypr/default/looknfeel.lua rename to default/hypr/looknfeel.lua diff --git a/hypr/default/nvidia.lua b/default/hypr/nvidia.lua similarity index 100% rename from hypr/default/nvidia.lua rename to default/hypr/nvidia.lua diff --git a/hypr/default/paths.lua b/default/hypr/paths.lua similarity index 100% rename from hypr/default/paths.lua rename to default/hypr/paths.lua diff --git a/hypr/default/require_all.lua b/default/hypr/require_all.lua similarity index 100% rename from hypr/default/require_all.lua rename to default/hypr/require_all.lua diff --git a/hypr/default/require_optional.lua b/default/hypr/require_optional.lua similarity index 100% rename from hypr/default/require_optional.lua rename to default/hypr/require_optional.lua diff --git a/hypr/default/toggles.lua b/default/hypr/toggles.lua similarity index 100% rename from hypr/default/toggles.lua rename to default/hypr/toggles.lua diff --git a/hypr/default/toggles/flags.lua b/default/hypr/toggles/flags.lua similarity index 100% rename from hypr/default/toggles/flags.lua rename to default/hypr/toggles/flags.lua diff --git a/hypr/default/toggles/single-window-aspect-ratio.lua b/default/hypr/toggles/single-window-aspect-ratio.lua similarity index 100% rename from hypr/default/toggles/single-window-aspect-ratio.lua rename to default/hypr/toggles/single-window-aspect-ratio.lua diff --git a/hypr/default/toggles/window-no-gaps.lua b/default/hypr/toggles/window-no-gaps.lua similarity index 100% rename from hypr/default/toggles/window-no-gaps.lua rename to default/hypr/toggles/window-no-gaps.lua diff --git a/hypr/default/windows.lua b/default/hypr/windows.lua similarity index 100% rename from hypr/default/windows.lua rename to default/hypr/windows.lua diff --git a/hypr/default/workspace-layouts.lua b/default/hypr/workspace-layouts.lua similarity index 100% rename from hypr/default/workspace-layouts.lua rename to default/hypr/workspace-layouts.lua diff --git a/default/systemd/faster-shutdown.conf b/default/systemd/faster-shutdown.conf new file mode 100644 index 0000000..90dce4f --- /dev/null +++ b/default/systemd/faster-shutdown.conf @@ -0,0 +1,2 @@ +[Manager] +DefaultTimeoutStopSec=5s diff --git a/default/systemd/system-sleep/force-igpu b/default/systemd/system-sleep/force-igpu new file mode 100644 index 0000000..e0eeaae --- /dev/null +++ b/default/systemd/system-sleep/force-igpu @@ -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 diff --git a/default/systemd/system-sleep/keyboard-backlight b/default/systemd/system-sleep/keyboard-backlight new file mode 100644 index 0000000..014ab4a --- /dev/null +++ b/default/systemd/system-sleep/keyboard-backlight @@ -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 diff --git a/default/systemd/system-sleep/unmount-fuse b/default/systemd/system-sleep/unmount-fuse new file mode 100644 index 0000000..7c27725 --- /dev/null +++ b/default/systemd/system-sleep/unmount-fuse @@ -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 diff --git a/default/systemd/system/plocate-updatedb.service.d/10-blob.conf b/default/systemd/system/plocate-updatedb.service.d/10-blob.conf new file mode 100644 index 0000000..571a53b --- /dev/null +++ b/default/systemd/system/plocate-updatedb.service.d/10-blob.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=/usr/bin/updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots diff --git a/default/systemd/system/supergfxd.service.d/delay-start.conf b/default/systemd/system/supergfxd.service.d/delay-start.conf new file mode 100644 index 0000000..19fc298 --- /dev/null +++ b/default/systemd/system/supergfxd.service.d/delay-start.conf @@ -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 diff --git a/default/systemd/user/app.slice.d/10-oomd.conf b/default/systemd/user/app.slice.d/10-oomd.conf new file mode 100644 index 0000000..22faec8 --- /dev/null +++ b/default/systemd/user/app.slice.d/10-oomd.conf @@ -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 diff --git a/default/systemd/user/blob-crash-watch.service b/default/systemd/user/blob-crash-watch.service new file mode 100644 index 0000000..da6906b --- /dev/null +++ b/default/systemd/user/blob-crash-watch.service @@ -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 diff --git a/default/systemd/user/blob-fcitx5.service b/default/systemd/user/blob-fcitx5.service new file mode 100644 index 0000000..9cc4521 --- /dev/null +++ b/default/systemd/user/blob-fcitx5.service @@ -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 diff --git a/default/systemd/user/blob-sleep-lock.service b/default/systemd/user/blob-sleep-lock.service new file mode 100644 index 0000000..377975c --- /dev/null +++ b/default/systemd/user/blob-sleep-lock.service @@ -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 diff --git a/default/systemd/user/blob-speaker-tuning.service b/default/systemd/user/blob-speaker-tuning.service new file mode 100644 index 0000000..c7a1387 --- /dev/null +++ b/default/systemd/user/blob-speaker-tuning.service @@ -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 diff --git a/default/systemd/user/bt-agent.service b/default/systemd/user/bt-agent.service new file mode 100644 index 0000000..4c0117f --- /dev/null +++ b/default/systemd/user/bt-agent.service @@ -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 diff --git a/default/systemd/user@.service.d/faster-shutdown.conf b/default/systemd/user@.service.d/faster-shutdown.conf new file mode 100644 index 0000000..449242c --- /dev/null +++ b/default/systemd/user@.service.d/faster-shutdown.conf @@ -0,0 +1,2 @@ +[Service] +TimeoutStopSec=5s diff --git a/themed/btop.theme.tpl b/default/themed/btop.theme.tpl similarity index 100% rename from themed/btop.theme.tpl rename to default/themed/btop.theme.tpl diff --git a/themed/chromium.theme.tpl b/default/themed/chromium.theme.tpl similarity index 100% rename from themed/chromium.theme.tpl rename to default/themed/chromium.theme.tpl diff --git a/themed/foot.ini.tpl b/default/themed/foot.ini.tpl similarity index 100% rename from themed/foot.ini.tpl rename to default/themed/foot.ini.tpl diff --git a/themed/hyprland-preview-share-picker.css.tpl b/default/themed/hyprland-preview-share-picker.css.tpl similarity index 100% rename from themed/hyprland-preview-share-picker.css.tpl rename to default/themed/hyprland-preview-share-picker.css.tpl diff --git a/themed/hyprland.lua.tpl b/default/themed/hyprland.lua.tpl similarity index 100% rename from themed/hyprland.lua.tpl rename to default/themed/hyprland.lua.tpl diff --git a/themed/kitty.conf.tpl b/default/themed/kitty.conf.tpl similarity index 100% rename from themed/kitty.conf.tpl rename to default/themed/kitty.conf.tpl diff --git a/themed/neovim.lua.tpl b/default/themed/neovim.lua.tpl similarity index 100% rename from themed/neovim.lua.tpl rename to default/themed/neovim.lua.tpl diff --git a/themed/shell.toml.tpl b/default/themed/shell.toml.tpl similarity index 100% rename from themed/shell.toml.tpl rename to default/themed/shell.toml.tpl diff --git a/themed/zen.css.tpl b/default/themed/zen.css.tpl similarity index 100% rename from themed/zen.css.tpl rename to default/themed/zen.css.tpl diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 0000000..27e8906 --- /dev/null +++ b/docs/commands.md @@ -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 | | +| `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 | | +| `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 | [--force] | +| `blob-audio-volume` | Adjust output volume and show the Blob OSD | | + +## bar + +| Command | Does | Arguments | +| --- | --- | --- | +| `blob-bar` | Configure the bar and its widget layout | use \| reset \| defaults \| position \| transparent \| put [placement] \| move [placement] \| set [--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) | | +| `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 | | +| `blob-bg-switcher` | Open the Blob background switcher | - | + +## bluetooth + +| Command | Does | Arguments | +| --- | --- | --- | +| `blob-bluetooth-device` | Control a Bluetooth device | [pair\|connect\|disconnect\|forget]
| +| `blob-bluetooth-power` | Turn Bluetooth on or off, remembered across reboots | | +| `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 | | +| `blob-branding-screensaver` | Edit, set, or reset screensaver branding | | + +## brightness + +| Command | Does | Arguments | +| --- | --- | --- | +| `blob-brightness-ddc` | Show or adjust DDC/CI display brightness for a Hyprland 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] | +| `blob-brightness-keyboard-mute` | Set the mic-mute indicator LED on laptops that expose a platform::micmute LED node. | | + +## 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=] [--webcam-size=] [--resolution=] [--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 | +| `blob-capture-screenshot` | Take a screenshot | [smart\|region\|windows\|fullscreen] [slurp\|copy\|save] [--editor=] | +| `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 | | + +## clipboard + +| Command | Does | Arguments | +| --- | --- | --- | +| `blob-clipboard-file` | Copy a file to the clipboard and paste it (hidden) | [--copy-only] | +| `blob-clipboard-open` | Open a clipboard history entry (hidden) | --history-index | +| `blob-clipboard-text` | Copy text to the clipboard and type or paste it (hidden) | [--shift-insert] [--copy-only] [--history-index \|] | + +## 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 | | +| `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 ] [--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. diff --git a/docs/keybinds.md b/docs/keybinds.md new file mode 100644 index 0000000..8a19a38 --- /dev/null +++ b/docs/keybinds.md @@ -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` diff --git a/docs/shell.md b/docs/shell.md new file mode 100644 index 0000000..c61e343 --- /dev/null +++ b/docs/shell.md @@ -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. diff --git a/docs/themes.md b/docs/themes.md new file mode 100644 index 0000000..10299dd --- /dev/null +++ b/docs/themes.md @@ -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. diff --git a/hooks/battery-low.sample b/hooks/battery-low.sample new file mode 100644 index 0000000..024fd0b --- /dev/null +++ b/hooks/battery-low.sample @@ -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 diff --git a/hooks/font-set.sample b/hooks/font-set.sample new file mode 100644 index 0000000..3f0109a --- /dev/null +++ b/hooks/font-set.sample @@ -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" diff --git a/hooks/post-update.sample b/hooks/post-update.sample new file mode 100644 index 0000000..a43ba64 --- /dev/null +++ b/hooks/post-update.sample @@ -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" diff --git a/hooks/theme-set.sample b/hooks/theme-set.sample new file mode 100644 index 0000000..2d13287 --- /dev/null +++ b/hooks/theme-set.sample @@ -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" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..424ee36 --- /dev/null +++ b/install.sh @@ -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 diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..7e034c0 --- /dev/null +++ b/uninstall.sh @@ -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."