1ed57291f9
An Omarchy shell plugin: a Python/asyncio daemon that discovers network displays and serves an encoded screen capture for them to pull, plus a Quickshell bar widget and panel to drive it. The screen comes from the xdg-desktop-portal ScreenCast interface (asked for every time, never remembered), goes through GStreamer, and is served from a local HTTP port. Desktop audio is mixed in from the default output's monitor. The container follows the receiver: WebM/VP8 for Cast, MPEG-TS/H.264 for DLNA, HLS for AirPlay video. The stream port has to be reachable from the LAN, and none of these protocols can carry a credential, so the capability is the URL: a fresh random path per session, refused to anything off the LAN, capped at four concurrent readers.
115 lines
5.2 KiB
Bash
Executable File
115 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
||
# Install the Screencast plugin for the current user.
|
||
# ./install.sh install the daemon venv, link + enable the shell plugin
|
||
# ./install.sh --no-root same, but never ask for a password (skips missing packages)
|
||
# ./install.sh --uninstall
|
||
#
|
||
# Nothing here needs root except installing missing Arch packages, and even that
|
||
# is skipped with --no-root: the daemon runs entirely as you.
|
||
set -euo pipefail
|
||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||
LIB=$HOME/.local/lib/screencast
|
||
BIN=$HOME/.local/bin
|
||
CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/screencast
|
||
STATE=${XDG_STATE_HOME:-$HOME/.local/state}/screencast
|
||
ID=io.github.alanfortlink.screencast
|
||
PLUGIN=$HOME/.config/omarchy/plugins/$ID
|
||
MODE=${1:-}
|
||
LOG=$CACHE/install.log
|
||
|
||
if [[ $MODE == --uninstall ]]; then
|
||
omarchy-plugin-disable "$ID" >/dev/null 2>&1 || true
|
||
# The daemon is a plain user process; ask it to quit, then remove its files.
|
||
"$LIB/screencast-server" stop >/dev/null 2>&1 || true
|
||
rm -rf "$LIB" "$CACHE" "$STATE" "$BIN/screencast-server"
|
||
[[ -L $PLUGIN ]] && rm -f "$PLUGIN" # dev symlink only
|
||
echo "uninstalled (settings left in ~/.config/screencast/config.json). Now run: omarchy plugin remove $ID"
|
||
echo "note: if you ever let it open the stream port, that firewall rule is still there."
|
||
exit 0
|
||
fi
|
||
|
||
# Runtime dependencies, all from the Arch repos.
|
||
missing=()
|
||
for p in python gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugin-pipewire gst-libav libpipewire xdg-desktop-portal; do
|
||
pacman -Q "$p" >/dev/null 2>&1 || missing+=("$p")
|
||
done
|
||
# Something has to answer org.freedesktop.portal.ScreenCast.
|
||
if ! pacman -Q xdg-desktop-portal-hyprland >/dev/null 2>&1 && ! pacman -Q xdg-desktop-portal-wlr >/dev/null 2>&1; then
|
||
missing+=(xdg-desktop-portal-hyprland)
|
||
fi
|
||
if ((${#missing[@]})); then
|
||
if [[ $MODE == --no-root ]]; then
|
||
: # reported at the end, where the panel can see it
|
||
else
|
||
echo "› installing missing packages: ${missing[*]} (password prompt)"
|
||
if sudo -n true 2>/dev/null; then sudo pacman -S --needed --noconfirm "${missing[@]}"
|
||
else pkexec pacman -S --needed --noconfirm "${missing[@]}"; fi
|
||
fi
|
||
fi
|
||
|
||
mkdir -p "$CACHE" "$LIB" "$BIN"
|
||
echo "› creating the Python environment in $LIB/venv (log: $LOG)"
|
||
{
|
||
python3 -m venv --upgrade-deps "$LIB/venv"
|
||
"$LIB/venv/bin/pip" install --upgrade -r "$HERE/daemon/requirements.txt"
|
||
} >"$LOG" 2>&1 || { tail -n 15 "$LOG" >&2; echo "error: could not build the environment; log: $LOG" >&2; exit 1; }
|
||
|
||
# AirPlay is optional: pyatv is a big dependency tree and lags new Python
|
||
# releases, and everything else works without it.
|
||
if "$LIB/venv/bin/pip" install --upgrade -r "$HERE/daemon/requirements-optional.txt" >>"$LOG" 2>&1; then
|
||
echo " AirPlay support installed (pyatv)"
|
||
else
|
||
echo " note: pyatv did not install — Cast and DLNA still work, AirPlay will not (see $LOG)"
|
||
fi
|
||
|
||
echo "› installing the daemon"
|
||
rm -rf "$LIB/screencast"
|
||
cp -r "$HERE/daemon/screencast" "$LIB/screencast"
|
||
find "$LIB/screencast" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true
|
||
cat > "$LIB/screencast-server" <<LAUNCH
|
||
#!/bin/sh
|
||
# Generated by install.sh — runs the daemon out of its own virtualenv.
|
||
exec env PYTHONPATH="$LIB" "$LIB/venv/bin/python" -m screencast "\$@"
|
||
LAUNCH
|
||
chmod 755 "$LIB/screencast-server"
|
||
ln -sfn "$LIB/screencast-server" "$BIN/screencast-server"
|
||
git -C "$HERE" rev-parse HEAD > "$LIB/installed-commit" 2>/dev/null || date +%s > "$LIB/installed-commit"
|
||
|
||
echo "› installing the shell plugin"
|
||
mkdir -p "$(dirname "$PLUGIN")"
|
||
if [[ $(readlink -f "$PLUGIN" 2>/dev/null) != "$(readlink -f "$HERE")" ]]; then
|
||
if [[ -L $PLUGIN || ! -e $PLUGIN ]]; then
|
||
ln -sfn "$HERE" "$PLUGIN" # dev mode; a real checkout comes from `omarchy plugin add`
|
||
omarchy-shell -q shell rescanPlugins 2>/dev/null || true
|
||
fi
|
||
fi
|
||
if command -v omarchy-shell >/dev/null && omarchy-shell -q shell ping >/dev/null 2>&1; then
|
||
enabled() { omarchy-plugin-list 2>/dev/null | awk -v id="$ID" '$1 == id && $2 == "enabled" { found = 1 } END { exit !found }'; }
|
||
for _ in 1 2 3 4 5; do
|
||
if enabled; then break; fi
|
||
if omarchy-plugin-enable "$ID" right >/dev/null 2>&1; then echo " enabled $ID"; break; fi
|
||
sleep 1
|
||
done
|
||
enabled || echo " note: not enabled yet; run: omarchy plugin enable $ID"
|
||
else
|
||
echo " note: omarchy-shell is not running; enable later with: omarchy plugin enable $ID"
|
||
fi
|
||
|
||
# The receiver pulls the stream from us, so the host firewall has to let it in.
|
||
# Installing stays root-free: the panel offers to open the port (via polkit) the
|
||
# first time a cast is actually blocked by it.
|
||
if [[ $(systemctl is-active ufw 2>/dev/null) == active || $(systemctl is-active firewalld 2>/dev/null) == active ]]; then
|
||
echo "› note: this machine runs a firewall. Receivers fetch the stream from us, so"
|
||
echo " the panel has an \"Open port\" button: one password dialog, once, and the"
|
||
echo " rule stays. Nothing to type here."
|
||
fi
|
||
|
||
echo "done. The cast icon appears in the bar (if not: omarchy-shell shell rescanPlugins)."
|
||
echo "From a terminal: screencast-server devices | cast \"<name>\" | stop"
|
||
|
||
# Missing system packages are the one thing this cannot fix without root, so say
|
||
# so last: the plugin shows the final line of this log verbatim.
|
||
if ((${#missing[@]})) && [[ $MODE == --no-root ]]; then
|
||
echo "MISSING: sudo pacman -S --needed ${missing[*]}"
|
||
fi
|