Restore the browser, gaming, development, and Windows VM menus

This commit is contained in:
2026-09-20 00:30:28 -04:00
parent 3e65d176e3
commit ffa9b2ca0e
42 changed files with 3575 additions and 7 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ and nothing to re-apply after somebody else's release.
- **[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`.
- **A `blob-*` CLI** of 290 commands, dispatched by `blob`.
- **24 themes** with a palette pipeline that retints the terminal, editor,
browser, shell, and lock screen from one `colors.toml`.
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# blob:summary=Native messaging host: copy a Chromium tab URL to the clipboard
# blob:hidden=true
set -euo pipefail
SCRIPT_PATH="${BASH_SOURCE[0]}"
export BLOB_PATH="${BLOB_PATH:-$(cd -- "$(dirname -- "$SCRIPT_PATH")/.." && pwd)}"
export PATH="$BLOB_PATH/bin:/usr/local/bin:/usr/bin:$PATH"
parse_url() {
jq -r '.url // empty' 2>/dev/null <<<"$1" || true
}
copy_url() {
local url="$1"
[[ -n $url ]] || return 1
printf '%s' "$url" | wl-copy --type text/plain
blob-notify-send -g 󰅍 "URL copied to clipboard"
}
reply_copied() {
if [[ $1 == "true" ]]; then
printf '\x0f\x00\x00\x00{"copied":true}'
else
printf '\x10\x00\x00\x00{"copied":false}'
fi
}
main() {
local length payload url
length=$(head -c4 | od -An -v -tu4 --endian=little | tr -d ' ')
[[ -n ${length:-} ]] && (( length > 0 )) || exit 0
payload=$(head -c "$length")
url=$(parse_url "$payload")
if copy_url "$url"; then
reply_copied true
else
reply_copied false
fi
}
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
main "$@"
fi
+196
View File
@@ -0,0 +1,196 @@
#!/bin/bash
# blob:summary=Native messaging host: download the URL sent by the yt-dlp Chromium extension
# blob:hidden=true
set -euo pipefail
SCRIPT_PATH="${BASH_SOURCE[0]}"
# The browser launches us without Blob's environment, so locate the repo from
# our own path when BLOB_PATH isn't already set. Export it — blob-shell (used
# by blob-osd) needs it to find the running shell, and silently no-ops without it.
export BLOB_PATH="${BLOB_PATH:-$(cd -- "$(dirname -- "$SCRIPT_PATH")/.." && pwd)}"
# Make sure the Blob bin and yt-dlp are reachable when launched by the browser.
export PATH="$BLOB_PATH/bin:/usr/local/bin:/usr/bin:$PATH"
DOWNLOAD_DIR="${BLOB_YTDLP_DIR:-$HOME/Videos}"
parse_url() {
jq -r '.url // empty' 2>/dev/null <<<"$1" || true
}
valid_url() {
[[ $1 =~ ^https?:// ]]
}
# A printed path is only usable if it is a regular file inside DOWNLOAD_DIR.
# Forged records (leading-dash mpv options, paths with control chars, or
# anything that escaped the download directory) must not reach the click command.
resolve_download_file() {
local candidate=$1 file_real dir_real
[[ -n $candidate ]] || return 1
[[ $candidate != *$'\n'* && $candidate != *$'\r'* && $candidate != *$'\t'* ]] || return 1
[[ -f $candidate ]] || return 1
# Read to a NUL: command substitution strips trailing newlines, which would
# resolve a name ending in one to a different file that may well exist.
IFS= read -r -d '' file_real < <(realpath -ze -- "$candidate") || return 1
IFS= read -r -d '' dir_real < <(realpath -ze -- "$DOWNLOAD_DIR") || return 1
[[ $file_real != *$'\n'* && $file_real != *$'\r'* && $file_real != *$'\t'* ]] || return 1
# Trim the slash so a download directory of "/" still leaves a usable prefix.
[[ $file_real == "${dir_real%/}"/* ]] || return 1
printf '%s' "$file_real"
}
# yt-dlp prints the title JSON-encoded, so a newline or tab in page metadata is an
# escape sequence rather than a record boundary. This is toast text, never a command.
decode_title() {
local decoded
decoded=$(jq -r 'if type == "string" then . else empty end' <<<"$1" 2>/dev/null) || return 1
decoded=${decoded%%[[:cntrl:]]*} # keep what a person would read, drop the forgery
[[ -n $decoded && $decoded != -* ]] || return 1
printf '%s' "$decoded"
}
title_from_file() {
local name=${1##*/}
name=${name%.*}
name=${name//[$'\n\r\t']/}
if [[ -z $name || $name == -* ]]; then
printf '%s' "Video"
else
printf '%s' "$name"
fi
}
# Drive the Quickshell OSD — a single overlay that updates in place (like the
# volume/brightness bar), so download progress never stacks like notifications.
osd_progress() {
blob-osd -i 󰇚 -p "$1" -d 8000 >/dev/null 2>&1 || true
}
osd_close() {
blob-shell -q osd close >/dev/null 2>&1 || true
}
download_url() {
local url="$1"
mkdir -p "$DOWNLOAD_DIR"
# Don't show anything until yt-dlp confirms there's actually a video to grab.
if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then
blob-notify-send -u critical -g 󰅖 "No video found for download" "$url"
exit 0
fi
osd_progress 0
# Stream the download: BLOB_PROG carries the percent (drives the OSD), and
# BLOB_FILE and BLOB_TITLE (printed only after a successful move) carry the
# path and the title. The title is JSON-encoded so metadata cannot forge a record,
# and the file is named after it: yt-dlp strips control characters from a filename
# with or without --restrict-filenames, so a record is still only ever one line.
local line pct intpct last="" er nowms lastms=0 title="" filepath="" resolved
while IFS= read -r line; do
case $line in
BLOB_PROG*)
pct=${line#BLOB_PROG$'\t'}
intpct=${pct%%.*}
intpct=${intpct//[^0-9]/}
[[ -n $intpct && $intpct != "$last" ]] || continue # skip no-op repeats
# Throttle to ~4 redraws/sec so fast downloads don't spawn a flurry of processes.
er=$EPOCHREALTIME
nowms=$((${er%[.,]*} * 1000 + 10#${er##*[.,]} / 1000))
((nowms - lastms >= 250)) || continue
last=$intpct
lastms=$nowms
osd_progress "$intpct"
;;
BLOB_FILE*)
resolved=$(resolve_download_file "${line#BLOB_FILE$'\t'}") || continue
filepath=$resolved
;;
BLOB_TITLE*)
title=$(decode_title "${line#BLOB_TITLE$'\t'}") || title=""
;;
esac
done < <(PYTHONUNBUFFERED=1 yt-dlp --no-playlist --no-simulate \
--quiet --no-warnings --no-exec --no-exec-before-download --progress --newline \
--progress-template $'download:BLOB_PROG\t%(progress._percent_str)s' \
--paths "$DOWNLOAD_DIR" -o '%(title)s.%(ext)s' \
--print $'after_move:BLOB_FILE\t%(filepath)s' \
--print $'after_move:BLOB_TITLE\t%(title)j' \
-- "$url" 2>&1)
osd_close
# after_move only prints on a successful download+move, so a captured path == success.
if [[ -n $filepath ]]; then
[[ -n $title ]] || title=$(title_from_file "$filepath")
((${#title} > 50)) && title="${title:0:50}…" # keep the toast compact
# Square, center-cropped thumbnail so the notification preview isn't stretched.
local preview
preview="$(mktemp --suffix=.jpg)"
ffmpeg -y -i "$filepath" -ss 00:00:00.1 -vframes 1 \
-vf "crop='min(iw,ih)':'min(iw,ih)',scale=256:256" -q:v 2 \
"$preview" -loglevel quiet 2>/dev/null || true
# Best-effort: the download already succeeded, and under `set -e` a failed
# toast would exit before the thumbnail cleanup below is ever scheduled.
# `--` keeps mpv from parsing a leading-dash filename as an option; the path
# is one discrete argument, so it never reaches a shell.
blob-notify-send -g 󰄬 "Download complete" "$title" \
-t 10000 --image "${preview:-$filepath}" \
--exec mpv -- "$filepath" || true
# The shell loads the thumbnail into memory when the toast appears and never
# re-reads the file, so the preview only has to outlive that load, not the
# toast.
(
sleep 2
rm -f "$preview"
) &
else
blob-notify-send -u critical -g 󰅖 "Download failed" "$url"
fi
exit 0
}
main() {
local length payload url
# Detached worker: this is what actually runs yt-dlp and fires notifications.
if [[ "${1:-}" == "--download" ]]; then
download_url "$2"
fi
# Native messaging frame: 4-byte little-endian length prefix, then UTF-8 JSON.
length=$(head -c4 | od -An -v -tu4 --endian=little | tr -d ' ')
[[ -n ${length:-} ]] && ((length > 0)) || exit 0
payload=$(head -c "$length")
# Ack with an empty message so the extension's sendNativeMessage callback resolves cleanly.
printf '\x02\x00\x00\x00{}'
url=$(parse_url "$payload")
[[ -n $url ]] || exit 0
valid_url "$url" || exit 0
# Detach the download so this host exits promptly and frees the browser's port.
setsid -f "$SCRIPT_PATH" --download "$url" </dev/null >/dev/null 2>&1
}
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
main "$@"
fi
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# blob:summary=List installed RetroArch core names
set -e
core_dir="/usr/lib/libretro"
preferred_cores=(
"Amstrad CPC|cap32"
"Arcade FBNeo|fbneo"
"Arcade MAME|mame"
"Commodore Amiga|puae"
"Commodore C128|vice_x128"
"Commodore C64|vice_x64"
"Commodore VIC-20|vice_xvic"
"Nintendo DS|desmume"
"Nintendo Game Boy / Color|gambatte"
"Nintendo Game Boy Advance|mgba"
"Nintendo GameCube / Wii|dolphin"
"Nintendo NES / Famicom|mesen"
"Nintendo 64|parallel_n64"
"Nintendo SNES / SFC|snes9x"
"NEC PC Engine / TurboGrafx-16|mednafen_pce_fast"
"NEC PC Engine CD / TurboGrafx-CD|mednafen_pce"
"NEC PC Engine SuperGrafx|mednafen_supergrafx"
"Sega Dreamcast|flycast"
"Sega Mega Drive / Master System / Game Gear|genesis_plus_gx"
"Sega Saturn|kronos"
"Sony PlayStation|mednafen_psx_hw"
"Sony PlayStation Portable|ppsspp"
)
[[ -d $core_dir ]] || exit 0
for preferred_core in "${preferred_cores[@]}"; do
label="${preferred_core%%|*}"
core="${preferred_core#*|}"
[[ -f $core_dir/${core}_libretro.so ]] && printf '%s (%s)\n' "$label" "$core"
done
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
# blob:summary=Create a desktop launcher for a RetroArch game
# blob:args=[core path-to-game]
# blob:examples=blob games retro install snes9x ~/Games/roms/snes/game.sfc | blob-games-retro-install /usr/lib/libretro/mgba_libretro.so ~/Games/roms/gba/game.gba
set -e
if (( $# == 0 )); then
mapfile -t cores < <(blob-games-retro-cores)
if (( ${#cores[@]} == 0 )); then
blob-notify-send -g 󰯉 "No RetroArch cores found" "/usr/lib/libretro"
exit 1
fi
core=$(blob-menu-select "RetroArch core" "${cores[@]}") || exit 0
[[ -n $core ]] || exit 0
core="${core##*(}"
core="${core%)}"
game_path=$(blob-menu-file "Retro game" "$HOME/Games/roms" "7z bin ccd chd cue dmg elf fds gb gba gbc iso lha m3u md n64 nds nes pbp sfc smc swc zip z64") || exit 0
[[ -n $game_path ]] || exit 0
elif (( $# == 2 )); then
core="$1"
game_path="$2"
else
echo "Usage: blob-games-retro-install [core path-to-game]"
echo "Example: blob-games-retro-install snes9x ~/Games/roms/snes/game.sfc"
exit 1
fi
if [[ ! -f $game_path ]]; then
echo "Game not found: $game_path"
exit 1
fi
if [[ $core == */* ]]; then
core_path="$core"
else
core_path="/usr/lib/libretro/${core}_libretro.so"
fi
if [[ ! -f $core_path ]]; then
echo "Core not found: $core_path"
exit 1
fi
game_name=$(printf '%s' "${game_path##*/}" | sed 's/\.[^.]*$//; s/[[:space:]]*([^)]*)//g; s/[[:space:]]\+/ /g; s/^ //; s/ $//' | perl -Mopen=locale -pe 's/(^|[[:space:]])([^[:space:]])/$1\U$2/g')
desktop_name="$game_name"
desktop_id=$(printf '%s' "$desktop_name" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//; s/-$//')
desktop_dir="$HOME/.local/share/applications"
desktop_file="$desktop_dir/$desktop_id.desktop"
mkdir -p "$desktop_dir"
cat >"$desktop_file" <<EOF
[Desktop Entry]
Version=1.0
Name=$desktop_name
Comment=Play $game_name with RetroArch
Exec=retroarch -L "$core_path" "$game_path"
Terminal=false
Type=Application
Icon=retro-gaming
StartupNotify=true
Categories=Game;Emulator;
EOF
chmod +x "$desktop_file"
update-desktop-database "$desktop_dir" &>/dev/null || true
blob-notify-send -g 󰯉 "$game_name installed" "Start it with Super + Space"
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# blob:summary=Install a supported browser
# blob:args=<chrome|brave|brave-origin|edge|firefox|zen>
# blob:examples=blob install browser firefox | blob install browser brave
source "$BLOB_PATH/install/helpers/browser-policy.sh"
setup_chromium_policy_directory() {
browser_policy_setup_dir "$1"
}
announce_browser_installed() {
echo ""
echo "$1 browser installed. Make it the default via Setup > Defaults > Browser."
}
copy_chromium_flags() {
mkdir -p ~/.config
cp -f "$BLOB_PATH/config/chromium-flags.conf" "$1"
blob-install-chromium-copy-url
blob-install-chromium-ytdlp
}
setup_firefox_wayland() {
mkdir -p ~/.config/environment.d
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/blob-firefox-wayland.conf
}
case $1 in
chrome)
echo "Installing Chrome..."
blob-pkg-aur-add google-chrome || exit 1
setup_chromium_policy_directory /etc/opt/chrome/policies/managed
copy_chromium_flags ~/.config/chrome-flags.conf
blob-theme-browser
announce_browser_installed "Chrome"
;;
edge)
echo "Installing Edge..."
blob-pkg-aur-add microsoft-edge-stable-bin || exit 1
setup_chromium_policy_directory /etc/opt/edge/policies/managed
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
blob-theme-browser
announce_browser_installed "Edge"
;;
brave)
echo "Installing Brave..."
blob-pkg-aur-add brave-bin || exit 1
setup_chromium_policy_directory /etc/brave/policies/managed
copy_chromium_flags ~/.config/brave-flags.conf
blob-theme-browser
announce_browser_installed "Brave"
;;
brave-origin)
echo "Installing Brave Origin..."
blob-pkg-aur-add brave-origin-bin || exit 1
setup_chromium_policy_directory /etc/brave/policies/managed
copy_chromium_flags ~/.config/brave-origin-flags.conf
blob-theme-browser
announce_browser_installed "Brave Origin"
;;
firefox)
echo "Installing Firefox..."
blob-pkg-add firefox || exit 1
browser_policy_setup_firefox_distribution /usr/lib/firefox/distribution
setup_firefox_wayland
announce_browser_installed "Firefox"
;;
zen)
echo "Installing Zen..."
blob-pkg-aur-add zen-browser-bin || exit 1
browser_policy_setup_firefox_distribution /opt/zen-browser/distribution
setup_firefox_wayland
announce_browser_installed "Zen"
;;
*)
echo "Usage: blob-install-browser <chrome|brave|brave-origin|edge|firefox|zen>"
exit 1
;;
esac
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# blob:summary=Install the native messaging host for the Copy URL Chromium extension
set -euo pipefail
HOST_NAME="com.blob.copy_url"
HOST_PATH="$BLOB_PATH/bin/blob-chromium-copy-url-host"
TEMPLATE="$BLOB_PATH/default/chromium/native-messaging-hosts/$HOST_NAME.json"
browser_dirs=(
"$HOME/.config/chromium"
"$HOME/.config/google-chrome"
"$HOME/.config/google-chrome-beta"
"$HOME/.config/google-chrome-unstable"
"$HOME/.config/BraveSoftware/Brave-Browser"
"$HOME/.config/BraveSoftware/Brave-Browser-Beta"
"$HOME/.config/BraveSoftware/Brave-Browser-Nightly"
"$HOME/.config/microsoft-edge"
"$HOME/.config/microsoft-edge-dev"
)
manifest=$(sed "s|__HOST_PATH__|$HOST_PATH|g" "$TEMPLATE")
for dir in "${browser_dirs[@]}"; do
mkdir -p "$dir/NativeMessagingHosts"
printf '%s\n' "$manifest" >"$dir/NativeMessagingHosts/$HOST_NAME.json"
done
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# blob:summary=Install the native messaging host for the yt-dlp Chromium extension
set -euo pipefail
HOST_NAME="com.blob.ytdlp"
HOST_PATH="$BLOB_PATH/bin/blob-chromium-ytdlp-host"
TEMPLATE="$BLOB_PATH/default/chromium/native-messaging-hosts/$HOST_NAME.json"
# Chromium-based browser profile roots that use the NativeMessagingHosts layout.
browser_dirs=(
"$HOME/.config/chromium"
"$HOME/.config/google-chrome"
"$HOME/.config/google-chrome-beta"
"$HOME/.config/google-chrome-unstable"
"$HOME/.config/BraveSoftware/Brave-Browser"
"$HOME/.config/BraveSoftware/Brave-Browser-Beta"
"$HOME/.config/BraveSoftware/Brave-Browser-Nightly"
"$HOME/.config/microsoft-edge"
"$HOME/.config/microsoft-edge-dev"
)
manifest=$(sed "s|__HOST_PATH__|$HOST_PATH|g" "$TEMPLATE")
for dir in "${browser_dirs[@]}"; do
mkdir -p "$dir/NativeMessagingHosts"
printf '%s\n' "$manifest" >"$dir/NativeMessagingHosts/$HOST_NAME.json"
done
+155
View File
@@ -0,0 +1,155 @@
#!/bin/bash
# blob:summary=Install a supported development environment
# blob:name=dev-env
# blob:args=<ruby|node|bun|deno|go|laravel|symfony|php|python|elixir|phoenix|rust|java|zig|ocaml|dotnet|clojure|scala>
# blob:examples=blob install dev-env ruby | blob install dev-env node
# blob:requires-sudo=true
if [[ -z $1 ]]; then
echo "Usage: blob-install-dev-env <ruby|node|bun|deno|go|laravel|symfony|php|python|elixir|phoenix|rust|java|zig|ocaml|dotnet|clojure|scala>" >&2
exit 1
fi
install_php() {
blob-pkg-add php composer php-sqlite xdebug
# Install Path for Composer
if [[ :$PATH: != *:$HOME/.config/composer/vendor/bin:* ]]; then
echo 'export PATH="$HOME/.config/composer/vendor/bin:$PATH"' >>"$HOME/.bashrc"
source "$HOME/.bashrc"
echo "Added Composer global bin directory to PATH."
else
echo "Composer global bin directory already in PATH."
fi
# Enable some extensions
local php_ini_path="/etc/php/php.ini"
local extensions_to_enable=(
"bcmath"
"intl"
"iconv"
"openssl"
"pdo_sqlite"
"pdo_mysql"
)
# Enable Xdebug
sudo sed -i \
-e 's/^;zend_extension=xdebug.so/zend_extension=xdebug.so/' \
-e 's/^;xdebug.mode=debug/xdebug.mode=debug/' \
/etc/php/conf.d/xdebug.ini
for ext in "${extensions_to_enable[@]}"; do
sudo sed -i "s/^;extension=${ext}/extension=${ext}/" "$php_ini_path"
done
}
install_node() {
echo -e "Installing Node.js...\n"
mise use --global node
}
case "$1" in
ruby)
echo -e "Installing Ruby on Rails...\n"
blob-pkg-add libyaml
mise settings add ruby.compile false
mise settings add idiomatic_version_file_enable_tools ruby
mise use --global ruby@latest
echo "gem: --no-document" >~/.gemrc
mise x ruby -- gem install rails --no-document
echo -e "\nYou can now run: rails new myproject"
;;
node)
install_node
;;
bun)
echo -e "Installing Bun...\n"
mise use -g bun@latest
;;
deno)
echo -e "Installing Deno...\n"
mise use -g deno@latest
;;
go)
echo -e "Installing Go...\n"
mise use --global go@latest
;;
php)
echo -e "Installing PHP...\n"
install_php
;;
laravel)
echo -e "Installing PHP and Laravel...\n"
install_php
install_node
composer global require laravel/installer
echo -e "\nYou can now run: laravel new myproject"
;;
symfony)
echo -e "Installing PHP and Symfony...\n"
install_php
blob-pkg-add symfony-cli
echo -e "\nYou can now run: symfony new --webapp myproject"
;;
python)
echo -e "Installing Python...\n"
mise use --global python@latest
echo -e "\nInstalling uv...\n"
curl -fsSL https://astral.sh/uv/install.sh | sh
;;
elixir)
echo -e "Installing Elixir...\n"
mise use --global erlang@latest
mise use --global elixir@latest
mise x elixir -- mix local.hex --force
;;
phoenix)
echo -e "Installing Phoenix Framework...\n"
# Ensure Erlang/Elixir first
mise use --global erlang@latest
mise use --global elixir@latest
# Hex & Rebar
mise x elixir -- mix local.hex --force
mise x elixir -- mix local.rebar --force
# Phoenix project (phx_new)
mise x elixir -- mix archive.install hex phx_new --force
echo -e "\nYou can now run: mix phx.new my_app"
;;
rust)
echo -e "Installing Rust...\n"
bash -c "$(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs)" -- -y
;;
java)
echo -e "Installing Java...\n"
mise use --global java@latest
;;
zig)
echo -e "Installing Zig...\n"
mise use --global zig@latest
mise use -g zls@latest
;;
ocaml)
echo -e "Installing OCaml...\n"
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"
opam init --yes
eval "$(opam env)"
opam install ocaml-lsp-server odoc ocamlformat utop --yes
;;
dotnet)
echo -e "Installing .NET...\n"
mise use --global dotnet@latest
;;
clojure)
echo -e "Installing Clojure...\n"
blob-pkg-add rlwrap
mise use --global clojure@latest
;;
scala)
echo -e "Installing Scala...\n"
mise use --global java@latest
mise use --global scala@latest
mise use --global scala-cli@latest
;;
esac
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# blob:summary=Install one of the supported databases in a Docker container with the suitable development options.
# blob:requires-sudo=true
options=("MySQL" "PostgreSQL" "Redis" "MongoDB" "MariaDB" "MSSQL")
if (( $# == 0 )); then
choices=$(printf "%s\n" "${options[@]}" | gum choose --header "Select database (return to install, esc to cancel)") || main_menu
else
choices="$@"
fi
if [[ -n $choices ]]; then
for db in $choices; do
echo "Installing $db..."
case $db in
MySQL) sudo docker run -d --restart unless-stopped -p "127.0.0.1:3306:3306" --name=mysql8 -e MYSQL_ROOT_PASSWORD= -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql:8.4 ;;
PostgreSQL) sudo docker run -d --restart unless-stopped -p "127.0.0.1:5432:5432" --name=postgres18 -e POSTGRES_HOST_AUTH_METHOD=trust postgres:18 ;;
MariaDB) sudo docker run -d --restart unless-stopped -p "127.0.0.1:3306:3306" --name=mariadb11 -e MARIADB_ROOT_PASSWORD= -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=true mariadb:11.8 ;;
Redis) sudo docker run -d --restart unless-stopped -p "127.0.0.1:6379:6379" --name=redis redis:7 ;;
MongoDB) sudo docker run -d --restart unless-stopped -p "127.0.0.1:27017:27017" --name mongodb -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin123 mongo:noble ;;
MSSQL) sudo docker run -d --restart unless-stopped -p "127.0.0.1:1433:1433" --name mssql -e MSSQL_PID=Developer -e ACCEPT_EULA=Y -e "MSSQL_SA_PASSWORD=@dmin123" mcr.microsoft.com/mssql/server:2022-CU12-ubuntu-22.04 ;;
esac
done
else
echo "No databases selected for installation."
fi
+88
View File
@@ -0,0 +1,88 @@
#!/bin/bash
# blob:summary=Install Battle.net standalone via umu-launcher + GE-Proton (no Steam, no Lutris, no Heroic).
# blob:requires-sudo=true
set -e
PREFIX="$HOME/Games/battlenet"
LAUNCHER="$PREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
INSTALLER_URL="https://downloader.battle.net/download/getInstallerForGame?os=win&gameProgram=BATTLENET_APP&version=Live"
echo "Installing Battle.net..."
blob-pkg-add umu-launcher
blob-install-gaming-gpu-lib32
# Detect a half-finished prefix from a closed/crashed previous run and offer
# to wipe it before trying again. Battle.net's installer isn't idempotent.
if [[ -d $PREFIX && ! -f $LAUNCHER ]]; then
echo
echo "Found a partial Battle.net install at $PREFIX (no Launcher.exe)."
echo "Battle.net's installer can't resume from this state."
if gum confirm "Wipe the partial prefix and start fresh?"; then
pkill -f "$PREFIX" 2>/dev/null || true
sleep 1
rm -rf "$PREFIX"
else
echo "Aborting. Re-run when ready to wipe."
exit 1
fi
fi
mkdir -p "$PREFIX"
export WINEPREFIX="$PREFIX"
export PROTONPATH=GE-Proton
export GAMEID=umu-battlenet
export PROTON_VERB=run
if [[ -f $LAUNCHER ]]; then
echo "Battle.net is already installed at $PREFIX."
launched_installer=0
else
cache_dir="$HOME/.cache/blob"
mkdir -p "$cache_dir"
installer="$cache_dir/Battle.net-Setup.exe"
echo
echo "Downloading Battle.net installer..."
curl --fail --location --retry 3 "$INSTALLER_URL" --output "$installer"
cat <<'EOF'
Launching the Battle.net setup wizard. Click through it normally — the
default install path is fine. When it finishes, Battle.net will be in your
app launcher.
EOF
log="/tmp/blob-battlenet-installer.log"
setsid -f sh -c "umu-run '$installer' >'$log' 2>&1" </dev/null >/dev/null 2>&1
echo "Installer log: $log"
launched_installer=1
fi
mkdir -p "$HOME/.local/share/applications"
install -m 644 "$BLOB_PATH/default/applications/battlenet.desktop" \
"$HOME/.local/share/applications/battlenet.desktop"
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
if (( launched_installer )); then
cat <<EOF
The Battle.net installer is running in the background. After it finishes,
find Battle.net in your app launcher, or run:
blob-launch-battlenet
EOF
else
cat <<EOF
Battle.net is installed. Find it in your app launcher, or run:
blob-launch-battlenet
EOF
fi
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# blob:summary=Install and launch Geforce Now.
# blob:group=install
# blob:name=gaming geforce-now
set -e
echo "Installing GeForce NOW..."
blob-pkg-add flatpak
cd /tmp
# Download and run GeForce NOW
curl -LO https://international.download.nvidia.com/GFNLinux/GeForceNOWSetup.bin
chmod +x GeForceNOWSetup.bin
./GeForceNOWSetup.bin
# Ensure a separate browser process not started by GFN is available.
# If not, it seems like GFN has a tendency to hang on login.
setsid blob-launch-browser
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# blob:summary=Install lib32 graphics drivers (Vulkan + NVIDIA) for any detected GPUs.
# blob:group=install
# blob:name=gaming gpu-lib32
# blob:requires-sudo=true
set -e
echo "Installing lib32 graphics drivers..."
PACKAGES=()
declare -A VULKAN_DRIVERS=(
[Intel]=lib32-vulkan-intel
[AMD]=lib32-vulkan-radeon
)
for vendor in "${!VULKAN_DRIVERS[@]}"; do
if lspci | grep -iE "(VGA|Display).*$vendor" >/dev/null; then
PACKAGES+=("${VULKAN_DRIVERS[$vendor]}")
fi
done
if blob-hw-nvidia-gsp; then
PACKAGES+=(lib32-nvidia-utils)
elif blob-hw-nvidia-without-gsp; then
PACKAGES+=(lib32-nvidia-580xx-utils)
fi
(( ${#PACKAGES[@]} > 0 )) && blob-pkg-add "${PACKAGES[@]}"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# blob:summary=Install Heroic Games Launcher (Epic, GOG, Amazon Prime Gaming) with graphics drivers.
# blob:requires-sudo=true
set -e
echo "Installing Heroic Games Launcher..."
blob-pkg-add heroic-games-launcher-bin
blob-install-gaming-gpu-lib32
setsid uwsm-app -- gtk-launch heroic >/dev/null 2>&1 &
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# blob:summary=Install Lutris with Wine + DXVK for running Windows games (Battle.net, EA, Ubisoft Connect, etc.)
# blob:requires-sudo=true
set -e
echo "Installing Lutris..."
blob-pkg-add lutris umu-launcher wine-staging wine-mono wine-gecko winetricks python-protobuf
blob-install-gaming-gpu-lib32
# Lutris ships with `#!/usr/bin/env python3`, which resolves to mise's Python and
# fails to import the lutris module. Pin the shebang to the system Python.
sudo sed -i '/env python3/ c\#!/bin/python3' /usr/bin/lutris
cat <<'EOF'
Lutris will open and auto-fetch its DXVK and VKD3D runtimes in the background
(watch the bottom status bar). Once that finishes, click the + to add or install games.
EOF
setsid lutris >/dev/null 2>&1 &
+85
View File
@@ -0,0 +1,85 @@
#!/bin/bash
# blob:summary=Install RetroArch with the full libretro core set plus FBNeo and a ~/Games ROM directory.
set -e
echo "Installing RetroArch..."
blob-pkg-add \
retroarch \
retroarch-assets-glui retroarch-assets-ozone retroarch-assets-xmb \
libretro-beetle-pce libretro-beetle-pce-fast libretro-beetle-psx libretro-beetle-psx-hw libretro-beetle-supergrafx \
libretro-blastem \
libretro-bsnes libretro-bsnes-hd \
libretro-core-info \
libretro-desmume libretro-dolphin libretro-flycast \
libretro-gambatte libretro-genesis-plus-gx \
libretro-kronos \
libretro-mame libretro-melonds libretro-mesen libretro-mesen-s libretro-mgba libretro-mupen64plus-next \
libretro-nestopia \
libretro-overlays \
libretro-parallel-n64 libretro-picodrive libretro-play libretro-ppsspp \
libretro-sameboy libretro-scummvm libretro-shaders-slang libretro-snes9x \
libretro-yabause \
libretro-cap32-git libretro-fbneo-git libretro-uae-git \
libretro-vice-x128-git libretro-vice-x64-git libretro-vice-x64dtv-git libretro-vice-x64sc-git \
libretro-vice-xcbm2-git libretro-vice-xcbm5x0-git libretro-vice-xpet-git \
libretro-vice-xplus4-git libretro-vice-xscpu64-git libretro-vice-xvic-git \
libretro-database-git \
retroarch-joypad-autoconfig-git
# Set up ~/Games for BIOS files and ROMs
mkdir -p "$HOME/Games/bios" "$HOME/Games/roms"
CFG="$HOME/.config/retroarch/retroarch.cfg"
mkdir -p "$(dirname "$CFG")"
touch "$CFG"
set_cfg() {
local key=$1 value=$2
if grep -q "^$key = " "$CFG"; then
sed -i "s|^$key = .*|$key = \"$value\"|" "$CFG"
else
echo "$key = \"$value\"" >>"$CFG"
fi
}
set_cfg rgui_browser_directory "$HOME/Games/roms"
set_cfg system_directory "$HOME/Games/bios"
# Point at the cores and assets installed by pacman
set_cfg libretro_directory "/usr/lib/libretro"
set_cfg libretro_info_path "/usr/share/libretro/info"
set_cfg overlay_directory "/usr/share/libretro/overlays"
set_cfg osk_overlay_directory "/usr/share/libretro/overlays/keyboards"
set_cfg video_shader_dir "/usr/share/libretro/shaders/shaders_slang"
set_cfg joypad_autoconfig_dir "/usr/share/libretro/autoconfig"
# Point at the database, cheats, and cursors from libretro-database-git
set_cfg content_database_path "/usr/share/libretro/database/rdb"
set_cfg cheat_database_path "/usr/share/libretro/database/cht"
set_cfg cursor_directory "/usr/share/libretro/database/cursors"
# Vulkan is required for slang shaders and unlocks hardware renderers in beetle-psx-hw, parallel-n64, dolphin
set_cfg video_driver "vulkan"
# XMB is the classic PS3-style menu (vs. ozone/rgui/glui)
set_cfg menu_driver "xmb"
# Default to crt-royale shader for that classic CRT look. The global preset is
# auto-loaded by RetroArch when auto_shaders_enable is true and no per-core/per-game
# preset takes precedence — setting video_shader alone in retroarch.cfg is not enough.
set_cfg video_shader_enable "true"
set_cfg auto_shaders_enable "true"
mkdir -p ~/.config/retroarch/config
echo '#reference "/usr/share/libretro/shaders/shaders_slang/crt/crt-royale.slangp"' \
> ~/.config/retroarch/config/global.slangp
# Hide Images and Video tabs in the main menu sidebar
set_cfg content_show_images "false"
set_cfg content_show_video "false"
echo ""
echo "Put your roms and bios files in ~/Games. Then start RetroArch from the app launcher (Super + Space)."
setsid nautilus "$HOME/Games" >/dev/null 2>&1 &
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# blob:summary=Install Steam and graphics drivers selected for this system
# blob:requires-sudo=true
set -e
echo "Installing Steam..."
blob-pkg-add steam
blob-install-gaming-gpu-lib32
echo ""
echo "Steam will start automatically now. This might take a while..."
setsid uwsm-app -- gtk-launch steam >/dev/null 2>&1 &
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# blob:summary=Install Xbox Cloud Gaming as a web app and launch it.
# blob:group=install
# blob:name=gaming xbox-cloud
set -e
echo "Installing Xbox Cloud Gaming..."
blob-webapp-install "Xbox Cloud Gaming" "https://www.xbox.com/en-US/play" "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/xbox.png"
setsid blob-launch-webapp "https://www.xbox.com/en-US/play" >/dev/null 2>&1 &
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# blob:summary=Install support for using Xbox controllers with Steam/RetroArch/etc.
# blob:group=install
# blob:name=gaming xbox-controllers
# blob:requires-sudo=true
set -e
echo "Installing Xbox controller Bluetooth support..."
# Install xpadneo to ensure controllers work out of the box
blob-pkg-add xpadneo-dkms
# Prevent xpad/xpadneo driver conflict
echo blacklist xpad | sudo tee /etc/modprobe.d/blacklist-xpad.conf >/dev/null
echo hid_xpadneo | sudo tee /etc/modules-load.d/xpadneo.conf >/dev/null
# Ensure user is in the input group (controllers need it)
needs_reboot=false
if ! id -nG "$USER" | grep -qw input; then
sudo usermod -aG input "$USER"
needs_reboot=true
fi
# Swap drivers in the running kernel so a reboot isn't needed otherwise
if lsmod | grep -q '^xpad '; then
sudo modprobe -r xpad 2>/dev/null || needs_reboot=true
fi
if $needs_reboot; then
gum confirm "Reboot needed to finish setup. Reboot now?" && sudo reboot now
exit 0
fi
sudo modprobe hid_xpadneo
echo ""
echo "Now you can pair your Xbox controller with Bluetooth using Super + Ctrl + B."
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# blob:summary=Launch the installed Battle.net client via umu-launcher + GE-Proton.
# blob:args=[--with-mangohud]
# blob:examples=blob launch battlenet | blob launch battlenet --with-mangohud
set -e
PREFIX="$HOME/Games/battlenet"
LAUNCHER="$PREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe"
with_mangohud=0
for arg in "$@"; do
case "$arg" in
--with-mangohud) with_mangohud=1 ;;
-h|--help)
cat <<'EOF'
Usage: blob-launch-battlenet [--with-mangohud]
Options:
--with-mangohud Enable the MangoHud FPS overlay for games launched from
Battle.net. Toggle perf logging in-game with Shift_L+F2;
CSV logs land in ~/mangohud/.
EOF
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
echo "Try: blob-launch-battlenet --help" >&2
exit 1
;;
esac
done
if [[ ! -f $LAUNCHER ]]; then
echo "Battle.net is not installed. Run blob-install-gaming-battlenet first." >&2
exit 1
fi
env_args=(
WINEPREFIX="$PREFIX"
PROTONPATH=GE-Proton
GAMEID=umu-battlenet
PROTON_VERB=run
)
(( with_mangohud )) && env_args+=(MANGOHUD=1)
env "${env_args[@]}" umu-run "$LAUNCHER"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# blob:summary=Launch or focus on a given web app identified by the window-pattern.
# blob:args=<window-pattern> <url-and-flags...>
if (($# == 0)); then
echo "Usage: blob-launch-or-focus-webapp [window-pattern] [url-and-flags...]"
exit 1
fi
WINDOW_PATTERN="$1"
shift
LAUNCH_COMMAND="blob-launch-webapp $@"
exec blob-launch-or-focus "$WINDOW_PATTERN" "$LAUNCH_COMMAND"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# blob:summary=Launch a URL as a web app in the default supported browser
# blob:args=<url>
browser=$(xdg-settings get default-web-browser)
case $browser in
google-chrome* | brave* | microsoft-edge* | opera* | vivaldi* | helium*) ;;
*) browser="chromium.desktop" ;;
esac
exec setsid uwsm-app -- $(sed -n 's/^Exec=\([^ ]*\).*/\1/p' {~/.local,~/.nix-profile,/usr}/share/applications/$browser 2>/dev/null | head -1) --app="$1" "${@:2}"
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# blob:summary=Add the named packages to the system from the AUR if they're missing. Returns false if it couldn't be done.
# blob:args=<packages...>
if blob-pkg-missing "$@"; then
yay -S --noconfirm --needed "$@" || exit 1
fi
for pkg in "$@"; do
# Secondary check to handle states where pacman doesn't actually register an error
if ! pacman -Q "$pkg" &>/dev/null; then
echo -e "\033[31mError: Package '$pkg' did not install\033[0m" >&2
exit 1
fi
done
exit 0
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# blob:summary=Remove a supported browser and clean up Blob browser defaults
# blob:args=<chrome|brave|brave-origin|edge|firefox|zen>
# blob:examples=blob remove browser firefox | blob remove browser brave
# blob:requires-sudo=true
set_fallback_default_browser() {
local current_browser
current_browser=$(env -u BROWSER xdg-settings get default-web-browser)
if [[ $current_browser != $1 ]]; then
return
fi
if blob-cmd-present chromium; then
env -u BROWSER xdg-settings set default-web-browser chromium.desktop || true
fi
}
case $1 in
chrome)
echo "Removing Chrome..."
set_fallback_default_browser google-chrome.desktop
blob-pkg-drop google-chrome
rm -f ~/.config/chrome-flags.conf
sudo rm -f /etc/opt/chrome/policies/managed/color.json
;;
edge)
echo "Removing Edge..."
set_fallback_default_browser microsoft-edge.desktop
blob-pkg-drop microsoft-edge-stable-bin
rm -f ~/.config/microsoft-edge-stable-flags.conf
sudo rm -f /etc/opt/edge/policies/managed/color.json
;;
brave)
echo "Removing Brave..."
set_fallback_default_browser brave-browser.desktop
blob-pkg-drop brave-bin
rm -f ~/.config/brave-flags.conf
if blob-pkg-missing brave-origin-bin; then
sudo rm -rf /etc/brave
fi
;;
brave-origin)
echo "Removing Brave Origin..."
set_fallback_default_browser brave-origin.desktop
blob-pkg-drop brave-origin-bin
rm -f ~/.config/brave-origin-flags.conf
if blob-pkg-missing brave-bin; then
sudo rm -rf /etc/brave
fi
;;
firefox)
echo "Removing Firefox..."
set_fallback_default_browser firefox.desktop
blob-pkg-drop firefox
;;
zen)
echo "Removing Zen..."
set_fallback_default_browser zen.desktop
blob-pkg-drop zen-browser-bin
;;
*)
echo "Usage: blob-remove-browser <chrome|brave|brave-origin|edge|firefox|zen>"
exit 1
;;
esac
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
# blob:summary=Remove a development environment that was previously installed via blob-install-dev-env.
# blob:args=<ruby|node|bun|deno|go|php|laravel|symfony|python|elixir|phoenix|zig|rust|java|dotnet|ocaml|clojure|scala>
# blob:requires-sudo=true
if [[ -z $1 ]]; then
echo "Usage: blob-remove-dev-env <ruby|node|bun|deno|go|php|laravel|symfony|python|elixir|phoenix|zig|rust|java|dotnet|ocaml|clojure|scala>" >&2
exit 1
fi
remove_php() {
sudo pacman -Rns --noconfirm php composer php-sqlite xdebug 2>/dev/null || true
}
case "$1" in
ruby)
echo -e "Removing Ruby...\n"
mise uninstall ruby --all
mise rm -g ruby
rm -f ~/.gemrc
;;
node)
echo -e "Removing Node.js...\n"
mise uninstall node --all
mise rm -g node
;;
bun)
echo -e "Removing Bun...\n"
mise uninstall bun --all
mise rm -g bun
;;
deno)
echo -e "Removing Deno...\n"
mise uninstall deno --all
mise rm -g deno
;;
go)
echo -e "Removing Go...\n"
mise uninstall go --all
mise rm -g go
;;
php)
echo -e "Removing PHP...\n"
remove_php
;;
laravel)
echo -e "Removing Laravel...\n"
composer global remove laravel/installer 2>/dev/null || true
;;
symfony)
echo -e "Removing Symfony CLI...\n"
sudo pacman -Rns --noconfirm symfony-cli 2>/dev/null || true
;;
python)
echo -e "Removing Python...\n"
mise uninstall python --all
mise rm -g python
rm -rf ~/.local/bin/uv ~/.local/bin/uvx ~/.cargo/bin/uv 2>/dev/null || true
;;
elixir|phoenix)
echo -e "Removing Elixir/Erlang...\n"
mise uninstall elixir --all
mise uninstall erlang --all
mise rm -g elixir
mise rm -g erlang
;;
zig)
echo -e "Removing Zig...\n"
mise uninstall zig --all
mise uninstall zls --all
mise rm -g zig
mise rm -g zls
;;
rust)
echo -e "Removing Rust...\n"
rustup self uninstall -y 2>/dev/null || true
;;
java)
echo -e "Removing Java...\n"
mise uninstall java --all
mise rm -g java
;;
dotnet)
echo -e "Removing .NET...\n"
mise uninstall dotnet --all
mise rm -g dotnet
;;
ocaml)
echo -e "Removing OCaml...\n"
opam switch remove default -y 2>/dev/null || true
rm -rf ~/.opam 2>/dev/null || true
sudo rm -f /usr/local/bin/opam 2>/dev/null || true
;;
clojure)
echo -e "Removing Clojure...\n"
mise uninstall clojure --all
mise rm -g clojure
;;
scala)
echo -e "Removing Scala...\n"
mise uninstall scala --all
mise uninstall scala-cli --all
mise rm -g scala
mise rm -g scala-cli
;;
*)
echo "Unknown environment: $1"
exit 1
;;
esac
echo -e "\nDone!"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# blob:summary=Remove Battle.net, its Proton prefix, installed games, and desktop entry.
set -e
PREFIX="$HOME/Games/battlenet"
# Stop any running Battle.net / wine processes tied to this prefix.
pkill -f "$PREFIX" 2>/dev/null || true
sleep 1
rm -rf "$PREFIX"
rm -f "$HOME/.local/share/applications/battlenet.desktop"
rm -f "$HOME/.cache/blob/Battle.net-Setup.exe"
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
echo
echo "Battle.net and its Proton prefix at $PREFIX have been removed."
if blob-pkg-present umu-launcher; then
echo
if gum confirm "Also remove umu-launcher? It's only used by this command."; then
blob-pkg-drop umu-launcher
fi
fi
PROTON_DIR="$HOME/.local/share/Steam/compatibilitytools.d"
if compgen -G "$PROTON_DIR/GE-Proton*" >/dev/null; then
echo
if gum confirm "Also remove GE-Proton runtimes downloaded by umu?"; then
rm -rf "$PROTON_DIR"/GE-Proton*
rm -rf "$HOME/.local/share/umu"
fi
fi
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# blob:summary=Remove the GeForce NOW Flatpak app and its data.
# blob:group=remove
# blob:name=gaming geforce-now
set -e
if blob-cmd-present flatpak && flatpak info com.nvidia.geforcenow &>/dev/null; then
flatpak uninstall -y --delete-data com.nvidia.geforcenow
fi
echo ""
echo "GeForce NOW removed."
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# blob:summary=Remove Heroic Games Launcher and its game libraries, configs, and caches.
# blob:requires-sudo=true
set -e
blob-pkg-drop heroic-games-launcher-bin
rm -rf \
"$HOME/.config/heroic" \
"$HOME/.local/share/heroic" \
"$HOME/.cache/heroic" \
"$HOME/Games/Heroic"
echo ""
echo "Heroic and its data have been removed."
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# blob:summary=Remove Lutris, Wine, umu-launcher, and all their configs and caches.
# blob:requires-sudo=true
set -e
blob-pkg-drop lutris wine-staging wine-mono wine-gecko winetricks python-protobuf umu-launcher
rm -rf \
"$HOME/.config/lutris" \
"$HOME/.local/share/lutris" \
"$HOME/.cache/lutris" \
"$HOME/.local/share/umu" \
"$HOME/.cache/umu" \
"$HOME/.wine" \
"$HOME/.cache/wine" \
"$HOME/.cache/winetricks"
echo ""
echo "Lutris, Wine, umu-launcher, and their configs have been removed."
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# blob:summary=Remove the Minecraft launcher along with its worlds, mods, and caches.
# blob:requires-sudo=true
set -e
blob-pkg-drop minecraft-launcher
rm -rf \
"$HOME/.minecraft" \
"$HOME/.config/Minecraft Launcher" \
"$HOME/.local/share/minecraft-launcher" \
"$HOME/.cache/minecraft"
echo ""
echo "Minecraft and its data have been removed."
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# blob:summary=Remove RetroArch, all libretro cores, and its config/saves. Leaves ~/Games/roms and ~/Games/bios alone.
# blob:requires-sudo=true
set -e
blob-pkg-drop \
retroarch \
retroarch-assets-glui retroarch-assets-ozone retroarch-assets-xmb \
libretro-beetle-pce libretro-beetle-pce-fast libretro-beetle-psx libretro-beetle-psx-hw libretro-beetle-supergrafx \
libretro-blastem \
libretro-bsnes libretro-bsnes-hd \
libretro-core-info \
libretro-desmume libretro-dolphin libretro-flycast \
libretro-gambatte libretro-genesis-plus-gx \
libretro-kronos \
libretro-mame libretro-melonds libretro-mesen libretro-mesen-s libretro-mgba libretro-mupen64plus-next \
libretro-nestopia \
libretro-overlays \
libretro-parallel-n64 libretro-picodrive libretro-play libretro-ppsspp \
libretro-sameboy libretro-scummvm libretro-shaders-slang libretro-snes9x \
libretro-yabause \
libretro-cap32-git libretro-fbneo-git libretro-uae-git \
libretro-vice-x128-git libretro-vice-x64-git libretro-vice-x64dtv-git libretro-vice-x64sc-git \
libretro-vice-xcbm2-git libretro-vice-xcbm5x0-git libretro-vice-xpet-git \
libretro-vice-xplus4-git libretro-vice-xscpu64-git libretro-vice-xvic-git \
retroarch-joypad-autoconfig-git
rm -rf \
"$HOME/.config/retroarch" \
"$HOME/.local/share/retroarch" \
"$HOME/.cache/retroarch"
echo ""
echo "RetroArch and its cores have been removed."
echo "ROMs and BIOS files at ~/Games/roms and ~/Games/bios were left in place."
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
# blob:summary=Remove Steam and all of its game libraries, configs, and caches.
# blob:requires-sudo=true
set -e
blob-pkg-drop steam
rm -rf \
"$HOME/.steam" \
"$HOME/.local/share/Steam" \
"$HOME/.config/steam" \
"$HOME/.cache/steam"
echo ""
echo "Steam and its data have been removed."
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# blob:summary=Remove the Xbox Cloud Gaming web app.
set -e
blob-webapp-remove "Xbox Cloud Gaming"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# blob:summary=Remove the xpadneo Xbox controller driver and undo its module/blacklist config.
# blob:group=remove
# blob:name=gaming xbox-controllers
# blob:requires-sudo=true
set -e
blob-pkg-drop xpadneo-dkms
sudo rm -f /etc/modprobe.d/blacklist-xpad.conf /etc/modules-load.d/xpadneo.conf
echo ""
echo "Xbox controller support removed. Reboot to fully unload xpadneo and restore xpad."
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# blob:summary=Open HEY webmail and translate mailto links
# blob:args=[url]
url="$1"
web_url="https://app.hey.com"
# Handle mailto: URLs
if [[ $url =~ ^mailto: ]]; then
email=$(echo "$url" | sed 's/mailto://')
web_url="https://app.hey.com/messages/new?to=$email"
fi
exec blob-launch-webapp "$web_url"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# blob:summary=Open Zoom web meetings from browser protocol links
# blob:args=[url]
url="$1"
web_url="https://app.zoom.us/wc/home"
if [[ $url =~ ^zoom(mtg|us):// ]]; then
confno=$(echo "$url" | sed -n 's/.*[?&]confno=\([^&]*\).*/\1/p')
if [[ -n $confno ]]; then
pwd=$(echo "$url" | sed -n 's/.*[?&]pwd=\([^&]*\).*/\1/p')
if [[ -n $pwd ]]; then
web_url="https://app.zoom.us/wc/join/$confno?pwd=$pwd"
else
web_url="https://app.zoom.us/wc/join/$confno"
fi
fi
fi
exec blob-launch-webapp "$web_url"
+241
View File
@@ -0,0 +1,241 @@
#!/bin/bash
# blob:summary=Create a desktop launcher for a web app
# blob:args=[name url icon-url-or-name [custom-exec] [mime-types]]
set -e
ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
safe_icon_name() {
printf '%s\n' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//'
}
require_plain_name() {
# The name becomes a filename. A slash would turn it into directory levels, so
# the launcher lands somewhere blob-webapp-remove cannot address and the app
# is stuck in the launcher; a leading ../ leaves the applications directory
# altogether. Refuse rather than silently renaming what the user typed -- most
# often it is a URL entered in the name field.
if [[ $1 == */* ]]; then
echo "App name cannot contain '/': $1"
exit 1
fi
}
icon_name_from_ref() {
local ref="$1"
local name
name=$(basename "$ref")
if [[ $name == *.* ]]; then
safe_icon_name "${name%.*}"
else
printf '%s\n' "$name"
fi
}
install_user_icon() {
local source="$1"
local name="$2"
local ext="${source##*.}"
[[ $ext == "$source" ]] && ext="png"
mkdir -p "$ICON_DIR"
cp "$source" "$ICON_DIR/$name.$ext"
gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
printf '%s\n' "$name"
}
download_icon() {
curl -fsSL --max-time 10 -o "$2" "$1" 2>/dev/null &&
[[ -s $2 && $(file -b --mime-type "$2") == image/* ]]
}
# Chromium --app= treats javascript:, file:, and data: as a document to
# run. Prefix schemeless input with https as before, then refuse anything
# that is not http(s).
normalize_webapp_url() {
local url=$1
if [[ ! $url =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
url="https://$url"
fi
printf '%s' "$url"
}
# Raw whitespace must be percent-encoded in a URL. Refuse it before serializing
# the desktop entry; before Exec argument quoting, it also split browser flags
# and additional URLs into separate arguments. Schemes are case-insensitive.
require_http_url() {
local url=$1
if [[ $url =~ [[:space:]] ]]; then
echo "Error: web app URL must not contain whitespace." >&2
exit 1
fi
if [[ ! ${url,,} =~ ^https?:// ]]; then
echo "Error: web app URL must be http or https." >&2
exit 1
fi
}
fetch_site_icon() {
local site_url="$1" dest="$2"
local origin page icon_url
origin=$(sed -E 's|^(https?://[^/]+).*|\1|' <<<"$site_url")
# Prefer the site's own high-res icon (apple-touch-icon is typically 180px+),
# then the well-known path, then Google's favicon service as a last resort.
page=$(curl -fsSL --max-time 5 "$site_url" 2>/dev/null | head -c 100000 | tr '\n' ' ')
icon_url=$(grep -oiE "<link[^>]*rel=[\"'][^\"']*apple-touch-icon[^\"']*[\"'][^>]*>" <<<"$page" |
grep -oiE "href=[\"'][^\"']+" | head -1 | sed -E "s/^href=[\"']//")
case $icon_url in
http://* | https://*) ;;
//*) icon_url="https:$icon_url" ;;
/*) icon_url="$origin$icon_url" ;;
?*) icon_url="$origin/$icon_url" ;;
esac
{ [[ -n $icon_url ]] && download_icon "$icon_url" "$dest"; } ||
download_icon "$origin/apple-touch-icon.png" "$dest" ||
download_icon "https://www.google.com/s2/favicons?domain=${site_url}&sz=256" "$dest"
}
desktop_string_escape() {
# Desktop Entry "string" value (freedesktop Desktop Entry Spec, "Value types"):
# a raw newline would start a new key line and let a value inject a second
# Exec=. Escape backslash first, then tab/CR/LF and a leading space. Every value
# written into the .desktop file passes through here.
#
# Parameter expansion rather than sed: GNU sed's N auto-prints the pattern space
# and exits at end of input, so a `:a;N;$!ba` slurp skips every following s///
# for a value with no newline in it - which is every value except the injection
# attempt this exists to stop.
local value="$1"
value=${value//\\/\\\\}
value=${value//$'\t'/\\t}
value=${value//$'\r'/\\r}
value=${value//$'\n'/\\n}
[[ $value == " "* ]] && value="\\s${value# }"
printf '%s' "$value"
}
desktop_exec_arg() {
# One Exec argument, double-quoted per the freedesktop Exec spec: inside quotes
# " ` $ \ take a backslash and a literal % becomes %%. Only the default Exec's
# URL needs this; $CUSTOM_EXEC stays a whole command line (file-syntax only).
local escaped
escaped=$(printf '%s' "$1" \
| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/`/\\`/g' -e 's/\$/\\$/g' -e 's/%/%%/g')
printf '"%s"' "$escaped"
}
if (( $# < 3 )); then
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
require_plain_name "$APP_NAME"
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
APP_URL=$(normalize_webapp_url "$APP_URL")
require_http_url "$APP_URL"
# Try to fetch the site's icon automatically first.
mkdir -p "$ICON_DIR"
ICON_VALUE=$(safe_icon_name "$APP_NAME")
if fetch_site_icon "$APP_URL" "$ICON_DIR/$ICON_VALUE.png"; then
gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
ICON_REF="$ICON_VALUE"
else
ICON_REF=$(gum input --prompt "Icon URL/name> " --placeholder "Could not fetch favicon automatically. Enter PNG icon URL or icon name")
fi
CUSTOM_EXEC=""
MIME_TYPES=""
INTERACTIVE_MODE=true
else
APP_NAME="$1"
APP_URL=$(normalize_webapp_url "$2")
require_http_url "$APP_URL"
ICON_REF="$3"
CUSTOM_EXEC="$4" # Optional custom exec command
MIME_TYPES="$5" # Optional mime types
INTERACTIVE_MODE=false
fi
# Ensure valid execution
if [[ -z $APP_NAME || -z $APP_URL ]]; then
echo "You must set app name and app URL!"
exit 1
fi
require_plain_name "$APP_NAME"
if [[ -z $ICON_REF ]]; then
ICON_VALUE=$(safe_icon_name "$APP_NAME")
mkdir -p "$ICON_DIR"
if ! fetch_site_icon "$APP_URL" "$ICON_DIR/$ICON_VALUE.png"; then
echo "Error: Failed to download icon."
exit 1
fi
gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
elif [[ $ICON_REF =~ ^https?:// ]]; then
ICON_VALUE=$(safe_icon_name "$APP_NAME")
mkdir -p "$ICON_DIR"
if ! download_icon "$ICON_REF" "$ICON_DIR/$ICON_VALUE.png"; then
echo "Error: Failed to download icon."
exit 1
fi
gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
elif [[ -f $ICON_REF ]]; then
ICON_VALUE=$(install_user_icon "$ICON_REF" "$(safe_icon_name "$APP_NAME")")
else
# Bundled Blob icons are package-owned under /usr/share/icons/hicolor.
# Accept either "HEY" or the historical "HEY.png" argument form.
ICON_VALUE=$(icon_name_from_ref "$ICON_REF")
fi
# Default Exec quotes the URL as one Exec-spec argument; the whole line then gets
# the file-syntax escaping below (unescaped first at read time per spec, so the
# layers compose). $CUSTOM_EXEC is a full command line, so it gets file-syntax only.
if [[ -n $CUSTOM_EXEC ]]; then
EXEC_COMMAND=$CUSTOM_EXEC
else
EXEC_COMMAND="blob-launch-webapp $(desktop_exec_arg "$APP_URL")"
fi
# Create application .desktop file
DESKTOP_DIR="$HOME/.local/share/applications"
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
mkdir -p "$DESKTOP_DIR"
name_field=$(desktop_string_escape "$APP_NAME")
exec_field=$(desktop_string_escape "$EXEC_COMMAND")
icon_field=$(desktop_string_escape "$ICON_VALUE")
cat >"$DESKTOP_FILE" <<EOF
[Desktop Entry]
Version=1.0
Name=$name_field
Comment=$name_field
Exec=$exec_field
Terminal=false
Type=Application
Icon=$icon_field
StartupNotify=true
EOF
# Add mime types if provided
if [[ -n $MIME_TYPES ]]; then
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
fi
chmod +x "$DESKTOP_FILE"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo -e "You can now find $APP_NAME using the app launcher (SUPER + SPACE)\n"
fi
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# blob:summary=Remove a web app desktop launcher
# blob:args=[name]
set -e
ICON_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
OLD_ICON_DIR="$HOME/.local/share/applications/icons"
DESKTOP_DIR="$HOME/.local/share/applications/"
# Always index the launchers, so removal deletes the file that was found rather
# than a path rebuilt from the displayed name. Installs predating the name
# validation could nest the launcher inside directories, and those are exactly
# the ones a reconstructed path cannot reach.
WEB_APP_PATHS=()
while IFS= read -r -d '' file; do
if grep -q '^Exec=.*\(blob-launch-webapp\|blob-webapp-handler\).*' "$file"; then
WEB_APPS+=("$(basename "${file%.desktop}")")
WEB_APP_PATHS+=("$file")
fi
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0 2>/dev/null)
# The launcher matching a chosen name, or empty when nothing was indexed under
# it (an app removed between the scan and the pick, say).
path_for_web_app() {
local wanted="$1" i
for i in "${!WEB_APPS[@]}"; do
if [[ ${WEB_APPS[$i]} == "$wanted" ]]; then
printf '%s\n' "${WEB_APP_PATHS[$i]}"
return 0
fi
done
}
if (( $# == 0 )); then
if ((${#WEB_APPS[@]})); then
mapfile -t SORTED_WEB_APPS < <(printf '%s\n' "${WEB_APPS[@]}" | sort)
APP_NAME=$(blob-menu-select "Select web app to remove" "${SORTED_WEB_APPS[@]}" -- --width 520 --maxheight 520)
else
echo "No web apps to remove."
exit 1
fi
else
APP_NAME="$*"
fi
if [[ -z $APP_NAME ]]; then
echo "You must select a web app to remove."
exit 1
fi
icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:alnum:]]\+/-/g; s/^-//; s/-$//')
desktop_file=$(path_for_web_app "$APP_NAME")
rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
if [[ ${BLOB_REMOVE_NOTIFY:-true} != "false" ]]; then
blob-notify-send -g  "Web app removed" "$APP_NAME"
fi
update-desktop-database "$DESKTOP_DIR" &>/dev/null
+1591
View File
File diff suppressed because it is too large Load Diff
+82
View File
@@ -24,6 +24,88 @@
"setup": {"icon":"","label":"Setup","aliases":["settings"]},
"install": {"icon":"󰉉","label":"Install"},
"remove": {"icon":"󰭌","label":"Remove","aliases":["uninstall"]},
"install.development": {"icon":"󰵮","label":"Development"},
"install.browser": {"icon":"","label":"Browser"},
"install.gaming": {"icon":"","label":"Gaming"},
"install.windows": {"icon":"󰍲","label":"Windows","when":"[[ ! -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"blob-launch-floating 'blob-windows-vm install'"},
"install.browser.chrome": {"icon":"","label":"Chrome","when":"! blob-pkg-present google-chrome","action":"blob-launch-floating 'blob-install-browser chrome'"},
"install.browser.edge": {"icon":"󰇩","label":"Edge","when":"! blob-pkg-present microsoft-edge-stable-bin","action":"blob-launch-floating 'blob-install-browser edge'"},
"install.browser.brave": {"icon":"","label":"Brave","when":"! blob-pkg-present brave-bin","action":"blob-launch-floating 'blob-install-browser brave'"},
"install.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"! blob-pkg-present brave-origin-bin","action":"blob-launch-floating 'blob-install-browser brave-origin'"},
"install.browser.firefox": {"icon":"","label":"Firefox","when":"! blob-pkg-present firefox","action":"blob-launch-floating 'blob-install-browser firefox'"},
"install.browser.zen": {"icon":"󰖟","label":"Zen","when":"! blob-pkg-present zen-browser-bin","action":"blob-launch-floating 'blob-install-browser zen'"},
"install.gaming.steam": {"icon":"","label":"Steam","when":"! blob-pkg-present steam","action":"blob-launch-floating blob-install-gaming-steam"},
"install.gaming.retroarch": {"icon":"󰯉","label":"RetroArch","when":"! blob-pkg-present retroarch","action":"blob-launch-floating blob-install-gaming-retroarch"},
"install.gaming.minecraft": {"icon":"󰍳","label":"Minecraft","when":"! blob-pkg-present minecraft-launcher","action":"blob-install-launch Minecraft minecraft-launcher minecraft-launcher"},
"install.gaming.geforce-now": {"icon":"󰢹","label":"NVIDIA GeForce NOW","when":"! flatpak info com.nvidia.geforcenow","action":"blob-launch-floating blob-install-gaming-geforce-now"},
"install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","when":"[[ ! -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"blob-launch-floating blob-install-gaming-xbox-cloud"},
"install.gaming.xbox-controllers": {"icon":"󰂯","label":"Xbox Controllers","when":"! blob-pkg-present xpadneo-dkms","action":"blob-launch-floating blob-install-gaming-xbox-controllers"},
"install.gaming.battlenet": {"icon":"","label":"Battle.net","when":"[[ ! -d $HOME/Games/battlenet ]]","action":"blob-launch-floating blob-install-gaming-battlenet"},
"install.gaming.lutris": {"icon":"","label":"Lutris","when":"! blob-pkg-present lutris","action":"blob-launch-floating blob-install-gaming-lutris"},
"install.gaming.heroic": {"icon":"󱓟","label":"Heroic (Epic Games)","when":"! blob-pkg-present heroic-games-launcher-bin","action":"blob-launch-floating blob-install-gaming-heroic"},
"install.gaming.retro-launcher": {"icon":"󰯉","label":"RetroArch Game Launcher","action":"blob-games-retro-install"},
"install.development.rails": {"icon":"󰫏","label":"Ruby on Rails","when":"[[ ! -d $HOME/.local/share/mise/installs/ruby ]]","action":"blob-launch-floating 'blob-install-dev-env ruby'"},
"install.development.docker-dbs": {"icon":"","label":"Docker DB","action":"blob-launch-floating blob-install-docker-dbs"},
"install.development.javascript": {"icon":"","label":"JavaScript"},
"install.development.go": {"icon":"","label":"Go","when":"[[ ! -d $HOME/.local/share/mise/installs/go ]]","action":"blob-launch-floating 'blob-install-dev-env go'"},
"install.development.php": {"icon":"","label":"PHP"},
"install.development.python": {"icon":"","label":"Python","when":"[[ ! -d $HOME/.local/share/mise/installs/python ]]","action":"blob-launch-floating 'blob-install-dev-env python'"},
"install.development.elixir": {"icon":"","label":"Elixir"},
"install.development.zig": {"icon":"","label":"Zig","when":"[[ ! -d $HOME/.local/share/mise/installs/zig ]]","action":"blob-launch-floating 'blob-install-dev-env zig'"},
"install.development.rust": {"icon":"","label":"Rust","when":"[[ ! -d $HOME/.rustup ]]","action":"blob-launch-floating 'blob-install-dev-env rust'"},
"install.development.java": {"icon":"","label":"Java","when":"[[ ! -d $HOME/.local/share/mise/installs/java ]]","action":"blob-launch-floating 'blob-install-dev-env java'"},
"install.development.dotnet": {"icon":"","label":".NET","when":"[[ ! -d $HOME/.local/share/mise/installs/dotnet ]]","action":"blob-launch-floating 'blob-install-dev-env dotnet'"},
"install.development.ocaml": {"icon":"","label":"OCaml","when":"[[ ! -d $HOME/.opam ]]","action":"blob-launch-floating 'blob-install-dev-env ocaml'"},
"install.development.clojure": {"icon":"","label":"Clojure","when":"[[ ! -d $HOME/.local/share/mise/installs/clojure ]]","action":"blob-launch-floating 'blob-install-dev-env clojure'"},
"install.development.scala": {"icon":"","label":"Scala","when":"[[ ! -d $HOME/.local/share/mise/installs/scala ]]","action":"blob-launch-floating 'blob-install-dev-env scala'"},
"install.development.javascript.node": {"icon":"","label":"Node.js","when":"[[ ! -d $HOME/.local/share/mise/installs/node ]]","action":"blob-launch-floating 'blob-install-dev-env node'"},
"install.development.javascript.bun": {"icon":"","label":"Bun","when":"[[ ! -d $HOME/.local/share/mise/installs/bun ]]","action":"blob-launch-floating 'blob-install-dev-env bun'"},
"install.development.javascript.deno": {"icon":"","label":"Deno","when":"[[ ! -d $HOME/.local/share/mise/installs/deno ]]","action":"blob-launch-floating 'blob-install-dev-env deno'"},
"install.development.php.php": {"icon":"","label":"PHP","when":"! blob-pkg-present php","action":"blob-launch-floating 'blob-install-dev-env php'"},
"install.development.php.laravel": {"icon":"","label":"Laravel","when":"[[ ! -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"blob-launch-floating 'blob-install-dev-env laravel'"},
"install.development.php.symfony": {"icon":"","label":"Symfony","when":"! blob-pkg-present symfony-cli","action":"blob-launch-floating 'blob-install-dev-env symfony'"},
"install.development.elixir.elixir": {"icon":"","label":"Elixir","when":"[[ ! -d $HOME/.local/share/mise/installs/elixir ]]","action":"blob-launch-floating 'blob-install-dev-env elixir'"},
"install.development.elixir.phoenix": {"icon":"","label":"Phoenix","when":"! compgen -G \"$HOME/.mix/archives/phx_new*\"","action":"blob-launch-floating 'blob-install-dev-env phoenix'"},
"remove.development": {"icon":"󰵮","label":"Development","title":"Remove"},
"remove.browser": {"icon":"","label":"Browser","title":"Remove"},
"remove.gaming": {"icon":"","label":"Gaming","title":"Remove"},
"remove.windows": {"icon":"󰍲","label":"Windows","when":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"blob-launch-floating 'blob-windows-vm remove'"},
"remove.browser.chrome": {"icon":"","label":"Chrome","when":"blob-pkg-present google-chrome","action":"blob-launch-floating 'blob-remove-browser chrome'"},
"remove.browser.edge": {"icon":"󰇩","label":"Edge","when":"blob-pkg-present microsoft-edge-stable-bin","action":"blob-launch-floating 'blob-remove-browser edge'"},
"remove.browser.brave": {"icon":"","label":"Brave","when":"blob-pkg-present brave-bin","action":"blob-launch-floating 'blob-remove-browser brave'"},
"remove.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"blob-pkg-present brave-origin-bin","action":"blob-launch-floating 'blob-remove-browser brave-origin'"},
"remove.browser.firefox": {"icon":"","label":"Firefox","when":"blob-pkg-present firefox","action":"blob-launch-floating 'blob-remove-browser firefox'"},
"remove.browser.zen": {"icon":"","label":"Zen","when":"blob-pkg-present zen-browser-bin","action":"blob-launch-floating 'blob-remove-browser zen'"},
"remove.gaming.steam": {"icon":"","label":"Steam","when":"blob-pkg-present steam","action":"blob-launch-floating blob-remove-gaming-steam"},
"remove.gaming.retroarch": {"icon":"","label":"RetroArch","when":"blob-pkg-present retroarch","action":"blob-launch-floating blob-remove-gaming-retroarch"},
"remove.gaming.minecraft": {"icon":"󰍳","label":"Minecraft","when":"blob-pkg-present minecraft-launcher","action":"blob-launch-floating blob-remove-gaming-minecraft"},
"remove.gaming.geforce-now": {"icon":"󰢹","label":"NVIDIA GeForce NOW","when":"flatpak info com.nvidia.geforcenow","action":"blob-launch-floating blob-remove-gaming-geforce-now"},
"remove.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","when":"[[ -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"blob-launch-floating blob-remove-gaming-xbox-cloud"},
"remove.gaming.xbox-controllers": {"icon":"󰖺","label":"Xbox Controllers (󰂯)","when":"blob-pkg-present xpadneo-dkms","action":"blob-launch-floating blob-remove-gaming-xbox-controllers"},
"remove.gaming.battlenet": {"icon":"","label":"Battle.net","when":"[[ -d $HOME/Games/battlenet ]]","action":"blob-launch-floating blob-remove-gaming-battlenet"},
"remove.gaming.lutris": {"icon":"","label":"Lutris","when":"blob-pkg-present lutris","action":"blob-launch-floating blob-remove-gaming-lutris"},
"remove.gaming.heroic": {"icon":"󱓟","label":"Heroic (Epic Games)","when":"blob-pkg-present heroic-games-launcher-bin","action":"blob-launch-floating blob-remove-gaming-heroic"},
"remove.development.rails": {"icon":"󰫏","label":"Ruby on Rails","when":"[[ -d $HOME/.local/share/mise/installs/ruby ]]","action":"blob-launch-floating 'blob-remove-dev-env ruby'"},
"remove.development.javascript": {"icon":"","label":"JavaScript","title":"Remove"},
"remove.development.go": {"icon":"","label":"Go","when":"[[ -d $HOME/.local/share/mise/installs/go ]]","action":"blob-launch-floating 'blob-remove-dev-env go'"},
"remove.development.php": {"icon":"","label":"PHP","title":"Remove"},
"remove.development.python": {"icon":"","label":"Python","when":"[[ -d $HOME/.local/share/mise/installs/python ]]","action":"blob-launch-floating 'blob-remove-dev-env python'"},
"remove.development.elixir": {"icon":"","label":"Elixir","title":"Remove"},
"remove.development.zig": {"icon":"","label":"Zig","when":"[[ -d $HOME/.local/share/mise/installs/zig ]]","action":"blob-launch-floating 'blob-remove-dev-env zig'"},
"remove.development.rust": {"icon":"","label":"Rust","when":"[[ -d $HOME/.rustup ]]","action":"blob-launch-floating 'blob-remove-dev-env rust'"},
"remove.development.java": {"icon":"","label":"Java","when":"[[ -d $HOME/.local/share/mise/installs/java ]]","action":"blob-launch-floating 'blob-remove-dev-env java'"},
"remove.development.dotnet": {"icon":"","label":".NET","when":"[[ -d $HOME/.local/share/mise/installs/dotnet ]]","action":"blob-launch-floating 'blob-remove-dev-env dotnet'"},
"remove.development.ocaml": {"icon":"","label":"OCaml","when":"[[ -d $HOME/.opam ]]","action":"blob-launch-floating 'blob-remove-dev-env ocaml'"},
"remove.development.clojure": {"icon":"","label":"Clojure","when":"[[ -d $HOME/.local/share/mise/installs/clojure ]]","action":"blob-launch-floating 'blob-remove-dev-env clojure'"},
"remove.development.scala": {"icon":"","label":"Scala","when":"[[ -d $HOME/.local/share/mise/installs/scala ]]","action":"blob-launch-floating 'blob-remove-dev-env scala'"},
"remove.development.javascript.node": {"icon":"","label":"Node.js","when":"[[ -d $HOME/.local/share/mise/installs/node ]]","action":"blob-launch-floating 'blob-remove-dev-env node'"},
"remove.development.javascript.bun": {"icon":"","label":"Bun","when":"[[ -d $HOME/.local/share/mise/installs/bun ]]","action":"blob-launch-floating 'blob-remove-dev-env bun'"},
"remove.development.javascript.deno": {"icon":"","label":"Deno","when":"[[ -d $HOME/.local/share/mise/installs/deno ]]","action":"blob-launch-floating 'blob-remove-dev-env deno'"},
"remove.development.php.php": {"icon":"","label":"PHP","when":"blob-pkg-present php","action":"blob-launch-floating 'blob-remove-dev-env php'"},
"remove.development.php.laravel": {"icon":"","label":"Laravel","when":"[[ -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"blob-launch-floating 'blob-remove-dev-env laravel'"},
"remove.development.php.symfony": {"icon":"","label":"Symfony","when":"blob-pkg-present symfony-cli","action":"blob-launch-floating 'blob-remove-dev-env symfony'"},
"remove.development.elixir.elixir": {"icon":"","label":"Elixir","when":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"blob-launch-floating 'blob-remove-dev-env elixir'"},
"remove.development.elixir.phoenix": {"icon":"","label":"Phoenix","when":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"blob-launch-floating 'blob-remove-dev-env phoenix'"},
"update": {"icon":"","label":"Update","aliases":["restart","refresh"]},
"about": {"icon":"","label":"About","action":"blob-launch-about"},
"system": {"icon":"","label":"System","aliases":["power-menu"]},
+59 -1
View File
@@ -91,6 +91,13 @@ of the `blob` listing.
| `blob-capture-webcam-list` | List webcam devices that support video capture (hidden) | - |
| `blob-capture-webcam-resize` | Resize the active webcam recording overlay | <smaller\|larger\|reset\|small\|medium\|large> |
## chromium
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-chromium-copy-url-host` | Native messaging host: copy a Chromium tab URL to the clipboard (hidden) | - |
| `blob-chromium-ytdlp-host` | Native messaging host: download the URL sent by the yt-dlp Chromium extension (hidden) | - |
## clipboard
| Command | Does | Arguments |
@@ -163,6 +170,13 @@ of the `blob` listing.
| `blob-font-list` | List available monospace fonts | - |
| `blob-font-set` | Set the system monospace font | <font-name> |
## games
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-games-retro-cores` | List installed RetroArch core names | - |
| `blob-games-retro-install` | Create a desktop launcher for a RetroArch game | [core path-to-game] |
## git
| Command | Does | Arguments |
@@ -235,7 +249,21 @@ of the `blob` listing.
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-install-browser` | Install a supported browser | <chrome\|brave\|brave-origin\|edge\|firefox\|zen> |
| `blob-install-chromium-copy-url` | Install the native messaging host for the Copy URL Chromium extension | - |
| `blob-install-chromium-ytdlp` | Install the native messaging host for the yt-dlp Chromium extension | - |
| `blob-install-dev-env` | Install a supported development environment | <ruby\|node\|bun\|deno\|go\|laravel\|symfony\|php\|python\|elixir\|phoenix\|rust\|java\|zig\|ocaml\|dotnet\|clojure\|scala> |
| `blob-install-docker-dbs` | Install one of the supported databases in a Docker container with the suitable development options. | - |
| `blob-install-font` | Install a Nerd Font package and switch the system to it | <display-name> <package> <family> |
| `blob-install-gaming-battlenet` | Install Battle.net standalone via umu-launcher + GE-Proton (no Steam, no Lutris, no Heroic). | - |
| `blob-install-gaming-geforce-now` | Install and launch Geforce Now. | - |
| `blob-install-gaming-gpu-lib32` | Install lib32 graphics drivers (Vulkan + NVIDIA) for any detected GPUs. | - |
| `blob-install-gaming-heroic` | Install Heroic Games Launcher (Epic, GOG, Amazon Prime Gaming) with graphics drivers. | - |
| `blob-install-gaming-lutris` | Install Lutris with Wine + DXVK for running Windows games (Battle.net, EA, Ubisoft Connect, etc.) | - |
| `blob-install-gaming-retroarch` | Install RetroArch with the full libretro core set plus FBNeo and a ~/Games ROM directory. | - |
| `blob-install-gaming-steam` | Install Steam and graphics drivers selected for this system | - |
| `blob-install-gaming-xbox-cloud` | Install Xbox Cloud Gaming as a web app and launch it. | - |
| `blob-install-gaming-xbox-controllers` | Install support for using Xbox controllers with Steam/RetroArch/etc. | - |
| `blob-install-launch` | Install a packaged app and launch it once it finishes | <display-name> <packages> <desktop-id> |
## launch
@@ -243,6 +271,7 @@ of the `blob` listing.
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-launch-about` | Launch the fastfetch TUI that gives information about the current system. | - |
| `blob-launch-battlenet` | Launch the installed Battle.net client via umu-launcher + GE-Proton. | [--with-mangohud] |
| `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) | - |
@@ -261,9 +290,11 @@ of the `blob` listing.
| `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-or-focus-webapp` | Launch or focus on a given web app identified by the window-pattern. | <window-pattern> <url-and-flags...> |
| `blob-launch-screensaver` | Launch the Blob screensaver in the default terminal on the system with the correct font configuration. | - |
| `blob-launch-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...] |
| `blob-launch-webapp` | Launch a URL as a web app in the default supported browser | <url> |
## menu
@@ -321,6 +352,7 @@ of the `blob` listing.
| --- | --- | --- |
| `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-add` | Add the named packages to the system from the AUR if they're missing. Returns false if it couldn't be done. | <packages...> |
| `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. | - |
@@ -384,6 +416,17 @@ of the `blob` listing.
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-remove-browser` | Remove a supported browser and clean up Blob browser defaults | <chrome\|brave\|brave-origin\|edge\|firefox\|zen> |
| `blob-remove-dev-env` | Remove a development environment that was previously installed via blob-install-dev-env. | <ruby\|node\|bun\|deno\|go\|php\|laravel\|symfony\|python\|elixir\|phoenix\|zig\|rust\|java\|dotnet\|ocaml\|clojure\|scala> |
| `blob-remove-gaming-battlenet` | Remove Battle.net, its Proton prefix, installed games, and desktop entry. | - |
| `blob-remove-gaming-geforce-now` | Remove the GeForce NOW Flatpak app and its data. | - |
| `blob-remove-gaming-heroic` | Remove Heroic Games Launcher and its game libraries, configs, and caches. | - |
| `blob-remove-gaming-lutris` | Remove Lutris, Wine, umu-launcher, and all their configs and caches. | - |
| `blob-remove-gaming-minecraft` | Remove the Minecraft launcher along with its worlds, mods, and caches. | - |
| `blob-remove-gaming-retroarch` | Remove RetroArch, all libretro cores, and its config/saves. Leaves ~/Games/roms and ~/Games/bios alone. | - |
| `blob-remove-gaming-steam` | Remove Steam and all of its game libraries, configs, and caches. | - |
| `blob-remove-gaming-xbox-cloud` | Remove the Xbox Cloud Gaming web app. | - |
| `blob-remove-gaming-xbox-controllers` | Remove the xpadneo Xbox controller driver and undo its module/blacklist config. | - |
| `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 | - |
@@ -554,6 +597,21 @@ of the `blob` listing.
| `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. | - |
## webapp
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-webapp-handler-hey` | Open HEY webmail and translate mailto links | [url] |
| `blob-webapp-handler-zoom` | Open Zoom web meetings from browser protocol links | [url] |
| `blob-webapp-install` | Create a desktop launcher for a web app | [name url icon-url-or-name [custom-exec] [mime-types]] |
| `blob-webapp-remove` | Remove a web app desktop launcher | [name] |
## windows
| Command | Does | Arguments |
| --- | --- | --- |
| `blob-windows-vm` | Install, launch, stop, inspect, or remove the Windows VM | <install\|remove\|launch\|stop\|status> [options] |
## Totals
252 commands.
290 commands.
+37 -5
View File
@@ -8,12 +8,12 @@ menu fork carried is gone.
## Trimmed from upstream
Upstream ships 338 rows. This tree has 178. What came out:
Upstream ships 338 rows. This tree has 260. What came out:
| Section | Dropped | Why |
| --- | --- | --- |
| `install.*` | 84 of 90 | the software catalogue: browsers, gaming, dev languages, AI tools, services. Kept: Package, AUR, TUI, and the Style subtree (theme, background, font) |
| `remove.*` | 56 of 59 | same catalogue in reverse. Kept: Package, TUI, Theme |
| `install.*` | 35 of 91 | AI tools, editors, terminals, services, web apps, preinstalls. Browsers, gaming, dev languages and the Windows VM were restored on request |
| `remove.*` | 16 of 60 | the same set in reverse, plus security and dictation removal |
| `setup.default.agent.*` | 14 | AI agent defaults |
| `update.channel.*` | 5 | release channels for a package that will not exist |
| `learn.*` | 4 | Omarchy's manual and Discord, plus tmux and herdr keybindings |
@@ -27,8 +27,8 @@ which also keeps them working whichever way the web app question lands.
## Commands the menu still needs
The tree references 95 commands. All of them are now in `bin/`. What follows is
kept as the record of what Phase 6 had to fill:
Every command the tree references is in `bin/`. What follows is the record of
what Phase 6 had to fill:
- **audio**: `blob-audio-restart`
- **bar**: `blob-bar`
@@ -78,3 +78,35 @@ Every row in this tree resolves to a command in `bin/`. The only unbacked
`hypr/default/helpers.lua` and reachable only from a binding that uses
`{ webapp = ... }`. No such binding ships. They stay as the hook for whichever
way the web app question lands.
## Restored install and remove subtrees
Browsers, gaming, dev languages and the Windows VM came back after the first
pass cut them, which took 82 rows and 38 commands with them.
| Subtree | Rows | Covers |
| --- | --- | --- |
| Browsers | 14 | Chrome, Edge, Brave, Brave Origin, Firefox, Zen, install and remove |
| Gaming | 20 | Steam, RetroArch, Minecraft, Lutris, Heroic, GeForce NOW, Xbox Cloud, Xbox controllers, Battle.net |
| Development | 44 | Ruby on Rails, JavaScript (Node/Bun/Deno), Go, PHP (+ Laravel, Symfony), Python, Elixir (+ Phoenix), Zig, Rust, Java, .NET, OCaml, Clojure, Scala |
| Windows VM | 2 | install and remove |
### This settled the web app question
Xbox Cloud Gaming and GeForce NOW are web apps, not packages. Restoring gaming
therefore pulled in `blob-webapp-install`, `blob-webapp-remove`,
`blob-launch-webapp` and `blob-launch-or-focus-webapp`, and those fall back to
`chromium.desktop` for any browser that is not Chrome, Brave, Edge, Opera,
Vivaldi or Helium. Zen is Firefox-based.
So chromium stays, as the web app runtime. The `webapp` helper in
`hypr/default/helpers.lua` is live again rather than a dangling hook, though no
binding ships that uses it. The `install.webapp` and `remove.webapp` menu rows
are still out; the machinery behind them now works, so adding them back is a
two-line change if you want to create web app launchers from the menu.
### Still out
AI tools, editors, terminals, services (1Password, Dropbox, Spotify, Signal,
Tailscale, NordVPN, ONCE, Bitwarden), preinstalls, dictation, and the security
removal rows. Say the word on any of them.