Compare commits
20 Commits
7cb7f10ff6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ea11fe0eb6 | |||
| 19e7d3f673 | |||
| bf86ffe258 | |||
| 8d73d2a37e | |||
| 54b91a72e2 | |||
| 788d4939f0 | |||
| 9a038b74c3 | |||
| 954dedf7cd | |||
| 90d0e2bed0 | |||
| 14f72af3f2 | |||
| 836848dedd | |||
| fbaffc0816 | |||
| ccae379490 | |||
| 7f0fad5922 | |||
| b40d4a9fa4 | |||
| 19d6936aa6 | |||
| 4a57e9902f | |||
| 1201e6a4cc | |||
| 626c7dbcc1 | |||
| 35374800bf |
10
README.md
10
README.md
@@ -39,7 +39,15 @@ mkdir Projects
|
|||||||
git clone ssh://git@gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles.git
|
git clone ssh://git@gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Once that is complete, run this command:
|
On a fresh install, run the bootstrap script. It installs drivers (auto-detected),
|
||||||
|
every package in `system/packages.txt`, yay, flatpaks, then links the dotfiles and
|
||||||
|
system config below:
|
||||||
|
```console
|
||||||
|
~/Projects/dotfiles/bootstrap.sh # asks about the optional SVT-AV1 + ffmpeg rebuild
|
||||||
|
~/Projects/dotfiles/bootstrap.sh --svt-av1 # or --no-svt-av1 to skip the question
|
||||||
|
```
|
||||||
|
|
||||||
|
To only link the dotfiles, run this command:
|
||||||
```console
|
```console
|
||||||
stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
|
stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
|
||||||
```
|
```
|
||||||
|
|||||||
168
bootstrap.sh
Executable file
168
bootstrap.sh
Executable file
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sets up a fresh Arch install: drivers, packages, dotfiles and system config.
|
||||||
|
# Safe to re-run; anything already installed is skipped.
|
||||||
|
#
|
||||||
|
# Usage: ./bootstrap.sh [--svt-av1 | --no-svt-av1]
|
||||||
|
# --svt-av1 also install svt-av1-essential-git and rebuild ffmpeg against it
|
||||||
|
# --no-svt-av1 skip that without asking
|
||||||
|
# (neither) ask
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
list="$repo/system/packages.txt"
|
||||||
|
|
||||||
|
svt_av1=ask
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--svt-av1) svt_av1=yes ;;
|
||||||
|
--no-svt-av1) svt_av1=no ;;
|
||||||
|
*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( EUID == 0 )); then
|
||||||
|
echo "Run this as your normal user, not root (it uses sudo where needed)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||||
|
|
||||||
|
# Prints the packages under a [section] of packages.txt, skipping comments
|
||||||
|
packages() {
|
||||||
|
awk -v want="[$1]" '
|
||||||
|
/^\[/ { section = $1; next }
|
||||||
|
section == want { sub(/#.*/, ""); if (NF) print $1 }
|
||||||
|
' "$list"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- System config -----------------------------------------------------------
|
||||||
|
|
||||||
|
step "System config (pacman options)"
|
||||||
|
"$repo/system/install.sh"
|
||||||
|
|
||||||
|
step "Enabling multilib (needed for Steam and 32-bit drivers)"
|
||||||
|
if ! grep -q '^\[multilib\]' /etc/pacman.conf; then
|
||||||
|
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
|
||||||
|
fi
|
||||||
|
sudo pacman -Syu --needed --noconfirm pciutils git base-devel
|
||||||
|
|
||||||
|
# --- Hardware ----------------------------------------------------------------
|
||||||
|
|
||||||
|
step "Detecting CPU and GPU"
|
||||||
|
hw=()
|
||||||
|
|
||||||
|
case "$(grep -m1 '^vendor_id' /proc/cpuinfo | awk '{print $3}')" in
|
||||||
|
AuthenticAMD) hw+=(amd-ucode); echo "CPU: AMD" ;;
|
||||||
|
GenuineIntel) hw+=(intel-ucode); echo "CPU: Intel" ;;
|
||||||
|
*) echo "CPU: unknown vendor, skipping microcode" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Vendor column of every display controller, e.g. "NVIDIA Corporation"
|
||||||
|
gpus="$(lspci -mm | awk -F'"' '$2 ~ /VGA|3D|Display/ { print $4 }')"
|
||||||
|
if grep -q NVIDIA <<<"$gpus"; then
|
||||||
|
echo "GPU: NVIDIA"
|
||||||
|
# nvidia-open only ships modules for the stock `linux` kernel; any other
|
||||||
|
# kernel needs the DKMS build. Supports Turing (GTX 16xx / RTX 20xx) and newer.
|
||||||
|
if pacman -Qq linux-lts linux-zen linux-hardened >/dev/null 2>&1; then
|
||||||
|
hw+=(nvidia-open-dkms linux-headers)
|
||||||
|
else
|
||||||
|
hw+=(nvidia-open)
|
||||||
|
fi
|
||||||
|
hw+=(nvidia-utils lib32-nvidia-utils libva-nvidia-driver)
|
||||||
|
fi
|
||||||
|
if grep -q 'Advanced Micro Devices' <<<"$gpus"; then
|
||||||
|
echo "GPU: AMD"
|
||||||
|
hw+=(mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon)
|
||||||
|
fi
|
||||||
|
if grep -q 'Intel Corporation' <<<"$gpus"; then
|
||||||
|
echo "GPU: Intel"
|
||||||
|
hw+=(mesa lib32-mesa vulkan-intel lib32-vulkan-intel intel-media-driver)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Installed before the main list so Steam's 32-bit Vulkan dependency is
|
||||||
|
# already satisfied by the right driver
|
||||||
|
if (( ${#hw[@]} )); then
|
||||||
|
sudo pacman -S --needed --noconfirm "${hw[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Packages ----------------------------------------------------------------
|
||||||
|
|
||||||
|
step "Official repo packages"
|
||||||
|
mapfile -t pkgs < <(packages pacman)
|
||||||
|
sudo pacman -S --needed --noconfirm "${pkgs[@]}"
|
||||||
|
|
||||||
|
step "yay (AUR helper)"
|
||||||
|
if ! command -v yay >/dev/null; then
|
||||||
|
build="$(mktemp -d)"
|
||||||
|
git clone https://aur.archlinux.org/yay-bin.git "$build/yay-bin"
|
||||||
|
(cd "$build/yay-bin" && makepkg -si --noconfirm)
|
||||||
|
rm -rf "$build"
|
||||||
|
else
|
||||||
|
echo "already installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "AUR packages"
|
||||||
|
mapfile -t pkgs < <(packages aur)
|
||||||
|
yay -S --needed --noconfirm "${pkgs[@]}"
|
||||||
|
|
||||||
|
step "Flatpak apps"
|
||||||
|
sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||||
|
mapfile -t pkgs < <(packages flatpak)
|
||||||
|
sudo flatpak install -y --noninteractive flathub "${pkgs[@]}"
|
||||||
|
|
||||||
|
step "Apps from official install scripts"
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
for app in $(packages installer); do
|
||||||
|
if command -v "$app" >/dev/null || [[ -x "$HOME/.local/bin/$app" ]]; then
|
||||||
|
echo "$app: already installed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$app" in
|
||||||
|
claude) curl -fsSL https://claude.ai/install.sh | bash ;;
|
||||||
|
oh-my-posh) curl -fsSL https://ohmyposh.dev/install.sh | bash -s -- -d "$HOME/.local/bin" ;;
|
||||||
|
*) echo "$app: no install recipe in bootstrap.sh, skipping" >&2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Optional: SVT-AV1 essential + ffmpeg rebuild ----------------------------
|
||||||
|
|
||||||
|
if [[ $svt_av1 == ask ]]; then
|
||||||
|
read -rp $'\nInstall svt-av1-essential-git and rebuild ffmpeg against it? (slow) [y/N] ' reply
|
||||||
|
[[ $reply == [yY]* ]] && svt_av1=yes || svt_av1=no
|
||||||
|
fi
|
||||||
|
if [[ $svt_av1 == yes ]]; then
|
||||||
|
step "svt-av1-essential-git"
|
||||||
|
# Interactive on purpose: pacman asks to replace the conflicting svt-av1,
|
||||||
|
# and --noconfirm would answer no
|
||||||
|
yay -S --needed svt-av1-essential-git
|
||||||
|
step "Rebuilding ffmpeg against svt-av1-essential-git"
|
||||||
|
"$repo/system/rebuild-ffmpeg.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Services ----------------------------------------------------------------
|
||||||
|
|
||||||
|
step "Enabling services"
|
||||||
|
# Not started now: sddm would take over the screen mid-script. They come up on reboot.
|
||||||
|
mapfile -t units < <(packages services)
|
||||||
|
sudo systemctl enable "${units[@]}"
|
||||||
|
|
||||||
|
# --- Dotfiles ----------------------------------------------------------------
|
||||||
|
|
||||||
|
step "Linking dotfiles with stow"
|
||||||
|
# Create real parent dirs first, otherwise stow symlinks the whole directory
|
||||||
|
# (e.g. ~/.config) into the repo and every app's files end up in git
|
||||||
|
mkdir -p "$HOME/.config" "$HOME/.local/bin" "$HOME/.local/share" "$HOME/Pictures"
|
||||||
|
# Sunshine keeps credentials and logs beside its config; those must stay out of git
|
||||||
|
mkdir -p "$HOME/.config/sunshine"
|
||||||
|
mkdir -p -m 700 "$HOME/.ssh"
|
||||||
|
if ! stow -d "$repo" -t "$HOME" --restow dotfiles; then
|
||||||
|
echo "stow hit conflicts (listed above). Move those files aside, then run:" >&2
|
||||||
|
echo " stow -d \"$repo\" -t \"\$HOME\" dotfiles" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$(getent passwd "$USER" | cut -d: -f7)" != */zsh ]]; then
|
||||||
|
step "Setting zsh as login shell"
|
||||||
|
chsh -s /usr/bin/zsh
|
||||||
|
fi
|
||||||
|
|
||||||
|
step "Done — reboot to load drivers and start Hyprland"
|
||||||
@@ -47,3 +47,11 @@ regular4=53BDFA # blue
|
|||||||
regular5=FAE994 # magenta
|
regular5=FAE994 # magenta
|
||||||
regular6=99faf2 # cyan
|
regular6=99faf2 # cyan
|
||||||
regular7=C7C7C7 # white
|
regular7=C7C7C7 # white
|
||||||
|
|
||||||
|
[key-bindings]
|
||||||
|
clipboard-copy=Control+c XF86Copy
|
||||||
|
clipboard-paste=Control+Shift+v XF86Paste
|
||||||
|
|
||||||
|
[text-bindings]
|
||||||
|
# Ctrl+C now copies, so send the interrupt signal (^C) from Ctrl+Shift+C instead
|
||||||
|
\x03=Control+Shift+c
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", sca
|
|||||||
-- (unbind = SUPER SHIFT, Print / unbind = SUPER, up from the source .conf have no Lua equivalent needed —
|
-- (unbind = SUPER SHIFT, Print / unbind = SUPER, up from the source .conf have no Lua equivalent needed —
|
||||||
-- we just don't define a conflicting bind elsewhere; SUPER + up -> movefocus u below is otherwise identical
|
-- we just don't define a conflicting bind elsewhere; SUPER + up -> movefocus u below is otherwise identical
|
||||||
-- to the one already in keybinds.lua, kept here too for parity with the original file)
|
-- to the one already in keybinds.lua, kept here too for parity with the original file)
|
||||||
hl.bind("SUPER + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m region"))
|
hl.bind("SUPER + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m region; else grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- drag an area (hyprshot if installed, else grim/slurp)
|
||||||
|
hl.bind("SUPER + SHIFT + S", hl.dsp.exec_cmd([[sh -c 'grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- region; Surface keyboard has no Print key
|
||||||
|
hl.bind("SUPER + F6", hl.dsp.exec_cmd([[sh -c 'grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- full screen
|
||||||
hl.bind("SUPER + up", hl.dsp.focus({ direction = "u" }))
|
hl.bind("SUPER + up", hl.dsp.focus({ direction = "u" }))
|
||||||
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m window"))
|
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m window; else grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- window (hyprshot) or full screen (fallback)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ hl.on("hyprland.start", function()
|
|||||||
-- Startup
|
-- Startup
|
||||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||||
|
-- restart portals once the env is imported, otherwise screen sharing breaks
|
||||||
|
hl.exec_cmd("sleep 1 && systemctl --user restart xdg-desktop-portal-hyprland && systemctl --user restart xdg-desktop-portal")
|
||||||
|
|
||||||
-- Sunshine (virtual monitor only on desktop; skipped on laptops, detected by a system battery)
|
-- Sunshine (virtual monitor only on desktop; skipped on laptops, detected by a system battery)
|
||||||
hl.exec_cmd("sleep 1 && ! ls -d /sys/class/power_supply/BAT* >/dev/null 2>&1 && hyprctl output create headless")
|
hl.exec_cmd("sleep 1 && ! ls -d /sys/class/power_supply/BAT* >/dev/null 2>&1 && hyprctl output create headless")
|
||||||
@@ -34,7 +36,8 @@ hl.on("hyprland.start", function()
|
|||||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||||
|
|
||||||
-- Starting hypridle to start hyprlock
|
-- Starting hypridle to start hyprlock
|
||||||
-- hl.exec_cmd("hypridle")
|
-- Sunshine streams hold an idle inhibitor (scripts/SunshineIdle.sh) so this never locks mid-stream
|
||||||
|
hl.exec_cmd("hypridle")
|
||||||
|
|
||||||
-- Sunshine for streaming media
|
-- Sunshine for streaming media
|
||||||
hl.exec_cmd("sunshine")
|
hl.exec_cmd("sunshine")
|
||||||
|
|||||||
@@ -116,23 +116,22 @@ hl.bind(mainMod .. " + SHIFT + tab", hl.dsp.focus({ workspace = "m-1" }))
|
|||||||
hl.bind(mainMod .. " + U", hl.dsp.workspace.toggle_special("nyx"))
|
hl.bind(mainMod .. " + U", hl.dsp.workspace.toggle_special("nyx"))
|
||||||
hl.bind(mainMod .. " + SHIFT + U", hl.dsp.window.move({ workspace = "special:nyx" }))
|
hl.bind(mainMod .. " + SHIFT + U", hl.dsp.window.move({ workspace = "special:nyx" }))
|
||||||
|
|
||||||
-- The following mappings use key codes to better support various keyboard layouts
|
-- Number-row keys (the Lua bind parser does not accept the code:NN keycode syntax)
|
||||||
-- 1 is code:10, 2 is code:11, etc
|
|
||||||
-- Switch workspaces with mainMod + [0-9]
|
-- Switch workspaces with mainMod + [0-9]
|
||||||
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
|
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||||
hl.bind(mainMod .. " + code:" .. code, hl.dsp.focus({ workspace = i })) -- code:10 = key 1 ... code:19 = key 0
|
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move active window and follow to workspace mainMod + SHIFT + [0-9]
|
-- Move active window and follow to workspace mainMod + SHIFT + [0-9]
|
||||||
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
|
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||||
hl.bind(mainMod .. " + SHIFT + code:" .. code, hl.dsp.window.move({ workspace = i, follow = true }))
|
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i, follow = true }))
|
||||||
end
|
end
|
||||||
hl.bind(mainMod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = -1, follow = true })) -- brackets [
|
hl.bind(mainMod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = -1, follow = true })) -- brackets [
|
||||||
hl.bind(mainMod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = 1, follow = true })) -- brackets ]
|
hl.bind(mainMod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = 1, follow = true })) -- brackets ]
|
||||||
|
|
||||||
-- Move active window to a workspace silently mainMod + CTRL + [0-9]
|
-- Move active window to a workspace silently mainMod + CTRL + [0-9]
|
||||||
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
|
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||||
hl.bind(mainMod .. " + CTRL + code:" .. code, hl.dsp.window.move({ workspace = i, follow = false }))
|
hl.bind(mainMod .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = i, follow = false }))
|
||||||
end
|
end
|
||||||
hl.bind(mainMod .. " + CTRL + bracketleft", hl.dsp.window.move({ workspace = -1, follow = false })) -- brackets [
|
hl.bind(mainMod .. " + CTRL + bracketleft", hl.dsp.window.move({ workspace = -1, follow = false })) -- brackets [
|
||||||
hl.bind(mainMod .. " + CTRL + bracketright", hl.dsp.window.move({ workspace = 1, follow = false })) -- brackets ]
|
hl.bind(mainMod .. " + CTRL + bracketright", hl.dsp.window.move({ workspace = 1, follow = false })) -- brackets ]
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
music_icon="$HOME/.config/swaync/icons/music.png"
|
music_icon="$HOME/.config/swaync/icons/music.png"
|
||||||
|
|
||||||
|
# Use the app's own icon for known players
|
||||||
|
case "$(playerctl metadata --format '{{playerName}}' 2>/dev/null)" in
|
||||||
|
Plexamp) music_icon="com.plexamp.Plexamp" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Play the next track
|
# Play the next track
|
||||||
play_next() {
|
play_next() {
|
||||||
playerctl next
|
playerctl next
|
||||||
|
|||||||
29
dotfiles/.config/hypr/scripts/SunshineIdle.sh
Executable file
29
dotfiles/.config/hypr/scripts/SunshineIdle.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Keeps hypridle from locking the screen while Sunshine is streaming.
|
||||||
|
# Hooked up as Sunshine's global_prep_cmd, so it runs for every app:
|
||||||
|
# start -> hold a systemd idle inhibitor (hypridle respects these) and
|
||||||
|
# unlock hyprlock if the session was already locked
|
||||||
|
# stop -> release the inhibitor so normal idle locking resumes
|
||||||
|
|
||||||
|
pidfile="${XDG_RUNTIME_DIR:-/tmp}/sunshine-idle-inhibit.pid"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
if ! { [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; }; then
|
||||||
|
setsid systemd-inhibit --what=idle --who=Sunshine --why="Streaming" \
|
||||||
|
sleep infinity >/dev/null 2>&1 &
|
||||||
|
echo $! > "$pidfile"
|
||||||
|
fi
|
||||||
|
# SIGUSR1 tells hyprlock to unlock
|
||||||
|
pkill -USR1 -x hyprlock
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
[ -f "$pidfile" ] && kill "$(cat "$pidfile")" 2>/dev/null
|
||||||
|
rm -f "$pidfile"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 start|stop" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
@@ -1,143 +1,37 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
# Volume controls for audio and mic via wpctl (PipeWire).
|
||||||
# Scripts for volume controls for audio and mic
|
# Quickshell's VolumeOsd watches PipeWire directly, so no notifications here.
|
||||||
|
|
||||||
iDIR="$HOME/.config/swaync/icons"
|
SINK="@DEFAULT_AUDIO_SINK@"
|
||||||
sDIR="$HOME/.config/hypr/scripts"
|
SOURCE="@DEFAULT_AUDIO_SOURCE@"
|
||||||
|
STEP="5%"
|
||||||
|
|
||||||
# Get Volume
|
# Print volume as an integer percent, or "Muted"
|
||||||
get_volume() {
|
get_volume() {
|
||||||
volume=$(pamixer --get-volume)
|
local out
|
||||||
if [[ "$volume" -eq "0" ]]; then
|
out=$(wpctl get-volume "$1")
|
||||||
|
if [[ "$out" == *MUTED* ]]; then
|
||||||
echo "Muted"
|
echo "Muted"
|
||||||
else
|
else
|
||||||
echo "$volume %"
|
awk '{ printf "%d %%\n", $2 * 100 + 0.5 }' <<< "$out"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get icons
|
is_muted() {
|
||||||
get_icon() {
|
[[ "$(wpctl get-volume "$1")" == *MUTED* ]]
|
||||||
current=$(get_volume)
|
|
||||||
if [[ "$current" == "Muted" ]]; then
|
|
||||||
echo "$iDIR/volume-mute.png"
|
|
||||||
elif [[ "${current%\%}" -le 30 ]]; then
|
|
||||||
echo "$iDIR/volume-low.png"
|
|
||||||
elif [[ "${current%\%}" -le 60 ]]; then
|
|
||||||
echo "$iDIR/volume-mid.png"
|
|
||||||
else
|
|
||||||
echo "$iDIR/volume-high.png"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Notify
|
# Raising/lowering while muted unmutes first, matching the old behaviour
|
||||||
notify_user() {
|
inc() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume -l 1.0 "$1" "$STEP+"; }
|
||||||
if [[ "$(get_volume)" == "Muted" ]]; then
|
dec() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume "$1" "$STEP-"; }
|
||||||
notify-send -e -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume:" " Muted"
|
|
||||||
else
|
|
||||||
notify-send -e -h int:value:"$(get_volume | sed 's/%//')" -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume Level:" " $(get_volume)" &&
|
|
||||||
"$sDIR/Sounds.sh" --volume
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Increase Volume
|
case "$1" in
|
||||||
inc_volume() {
|
--inc) inc "$SINK" ;;
|
||||||
if [ "$(pamixer --get-mute)" == "true" ]; then
|
--dec) dec "$SINK" ;;
|
||||||
toggle_mute
|
--toggle) wpctl set-mute "$SINK" toggle ;;
|
||||||
else
|
--toggle-mic) wpctl set-mute "$SOURCE" toggle ;;
|
||||||
pamixer -i 5 --allow-boost --set-limit 150 && notify_user
|
--mic-inc) inc "$SOURCE" ;;
|
||||||
fi
|
--mic-dec) dec "$SOURCE" ;;
|
||||||
}
|
--get-mic) get_volume "$SOURCE" ;;
|
||||||
|
*) get_volume "$SINK" ;;
|
||||||
# Decrease Volume
|
esac
|
||||||
dec_volume() {
|
|
||||||
if [ "$(pamixer --get-mute)" == "true" ]; then
|
|
||||||
toggle_mute
|
|
||||||
else
|
|
||||||
pamixer -d 5 && notify_user
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Toggle Mute
|
|
||||||
toggle_mute() {
|
|
||||||
if [ "$(pamixer --get-mute)" == "false" ]; then
|
|
||||||
pamixer -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/volume-mute.png" " Mute"
|
|
||||||
elif [ "$(pamixer --get-mute)" == "true" ]; then
|
|
||||||
pamixer -u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$(get_icon)" " Volume:" " Switched ON"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Toggle Mic
|
|
||||||
toggle_mic() {
|
|
||||||
if [ "$(pamixer --default-source --get-mute)" == "false" ]; then
|
|
||||||
pamixer --default-source -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone-mute.png" " Microphone:" " Switched OFF"
|
|
||||||
elif [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
|
||||||
pamixer -u --default-source u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone.png" " Microphone:" " Switched ON"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
# Get Mic Icon
|
|
||||||
get_mic_icon() {
|
|
||||||
current=$(pamixer --default-source --get-volume)
|
|
||||||
if [[ "$current" -eq "0" ]]; then
|
|
||||||
echo "$iDIR/microphone-mute.png"
|
|
||||||
else
|
|
||||||
echo "$iDIR/microphone.png"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get Microphone Volume
|
|
||||||
get_mic_volume() {
|
|
||||||
volume=$(pamixer --default-source --get-volume)
|
|
||||||
if [[ "$volume" -eq "0" ]]; then
|
|
||||||
echo "Muted"
|
|
||||||
else
|
|
||||||
echo "$volume %"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Notify for Microphone
|
|
||||||
notify_mic_user() {
|
|
||||||
volume=$(get_mic_volume)
|
|
||||||
icon=$(get_mic_icon)
|
|
||||||
notify-send -e -h int:value:"$volume" -h "string:x-canonical-private-synchronous:volume_notif" -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$icon" " Mic Level:" " $volume"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Increase MIC Volume
|
|
||||||
inc_mic_volume() {
|
|
||||||
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
|
||||||
toggle_mic
|
|
||||||
else
|
|
||||||
pamixer --default-source -i 5 && notify_mic_user
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Decrease MIC Volume
|
|
||||||
dec_mic_volume() {
|
|
||||||
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
|
||||||
toggle-mic
|
|
||||||
else
|
|
||||||
pamixer --default-source -d 5 && notify_mic_user
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Execute accordingly
|
|
||||||
if [[ "$1" == "--get" ]]; then
|
|
||||||
get_volume
|
|
||||||
elif [[ "$1" == "--inc" ]]; then
|
|
||||||
inc_volume
|
|
||||||
elif [[ "$1" == "--dec" ]]; then
|
|
||||||
dec_volume
|
|
||||||
elif [[ "$1" == "--toggle" ]]; then
|
|
||||||
toggle_mute
|
|
||||||
elif [[ "$1" == "--toggle-mic" ]]; then
|
|
||||||
toggle_mic
|
|
||||||
elif [[ "$1" == "--get-icon" ]]; then
|
|
||||||
get_icon
|
|
||||||
elif [[ "$1" == "--get-mic-icon" ]]; then
|
|
||||||
get_mic_icon
|
|
||||||
elif [[ "$1" == "--mic-inc" ]]; then
|
|
||||||
inc_mic_volume
|
|
||||||
elif [[ "$1" == "--mic-dec" ]]; then
|
|
||||||
dec_mic_volume
|
|
||||||
else
|
|
||||||
get_volume
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
// Tailscale, Mullvad, and ProtonVPN all fight over the default route/DNS if
|
// Tailscale, Mullvad, ProtonVPN, and the home UniFi WireGuard tunnel all
|
||||||
// more than one is active at once (see VpnIndicator.qml's header comment) —
|
// fight over the default route/DNS if more than one is active at once (see
|
||||||
// only one of the three should ever actually be connected. This singleton
|
// VpnIndicator.qml's header comment) — only one should ever actually be
|
||||||
// is the shared signal between the two widget files: VpnIndicator.qml
|
// connected. This singleton is the shared signal between the two widget
|
||||||
// writes Mullvad/ProtonVPN's live connection state here as it polls them;
|
// files: VpnIndicator.qml writes each tunnel's live connection state here
|
||||||
// TailscaleIndicator.qml reads it to back off (`tailscale down`) rather
|
// as it polls them; TailscaleIndicator.qml reads it to back off (`tailscale
|
||||||
// than stay up alongside either one, and to refuse turning Tailscale back
|
// down`) rather than stay up alongside one, and to refuse turning Tailscale
|
||||||
// on while one is still connected.
|
// back on while one is still connected.
|
||||||
QtObject {
|
QtObject {
|
||||||
property bool mullvadConnected: false
|
property bool mullvadConnected: false
|
||||||
property bool protonConnected: false
|
property bool protonConnected: false
|
||||||
readonly property bool commercialVpnActive: mullvadConnected || protonConnected
|
property bool homeWgConnected: false
|
||||||
|
readonly property bool commercialVpnActive: mullvadConnected || protonConnected || homeWgConnected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ RowLayout {
|
|||||||
icon: Qt.resolvedUrl("../../assets/sunshine.svg"),
|
icon: Qt.resolvedUrl("../../assets/sunshine.svg"),
|
||||||
binary: "sunshine",
|
binary: "sunshine",
|
||||||
launch: ["systemctl", "--user", "start", "app-dev.lizardbyte.app.Sunshine.service"],
|
launch: ["systemctl", "--user", "start", "app-dev.lizardbyte.app.Sunshine.service"],
|
||||||
secondary: ["xdg-open", "https://xol.twhomelab.local:47990"],
|
secondary: ["xdg-open", "https://xol.twhomelab.tailnet:47990"],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Item {
|
|||||||
|
|
||||||
// Set when the VPN-exclusivity guard (below) forces Tailscale down —
|
// Set when the VPN-exclusivity guard (below) forces Tailscale down —
|
||||||
// as opposed to you turning it off yourself — so it knows to bring
|
// as opposed to you turning it off yourself — so it knows to bring
|
||||||
// Tailscale back up automatically once Mullvad/ProtonVPN disconnect,
|
// Tailscale back up automatically once the other VPN disconnects,
|
||||||
// without overriding a deliberate manual "off".
|
// without overriding a deliberate manual "off".
|
||||||
property bool _forcedDownByVpn: false
|
property bool _forcedDownByVpn: false
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ Item {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (VpnExclusivity.commercialVpnActive) {
|
if (VpnExclusivity.commercialVpnActive) {
|
||||||
root.actionStatus = "Disconnect Mullvad/ProtonVPN first"
|
root.actionStatus = "Disconnect the active VPN first"
|
||||||
actionStatusTimer.restart()
|
actionStatusTimer.restart()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,15 +137,25 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
// Scroll distance is accumulated rather than stepping per event:
|
||||||
|
// a mouse wheel sends one 120-unit event per notch, but a touchpad
|
||||||
|
// sends a stream of tiny events, which at a fixed step per event
|
||||||
|
// made touchpad scrolling far too fast. One notch's worth (120) of
|
||||||
|
// scrolling = one volume step, regardless of input device.
|
||||||
|
property real wheelAccum: 0
|
||||||
|
readonly property real volumeStep: 0.05
|
||||||
|
readonly property real unitsPerStep: 120
|
||||||
onClicked: {
|
onClicked: {
|
||||||
panel.visible = !panel.visible
|
panel.visible = !panel.visible
|
||||||
grab.active = panel.visible
|
grab.active = panel.visible
|
||||||
}
|
}
|
||||||
onWheel: (wheel) => {
|
onWheel: (wheel) => {
|
||||||
if (!root.sink || !root.sink.audio) return
|
if (!root.sink || !root.sink.audio) return
|
||||||
const step = 0.05
|
wheelAccum += wheel.angleDelta.y
|
||||||
const delta = wheel.angleDelta.y > 0 ? step : -step
|
const steps = Math.trunc(wheelAccum / unitsPerStep)
|
||||||
root.sink.audio.volume = Math.max(0, Math.min(1, root.sink.audio.volume + delta))
|
if (steps === 0) return
|
||||||
|
wheelAccum -= steps * unitsPerStep
|
||||||
|
root.sink.audio.volume = Math.max(0, Math.min(1, root.sink.audio.volume + steps * volumeStep))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ import "../../config"
|
|||||||
// - ProtonVPN: official `protonvpn` CLI (proton-vpn-cli package), plain-
|
// - ProtonVPN: official `protonvpn` CLI (proton-vpn-cli package), plain-
|
||||||
// text status only — parsed defensively since its exact wording isn't
|
// text status only — parsed defensively since its exact wording isn't
|
||||||
// guaranteed across versions.
|
// guaranteed across versions.
|
||||||
|
// - Home (UniFi): the UniFi gateway's built-in WireGuard server, driven
|
||||||
|
// through a NetworkManager profile via `nmcli` (no sudo needed). The
|
||||||
|
// row's "Add config" button imports the client .conf UniFi generates
|
||||||
|
// (via scripts/ImportUnifiWireguard.sh --pick), which names the profile
|
||||||
|
// to match homeWgConnection below.
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
implicitWidth: label.implicitWidth + 4
|
implicitWidth: label.implicitWidth + 4
|
||||||
@@ -39,9 +44,17 @@ Item {
|
|||||||
property string protonState: "disconnected"
|
property string protonState: "disconnected"
|
||||||
property string protonDetail: ""
|
property string protonDetail: ""
|
||||||
|
|
||||||
readonly property bool anyConnected: mullvadState === "connected" || protonState === "connected"
|
// NetworkManager profile name for the home UniFi WireGuard tunnel.
|
||||||
|
// "Installed" here means the profile exists — `nmcli connection show`
|
||||||
|
// exits non-zero for an unknown name.
|
||||||
|
property string homeWgConnection: "unifi-home"
|
||||||
|
property bool homeWgInstalled: true
|
||||||
|
property string homeWgState: "disconnected"
|
||||||
|
property string homeWgDetail: ""
|
||||||
|
|
||||||
// Set while switching from one provider to the other: holds which one
|
readonly property bool anyConnected: mullvadState === "connected" || protonState === "connected" || homeWgState === "connected"
|
||||||
|
|
||||||
|
// Set while switching from one provider to another: holds which one
|
||||||
// to connect once the current one has finished disconnecting.
|
// to connect once the current one has finished disconnecting.
|
||||||
property string pendingConnect: ""
|
property string pendingConnect: ""
|
||||||
|
|
||||||
@@ -51,6 +64,11 @@ Item {
|
|||||||
// show up hanging under the other's row.
|
// show up hanging under the other's row.
|
||||||
property string mullvadActionStatus: ""
|
property string mullvadActionStatus: ""
|
||||||
property string protonActionStatus: ""
|
property string protonActionStatus: ""
|
||||||
|
property string homeWgActionStatus: ""
|
||||||
|
property bool homeWgImporting: homeWgImportProc.running
|
||||||
|
// Whether Home's options (Replace config) are expanded — kept across
|
||||||
|
// panel opens, like mullvadOptionsOpen.
|
||||||
|
property bool homeWgOptionsOpen: false
|
||||||
|
|
||||||
// --- Mullvad advanced options (auto-connect, relay location, multihop) --
|
// --- Mullvad advanced options (auto-connect, relay location, multihop) --
|
||||||
// ProtonVPN's CLI is plain-text-only and far less predictable across
|
// ProtonVPN's CLI is plain-text-only and far less predictable across
|
||||||
@@ -114,6 +132,20 @@ Item {
|
|||||||
VpnExclusivity.protonConnected = root.protonState === "connected"
|
VpnExclusivity.protonConnected = root.protonState === "connected"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `nmcli -g GENERAL.STATE,IP4.ADDRESS connection show <name>` prints
|
||||||
|
// nothing for an inactive profile, otherwise "activated"/"activating"/
|
||||||
|
// "deactivating" followed by the tunnel address(es).
|
||||||
|
function applyHomeWgStatus(text) {
|
||||||
|
const lines = String(text || "").trim().split("\n").map(function(l) { return l.trim() }).filter(Boolean)
|
||||||
|
const raw = (lines[0] || "").toLowerCase()
|
||||||
|
root.homeWgState = raw === "activated" ? "connected"
|
||||||
|
: raw === "activating" ? "connecting"
|
||||||
|
: raw === "deactivating" ? "disconnecting"
|
||||||
|
: "disconnected"
|
||||||
|
root.homeWgDetail = root.homeWgState === "connected" && lines[1] ? lines[1].split(" | ")[0] : ""
|
||||||
|
VpnExclusivity.homeWgConnected = root.homeWgState === "connected"
|
||||||
|
}
|
||||||
|
|
||||||
function applyMullvadAutoConnect(text) {
|
function applyMullvadAutoConnect(text) {
|
||||||
const m = String(text || "").match(/autoconnect:\s*(on|off)/i)
|
const m = String(text || "").match(/autoconnect:\s*(on|off)/i)
|
||||||
root.mullvadAutoConnect = !!m && m[1].toLowerCase() === "on"
|
root.mullvadAutoConnect = !!m && m[1].toLowerCase() === "on"
|
||||||
@@ -225,33 +257,61 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshHomeWg() {
|
||||||
|
if (!homeWgStatusProc.running) homeWgStatusProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
root.refreshMullvad()
|
root.refreshMullvad()
|
||||||
root.refreshProton()
|
root.refreshProton()
|
||||||
|
root.refreshHomeWg()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnects every provider other than `keep` that isn't already
|
||||||
|
// down; returns whether it had to, so the caller can defer its own
|
||||||
|
// connect to root.resumePendingConnect() instead of racing it.
|
||||||
|
function stopOthers(keep) {
|
||||||
|
let stopping = false
|
||||||
|
if (keep !== "mullvad" && root.mullvadState !== "disconnected") { root.disconnectMullvad(); stopping = true }
|
||||||
|
if (keep !== "protonvpn" && root.protonState !== "disconnected") { root.disconnectProton(); stopping = true }
|
||||||
|
if (keep !== "homewg" && root.homeWgState !== "disconnected") { root.disconnectHomeWg(); stopping = true }
|
||||||
|
return stopping
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from each disconnect's onExited. The provider that just went
|
||||||
|
// down is marked disconnected optimistically before this runs — its
|
||||||
|
// status poll is async, and without that the pending connect would see
|
||||||
|
// it still "connected" and try to disconnect it all over again.
|
||||||
|
function resumePendingConnect() {
|
||||||
|
const next = root.pendingConnect
|
||||||
|
root.pendingConnect = ""
|
||||||
|
if (next === "mullvad") root.connectMullvad()
|
||||||
|
else if (next === "protonvpn") root.connectProton()
|
||||||
|
else if (next === "homewg") root.connectHomeWg()
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectMullvad() {
|
function connectMullvad() {
|
||||||
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
|
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
|
||||||
if (root.protonState !== "disconnected") {
|
if (root.stopOthers("mullvad")) { root.pendingConnect = "mullvad"; return }
|
||||||
root.pendingConnect = "mullvad"
|
|
||||||
root.disconnectProton()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
root.pendingConnect = ""
|
root.pendingConnect = ""
|
||||||
mullvadConnectProc.running = true
|
mullvadConnectProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectProton() {
|
function connectProton() {
|
||||||
if (protonConnectProc.running || protonDisconnectProc.running) return
|
if (protonConnectProc.running || protonDisconnectProc.running) return
|
||||||
if (root.mullvadState !== "disconnected") {
|
if (root.stopOthers("protonvpn")) { root.pendingConnect = "protonvpn"; return }
|
||||||
root.pendingConnect = "protonvpn"
|
|
||||||
root.disconnectMullvad()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
root.pendingConnect = ""
|
root.pendingConnect = ""
|
||||||
protonConnectProc.running = true
|
protonConnectProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectHomeWg() {
|
||||||
|
if (homeWgConnectProc.running || homeWgDisconnectProc.running) return
|
||||||
|
if (root.stopOthers("homewg")) { root.pendingConnect = "homewg"; return }
|
||||||
|
root.pendingConnect = ""
|
||||||
|
root.homeWgState = "connecting"
|
||||||
|
homeWgConnectProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
function disconnectMullvad() {
|
function disconnectMullvad() {
|
||||||
if (mullvadDisconnectProc.running) return
|
if (mullvadDisconnectProc.running) return
|
||||||
mullvadDisconnectProc.running = true
|
mullvadDisconnectProc.running = true
|
||||||
@@ -262,6 +322,12 @@ Item {
|
|||||||
protonDisconnectProc.running = true
|
protonDisconnectProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disconnectHomeWg() {
|
||||||
|
if (homeWgDisconnectProc.running) return
|
||||||
|
root.homeWgState = "disconnecting"
|
||||||
|
homeWgDisconnectProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
function toggleMullvad() {
|
function toggleMullvad() {
|
||||||
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
|
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
|
||||||
if (root.mullvadState === "connected") root.disconnectMullvad()
|
if (root.mullvadState === "connected") root.disconnectMullvad()
|
||||||
@@ -274,7 +340,23 @@ Item {
|
|||||||
else root.connectProton()
|
else root.connectProton()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tailscale fights Mullvad/ProtonVPN for the default route/DNS, so fire
|
// Opens a file chooser for the UniFi client .conf and imports it as
|
||||||
|
// homeWgConnection, replacing any existing profile. The chooser takes
|
||||||
|
// focus, which clears the focus grab and closes the panel — so the
|
||||||
|
// import reopens it when done to show the result.
|
||||||
|
function importHomeWgConfig() {
|
||||||
|
if (homeWgImportProc.running || root.homeWgState !== "disconnected") return
|
||||||
|
root.homeWgActionStatus = ""
|
||||||
|
homeWgImportProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleHomeWg() {
|
||||||
|
if (homeWgConnectProc.running || homeWgDisconnectProc.running) return
|
||||||
|
if (root.homeWgState === "connected") root.disconnectHomeWg()
|
||||||
|
else root.connectHomeWg()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tailscale fights Mullvad/ProtonVPN/home WireGuard for the default route/DNS, so fire
|
||||||
// this right after either one finishes connecting — a harmless no-op
|
// this right after either one finishes connecting — a harmless no-op
|
||||||
// if Tailscale was already down. TailscaleIndicator.qml also backs
|
// if Tailscale was already down. TailscaleIndicator.qml also backs
|
||||||
// itself off reactively (via VpnExclusivity) as a backstop, but doing
|
// itself off reactively (via VpnExclusivity) as a backstop, but doing
|
||||||
@@ -336,10 +418,11 @@ Item {
|
|||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.mullvadActionStatus = root.shortLine(mullvadDisconnectErr.text || "Disconnect failed")
|
root.mullvadActionStatus = root.shortLine(mullvadDisconnectErr.text || "Disconnect failed")
|
||||||
mullvadActionStatusTimer.restart()
|
mullvadActionStatusTimer.restart()
|
||||||
|
} else {
|
||||||
|
root.mullvadState = "disconnected"
|
||||||
}
|
}
|
||||||
root.refreshMullvad()
|
root.refreshMullvad()
|
||||||
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
root.resumePendingConnect()
|
||||||
else if (root.pendingConnect === "protonvpn") { root.pendingConnect = ""; root.connectProton() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,10 +554,11 @@ Item {
|
|||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
root.protonActionStatus = root.shortLine(protonDisconnectErr.text || "Disconnect failed")
|
root.protonActionStatus = root.shortLine(protonDisconnectErr.text || "Disconnect failed")
|
||||||
protonActionStatusTimer.restart()
|
protonActionStatusTimer.restart()
|
||||||
|
} else {
|
||||||
|
root.protonState = "disconnected"
|
||||||
}
|
}
|
||||||
root.refreshProton()
|
root.refreshProton()
|
||||||
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
|
root.resumePendingConnect()
|
||||||
else if (root.pendingConnect === "protonvpn") { root.pendingConnect = ""; root.connectProton() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,6 +569,75 @@ Item {
|
|||||||
onTriggered: if (protonStatusProc.running) protonStatusProc.running = false
|
onTriggered: if (protonStatusProc.running) protonStatusProc.running = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Home WireGuard (UniFi, via NetworkManager) ------------------------
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: homeWgStatusProc
|
||||||
|
command: ["nmcli", "-g", "GENERAL.STATE,IP4.ADDRESS", "connection", "show", root.homeWgConnection]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: homeWgStatusCollector
|
||||||
|
onStreamFinished: root.applyHomeWgStatus(homeWgStatusCollector.text)
|
||||||
|
}
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.homeWgInstalled = exitCode === 0
|
||||||
|
if (!root.homeWgInstalled) root.applyHomeWgStatus("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `connection up` blocks until NetworkManager reports the profile
|
||||||
|
// activated (or gives up), so its exit code is the real result. Note
|
||||||
|
// WireGuard "activated" only means the interface is configured — the
|
||||||
|
// handshake itself happens lazily on first traffic.
|
||||||
|
Process {
|
||||||
|
id: homeWgConnectProc
|
||||||
|
command: ["nmcli", "connection", "up", root.homeWgConnection]
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector { id: homeWgConnectErr }
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
root.homeWgActionStatus = root.shortLine(homeWgConnectErr.text || "Connect failed")
|
||||||
|
homeWgActionStatusTimer.restart()
|
||||||
|
} else if (!tailscaleDownProc.running) {
|
||||||
|
tailscaleDownProc.running = true
|
||||||
|
}
|
||||||
|
root.refreshHomeWg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: homeWgDisconnectProc
|
||||||
|
command: ["nmcli", "connection", "down", root.homeWgConnection]
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector { id: homeWgDisconnectErr }
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
root.homeWgActionStatus = root.shortLine(homeWgDisconnectErr.text || "Disconnect failed")
|
||||||
|
homeWgActionStatusTimer.restart()
|
||||||
|
} else {
|
||||||
|
root.homeWgState = "disconnected"
|
||||||
|
}
|
||||||
|
root.refreshHomeWg()
|
||||||
|
root.resumePendingConnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: homeWgImportProc
|
||||||
|
command: ["bash", Quickshell.shellPath("scripts/ImportUnifiWireguard.sh"),
|
||||||
|
"--pick", root.homeWgConnection, "--replace"]
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector { id: homeWgImportErr }
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
// 3 = chooser cancelled — nothing to report.
|
||||||
|
if (exitCode === 0) root.homeWgActionStatus = "Config imported"
|
||||||
|
else if (exitCode !== 3) root.homeWgActionStatus = root.shortLine(homeWgImportErr.text || "Import failed")
|
||||||
|
if (root.homeWgActionStatus !== "") homeWgActionStatusTimer.restart()
|
||||||
|
root.refreshHomeWg()
|
||||||
|
panel.visible = true
|
||||||
|
grab.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Shared timers -----------------------------------------------------
|
// --- Shared timers -----------------------------------------------------
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -510,6 +663,13 @@ Item {
|
|||||||
onTriggered: root.protonActionStatus = ""
|
onTriggered: root.protonActionStatus = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: homeWgActionStatusTimer
|
||||||
|
interval: 4000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.homeWgActionStatus = ""
|
||||||
|
}
|
||||||
|
|
||||||
// --- Bar icon ------------------------------------------------------------
|
// --- Bar icon ------------------------------------------------------------
|
||||||
|
|
||||||
// A fixed, wallpaper-independent green rather than Colors.primary — the
|
// A fixed, wallpaper-independent green rather than Colors.primary — the
|
||||||
@@ -615,6 +775,7 @@ Item {
|
|||||||
property string detailText: ""
|
property string detailText: ""
|
||||||
property string state: "disconnected"
|
property string state: "disconnected"
|
||||||
property bool installed: true
|
property bool installed: true
|
||||||
|
property string missingText: "Not installed"
|
||||||
// When true, shows a chevron that expands a provider-specific
|
// When true, shows a chevron that expands a provider-specific
|
||||||
// options section below this row (used for Mullvad's advanced
|
// options section below this row (used for Mullvad's advanced
|
||||||
// options; ProtonVPN's row leaves this false).
|
// options; ProtonVPN's row leaves this false).
|
||||||
@@ -664,7 +825,7 @@ Item {
|
|||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: !provRow.installed ? "Not installed"
|
text: !provRow.installed ? provRow.missingText
|
||||||
: provRow.state === "connecting" ? "Connecting…"
|
: provRow.state === "connecting" ? "Connecting…"
|
||||||
: provRow.state === "disconnecting" ? "Disconnecting…"
|
: provRow.state === "disconnecting" ? "Disconnecting…"
|
||||||
: provRow.state === "connected" ? (provRow.detailText || "Connected")
|
: provRow.state === "connected" ? (provRow.detailText || "Connected")
|
||||||
@@ -848,7 +1009,124 @@ Item {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 2
|
// Room for the cards' outlines (drawn 3px outside them)
|
||||||
|
// so two expanded ones don't touch.
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// Same collapsible card as Mullvad below — framed only
|
||||||
|
// while its options (Replace config) are expanded.
|
||||||
|
Item {
|
||||||
|
readonly property bool _framed: root.homeWgOptionsOpen && root.homeWgInstalled
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: homeWgSectionColumn.implicitHeight
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outline offset outside the content rather than drawn on
|
||||||
|
// its own edge — same as Volume.qml's DeviceRow hover —
|
||||||
|
// so the row doesn't shift when the options open.
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -3
|
||||||
|
radius: 0
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Colors.foreground
|
||||||
|
border.width: parent._framed ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipped separately so the outline above isn't.
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: homeWgSectionColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ProviderRow {
|
||||||
|
label: "Home (UniFi)"
|
||||||
|
subIcon: "home"
|
||||||
|
state: root.homeWgState
|
||||||
|
detailText: root.homeWgDetail
|
||||||
|
installed: root.homeWgInstalled
|
||||||
|
missingText: "Not set up"
|
||||||
|
expandable: root.homeWgInstalled
|
||||||
|
expanded: root.homeWgOptionsOpen
|
||||||
|
onToggle: root.toggleHomeWg()
|
||||||
|
onExpandToggle: root.homeWgOptionsOpen = !root.homeWgOptionsOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusLine { message: root.homeWgActionStatus }
|
||||||
|
|
||||||
|
// Imports (or replaces) the UniFi client .conf through a
|
||||||
|
// file chooser. Shown outright until a profile exists
|
||||||
|
// (so first-time setup isn't hidden behind the
|
||||||
|
// chevron), then only inside the expanded options.
|
||||||
|
// Hidden while connected — replacing deletes the
|
||||||
|
// profile, which would drop the tunnel.
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: importRow.implicitHeight + 8
|
||||||
|
visible: root.homeWgState === "disconnected"
|
||||||
|
&& (!root.homeWgInstalled || root.homeWgOptionsOpen)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: importRow
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 34
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 4
|
||||||
|
opacity: root.homeWgImporting ? 0.5 : 1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.homeWgInstalled ? "sync" : "upload_file"
|
||||||
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.homeWgImporting ? "Choosing file…"
|
||||||
|
: root.homeWgInstalled ? "Replace config" : "Add config"
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
font.underline: importMouse.containsMouse
|
||||||
|
color: importMouse.containsMouse ? Colors.foreground : Colors.primary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: importMouse
|
||||||
|
anchors.fill: importRow
|
||||||
|
anchors.margins: -4
|
||||||
|
enabled: !root.homeWgImporting
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.importHomeWgConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
x: 34
|
||||||
|
width: parent.width - x
|
||||||
|
visible: root.homeWgOptionsOpen && root.homeWgInstalled
|
||||||
|
&& root.homeWgState !== "disconnected"
|
||||||
|
text: "Disconnect to replace config"
|
||||||
|
bottomPadding: 6
|
||||||
|
font.family: Appearance.fontFamily
|
||||||
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
|
color: Colors.muted
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mullvad's connect row and its advanced options share a
|
// Mullvad's connect row and its advanced options share a
|
||||||
// single bordered card — the border wraps the whole
|
// single bordered card — the border wraps the whole
|
||||||
@@ -856,14 +1134,11 @@ Item {
|
|||||||
// grows to include the options/picker as they open,
|
// grows to include the options/picker as they open,
|
||||||
// rather than the options having a separate box of
|
// rather than the options having a separate box of
|
||||||
// their own below an unbordered connect row.
|
// their own below an unbordered connect row.
|
||||||
Rectangle {
|
// Only outlined while the options are expanded.
|
||||||
|
Item {
|
||||||
|
readonly property bool _framed: root.mullvadOptionsOpen && root.mullvadInstalled
|
||||||
width: parent.width
|
width: parent.width
|
||||||
radius: 0
|
implicitHeight: mullvadSectionColumn.implicitHeight
|
||||||
color: Colors.background
|
|
||||||
border.color: Colors.muted
|
|
||||||
border.width: 1
|
|
||||||
implicitHeight: mullvadSectionColumn.implicitHeight + 16
|
|
||||||
clip: true
|
|
||||||
// Without this, the card snapped to its new size
|
// Without this, the card snapped to its new size
|
||||||
// instantly while the popup around it was still
|
// instantly while the popup around it was still
|
||||||
// animating open, so the border visibly lagged
|
// animating open, so the border visibly lagged
|
||||||
@@ -873,12 +1148,28 @@ Item {
|
|||||||
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outline offset outside the content rather than drawn on
|
||||||
|
// its own edge — same as Volume.qml's DeviceRow hover —
|
||||||
|
// so the row doesn't shift when the options open.
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: -3
|
||||||
|
radius: 0
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Colors.foreground
|
||||||
|
border.width: parent._framed ? 1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipped separately so the outline above isn't.
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: mullvadSectionColumn
|
id: mullvadSectionColumn
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
@@ -1041,6 +1332,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProviderRow {
|
ProviderRow {
|
||||||
label: "ProtonVPN"
|
label: "ProtonVPN"
|
||||||
@@ -1056,7 +1348,7 @@ Item {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: "Connecting one disconnects the other — only one VPN tunnel at a time."
|
text: "Connecting one disconnects the others — only one VPN tunnel at a time."
|
||||||
font.family: Appearance.fontFamily
|
font.family: Appearance.fontFamily
|
||||||
font.pixelSize: Appearance.fontSizeSmall
|
font.pixelSize: Appearance.fontSizeSmall
|
||||||
color: Colors.muted
|
color: Colors.muted
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ RowLayout {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: Hyprland.dispatch("workspace " + modelData.name)
|
// Hyprland's Lua config expects Lua syntax from dispatch
|
||||||
|
onClicked: Hyprland.dispatch("hl.dsp.focus({ workspace = " + JSON.stringify(modelData.id > 0 ? modelData.id : modelData.name) + " })")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,13 @@ PanelWindow {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
exclusiveZone: 0
|
exclusiveZone: 0
|
||||||
|
|
||||||
// Top-right, just under the bar — same fixed distance from the bar and
|
// Bottom-center: anchoring only the bottom edge lets the layer shell
|
||||||
// screen edge (Appearance.spacing) that every popup opened from the bar
|
// center it horizontally.
|
||||||
// itself uses, so this lines up with them instead of floating
|
anchors { bottom: true }
|
||||||
// bottom-center.
|
margins.bottom: Appearance.spacing * 4
|
||||||
anchors { top: true; right: true }
|
|
||||||
margins.top: Appearance.spacing
|
|
||||||
margins.right: Appearance.spacing
|
|
||||||
|
|
||||||
implicitWidth: 272
|
implicitWidth: 300
|
||||||
implicitHeight: 54
|
implicitHeight: 60
|
||||||
|
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.namespace: "quickshell:osd"
|
WlrLayershell.namespace: "quickshell:osd"
|
||||||
@@ -49,13 +46,13 @@ PanelWindow {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 14
|
anchors.margins: 15
|
||||||
spacing: 12
|
spacing: 13
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
font.family: Appearance.iconFontFamilyRounded
|
font.family: Appearance.iconFontFamilyRounded
|
||||||
font.variableAxes: Appearance.iconFontAxes
|
font.variableAxes: Appearance.iconFontAxes
|
||||||
font.pixelSize: 18
|
font.pixelSize: 21
|
||||||
color: Colors.foreground
|
color: Colors.foreground
|
||||||
text: {
|
text: {
|
||||||
const audio = osd.sink ? osd.sink.audio : null
|
const audio = osd.sink ? osd.sink.audio : null
|
||||||
@@ -67,7 +64,7 @@ PanelWindow {
|
|||||||
Item {
|
Item {
|
||||||
id: slider
|
id: slider
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: 16
|
implicitHeight: 18
|
||||||
|
|
||||||
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
|
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
|
||||||
readonly property bool enabled: !!osd.sink
|
readonly property bool enabled: !!osd.sink
|
||||||
@@ -76,7 +73,7 @@ PanelWindow {
|
|||||||
id: track
|
id: track
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 4
|
height: 5
|
||||||
radius: 0
|
radius: 0
|
||||||
color: Colors.muted
|
color: Colors.muted
|
||||||
opacity: 0.35
|
opacity: 0.35
|
||||||
@@ -85,15 +82,15 @@ PanelWindow {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
width: track.width * Math.max(0, Math.min(1, slider.value))
|
width: track.width * Math.max(0, Math.min(1, slider.value))
|
||||||
height: 4
|
height: 5
|
||||||
radius: 0
|
radius: 0
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
opacity: slider.enabled ? 1 : 0.4
|
opacity: slider.enabled ? 1 : 0.4
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 12
|
width: 14
|
||||||
height: 12
|
height: 14
|
||||||
radius: 6
|
radius: 7
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
opacity: slider.enabled ? 1 : 0.4
|
opacity: slider.enabled ? 1 : 0.4
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -113,10 +110,10 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.preferredWidth: 32
|
Layout.preferredWidth: 38
|
||||||
horizontalAlignment: Text.AlignRight
|
horizontalAlignment: Text.AlignRight
|
||||||
font.family: Appearance.fontFamily
|
font.family: Appearance.fontFamily
|
||||||
font.pixelSize: Appearance.fontSizeSmall
|
font.pixelSize: 13
|
||||||
color: Colors.muted
|
color: Colors.muted
|
||||||
text: Math.round(slider.value * 100) + "%"
|
text: Math.round(slider.value * 100) + "%"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,21 @@ if [ $have_mullvad -eq 0 ] && [ $have_proton -eq 0 ]; then
|
|||||||
warn "neither Mullvad nor ProtonVPN CLI is installed — VpnIndicator.qml will show both as 'Not installed'"
|
warn "neither Mullvad nor ProtonVPN CLI is installed — VpnIndicator.qml will show both as 'Not installed'"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Keep in sync with homeWgConnection in VpnIndicator.qml.
|
||||||
|
if nmcli -g connection.type connection show unifi-home >/dev/null 2>&1; then
|
||||||
|
pass "NetworkManager profile 'unifi-home' exists — used by VpnIndicator.qml (Home/UniFi row)"
|
||||||
|
else
|
||||||
|
warn "no NetworkManager profile 'unifi-home' — import one from the VPN panel's 'Add config' button (optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The 'Add config' button's file chooser (scripts/PickFile.py) talks to
|
||||||
|
# xdg-desktop-portal over D-Bus via PyGObject.
|
||||||
|
if python3 -c "from gi.repository import Gio" >/dev/null 2>&1; then
|
||||||
|
pass "PyGObject available — used by PickFile.py (VPN 'Add config' file chooser)"
|
||||||
|
else
|
||||||
|
warn "PyGObject (python-gobject) missing — the VPN panel's 'Add config' button can't open a file chooser"
|
||||||
|
fi
|
||||||
|
|
||||||
# --- 4. VPN daemons + functional status (Mullvad/ProtonVPN/Tailscale) -------
|
# --- 4. VPN daemons + functional status (Mullvad/ProtonVPN/Tailscale) -------
|
||||||
# The exclusivity logic (VpnExclusivity.qml backing off Tailscale when a
|
# The exclusivity logic (VpnExclusivity.qml backing off Tailscale when a
|
||||||
# commercial VPN connects) is pure QML and ships with the dotfiles — it
|
# commercial VPN connects) is pure QML and ships with the dotfiles — it
|
||||||
|
|||||||
84
dotfiles/.config/quickshell/scripts/ImportUnifiWireguard.sh
Executable file
84
dotfiles/.config/quickshell/scripts/ImportUnifiWireguard.sh
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Imports a UniFi WireGuard VPN Server client config into NetworkManager
|
||||||
|
# under the profile name VpnIndicator.qml expects (homeWgConnection,
|
||||||
|
# "unifi-home" by default), so the bar's VPN panel can toggle it.
|
||||||
|
#
|
||||||
|
# Get the .conf from UniFi Network: Settings → VPN → VPN Server → your
|
||||||
|
# WireGuard server → Add Client → Download.
|
||||||
|
#
|
||||||
|
# Usage: ./ImportUnifiWireguard.sh <client.conf> [profile-name] [--replace]
|
||||||
|
# ./ImportUnifiWireguard.sh --pick [profile-name] [--replace]
|
||||||
|
#
|
||||||
|
# --pick opens a file chooser (PickFile.py) instead of taking a path — this
|
||||||
|
# is what the VPN panel's "Add config" button runs. Exits 3 if cancelled.
|
||||||
|
#
|
||||||
|
# NetworkManager names both the profile and the interface after the file's
|
||||||
|
# basename, so the config is copied to <profile-name>.conf first (interface
|
||||||
|
# names max out at 15 chars). The private key ends up in a root-only file
|
||||||
|
# under /etc/NetworkManager/system-connections — delete the downloaded
|
||||||
|
# .conf afterwards.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
replace=0
|
||||||
|
pick=0
|
||||||
|
args=()
|
||||||
|
for a in "$@"; do
|
||||||
|
case "$a" in
|
||||||
|
--replace) replace=1 ;;
|
||||||
|
--pick) pick=1 ;;
|
||||||
|
-h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
|
*) args+=("$a") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $pick -eq 1 ]; then
|
||||||
|
conf=""
|
||||||
|
name="${args[0]:-unifi-home}"
|
||||||
|
rc=0
|
||||||
|
conf=$("$(dirname "$0")/PickFile.py" "Select UniFi WireGuard config" "*.conf") || rc=$?
|
||||||
|
if [ $rc -eq 1 ]; then echo "Cancelled" >&2; exit 3; fi
|
||||||
|
if [ $rc -ne 0 ]; then echo "Couldn't open a file chooser" >&2; exit 1; fi
|
||||||
|
else
|
||||||
|
conf="${args[0]:-}"
|
||||||
|
name="${args[1]:-unifi-home}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$conf" ] || [ ! -f "$conf" ]; then
|
||||||
|
echo "Usage: $0 <client.conf> [profile-name] [--replace]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! [[ "$name" =~ ^[A-Za-z0-9_-]{1,15}$ ]]; then
|
||||||
|
echo "Profile name must be 1-15 chars of [A-Za-z0-9_-] (it doubles as the interface name)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if nmcli -g connection.id connection show "$name" >/dev/null 2>&1; then
|
||||||
|
if [ $replace -eq 1 ]; then
|
||||||
|
nmcli connection delete "$name" >/dev/null
|
||||||
|
else
|
||||||
|
echo "A NetworkManager profile named '$name' already exists — rerun with --replace to overwrite it" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
install -m 600 "$conf" "$tmpdir/$name.conf"
|
||||||
|
|
||||||
|
if ! grep -qi '^\[Interface\]' "$tmpdir/$name.conf" || ! grep -qi '^\[Peer\]' "$tmpdir/$name.conf"; then
|
||||||
|
echo "$(basename "$conf") doesn't look like a WireGuard config" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nmcli connection import type wireguard file "$tmpdir/$name.conf"
|
||||||
|
# Only ever brought up deliberately from the bar — never on boot or on
|
||||||
|
# joining a network, where it'd silently fight Tailscale/Mullvad/ProtonVPN.
|
||||||
|
nmcli connection modify "$name" connection.autoconnect no
|
||||||
|
# Import itself defaults to autoconnect=yes, so NetworkManager may already
|
||||||
|
# have started bringing it up in the moment before the modify above.
|
||||||
|
nmcli connection down "$name" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Imported as '$name'. Toggle it from the VPN panel, or: nmcli connection up $name"
|
||||||
|
echo "You can now delete $conf"
|
||||||
74
dotfiles/.config/quickshell/scripts/PickFile.py
Executable file
74
dotfiles/.config/quickshell/scripts/PickFile.py
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Opens the xdg-desktop-portal file chooser and prints the picked file's
|
||||||
|
# path. Quickshell has no usable file dialog of its own (QtQuick.Dialogs'
|
||||||
|
# fallback renders inside the calling window, i.e. the tiny bar popup), and
|
||||||
|
# no zenity/kdialog is installed — the portal goes through
|
||||||
|
# xdg-desktop-portal-gtk, which is.
|
||||||
|
#
|
||||||
|
# Usage: PickFile.py [title] [glob ...]
|
||||||
|
# Exits 0 and prints the path on a pick, 1 on cancel, 2 on error.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
|
from gi.repository import Gio, GLib
|
||||||
|
|
||||||
|
title = sys.argv[1] if len(sys.argv) > 1 else "Open File"
|
||||||
|
globs = sys.argv[2:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||||
|
except GLib.Error as e:
|
||||||
|
print(f"No session bus: {e.message}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
# The portal answers on a Request object whose path is derived from our
|
||||||
|
# unique bus name and handle_token, so subscribe to it before calling —
|
||||||
|
# otherwise a fast response could arrive before we're listening.
|
||||||
|
token = f"quickshell_{os.getpid()}"
|
||||||
|
sender = bus.get_unique_name().lstrip(":").replace(".", "_")
|
||||||
|
handle = f"/org/freedesktop/portal/desktop/request/{sender}/{token}"
|
||||||
|
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
result = {"code": 2, "uris": []}
|
||||||
|
|
||||||
|
|
||||||
|
def on_response(_conn, _sender, _path, _iface, _signal, params):
|
||||||
|
code, results = params.unpack()
|
||||||
|
result["code"] = code
|
||||||
|
result["uris"] = results.get("uris", [])
|
||||||
|
loop.quit()
|
||||||
|
|
||||||
|
|
||||||
|
bus.signal_subscribe("org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
|
||||||
|
"Response", handle, None, Gio.DBusSignalFlags.NONE, on_response)
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"handle_token": GLib.Variant("s", token),
|
||||||
|
"modal": GLib.Variant("b", True),
|
||||||
|
}
|
||||||
|
filters = []
|
||||||
|
if globs:
|
||||||
|
filters.append((", ".join(globs), [(0, g) for g in globs]))
|
||||||
|
filters.append(("All files", [(0, "*")]))
|
||||||
|
options["filters"] = GLib.Variant("a(sa(us))", filters)
|
||||||
|
downloads = os.path.expanduser("~/Downloads")
|
||||||
|
if os.path.isdir(downloads):
|
||||||
|
options["current_folder"] = GLib.Variant("ay", downloads.encode() + b"\0")
|
||||||
|
|
||||||
|
try:
|
||||||
|
bus.call_sync("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
|
||||||
|
"org.freedesktop.portal.FileChooser", "OpenFile",
|
||||||
|
GLib.Variant("(ssa{sv})", ("", title, options)),
|
||||||
|
GLib.VariantType("(o)"), Gio.DBusCallFlags.NONE, -1, None)
|
||||||
|
except GLib.Error as e:
|
||||||
|
print(f"File chooser portal unavailable: {e.message}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
loop.run()
|
||||||
|
|
||||||
|
# Response codes: 0 = picked, 1 = cancelled, 2 = other failure.
|
||||||
|
if result["code"] != 0 or not result["uris"]:
|
||||||
|
sys.exit(1 if result["code"] == 1 else 2)
|
||||||
|
print(unquote(urlparse(result["uris"][0]).path))
|
||||||
50
dotfiles/.config/quickshell/scripts/sync-dotfiles.sh
Executable file
50
dotfiles/.config/quickshell/scripts/sync-dotfiles.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Run by update-system.sh inside the update terminal, before the system
|
||||||
|
# update — pulls the dotfiles repo and re-stows it so both machines pick up
|
||||||
|
# config changes on every update.
|
||||||
|
#
|
||||||
|
# Never fails the update: a pull that can't fast-forward (local commits or
|
||||||
|
# uncommitted changes in the way) or a stow conflict just prints a warning
|
||||||
|
# and leaves things as they were, then the system update carries on.
|
||||||
|
|
||||||
|
DOTFILES="$HOME/Projects/dotfiles"
|
||||||
|
|
||||||
|
warn() { printf '\033[1;33m%s\033[0m\n' "$*"; }
|
||||||
|
info() { printf '\033[1;36m%s\033[0m\n' "$*"; }
|
||||||
|
|
||||||
|
if [ ! -d "$DOTFILES/.git" ]; then
|
||||||
|
warn "Dotfiles repo not found at $DOTFILES — skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Pulling dotfiles..."
|
||||||
|
# Fetch and merge separately so the warning says which one went wrong — the
|
||||||
|
# remote is SSH via Bitwarden's agent, so a failed fetch is almost always
|
||||||
|
# Bitwarden being closed or locked.
|
||||||
|
if ! git -C "$DOTFILES" fetch; then
|
||||||
|
warn "Couldn't reach the dotfiles remote — is Bitwarden open and unlocked? Skipping stow."
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# --ff-only so a diverged branch never produces a surprise merge commit;
|
||||||
|
# sort that out by hand instead.
|
||||||
|
if ! git -C "$DOTFILES" merge --ff-only; then
|
||||||
|
warn "Couldn't fast-forward dotfiles (local changes or diverged branch) — skipping stow."
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v stow &> /dev/null; then
|
||||||
|
warn "stow not installed — skipping."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Applying dotfiles with stow..."
|
||||||
|
# -R restows: drops links for files removed from the repo and adds new ones.
|
||||||
|
if stow -R -d "$DOTFILES" -t "$HOME" dotfiles; then
|
||||||
|
info "Dotfiles applied."
|
||||||
|
else
|
||||||
|
warn "stow hit a conflict — nothing was changed. Fix it and re-run:"
|
||||||
|
warn " stow -R -d $DOTFILES -t ~ dotfiles"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
@@ -58,5 +58,9 @@ fi
|
|||||||
# any output/errors with it.
|
# any output/errors with it.
|
||||||
WAIT_PROMPT="echo; read -p 'Update finished — press Enter to close...'"
|
WAIT_PROMPT="echo; read -p 'Update finished — press Enter to close...'"
|
||||||
|
|
||||||
foot -T update bash -c "$BANNER; $CMD; $WAIT_PROMPT"
|
# Pull + re-stow the dotfiles first, so config changes from the other machine
|
||||||
|
# land alongside the package update.
|
||||||
|
SYNC="$(printf '%q' "$(dirname "$0")/sync-dotfiles.sh")"
|
||||||
|
|
||||||
|
foot -T update bash -c "$BANNER; $SYNC; $CMD; $WAIT_PROMPT"
|
||||||
notify-send -i "$iDIR/profile-pic.jpg" -u low "$LABEL" 'has been updated.'
|
notify-send -i "$iDIR/profile-pic.jpg" -u low "$LABEL" 'has been updated.'
|
||||||
|
|||||||
5
dotfiles/.config/sunshine/.gitignore
vendored
Normal file
5
dotfiles/.config/sunshine/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Machine-local Sunshine state lives next to the stowed config; never commit it
|
||||||
|
credentials/
|
||||||
|
*.log
|
||||||
|
*.log.*
|
||||||
|
sunshine_state.json
|
||||||
47
dotfiles/.config/sunshine/apps.json
Normal file
47
dotfiles/.config/sunshine/apps.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"PATH": "$(PATH):$(HOME)/.local/bin"
|
||||||
|
},
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"name": "Desktop",
|
||||||
|
"image-path": "desktop.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Low Res Desktop",
|
||||||
|
"image-path": "desktop.png",
|
||||||
|
"prep-cmd": [
|
||||||
|
{
|
||||||
|
"do": "xrandr --output HDMI-1 --mode 1920x1080",
|
||||||
|
"undo": "xrandr --output HDMI-1 --mode 1920x1200"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Steam Big Picture",
|
||||||
|
"detached": [
|
||||||
|
"setsid steam steam://open/bigpicture"
|
||||||
|
],
|
||||||
|
"prep-cmd": [
|
||||||
|
{
|
||||||
|
"do": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-prep.sh",
|
||||||
|
"undo": "setsid steam steam://close/bigpicture"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image-path": "steam.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Steam Big Picture (TV)",
|
||||||
|
"detached": [
|
||||||
|
"setsid steam steam://open/bigpicture"
|
||||||
|
],
|
||||||
|
"prep-cmd": [
|
||||||
|
{
|
||||||
|
"do": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh",
|
||||||
|
"undo": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"image-path": "steam.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
dotfiles/.config/sunshine/scripts/steam-bigpicture-prep.sh
Executable file
17
dotfiles/.config/sunshine/scripts/steam-bigpicture-prep.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Run before Sunshine launches Steam Big Picture: focus the headless virtual
|
||||||
|
# monitor and park the cursor on it, so Big Picture (which spawns on whatever
|
||||||
|
# monitor currently has focus/the mouse) opens there instead of wherever the
|
||||||
|
# mouse happened to be.
|
||||||
|
set -e
|
||||||
|
hyprctl dispatch 'hl.dsp.focus({monitor = "HEADLESS-1"})'
|
||||||
|
hyprctl dispatch 'hl.dsp.cursor.move({x = 5344, y = 540})'
|
||||||
|
|
||||||
|
# Sunshine only flips the system default audio sink to sink-sunshine-stereo
|
||||||
|
# after the streaming session/client handshake completes (~500ms after this
|
||||||
|
# script runs and Steam is spawned) — see sunshine.log timestamps. If Steam's
|
||||||
|
# own audio (SDL) initializes in that window, it binds to whatever was
|
||||||
|
# default at the time (the real HDMI output) and never re-homes even after
|
||||||
|
# Sunshine's swap. Set it here first so it's already correct before Steam
|
||||||
|
# ever launches; Sunshine's own (redundant) swap right after is harmless.
|
||||||
|
pactl set-default-sink sink-sunshine-stereo || true
|
||||||
18
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh
Executable file
18
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Run before Sunshine launches Steam Big Picture for the 4K TV target.
|
||||||
|
# Switches the headless virtual monitor from its default 1728x1080@60
|
||||||
|
# (used for the Steam Deck target) up to 3840x2160@60, then focuses it
|
||||||
|
# and parks the cursor there so Big Picture opens on it. Reverted by
|
||||||
|
# steam-bigpicture-tv-undo.sh once the stream ends.
|
||||||
|
set -e
|
||||||
|
# NOTE: this Hyprland build uses the Lua config layer, which rejects the
|
||||||
|
# usual `hyprctl keyword monitor ...` syntax ("keyword can't work with
|
||||||
|
# non-legacy parsers. Use eval.") — hl.monitor() via `hyprctl eval` is the
|
||||||
|
# runtime equivalent of the hl.monitor() call in layout/monitors.lua.
|
||||||
|
hyprctl eval 'hl.monitor({ output = "HEADLESS-1", mode = "3840x2160@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })'
|
||||||
|
hyprctl dispatch 'hl.dsp.focus({monitor = "HEADLESS-1"})'
|
||||||
|
hyprctl dispatch 'hl.dsp.cursor.move({x = 6400, y = 1080})'
|
||||||
|
|
||||||
|
# See steam-bigpicture-prep.sh for why this runs here instead of relying on
|
||||||
|
# Sunshine's own post-handshake audio-sink swap.
|
||||||
|
pactl set-default-sink sink-sunshine-stereo || true
|
||||||
6
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh
Executable file
6
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Undo for steam-bigpicture-tv-prep.sh: close Big Picture and restore
|
||||||
|
# HEADLESS-1 to its default mode (the one the Steam Deck target expects).
|
||||||
|
set -e
|
||||||
|
setsid steam steam://close/bigpicture
|
||||||
|
hyprctl eval 'hl.monitor({ output = "HEADLESS-1", mode = "1728x1080@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })'
|
||||||
5
dotfiles/.config/sunshine/sunshine.conf
Normal file
5
dotfiles/.config/sunshine/sunshine.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
audio_sink = sink-sunshine-stereo
|
||||||
|
csrf_allowed_origins = https://xol.twhomelab.local:47990
|
||||||
|
nvenc_preset = 4
|
||||||
|
output_name = HEADLESS-1
|
||||||
|
global_prep_cmd = [{"do":"/home/thomas/.config/hypr/scripts/SunshineIdle.sh start","undo":"/home/thomas/.config/hypr/scripts/SunshineIdle.sh stop"}]
|
||||||
@@ -9,5 +9,10 @@
|
|||||||
# ~/.local/share/plex/Logs/Plex.log). This dir is first on $PATH, so it
|
# ~/.local/share/plex/Logs/Plex.log). This dir is first on $PATH, so it
|
||||||
# shadows /usr/bin/Plex for both a terminal launch and the .desktop entry
|
# shadows /usr/bin/Plex for both a terminal launch and the .desktop entry
|
||||||
# (Exec=Plex is PATH-resolved) without touching the global env var.
|
# (Exec=Plex is PATH-resolved) without touching the global env var.
|
||||||
|
# QT_QPA_PLATFORM: hypr env sets "wayland;xcb"; under Wayland the bundled
|
||||||
|
# QtWebEngine never runs the web client and the window stays blank.
|
||||||
unset QT_QUICK_CONTROLS_STYLE
|
unset QT_QUICK_CONTROLS_STYLE
|
||||||
|
export QT_QPA_PLATFORM=xcb
|
||||||
|
# Unset in the session; without it the X11 cursor in Plex is huge.
|
||||||
|
export XCURSOR_SIZE=24
|
||||||
exec /opt/plex-desktop/Plex.sh "$@"
|
exec /opt/plex-desktop/Plex.sh "$@"
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
# same filename, so this is picked up by app launchers instead automatically
|
# same filename, so this is picked up by app launchers instead automatically
|
||||||
# — no association/mimeapps changes needed).
|
# — no association/mimeapps changes needed).
|
||||||
#
|
#
|
||||||
# Only change from upstream: Exec unsets QT_QUICK_CONTROLS_STYLE and calls
|
# QT_QPA_PLATFORM=xcb is kept from upstream: hypr env sets "wayland;xcb", and
|
||||||
|
# under Wayland the bundled QtWebEngine never runs the web client (blank
|
||||||
|
# window, no [Web] lines in Plex.log). XCURSOR_SIZE=24 is also kept from
|
||||||
|
# upstream: it is unset in the session, so the X11 cursor comes out huge.
|
||||||
|
#
|
||||||
|
# Change from upstream: Exec unsets QT_QUICK_CONTROLS_STYLE and calls
|
||||||
# the package's Plex.sh directly, bypassing $PATH entirely. Plex Desktop
|
# the package's Plex.sh directly, bypassing $PATH entirely. Plex Desktop
|
||||||
# bundles its own private Qt/QML runtime that doesn't ship the
|
# bundles its own private Qt/QML runtime that doesn't ship the
|
||||||
# "org.hyprland.style" QQC2 style hypr/core/env.lua sets globally for other
|
# "org.hyprland.style" QQC2 style hypr/core/env.lua sets globally for other
|
||||||
@@ -17,7 +22,7 @@
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=Plex
|
Name=Plex
|
||||||
Comment=Plex client for desktop computers
|
Comment=Plex client for desktop computers
|
||||||
Exec=env -u QT_QUICK_CONTROLS_STYLE /opt/plex-desktop/Plex.sh
|
Exec=env -u QT_QUICK_CONTROLS_STYLE QT_QPA_PLATFORM=xcb XCURSOR_SIZE=24 /opt/plex-desktop/Plex.sh
|
||||||
Icon=tv.plex.PlexDesktop
|
Icon=tv.plex.PlexDesktop
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;Player;X-Media;
|
Categories=AudioVideo;Player;X-Media;
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
Host gavin.twhomelab.local
|
||||||
|
HostName gavin.twhomelab.local
|
||||||
|
User thoams
|
||||||
|
|
||||||
Host 192.168.1.87
|
Host 192.168.1.87
|
||||||
HostName 192.168.1.87
|
HostName 192.168.1.87
|
||||||
User thoams
|
User thoams
|
||||||
|
|||||||
176
system/packages-review.txt
Normal file
176
system/packages-review.txt
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# New-install package review — generated from this machine on 2026-10-11
|
||||||
|
#
|
||||||
|
# HOW TO REVIEW
|
||||||
|
# - Keep a line -> it gets installed
|
||||||
|
# - Delete the line, or put a "#" at the start -> it gets skipped
|
||||||
|
# - Everything after "#" on a line is just a description for you
|
||||||
|
#
|
||||||
|
# Notes I flagged (look for "!!" below):
|
||||||
|
# - Hardware-specific packages (CPU microcode, NVIDIA driver) only make
|
||||||
|
# sense on matching hardware — the script can auto-detect these instead.
|
||||||
|
# - Some packages are leftovers from other setups (e.g. XFCE, waybar,
|
||||||
|
# orbit-wifi which you removed in commit 88484f8).
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
## ---------------- Official repos (pacman) ----------------
|
||||||
|
adw-gtk-theme # Unofficial GTK 3 port of the libadwaita theme
|
||||||
|
amd-ucode # Microcode update image for AMD CPUs !! AMD CPU only — auto-detect?
|
||||||
|
awww # An Answer to your Wayland Wallpaper Woes
|
||||||
|
baobab # A graphical directory tree analyzer
|
||||||
|
base # Minimal package set to define a basic Arch Linux installation
|
||||||
|
base-devel # Basic tools to build Arch Linux packages
|
||||||
|
blueman # GTK+ Bluetooth Manager
|
||||||
|
bluez # Daemons for the bluetooth protocol stack
|
||||||
|
bluez-utils # Development and debugging utilities for the bluetooth protocol stack
|
||||||
|
btop # A monitor of system resources, bpytop ported to C++
|
||||||
|
dolphin # KDE File Manager
|
||||||
|
dunst # Customizable and lightweight notification-daemon !! you use swaync for notifications
|
||||||
|
efibootmgr # Linux user-space application to modify the EFI Boot Manager
|
||||||
|
fastfetch # A feature-rich and performance oriented neofetch like system information tool
|
||||||
|
ffmpeg # Complete solution to record, convert and stream audio and video
|
||||||
|
filezilla # Fast and reliable FTP, FTPS and SFTP client
|
||||||
|
firefox # Fast, Private & Safe Web Browser
|
||||||
|
flatpak # Linux application sandboxing and distribution framework (formerly xdg-app)
|
||||||
|
foot # Fast, lightweight, and minimalistic Wayland terminal emulator
|
||||||
|
fzf # Command-line fuzzy finder
|
||||||
|
galculator # GTK based scientific calculator
|
||||||
|
#garcon # Freedesktop.org compliant menu library !! XFCE desktop leftover?
|
||||||
|
gdu # Fast disk usage analyzer
|
||||||
|
git # the fast distributed version control system
|
||||||
|
gnome-calendar # Simple and beautiful calendar application designed to perfectly fit the GNOME desktop
|
||||||
|
gnome-disk-utility # Disk Management Utility for GNOME
|
||||||
|
gnome-font-viewer # A font viewer utility for GNOME
|
||||||
|
gnome-keyring # Stores passwords and encryption keys
|
||||||
|
gnome-text-editor # A simple text editor for the GNOME desktop
|
||||||
|
grim # Screenshot utility for Wayland
|
||||||
|
gst-plugin-pipewire # Multimedia graph framework - pipewire plugin
|
||||||
|
gvfs # Virtual filesystem implementation for GIO
|
||||||
|
gvfs-smb # Virtual filesystem implementation for GIO - SMB/CIFS backend (Windows file sharing)
|
||||||
|
htop # Interactive process viewer
|
||||||
|
hypridle # hyprland’s idle daemon !! commented out in startup.lua
|
||||||
|
hyprland # a highly customizable dynamic tiling Wayland compositor
|
||||||
|
hyprlock # hyprland’s GPU-accelerated screen locking utility !! hypridle is commented out in startup.lua
|
||||||
|
hyprshot # Hyprland screenshot utility
|
||||||
|
imv # Image viewer for Wayland and X11
|
||||||
|
jq # Command-line JSON processor
|
||||||
|
kitty # A modern, hackable, featureful, OpenGL-based terminal emulator !! you use foot as terminal (kitty config not in dotfiles)
|
||||||
|
libgsf # Extensible I/O abstraction library for dealing with structured file formats
|
||||||
|
libmatroska # Matroska library
|
||||||
|
libpulse # A featureful, general-purpose sound server (client library)
|
||||||
|
libva-nvidia-driver # VA-API implementation that uses NVDEC as a backend !! NVIDIA GPU only — auto-detect?
|
||||||
|
linux # The Linux kernel and modules
|
||||||
|
linux-firmware # Firmware files for Linux - Default set
|
||||||
|
mkinitcpio # Modular initramfs image creation utility
|
||||||
|
mkvtoolnix-cli # Set of tools to create, edit and inspect Matroska files
|
||||||
|
mpv # a free, open source, and cross-platform media player
|
||||||
|
mullvad-vpn-daemon # Mullvad VPN client (daemon and CLI)
|
||||||
|
nano # Pico editor clone with enhancements
|
||||||
|
network-manager-applet # Applet for managing network connections
|
||||||
|
networkmanager # Network connection manager and user applications
|
||||||
|
noto-fonts # Google Noto TTF fonts
|
||||||
|
noto-fonts-cjk # Google Noto CJK fonts
|
||||||
|
noto-fonts-emoji # Google Noto Color Emoji font
|
||||||
|
noto-fonts-extra # Google Noto TTF fonts - additional variants
|
||||||
|
npm # JavaScript package manager
|
||||||
|
nss-mdns # glibc plugin providing host name resolution via mDNS
|
||||||
|
nvidia-open # NVIDIA open kernel modules !! NVIDIA GPU only — auto-detect?
|
||||||
|
pacman-contrib # Contributed scripts and tools for pacman systems
|
||||||
|
pam-u2f # Universal 2nd Factor (U2F) PAM authentication module from Yubico
|
||||||
|
papirus-icon-theme # Papirus icon theme
|
||||||
|
pipewire # Low-latency audio/video router and processor
|
||||||
|
pipewire-alsa # Low-latency audio/video router and processor - ALSA configuration
|
||||||
|
pipewire-jack # Low-latency audio/video router and processor - JACK replacement
|
||||||
|
pipewire-pulse # Low-latency audio/video router and processor - PulseAudio replacement
|
||||||
|
pnpm # Fast, disk space efficient package manager
|
||||||
|
polkit-kde-agent # Daemon providing a polkit authentication UI for KDE
|
||||||
|
postgresql # Sophisticated object-relational DBMS
|
||||||
|
postgresql-libs # Sophisticated object-relational DBMS - Client binaries and libraries
|
||||||
|
proton-vpn-cli # Official ProtonVPN CLI Linux app
|
||||||
|
qt5-wayland # Provides APIs for Wayland
|
||||||
|
qt6-quickeffectmaker # Qt Quick Effect Maker (QQEM) for creating and editing custom shader effects
|
||||||
|
qt6-wayland # Provides APIs for Wayland
|
||||||
|
rofi # A window switcher, application launcher and dmenu replacement
|
||||||
|
sddm # QML based X11 and Wayland display manager
|
||||||
|
slurp # Select a region in a Wayland compositor
|
||||||
|
smartmontools # Control and monitor S.M.A.R.T. enabled ATA and SCSI Hard Drives
|
||||||
|
steam # Valve's digital software delivery system
|
||||||
|
stow # Manage installation of multiple softwares in the same directory tree
|
||||||
|
sudo # Give certain users the ability to run some commands as root
|
||||||
|
swaync # A simple GTK based notification daemon for Sway
|
||||||
|
tailscale # A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
thunar # Modern, fast and easy-to-use file manager for Xfce
|
||||||
|
thunar-archive-plugin # Adds archive operations to the Thunar file context menus
|
||||||
|
thunar-media-tags-plugin # Adds special features for media files to the Thunar File Manager
|
||||||
|
thunar-shares-plugin # Thunar plugin to quickly share a folder using Samba without requiring root access
|
||||||
|
thunar-volman # Automatic management of removable drives and media for Thunar
|
||||||
|
ttf-dejavu # Font family based on the Bitstream Vera Fonts with a wider range of characters
|
||||||
|
ttf-iosevka-nerd # Patched font Iosevka from nerd fonts library
|
||||||
|
ttf-jetbrains-mono # Typeface for developers, by JetBrains
|
||||||
|
ttf-jetbrains-mono-nerd # Patched font JetBrains Mono from nerd fonts library
|
||||||
|
ttf-liberation # Font family which aims at metric compatibility with Arial, Times New Roman, and Courier New
|
||||||
|
ttf-material-icons # Material Design icons by Google (deprecated classic Material Icons static style)
|
||||||
|
ttf-material-symbols-variable # Material Design icons by Google (Material Symbols - 4 axis variable)
|
||||||
|
ttf-noto-nerd # Patched font Noto from nerd fonts library
|
||||||
|
tumbler # Thumbnail service implementing the thumbnail management D-Bus specification
|
||||||
|
uv # An extremely fast Python package installer and resolver written in Rust
|
||||||
|
uwsm # A standalone Wayland session manager
|
||||||
|
vim # Vi Improved, a highly configurable, improved version of the vi text editor
|
||||||
|
#waybar # Highly customizable Wayland bar for Sway and Wlroots based compositors !! you've switched to quickshell (waybar is commented out in startup.lua)
|
||||||
|
webp-pixbuf-loader # WebM GDK Pixbuf Loader library
|
||||||
|
wget # Network utility to retrieve files from the web
|
||||||
|
wireplumber # Session / policy manager implementation for PipeWire
|
||||||
|
wofi # launcher for wlroots-based wayland compositors !! you also have rofi — need both?
|
||||||
|
wpa_supplicant # A utility providing key negotiation for WPA wireless networks
|
||||||
|
xarchiver # GTK frontend to various command line archivers
|
||||||
|
xdg-desktop-portal-hyprland # xdg-desktop-portal backend for hyprland
|
||||||
|
xdg-utils # Command line tools that assist applications with a variety of desktop integration tasks
|
||||||
|
xfce4-appfinder # Application launcher and finder !! XFCE desktop leftover?
|
||||||
|
xfce4-panel # Panel for the Xfce desktop environment !! XFCE desktop leftover?
|
||||||
|
xfce4-power-manager # Power Manager for Xfce !! XFCE desktop leftover?
|
||||||
|
xfce4-session # Xfce's session manager !! XFCE desktop leftover?
|
||||||
|
xfce4-settings # Xfce's Configuration System !! XFCE desktop leftover?
|
||||||
|
xfce4-terminal # A modern terminal emulator primarily for the Xfce desktop environment !! XFCE desktop leftover?
|
||||||
|
xfdesktop # Xfce's desktop manager !! XFCE desktop leftover?
|
||||||
|
xfwm4 # Xfce's window manager !! XFCE desktop leftover?
|
||||||
|
zram-generator # Systemd unit generator for zram devices
|
||||||
|
zsh # A very advanced and programmable command interpreter (shell) for UNIX
|
||||||
|
|
||||||
|
## ---------------- AUR (installed via yay) ----------------
|
||||||
|
bitwarden-bin # A secure and free password manager for all of your devices.
|
||||||
|
caelestia-shell # The desktop shell for the Caelestia dotfiles !! alternative quickshell config, not used by your shell.qml
|
||||||
|
flutter-bin # Cross platform widget toolkit for Dart (monolithic)
|
||||||
|
google-chrome # The popular web browser by Google (Stable Channel)
|
||||||
|
hyprmod # Native GTK4/libadwaita settings app for Hyprland
|
||||||
|
jellyfin-desktop-debug # Detached debugging symbols for jellyfin-desktop !! debug symbols, not needed
|
||||||
|
minecraft-launcher # Official Minecraft Launcher
|
||||||
|
orbit-wifi # A WiFi/Bluetooth manager for Wayland with glassmorphism UI !! removed from your config in commit 88484f8
|
||||||
|
plex-desktop # Plex desktop client for linux
|
||||||
|
plexamp-bin # Modern music client for Plex.(Prebuilt version.Use system-wide electron)
|
||||||
|
puddletag # An audio tag editor for GNU/Linux
|
||||||
|
pwvucontrol # Pipewire volume control for GNOME
|
||||||
|
python-pywal # Generate and change colorschemes on the fly
|
||||||
|
raw-thumbnailer # A lightweight and fast raw image thumbnailer that can be used by file managers.
|
||||||
|
seafile-client # GUI client for synchronizing your local files with seafile server
|
||||||
|
sunshine # Self-hosted game stream host for Moonlight
|
||||||
|
svt-av1-essential-git # A no-nonsense SVT-AV1 encoder fork with sensible defaults and Quality of Life improvements !! -git build, check if still needed
|
||||||
|
ttf-icomoon-feather # Icomoon feather font
|
||||||
|
tumbler-extra-thumbnailers # Customized thumbnailers for Tumbler
|
||||||
|
turritopsis # Desktop Jellyfin client with mpv playback
|
||||||
|
vesktop # A standalone Electron-based Discord app with Vencord & improved Linux support
|
||||||
|
visual-studio-code-bin # Visual Studio Code (vscode)
|
||||||
|
waypaper # GUI wallpaper setter for Wayland and Xorg window managers. It works as a frontend for popular wallpaper backends like swaybg, swww, wallutils, hyprpaper and feh.
|
||||||
|
yay # Yet another yogurt. Pacman wrapper and AUR helper written in go. !! bootstrapped by the script itself anyway
|
||||||
|
yay-debug # Detached debugging symbols for yay !! debug symbols, not needed
|
||||||
|
zen-browser-bin # Official package for Zen, a privacy-focused, feature packed Firefox-based web browser
|
||||||
|
|
||||||
|
## ---------------- Flatpak (Flathub) ----------------
|
||||||
|
com.makemkv.MakeMKV # MakeMKV
|
||||||
|
org.jellyfin.JellyfinDesktop # Jellyfin Desktop
|
||||||
|
|
||||||
|
## ---------------- Manually installed binaries (~/.local/bin) ----------------
|
||||||
|
## These aren't from a package manager; I'd install them via their official installers.
|
||||||
|
claude # Claude Code CLI (official install script)
|
||||||
|
oh-my-posh # Prompt theme engine (official install script; your theme is already in dotfiles)
|
||||||
|
ffmpeg # Static ffmpeg build — !! duplicate, ffmpeg is also installed via pacman above
|
||||||
|
python3.13 # Standalone Python 3.13 — !! probably not needed, uv can provide Python
|
||||||
197
system/packages.txt
Normal file
197
system/packages.txt
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Packages installed by bootstrap.sh on a new machine.
|
||||||
|
#
|
||||||
|
# - One package per line; anything after "#" is a description.
|
||||||
|
# - Lines starting with "#" are skipped (left here for reference).
|
||||||
|
# - [section] lines tell bootstrap.sh which installer to use;
|
||||||
|
# [services] lists systemd units to enable.
|
||||||
|
#
|
||||||
|
# Not listed here (bootstrap.sh handles them itself):
|
||||||
|
# - CPU microcode and GPU drivers: auto-detected from the hardware
|
||||||
|
# - yay: bootstrapped from the AUR before the [aur] section
|
||||||
|
# - svt-av1-essential-git: optional, also rebuilds ffmpeg against it
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
[pacman]
|
||||||
|
adw-gtk-theme # Unofficial GTK 3 port of the libadwaita theme
|
||||||
|
# amd-ucode # Microcode update image for AMD CPUs -- auto-detected by bootstrap.sh
|
||||||
|
awww # An Answer to your Wayland Wallpaper Woes
|
||||||
|
baobab # A graphical directory tree analyzer
|
||||||
|
base # Minimal package set to define a basic Arch Linux installation
|
||||||
|
base-devel # Basic tools to build Arch Linux packages
|
||||||
|
blueman # GTK+ Bluetooth Manager
|
||||||
|
bluez # Daemons for the bluetooth protocol stack
|
||||||
|
bluez-utils # Development and debugging utilities for the bluetooth protocol stack
|
||||||
|
btop # A monitor of system resources, bpytop ported to C++
|
||||||
|
dolphin # KDE File Manager
|
||||||
|
# dunst # Customizable and lightweight notification-daemon !! you use swaync for notifications
|
||||||
|
efibootmgr # Linux user-space application to modify the EFI Boot Manager
|
||||||
|
fastfetch # A feature-rich and performance oriented neofetch like system information tool
|
||||||
|
ffmpeg # Complete solution to record, convert and stream audio and video
|
||||||
|
filezilla # Fast and reliable FTP, FTPS and SFTP client
|
||||||
|
firefox # Fast, Private & Safe Web Browser
|
||||||
|
flatpak # Linux application sandboxing and distribution framework (formerly xdg-app)
|
||||||
|
foot # Fast, lightweight, and minimalistic Wayland terminal emulator
|
||||||
|
fzf # Command-line fuzzy finder
|
||||||
|
galculator # GTK based scientific calculator
|
||||||
|
# garcon # Freedesktop.org compliant menu library !! XFCE desktop leftover?
|
||||||
|
gdu # Fast disk usage analyzer
|
||||||
|
git # the fast distributed version control system
|
||||||
|
gnome-calendar # Simple and beautiful calendar application designed to perfectly fit the GNOME desktop
|
||||||
|
gnome-disk-utility # Disk Management Utility for GNOME
|
||||||
|
gnome-font-viewer # A font viewer utility for GNOME
|
||||||
|
gnome-keyring # Stores passwords and encryption keys
|
||||||
|
gnome-text-editor # A simple text editor for the GNOME desktop
|
||||||
|
grim # Screenshot utility for Wayland
|
||||||
|
gst-plugin-pipewire # Multimedia graph framework - pipewire plugin
|
||||||
|
gvfs # Virtual filesystem implementation for GIO
|
||||||
|
gvfs-smb # Virtual filesystem implementation for GIO - SMB/CIFS backend (Windows file sharing)
|
||||||
|
htop # Interactive process viewer
|
||||||
|
hypridle # hyprland’s idle daemon
|
||||||
|
hyprland # a highly customizable dynamic tiling Wayland compositor
|
||||||
|
hyprlock # hyprland’s GPU-accelerated screen locking utility
|
||||||
|
hyprshot # Hyprland screenshot utility
|
||||||
|
imv # Image viewer for Wayland and X11
|
||||||
|
jq # Command-line JSON processor
|
||||||
|
# kitty # A modern, hackable, featureful, OpenGL-based terminal emulator !! you use foot as terminal (kitty config not in dotfiles)
|
||||||
|
libgsf # Extensible I/O abstraction library for dealing with structured file formats
|
||||||
|
libmatroska # Matroska library
|
||||||
|
libpulse # A featureful, general-purpose sound server (client library)
|
||||||
|
# libva-nvidia-driver # VA-API implementation that uses NVDEC as a backend -- auto-detected by bootstrap.sh
|
||||||
|
linux # The Linux kernel and modules
|
||||||
|
linux-firmware # Firmware files for Linux - Default set
|
||||||
|
mkinitcpio # Modular initramfs image creation utility
|
||||||
|
mkvtoolnix-cli # Set of tools to create, edit and inspect Matroska files
|
||||||
|
mpv # a free, open source, and cross-platform media player
|
||||||
|
mullvad-vpn-daemon # Mullvad VPN client (daemon and CLI)
|
||||||
|
nano # Pico editor clone with enhancements
|
||||||
|
network-manager-applet # Applet for managing network connections
|
||||||
|
networkmanager # Network connection manager and user applications
|
||||||
|
noto-fonts # Google Noto TTF fonts
|
||||||
|
noto-fonts-cjk # Google Noto CJK fonts
|
||||||
|
noto-fonts-emoji # Google Noto Color Emoji font
|
||||||
|
noto-fonts-extra # Google Noto TTF fonts - additional variants
|
||||||
|
npm # JavaScript package manager
|
||||||
|
nss-mdns # glibc plugin providing host name resolution via mDNS
|
||||||
|
# nvidia-open # NVIDIA open kernel modules -- auto-detected by bootstrap.sh
|
||||||
|
pacman-contrib # Contributed scripts and tools for pacman systems
|
||||||
|
pam-u2f # Universal 2nd Factor (U2F) PAM authentication module from Yubico
|
||||||
|
papirus-icon-theme # Papirus icon theme
|
||||||
|
pipewire # Low-latency audio/video router and processor
|
||||||
|
pipewire-alsa # Low-latency audio/video router and processor - ALSA configuration
|
||||||
|
pipewire-jack # Low-latency audio/video router and processor - JACK replacement
|
||||||
|
pipewire-pulse # Low-latency audio/video router and processor - PulseAudio replacement
|
||||||
|
pnpm # Fast, disk space efficient package manager
|
||||||
|
polkit-kde-agent # Daemon providing a polkit authentication UI for KDE
|
||||||
|
postgresql # Sophisticated object-relational DBMS
|
||||||
|
postgresql-libs # Sophisticated object-relational DBMS - Client binaries and libraries
|
||||||
|
proton-vpn-cli # Official ProtonVPN CLI Linux app
|
||||||
|
qt5-wayland # Provides APIs for Wayland
|
||||||
|
qt6-quickeffectmaker # Qt Quick Effect Maker (QQEM) for creating and editing custom shader effects
|
||||||
|
qt6-wayland # Provides APIs for Wayland
|
||||||
|
rofi # A window switcher, application launcher and dmenu replacement
|
||||||
|
sddm # QML based X11 and Wayland display manager
|
||||||
|
slurp # Select a region in a Wayland compositor
|
||||||
|
smartmontools # Control and monitor S.M.A.R.T. enabled ATA and SCSI Hard Drives
|
||||||
|
steam # Valve's digital software delivery system
|
||||||
|
stow # Manage installation of multiple softwares in the same directory tree
|
||||||
|
sudo # Give certain users the ability to run some commands as root
|
||||||
|
swaync # A simple GTK based notification daemon for Sway
|
||||||
|
tailscale # A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
thunar # Modern, fast and easy-to-use file manager for Xfce
|
||||||
|
thunar-archive-plugin # Adds archive operations to the Thunar file context menus
|
||||||
|
thunar-media-tags-plugin # Adds special features for media files to the Thunar File Manager
|
||||||
|
thunar-shares-plugin # Thunar plugin to quickly share a folder using Samba without requiring root access
|
||||||
|
thunar-volman # Automatic management of removable drives and media for Thunar
|
||||||
|
ttf-dejavu # Font family based on the Bitstream Vera Fonts with a wider range of characters
|
||||||
|
ttf-iosevka-nerd # Patched font Iosevka from nerd fonts library
|
||||||
|
ttf-jetbrains-mono # Typeface for developers, by JetBrains
|
||||||
|
ttf-jetbrains-mono-nerd # Patched font JetBrains Mono from nerd fonts library
|
||||||
|
ttf-liberation # Font family which aims at metric compatibility with Arial, Times New Roman, and Courier New
|
||||||
|
ttf-material-icons # Material Design icons by Google (deprecated classic Material Icons static style)
|
||||||
|
ttf-material-symbols-variable # Material Design icons by Google (Material Symbols - 4 axis variable)
|
||||||
|
ttf-noto-nerd # Patched font Noto from nerd fonts library
|
||||||
|
tumbler # Thumbnail service implementing the thumbnail management D-Bus specification
|
||||||
|
uv # An extremely fast Python package installer and resolver written in Rust
|
||||||
|
uwsm # A standalone Wayland session manager
|
||||||
|
vim # Vi Improved, a highly configurable, improved version of the vi text editor
|
||||||
|
# waybar # Highly customizable Wayland bar for Sway and Wlroots based compositors !! you've switched to quickshell (waybar is commented out in startup.lua)
|
||||||
|
webp-pixbuf-loader # WebM GDK Pixbuf Loader library
|
||||||
|
wget # Network utility to retrieve files from the web
|
||||||
|
wireplumber # Session / policy manager implementation for PipeWire
|
||||||
|
# wofi # launcher for wlroots-based wayland compositors !! you also have rofi — need both?
|
||||||
|
wpa_supplicant # A utility providing key negotiation for WPA wireless networks
|
||||||
|
xarchiver # GTK frontend to various command line archivers
|
||||||
|
xdg-desktop-portal-hyprland # xdg-desktop-portal backend for hyprland
|
||||||
|
xdg-utils # Command line tools that assist applications with a variety of desktop integration tasks
|
||||||
|
# xfce4-appfinder # Application launcher and finder !! XFCE desktop leftover?
|
||||||
|
# xfce4-panel # Panel for the Xfce desktop environment !! XFCE desktop leftover?
|
||||||
|
# xfce4-power-manager # Power Manager for Xfce !! XFCE desktop leftover?
|
||||||
|
# xfce4-session # Xfce's session manager !! XFCE desktop leftover?
|
||||||
|
# xfce4-settings # Xfce's Configuration System !! XFCE desktop leftover?
|
||||||
|
# xfce4-terminal # A modern terminal emulator primarily for the Xfce desktop environment !! XFCE desktop leftover?
|
||||||
|
# xfdesktop # Xfce's desktop manager !! XFCE desktop leftover?
|
||||||
|
# xfwm4 # Xfce's window manager !! XFCE desktop leftover?
|
||||||
|
zram-generator # Systemd unit generator for zram devices
|
||||||
|
zsh # A very advanced and programmable command interpreter (shell) for UNIX
|
||||||
|
|
||||||
|
# Used by the dotfiles, previously only pulled in by caelestia-shell / waybar
|
||||||
|
brightnessctl # Backlight control (quickshell, hypr keybinds)
|
||||||
|
cliphist # Clipboard history (wl-paste watcher in startup.lua)
|
||||||
|
playerctl # Media key control
|
||||||
|
power-profiles-daemon # Power profiles (quickshell battery widget)
|
||||||
|
wl-clipboard # wl-copy / wl-paste
|
||||||
|
openssh # SSH client/server (sshd service, git over ssh)
|
||||||
|
avahi # mDNS daemon (avahi-daemon service, used by nss-mdns)
|
||||||
|
|
||||||
|
[aur]
|
||||||
|
bitwarden-bin # A secure and free password manager for all of your devices.
|
||||||
|
# caelestia-shell # The desktop shell for the Caelestia dotfiles !! alternative quickshell config, not used by your shell.qml
|
||||||
|
flutter-bin # Cross platform widget toolkit for Dart (monolithic)
|
||||||
|
google-chrome # The popular web browser by Google (Stable Channel)
|
||||||
|
hyprmod # Native GTK4/libadwaita settings app for Hyprland
|
||||||
|
# jellyfin-desktop-debug # Detached debugging symbols for jellyfin-desktop !! debug symbols, not needed
|
||||||
|
minecraft-launcher # Official Minecraft Launcher
|
||||||
|
# orbit-wifi # A WiFi/Bluetooth manager for Wayland with glassmorphism UI !! removed from your config in commit 88484f8
|
||||||
|
plex-desktop # Plex desktop client for linux
|
||||||
|
plexamp-bin # Modern music client for Plex.(Prebuilt version.Use system-wide electron)
|
||||||
|
puddletag # An audio tag editor for GNU/Linux
|
||||||
|
quickshell-git # The shell your bar/OSD run on (previously a caelestia-shell dependency)
|
||||||
|
pwvucontrol # Pipewire volume control for GNOME
|
||||||
|
python-pywal # Generate and change colorschemes on the fly
|
||||||
|
raw-thumbnailer # A lightweight and fast raw image thumbnailer that can be used by file managers.
|
||||||
|
seafile-client # GUI client for synchronizing your local files with seafile server
|
||||||
|
sunshine # Self-hosted game stream host for Moonlight
|
||||||
|
# svt-av1-essential-git # A no-nonsense SVT-AV1 encoder fork with sensible defaults and Quality of Life improvements -- optional step in bootstrap.sh (rebuilds ffmpeg)
|
||||||
|
ttf-icomoon-feather # Icomoon feather font
|
||||||
|
tumbler-extra-thumbnailers # Customized thumbnailers for Tumbler
|
||||||
|
turritopsis # Desktop Jellyfin client with mpv playback
|
||||||
|
vesktop # A standalone Electron-based Discord app with Vencord & improved Linux support
|
||||||
|
visual-studio-code-bin # Visual Studio Code (vscode)
|
||||||
|
waypaper # GUI wallpaper setter for Wayland and Xorg window managers. It works as a frontend for popular wallpaper backends like swaybg, swww, wallutils, hyprpaper and feh.
|
||||||
|
# yay # Yet another yogurt. Pacman wrapper and AUR helper written in go. !! bootstrapped by the script itself anyway
|
||||||
|
# yay-debug # Detached debugging symbols for yay !! debug symbols, not needed
|
||||||
|
zen-browser-bin # Official package for Zen, a privacy-focused, feature packed Firefox-based web browser
|
||||||
|
|
||||||
|
[flatpak]
|
||||||
|
com.makemkv.MakeMKV # MakeMKV
|
||||||
|
org.jellyfin.JellyfinDesktop # Jellyfin Desktop
|
||||||
|
|
||||||
|
[installer] # official install scripts, see bootstrap.sh
|
||||||
|
claude # Claude Code CLI (official install script)
|
||||||
|
oh-my-posh # Prompt theme engine (official install script; your theme is already in dotfiles)
|
||||||
|
# ffmpeg # Static ffmpeg build — !! duplicate, ffmpeg is also installed via pacman above
|
||||||
|
# python3.13 # Standalone Python 3.13 — !! probably not needed, uv can provide Python
|
||||||
|
|
||||||
|
[services] # enabled with systemctl, start on next boot
|
||||||
|
NetworkManager.service # Networking
|
||||||
|
bluetooth.service # Bluetooth
|
||||||
|
sddm.service # Login screen
|
||||||
|
sshd.service # SSH server
|
||||||
|
avahi-daemon.service # mDNS / .local hostnames
|
||||||
|
systemd-resolved.service # DNS resolver (used by the VPNs)
|
||||||
|
systemd-timesyncd.service # Clock sync
|
||||||
|
fstrim.timer # Weekly SSD trim
|
||||||
|
tailscaled.service # Tailscale
|
||||||
|
mullvad-daemon.service # Mullvad VPN
|
||||||
|
proton.VPN.service # Proton VPN
|
||||||
27
system/rebuild-ffmpeg.sh
Executable file
27
system/rebuild-ffmpeg.sh
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Rebuilds Arch's ffmpeg package from source so it links against the installed
|
||||||
|
# svt-av1-essential-git instead of the repo svt-av1 it was built with.
|
||||||
|
#
|
||||||
|
# Re-run this after a `pacman -Syu` that updates ffmpeg, otherwise the repo
|
||||||
|
# build replaces this one.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if ! pacman -Qq svt-av1-essential-git >/dev/null 2>&1; then
|
||||||
|
echo "svt-av1-essential-git is not installed; nothing to rebuild against." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
build="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$build"' EXIT
|
||||||
|
|
||||||
|
git clone --depth 1 https://gitlab.archlinux.org/archlinux/packaging/packages/ffmpeg.git "$build/ffmpeg"
|
||||||
|
cd "$build/ffmpeg"
|
||||||
|
|
||||||
|
# The PKGBUILD verifies a signed ffmpeg tag; makepkg won't fetch the keys itself
|
||||||
|
mapfile -t keys < <(source ./PKGBUILD && printf '%s\n' "${validpgpkeys[@]}")
|
||||||
|
if (( ${#keys[@]} )); then
|
||||||
|
gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys "${keys[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No --needed: the version matches what's installed, we want it replaced anyway
|
||||||
|
makepkg --syncdeps --install --cleanbuild --noconfirm
|
||||||
Reference in New Issue
Block a user