Add the installer, uninstaller, and generated docs
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
[Manager]
|
||||
DefaultTimeoutStopSec=5s
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Use the Vfio to Integrated trick to turn off NVIDIA dgpu when in integrated mode
|
||||
# without needing to restart the computer. This is needed because computers like the Asus G14
|
||||
# will wake after suspend in Hybrid mode, even if the system was in Integrated mode before
|
||||
# suspending.
|
||||
|
||||
restore_marker=/run/blob-force-igpu-integrated
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
[[ -x /usr/bin/supergfxctl ]] || exit 0
|
||||
|
||||
switch_mode() {
|
||||
local expected="$1" current
|
||||
|
||||
if ! /usr/bin/timeout --kill-after=1s 3s /usr/bin/supergfxctl -m "$expected"; then
|
||||
echo "Could not request the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
fi
|
||||
for _ in {1..10}; do
|
||||
if current=$(/usr/bin/timeout --kill-after=1s 2s /usr/bin/supergfxctl -g 2>/dev/null) &&
|
||||
[[ $current == "$expected" ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Could not confirm the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
pre)
|
||||
# Remember the mode this sleep cycle started in. supergfxctl persists the
|
||||
# temporary hibernate switch to Vfio, so post must not consult that mutable
|
||||
# value when deciding whether to restore Integrated mode.
|
||||
if [[ -L $restore_marker ]]; then
|
||||
exit 1
|
||||
elif [[ ! -f $restore_marker ]]; then
|
||||
/usr/bin/grep -Eq '"mode"[[:space:]]*:[[:space:]]*"Integrated"' /etc/supergfxd.conf 2>/dev/null || exit 0
|
||||
/usr/bin/install -m 0600 -o root -g root -T /dev/null "$restore_marker"
|
||||
fi
|
||||
|
||||
# Before hibernating, switch to Vfio so the nvidia driver is detached from the dGPU.
|
||||
# Without this, hibernate resume fails because the nvidia driver can't freeze a
|
||||
# powered-off dGPU (returns -EIO), which aborts the entire resume.
|
||||
if [[ $sleep_action == "hibernate" ]]; then
|
||||
switch_mode Vfio
|
||||
fi
|
||||
;;
|
||||
post)
|
||||
[[ -f $restore_marker && ! -L $restore_marker ]] || exit 0
|
||||
|
||||
# small delay so the device is fully re-enumerated
|
||||
sleep 4
|
||||
|
||||
# force-bind dGPU to vfio (fully detached from nvidia)
|
||||
switch_mode Vfio
|
||||
|
||||
# then go back to Integrated, which powers it off again
|
||||
switch_mode Integrated
|
||||
/usr/bin/rm -f -- "$restore_marker"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Turn off keyboard backlight before hibernate to prevent hang on power-off.
|
||||
# The ASUS keyboard controller can block S4 shutdown if LEDs are active.
|
||||
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
|
||||
if [[ $1 == "pre" && $sleep_action == "hibernate" ]]; then
|
||||
device=""
|
||||
for candidate in /sys/class/leds/*kbd_backlight*; do
|
||||
if [[ -e "$candidate" ]]; then
|
||||
device="$(basename "$candidate")"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$device" ]]; then
|
||||
brightnessctl -d "$device" set 0 >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Lazy-unmount gvfsd-fuse filesystems before suspend/hibernate to prevent the
|
||||
# kernel's process freeze from timing out. FUSE daemons (like gvfsd-fuse from
|
||||
# Nautilus) can block in uninterruptible sleep during freeze, causing suspend
|
||||
# to silently fail. After wake, restart gvfs so the FUSE mount is restored.
|
||||
|
||||
if [[ $1 == "pre" ]]; then
|
||||
while IFS=' ' read -r _ mountpoint fstype _; do
|
||||
if [[ $fstype == fuse.gvfsd-fuse ]]; then
|
||||
mountpoint=$(printf '%b' "$mountpoint")
|
||||
fusermount3 -uz "$mountpoint" 2>/dev/null || fusermount -uz "$mountpoint" 2>/dev/null || true
|
||||
fi
|
||||
done < /proc/mounts
|
||||
fi
|
||||
|
||||
if [[ $1 == "post" ]]; then
|
||||
# Run in background — user.slice is still frozen at this point, so a
|
||||
# synchronous restart would block the thaw for up to 90 seconds.
|
||||
(
|
||||
sleep 5
|
||||
for uid_dir in /run/user/*; do
|
||||
uid=$(basename "$uid_dir")
|
||||
if [[ -S $uid_dir/bus ]]; then
|
||||
sudo -u "#$uid" env \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=$uid_dir/bus" \
|
||||
XDG_RUNTIME_DIR="$uid_dir" \
|
||||
systemctl --user restart gvfs-daemon.service 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
) &
|
||||
fi
|
||||
@@ -0,0 +1,3 @@
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/bin/updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots
|
||||
@@ -0,0 +1,6 @@
|
||||
[Service]
|
||||
# Delay startup to avoid race condition with display manager initialization
|
||||
# when booting in Integrated mode. Without this delay, the system can freeze
|
||||
# on boot because supergfxd tries to disable the dGPU while the display
|
||||
# subsystem is still initializing.
|
||||
ExecStartPre=/bin/sleep 5
|
||||
@@ -0,0 +1,16 @@
|
||||
# Make user apps the only thing systemd-oomd is allowed to kill.
|
||||
#
|
||||
# Hyprland runs in session.slice, as wayland-wm@hyprland.desktop.service, while
|
||||
# everything launched through uwsm-app lands in app.slice/app-*.scope. Marking
|
||||
# only app.slice as a kill candidate means the compositor is structurally
|
||||
# ineligible: oomd takes the browser or terminal that caused the pressure, and
|
||||
# the session survives to show the notification about it. Setting this on
|
||||
# user@.service instead would put the compositor back in the candidate pool.
|
||||
#
|
||||
# Swap kill is a backstop for the slower shape of the same problem, where swap
|
||||
# fills before pressure spikes. It uses the global SwapUsedLimit (90%) from
|
||||
# /etc/systemd/oomd.conf.d/10-blob.conf, which also carries the pressure
|
||||
# thresholds.
|
||||
[Slice]
|
||||
ManagedOOMMemoryPressure=kill
|
||||
ManagedOOMSwap=kill
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Announce process crashes and offer an AI diagnosis
|
||||
# Needs the session bus to notify, and uwsm-app to open the diagnosis terminal.
|
||||
# Both are up only after graphical-session.target.
|
||||
After=graphical-session.target
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
# Set by blob-toggle-crash-capture. Checked here so a disabled watcher stays
|
||||
# disabled across logins without the unit having to be disabled.
|
||||
ConditionPathExists=!%h/.local/state/blob/toggles/crash-capture-off
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/blob-crash-watch
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Fcitx5 input method (XCompose sequences)
|
||||
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
|
||||
# Wayland clients.
|
||||
#
|
||||
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
|
||||
# imports into the user manager before it reaches graphical-session.target.
|
||||
After=graphical-session.target
|
||||
# The wayland connection dies with the compositor, so follow the session rather
|
||||
# than linger against a socket that is gone.
|
||||
PartOf=graphical-session.target
|
||||
# After= is ordering only -- it does not stop anything from starting this unit
|
||||
# while the target is inactive. An `blob update` over SSH has a live user
|
||||
# manager (pam_systemd) and no graphical session, and a fcitx5 started there
|
||||
# comes up with no WAYLAND_DISPLAY and no way to reach any client. Worse, it
|
||||
# stays active, so the later graphical-session.target activation won't pull in a
|
||||
# working one -- Wants= does not restart what is already running. Skip the start
|
||||
# instead; the unit stays enabled and starts for real at graphical login.
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# notificationitem duplicates the tray entry blob already renders itself.
|
||||
ExecStart=/usr/bin/fcitx5 --disable notificationitem
|
||||
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
|
||||
# its bus name, and a clean exit still leaves the user with no input method.
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Lock Blob before suspend
|
||||
# The monitor calls into the running Blob shell. Wait until UWSM has imported
|
||||
# BLOB_PATH and WAYLAND_DISPLAY, but keep the default target ordering so the
|
||||
# monitor starts before graphical-session.target is reached.
|
||||
After=dbus.socket wayland-session-waitenv.service
|
||||
Requires=dbus.socket
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=BLOB_PATH
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/blob-system-sleep-monitor
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,32 @@
|
||||
[Unit]
|
||||
Description=Blob speaker tuning filter-chain
|
||||
Documentation=https://github.com/basecamp/omarchy/blob/master/docs/AUDIO-TUNING.md
|
||||
# WirePlumber does the linking, so starting before it is up risks the output being
|
||||
# linked before the speaker device has been discovered.
|
||||
After=pipewire.service wireplumber.service
|
||||
Requires=pipewire.service
|
||||
Wants=wireplumber.service
|
||||
# Restart with the audio daemon, since the filter-chain loses its connection when
|
||||
# PipeWire goes away.
|
||||
PartOf=pipewire.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Hosts the tuning as a PipeWire *client* rather than loading it into the daemon
|
||||
# from pipewire.conf.d, which is only read at daemon startup. That is what lets
|
||||
# the tuning be switched on and off without restarting pipewire-pulse -- a
|
||||
# restart drops every PulseAudio client's connection, and applications that do
|
||||
# not reconnect (Spotify) have to be restarted by hand.
|
||||
#
|
||||
# It also contains failure: a malformed tuning breaks only this service, where a
|
||||
# bad drop-in in the daemon's own config stops PipeWire from starting at all.
|
||||
#
|
||||
# The config name is deliberately not PipeWire's stock filter-chain.conf, which
|
||||
# merges every fragment in ~/.config/pipewire/filter-chain.conf.d/ and would make
|
||||
# this service host unrelated user filters too.
|
||||
ExecStart=/usr/bin/pipewire -c blob-speaker-tuning.conf
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=Bluetooth pairing agent (auto-accept)
|
||||
Documentation=man:bt-agent(1)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
# bluez must be reachable on the system bus before we can register.
|
||||
After=dbus.socket
|
||||
Requires=dbus.socket
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# If bluetoothd is unavailable (for example in VMs or machines without a
|
||||
# usable adapter), skip cleanly instead of entering a restart loop.
|
||||
ExecCondition=/usr/bin/systemctl is-active --quiet bluetooth.service
|
||||
# NoInputNoOutput auto-accepts pair requests. Safe because the adapter
|
||||
# is only `pairable: true` when the user explicitly opens the blob
|
||||
# bluetoothPanel and starts scanning; outside that window bluez refuses
|
||||
# inbound pair attempts at a lower layer.
|
||||
ExecStart=/usr/bin/bt-agent -c NoInputNoOutput
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,2 @@
|
||||
[Service]
|
||||
TimeoutStopSec=5s
|
||||
Reference in New Issue
Block a user