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 - **[Quickshell](https://quickshell.org/)** as the desktop: `blob-shell` is one
process hosting the bar, both menus, the panels, notifications, the lock process hosting the bar, both menus, the panels, notifications, the lock
screen, and the OSD as plugins. 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, - **24 themes** with a palette pipeline that retints the terminal, editor,
browser, shell, and lock screen from one `colors.toml`. 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"]}, "setup": {"icon":"","label":"Setup","aliases":["settings"]},
"install": {"icon":"󰉉","label":"Install"}, "install": {"icon":"󰉉","label":"Install"},
"remove": {"icon":"󰭌","label":"Remove","aliases":["uninstall"]}, "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"]}, "update": {"icon":"","label":"Update","aliases":["restart","refresh"]},
"about": {"icon":"","label":"About","action":"blob-launch-about"}, "about": {"icon":"","label":"About","action":"blob-launch-about"},
"system": {"icon":"","label":"System","aliases":["power-menu"]}, "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-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> | | `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 ## clipboard
| Command | Does | Arguments | | Command | Does | Arguments |
@@ -163,6 +170,13 @@ of the `blob` listing.
| `blob-font-list` | List available monospace fonts | - | | `blob-font-list` | List available monospace fonts | - |
| `blob-font-set` | Set the system monospace font | <font-name> | | `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 ## git
| Command | Does | Arguments | | Command | Does | Arguments |
@@ -235,7 +249,21 @@ of the `blob` listing.
| Command | Does | Arguments | | 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-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> | | `blob-install-launch` | Install a packaged app and launch it once it finishes | <display-name> <packages> <desktop-id> |
## launch ## launch
@@ -243,6 +271,7 @@ of the `blob` listing.
| Command | Does | Arguments | | Command | Does | Arguments |
| --- | --- | --- | | --- | --- | --- |
| `blob-launch-about` | Launch the fastfetch TUI that gives information about the current system. | - | | `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-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-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-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-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` | 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-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-screensaver` | Launch the Blob screensaver in the default terminal on the system with the correct font configuration. | - |
| `blob-launch-shell` | Launch the Blob shell with its log kept in the journal (hidden) | - | | `blob-launch-shell` | Launch the Blob shell with its log kept in the journal (hidden) | - |
| `blob-launch-tui` | Launch a TUI command in the default terminal with Blob styling | [--app-id=<app-id>] <command> [args...] | | `blob-launch-tui` | Launch a TUI command in the default terminal with Blob styling | [--app-id=<app-id>] <command> [args...] |
| `blob-launch-webapp` | Launch a URL as a web app in the default supported browser | <url> |
## menu ## menu
@@ -321,6 +352,7 @@ of the `blob` listing.
| --- | --- | --- | | --- | --- | --- |
| `blob-pkg-add` | Install Arch packages if they are missing | <packages...> | | `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` | 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-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-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-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 | | 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-fido2` | Remove FIDO2 authentication from sudo and polkit | - |
| `blob-remove-security-sudoless-docker` | Disable sudoless Docker by removing your user from the docker group | - | | `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-location` | Show or set the location used for weather reports | - |
| `blob-weather-status` | Returns a formatted weather status string with temperature and wind speed. | - | | `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 ## Totals
252 commands. 290 commands.
+37 -5
View File
@@ -8,12 +8,12 @@ menu fork carried is gone.
## Trimmed from upstream ## 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 | | 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) | | `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.*` | 56 of 59 | same catalogue in reverse. Kept: Package, TUI, Theme | | `remove.*` | 16 of 60 | the same set in reverse, plus security and dictation removal |
| `setup.default.agent.*` | 14 | AI agent defaults | | `setup.default.agent.*` | 14 | AI agent defaults |
| `update.channel.*` | 5 | release channels for a package that will not exist | | `update.channel.*` | 5 | release channels for a package that will not exist |
| `learn.*` | 4 | Omarchy's manual and Discord, plus tmux and herdr keybindings | | `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 ## Commands the menu still needs
The tree references 95 commands. All of them are now in `bin/`. What follows is Every command the tree references is in `bin/`. What follows is the record of
kept as the record of what Phase 6 had to fill: what Phase 6 had to fill:
- **audio**: `blob-audio-restart` - **audio**: `blob-audio-restart`
- **bar**: `blob-bar` - **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 `hypr/default/helpers.lua` and reachable only from a binding that uses
`{ webapp = ... }`. No such binding ships. They stay as the hook for whichever `{ webapp = ... }`. No such binding ships. They stay as the hook for whichever
way the web app question lands. 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.