├── .gitignore ├── gamepad-power ├── locales │ ├── .gitignore │ ├── en.po │ └── it.po ├── lib-gamepad-power-interface.sh ├── gamepad-power.service ├── Makefile ├── lib-gamepad-power-upower.sh ├── lib-gamepad-power-sysfs.sh └── gamepad-power.sh ├── moonlight-usbip ├── server │ ├── modules-load.conf │ ├── remote-devices │ │ ├── ds4.conf │ │ └── ds5.conf │ ├── polkit.rules │ ├── usbip@.service │ └── Makefile └── client │ ├── modules-load.conf │ ├── bind-devices │ ├── ds4.conf │ └── ds5.conf │ ├── usbipd.service │ ├── moonlight-usbip.desktop │ ├── polkit.rules │ ├── usbip-bind@.service │ ├── moonlight.svg │ ├── Makefile │ └── moonlight-usbip.sh ├── experimental ├── README.md └── list-steam-games.py ├── steam-compat-updater ├── steam-compat-updater.service ├── Makefile └── steam-compat-updater.sh ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .venv 4 | -------------------------------------------------------------------------------- /gamepad-power/locales/.gitignore: -------------------------------------------------------------------------------- 1 | *.mo 2 | -------------------------------------------------------------------------------- /moonlight-usbip/server/modules-load.conf: -------------------------------------------------------------------------------- 1 | vhci-hcd 2 | -------------------------------------------------------------------------------- /moonlight-usbip/client/modules-load.conf: -------------------------------------------------------------------------------- 1 | usbip-core 2 | usbip-host 3 | -------------------------------------------------------------------------------- /experimental/README.md: -------------------------------------------------------------------------------- 1 | # Experiments 2 | 3 | Random stuff that I might do one day. 4 | -------------------------------------------------------------------------------- /moonlight-usbip/client/bind-devices/ds4.conf: -------------------------------------------------------------------------------- 1 | # Specifiy device by vendor and product id 2 | # e.g. USBIP_DEVICE=04f9:0169 3 | # then enable service: systemctl enable usbip-bind@bind.service 4 | USBIP_DEVICE=054c:09cc 5 | -------------------------------------------------------------------------------- /moonlight-usbip/client/bind-devices/ds5.conf: -------------------------------------------------------------------------------- 1 | # Specifiy device by vendor and product id 2 | # e.g. USBIP_DEVICE=04f9:0169 3 | # then enable service: systemctl enable usbip-bind@bind.service 4 | USBIP_DEVICE=054c:0ce6 5 | -------------------------------------------------------------------------------- /moonlight-usbip/client/usbipd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=USB/IP server 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/usr/sbin/usbipd 7 | Type=simple 8 | Restart=on-failure 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /steam-compat-updater/steam-compat-updater.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Steam compatibility tools updater 3 | 4 | [Service] 5 | ExecStartPre=/bin/sleep 5 6 | ExecStart=/usr/local/bin/steam-compat-updater 7 | Type=oneshot 8 | Restart=no 9 | 10 | [Install] 11 | WantedBy=graphical-session.target 12 | -------------------------------------------------------------------------------- /moonlight-usbip/server/remote-devices/ds4.conf: -------------------------------------------------------------------------------- 1 | # Specify host by ip or hostname 2 | # USBIP_HOST=example.com 3 | # Specifiy device by vendor and product id 4 | # e.g. USBIP_DEVICE=012f:34ab 5 | # then start|stop service: systemctl start|stop usbip@remote.service 6 | USBIP_HOST=notebook.local 7 | USBIP_DEVICE=054c:09cc 8 | -------------------------------------------------------------------------------- /moonlight-usbip/server/remote-devices/ds5.conf: -------------------------------------------------------------------------------- 1 | # Specify host by ip or hostname 2 | # USBIP_HOST=example.com 3 | # Specifiy device by vendor and product id 4 | # e.g. USBIP_DEVICE=012f:34ab 5 | # then start|stop service: systemctl start|stop usbip@remote.service 6 | USBIP_HOST=notebook.local 7 | USBIP_DEVICE=054c:0ce6 8 | -------------------------------------------------------------------------------- /gamepad-power/lib-gamepad-power-interface.sh: -------------------------------------------------------------------------------- 1 | # abstract functions to be implemented 2 | 3 | check_prereqs() { 4 | die "Not implemented" 5 | } 6 | 7 | monitor_events() { 8 | die "Not implemented" 9 | } 10 | 11 | is_device_excluded() { 12 | die "Not implemented" 13 | } 14 | 15 | get_device_info() { 16 | die "Not implemented" 17 | } 18 | -------------------------------------------------------------------------------- /gamepad-power/locales/en.po: -------------------------------------------------------------------------------- 1 | msgid "Notification: Battery charged" 2 | msgstr "Battery charged" 3 | 4 | msgid "Notification: Battery charging" 5 | msgstr "Battery charging: %d%%" 6 | 7 | msgid "Notification: Battery discharging" 8 | msgstr "Battery discharging: %d%%" 9 | 10 | msgid "Notification: Battery low" 11 | msgstr "Battery low: %d%%" 12 | -------------------------------------------------------------------------------- /gamepad-power/gamepad-power.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Gamepad power monitor 3 | # FIXME systems services 4 | #Wants=upower.service 5 | #After=upower.service 6 | 7 | [Service] 8 | ExecStartPre=/bin/sleep 5 9 | ExecStart=/usr/local/bin/gamepad-power 10 | Type=simple 11 | Restart=on-failure 12 | 13 | [Install] 14 | WantedBy=graphical-session.target 15 | -------------------------------------------------------------------------------- /moonlight-usbip/client/moonlight-usbip.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Moonlight USB/IP 3 | Comment=Stream games and other applications from another PC running Sunshine or GeForce Experience 4 | Exec=moonlight-usbip 5 | Icon=moonlight 6 | Terminal=false 7 | Type=Application 8 | Categories=Qt;Game; 9 | Keywords=nvidia;gamestream;stream;sunshine;remote play; 10 | -------------------------------------------------------------------------------- /gamepad-power/locales/it.po: -------------------------------------------------------------------------------- 1 | msgid "Notification: Battery charged" 2 | msgstr "Batteria carica" 3 | 4 | msgid "Notification: Battery charging" 5 | msgstr "Batteria in caricamento: %d%%" 6 | 7 | msgid "Notification: Battery discharging" 8 | msgstr "Batteria in scaricamento: %d%%" 9 | 10 | msgid "Notification: Battery low" 11 | msgstr "Batteria scarica: %d%%" 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install uninstall 2 | 3 | # TODO parametrize subdirectories 4 | 5 | all: 6 | make -C gamepad-power all 7 | make -C steam-compat-updater all 8 | 9 | install: 10 | make -C gamepad-power install 11 | make -C steam-compat-updater install 12 | 13 | uninstall: 14 | make -C gamepad-power uninstall 15 | make -C steam-compat-updater uninstall 16 | -------------------------------------------------------------------------------- /steam-compat-updater/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install uninstall 2 | 3 | all: 4 | @echo "Use 'make install' or 'make uninstall'." 5 | 6 | install: 7 | install -Dm 0755 steam-compat-updater.sh /usr/local/bin/steam-compat-updater 8 | install -m 0644 steam-compat-updater.service /etc/systemd/user 9 | 10 | uninstall: 11 | rm -f /usr/local/bin/steam-compat-updater 12 | rm -f /etc/systemd/user/steam-compat-updater.service 13 | -------------------------------------------------------------------------------- /moonlight-usbip/server/polkit.rules: -------------------------------------------------------------------------------- 1 | polkit.addRule(function(action, subject) { 2 | if (action.id == "org.freedesktop.systemd1.manage-units") { 3 | var unit = action.lookup("unit"); 4 | if (/usbip@[a-z0-9-]+\.service/.test(unit)) { 5 | var verb = action.lookup("verb"); 6 | if (verb == "start" || verb == "stop" || verb == "restart") { 7 | return polkit.Result.YES; 8 | } 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /moonlight-usbip/client/polkit.rules: -------------------------------------------------------------------------------- 1 | polkit.addRule(function(action, subject) { 2 | if (action.id == "org.freedesktop.systemd1.manage-units") { 3 | var unit = action.lookup("unit"); 4 | if (unit == "usbipd.service" || /usbip-bind@[a-z0-9-]+\.service/.test(unit)) { 5 | var verb = action.lookup("verb"); 6 | if (verb == "start" || verb == "stop" || verb == "restart") { 7 | return polkit.Result.YES; 8 | } 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /moonlight-usbip/server/usbip@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=usbip client 3 | After=network.target 4 | 5 | [Service] 6 | Type=oneshot 7 | RemainAfterExit=yes 8 | Restart=no 9 | EnvironmentFile=/etc/usbip/remote-devices/%i.conf 10 | ExecStart=/bin/sh -c "/usr/sbin/usbip attach -r $USBIP_HOST -b $(/usr/sbin/usbip list -r $USBIP_HOST | grep $USBIP_DEVICE | cut -d: -f1)" 11 | ExecStop=/bin/sh -c "/usr/sbin/usbip detach -p $(/usr/sbin/usbip port | grep -B1 $USBIP_DEVICE | grep '' | sed -E 's/^Port ([0-9][0-9]).*/\\1/')" 12 | 13 | #[Install] 14 | #WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /moonlight-usbip/client/usbip-bind@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=USB-IP Binding for device %I 3 | After=network-online.target usbipd.service 4 | Wants=network-online.target 5 | Requires=usbipd.service 6 | 7 | [Service] 8 | Type=simple 9 | EnvironmentFile=/etc/usbip/bind-devices/%i.conf 10 | ExecStart=/bin/sh -c "/usr/sbin/usbip bind -b $(/usr/sbin/usbip list -p -l | grep '#usbid=${USBIP_DEVICE}#' | cut '-d#' -f1 | cut '-d=' -f2)" 11 | RemainAfterExit=yes 12 | ExecStop=/bin/sh -c "/usr/sbin/usbip unbind -b $(/usr/sbin/usbip list -p -l | grep '#usbid=${USBIP_DEVICE}#' | cut '-d#' -f1 | cut '-d=' -f2)" 13 | Restart=no 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /experimental/list-steam-games.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # List all installed games in Steam. This would help writing a script that autoconfigures games on Sunshine as 3 | # applications and keeps them synced. 4 | 5 | import vdf 6 | 7 | from pathlib import Path 8 | home = Path.home() 9 | 10 | d = vdf.load(open(home / '.local' / 'share' / 'Steam' / 'steamapps' / 'libraryfolders.vdf', 'r')) 11 | apps = d['libraryfolders']['0']['apps'] 12 | print(apps) 13 | 14 | for app_id in apps.keys(): 15 | manifest_file = home / '.local' / 'share' / 'Steam' / 'steamapps' / ('appmanifest_' + app_id + '.acf') 16 | if manifest_file.exists(): 17 | app_data = vdf.load(open(manifest_file, 'r')) 18 | print(app_data['AppState']) 19 | -------------------------------------------------------------------------------- /moonlight-usbip/server/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install uninstall 2 | 3 | all: 4 | @echo "Use 'make install' or 'make uninstall'." 5 | 6 | install: 7 | install -m 0644 modules-load.conf /etc/modules-load.d/usbip.conf 8 | install -m 0644 usbip@.service /etc/systemd/system/usbip@.service 9 | install -Dm 0644 -t /etc/usbip/remote-devices/ remote-devices/* 10 | install -m 0644 polkit.rules /etc/polkit-1/rules.d/10-usbip-gaming.rules 11 | systemctl daemon-reload 12 | systemctl restart polkit.service 13 | 14 | uninstall: 15 | rm -f /etc/modules-load.d/usbip.conf 16 | rm -f /etc/systemd/system/usbip@.service 17 | # systemd will realize at next reboot 18 | rm -fr /etc/usbip/remote-devices/ 19 | rm -f /etc/polkit-1/rules.d/10-usbip-gaming.rules 20 | systemctl restart polkit.service 21 | -------------------------------------------------------------------------------- /moonlight-usbip/client/moonlight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gamepad-power/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install uninstall 2 | 3 | all: locale 4 | @echo "Use 'make install' or 'make uninstall'." 5 | 6 | # TODO generify 7 | locale: 8 | msgfmt -o locales/en.mo locales/en.po 9 | msgfmt -o locales/it.mo locales/it.po 10 | 11 | install: 12 | install -Dm 0755 gamepad-power.sh /usr/local/bin/gamepad-power 13 | install -Dm 0755 lib-gamepad-power-sysfs.sh /usr/local/lib/gamepad-power/lib-sysfs.sh 14 | install -Dm 0755 lib-gamepad-power-upower.sh /usr/local/lib/gamepad-power/lib-upower.sh 15 | install -m 0644 gamepad-power.service /etc/systemd/user 16 | install -Dm 0644 locales/en.mo /usr/local/share/locale/en/LC_MESSAGES/gamepad-power.mo 17 | install -Dm 0644 locales/it.mo /usr/local/share/locale/it/LC_MESSAGES/gamepad-power.mo 18 | 19 | uninstall: 20 | rm -f /usr/local/bin/gamepad-power 21 | rm -fr /usr/local/lib/gamepad-power 22 | rm -f /etc/systemd/user/gamepad-power.service 23 | rm -f /usr/local/share/locale/en/LC_MESSAGES/gamepad-power.mo 24 | rm -f /usr/local/share/locale/it/LC_MESSAGES/gamepad-power.mo 25 | -------------------------------------------------------------------------------- /moonlight-usbip/client/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all install uninstall 2 | 3 | all: 4 | @echo "Use 'make install' or 'make uninstall'." 5 | 6 | install: 7 | # no way to do this with a xdg command 8 | install -Dm 0644 moonlight.svg /usr/local/share/icons/hicolor/scalable/apps/moonlight 9 | # no way to do this with a xdg command 10 | install -Dm 0644 moonlight-usbip.desktop /usr/local/share/applications/moonlight-usbip.desktop 11 | install -m 0755 moonlight-usbip.sh /usr/local/bin/moonlight-usbip 12 | install -m 0644 modules-load.conf /etc/modules-load.d/usbip.conf 13 | install -m 0644 usbipd.service /etc/systemd/system/usbipd.service 14 | install -m 0644 usbip-bind@.service /etc/systemd/system/usbip-bind@.service 15 | install -Dm 0644 -t /etc/usbip/bind-devices/ bind-devices/* 16 | install -m 0644 polkit.rules /etc/polkit-1/rules.d/10-usbip-gaming.rules 17 | systemctl daemon-reload 18 | systemctl restart polkit.service 19 | 20 | uninstall: 21 | rm -f /etc/modules-load.d/usbip.conf 22 | rm -f /etc/systemd/system/usbipd.service 23 | rm -f /etc/systemd/system/usbip-bind@.service 24 | # systemd will realize at next reboot 25 | rm -fr /etc/usbip/bind-devices/ 26 | rm -f /etc/polkit-1/rules.d/10-usbip-gaming.rules 27 | systemctl restart polkit.service 28 | -------------------------------------------------------------------------------- /gamepad-power/lib-gamepad-power-upower.sh: -------------------------------------------------------------------------------- 1 | 2 | ### BEGIN IMPLEMENTATION 3 | 4 | # FIXME we should detect gamepads and whitelist them, not blacklist other devices! 5 | EXCLUDED_DEVICES=$(cat </dev/null || 19 | echo "$1" | systemd-cat -p warning 20 | } 21 | 22 | # shellcheck disable=SC2317 23 | finish() { 24 | systemctl stop usbipd.service 25 | 26 | find "$USBIP_DEVICES_PATH" -type f -name '*.conf' | while read -r line; do 27 | bind_name="$(basename "$line" .conf)" 28 | systemctl stop "usbip-bind@$bind_name.service" 29 | done 30 | } 31 | 32 | if ! start_and_check usbipd.service; then 33 | notify "Failed to start usbipd. Controllers will not work." 34 | else 35 | find "$USBIP_DEVICES_PATH" -type f -name '*.conf' | while read -r line; do 36 | bind_name="$(basename "$line" .conf)" 37 | start_and_check "usbip-bind@$bind_name.service" || notify "Failed to bind controller $bind_name." 38 | done 39 | fi 40 | 41 | trap finish QUIT TERM EXIT 42 | 43 | "$MOONLIGHT_BIN" "$@" 44 | status="$?" 45 | if [[ "$status" != "0" ]]; then 46 | notify "Moonlight exited with status $status" 47 | fi 48 | exit "$status" 49 | -------------------------------------------------------------------------------- /steam-compat-updater/steam-compat-updater.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Updater for Steam compatibility tools 3 | 4 | set -euo pipefail 5 | 6 | COMPAT_PATH="$HOME/.local/share/Steam/compatibilitytools.d" 7 | 8 | notify() { 9 | # critical notifications should stick until manual dismissal 10 | notify-send -i "${3:-steam}" -u "${4:-critical}" "${2:-Steam compat updater}" "$1" 11 | } 12 | 13 | die() { 14 | notify "$@" 15 | exit 1 16 | } 17 | 18 | # ensure Steam compat tools directory exists 19 | # TODO i18n 20 | mkdir -p "$COMPAT_PATH" || die "Unable to create Steam compat tools directory." 21 | 22 | tempdir="$(mktemp -d --tmpdir scupd.XXXXX)" 23 | 24 | cleanup() { 25 | rm -fr "$tempdir" 26 | } 27 | 28 | trap cleanup EXIT 29 | 30 | # TODO download all missing releases between latest released and latest installed 31 | update_ge_proton() { 32 | ge_repo_url="https://github.com/GloriousEggroll/proton-ge-custom" 33 | 34 | # credit goes to https://www.reddit.com/r/linux_gaming/comments/1cf1vkk/i_made_a_little_script_to_update_protonge_to_use/ 35 | ge_latest_url="$(curl -Lsf -o /dev/null -w '%{url_effective}' "$ge_repo_url/releases/latest")" 36 | ge_latest="${ge_latest_url##*/}" 37 | 38 | if [[ ! -d "$COMPAT_PATH/$ge_latest" ]]; then 39 | ge_url_base="$ge_repo_url/releases/download/$ge_latest/$ge_latest" 40 | 41 | curl -Lsf -o "$tempdir/${ge_latest}.tar.gz" "${ge_url_base}.tar.gz" || die "Unable to download latest GE-Proton release." 42 | curl -Lsf -o "$tempdir/${ge_latest}.sha512sum" "${ge_url_base}.sha512sum" || die "Unable to download latest GE-Proton release." 43 | 44 | # TODO i18n 45 | (cd "$tempdir" && sha512sum -c "$tempdir/${ge_latest}.sha512sum") || die "GE-Proton package verification failed." 46 | # TODO i18n 47 | tar -C "$COMPAT_PATH" -xzf "$tempdir/${ge_latest}.tar.gz" || die "GE-Proton installation failed." 48 | # TODO i18n 49 | notify "Updated to version ${ge_latest}." 50 | fi 51 | } 52 | 53 | update_ge_proton 54 | -------------------------------------------------------------------------------- /gamepad-power/lib-gamepad-power-sysfs.sh: -------------------------------------------------------------------------------- 1 | 2 | ### BEGIN IMPLEMENTATION 3 | 4 | udevadm_path=$(which udevadm) 5 | 6 | install_udev() { 7 | die "udev not found" 8 | } 9 | 10 | last_device_state="$(mktemp --tmpdir gpp_devices_last.XXXXX)" 11 | current_device_state="$(mktemp --tmpdir gpp_devices_current.XXXXX)" 12 | 13 | cleanup() { 14 | rm -f "$current_device_state" "$last_device_state" 15 | } 16 | 17 | trap cleanup EXIT 18 | 19 | _state_add_device() { 20 | local device_path="$1" 21 | grep -F -q -x "$device_path" < "$current_device_state" || echo "$device_path" >>"$current_device_state" 22 | 23 | if grep -F -q -x "$device_path" < "$last_device_state"; then 24 | echo "changed" 25 | else 26 | echo "added" 27 | fi 28 | } 29 | 30 | sysfs_monitor_events() { 31 | while true; do 32 | while read -r js_dev; do 33 | js_devpath="$(udevadm info --query=all --json=pretty --name="$js_dev" | jq -r .DEVPATH)" 34 | js_syspath="/sys${js_devpath}" 35 | 36 | if [[ -d "${js_syspath}/../../../power_supply" ]]; then 37 | state="$(_state_add_device "$js_syspath")" 38 | echo "${js_syspath}|$state" 39 | fi 40 | 41 | done < <(find /dev -name "js*") 42 | 43 | # handle disappeared devices 44 | while read -r js_syspath; do 45 | echo "${js_syspath}|removed" 46 | # this will output lines that are present in the last state file that are not in the current state file 47 | done < <(join -v 2 <(sort "$current_device_state") <(sort "$last_device_state")) 48 | 49 | cp "$current_device_state" "$last_device_state" 50 | truncate -s0 "$current_device_state" 51 | 52 | sleep 10s 53 | done 54 | } 55 | 56 | sysfs_get_device_info() { 57 | device_name="$(cat "${1}/device/name")" 58 | power_supply_path="$(find "${1}/../../../power_supply" -mindepth 1 -maxdepth 1 -type d -exec realpath "{}" \; | head -n1)" 59 | power_state="$(awk '{print tolower($0)}' < "$power_supply_path/status")" 60 | charge_state="$(cat "$power_supply_path/capacity")" 61 | 62 | echo "model: $device_name" 63 | echo "state: $power_state" 64 | echo "percentage: $charge_state" 65 | } 66 | 67 | ### END IMPLEMENTATION 68 | 69 | ### BEGIN INTERFACE 70 | 71 | check_prereqs() { 72 | if [[ -z "${udevadm_path}" ]]; then 73 | install_udev 74 | fi 75 | } 76 | 77 | monitor_events() { 78 | sysfs_monitor_events 79 | } 80 | 81 | is_device_excluded() { 82 | false 83 | } 84 | 85 | get_device_info() { 86 | sysfs_get_device_info "$1" 87 | } 88 | 89 | ## END INTERFACE 90 | -------------------------------------------------------------------------------- /gamepad-power/gamepad-power.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Daemon that monitors power of wireless gamepads. 3 | # Needs upower or sysfs (choose implementation in source directives below). 4 | 5 | # charge status less or equal than this value will be notified as low battery 6 | LOW_BATTERY_THRESHOLD=15 7 | 8 | # for gettext 9 | # shellcheck disable=SC2034 10 | TEXTDOMAIN=gamepad-power 11 | # shellcheck disable=SC2034 12 | TEXTDOMAINDIR=/usr/local/share/locale 13 | 14 | set -eo pipefail 15 | declare -A LAST_STATUS 16 | 17 | notify() { 18 | notify-send -i "${3:-battery}" -u "${4:-normal}" "${2:-Battery notifications}" "$1" 19 | } 20 | 21 | die() { 22 | notify "$@" 23 | exit 1 24 | } 25 | 26 | # implementation to be used - keep one line uncommented only 27 | # see lib-gamepad-power-interface.sh for abstract functions to be implemented 28 | source /usr/local/lib/gamepad-power/lib-sysfs.sh || source "$(dirname "$0")/lib-gamepad-power-sysfs.sh" 29 | #source /usr/local/lib/gamepad-power/lib-upower.sh || source "$(dirname "$0")/lib-gamepad-power-upower.sh" 30 | 31 | get_device_name() { 32 | grep "model: " | awk -F':' '{print $2}' | sed 's/^\s*\|\s*$//g' 33 | } 34 | 35 | get_charging_status() { 36 | grep "state: " | awk -F':' '{print $2}' | sed 's/^\s*\|\s*$//g' 37 | } 38 | 39 | get_charging_status_sysfs() { 40 | native_path="$(grep "native-path: " | awk -F'native-path:' '{print $2}' | sed 's/^\s*\|\s*$//g')" 41 | grep "POWER_SUPPLY_STATUS=" "/sys/class/power_supply/$native_path/uevent" | awk -F'=' '{print tolower($2)}' 42 | } 43 | 44 | get_battery_status() { 45 | grep "percentage: " | awk '{print $2}' | tr -d '%' 46 | } 47 | 48 | notify_fully_charged() { 49 | notify $"Notification: Battery charged" "$1" 50 | } 51 | 52 | notify_charging() { 53 | # shellcheck disable=SC2182 54 | notify "$(printf $"Notification: Battery charging" "$2")" "$1" 55 | } 56 | 57 | notify_discharging() { 58 | # shellcheck disable=SC2182 59 | notify "$(printf $"Notification: Battery discharging" "$2")" "$1" 60 | } 61 | 62 | notify_low_battery() { 63 | # shellcheck disable=SC2182 64 | notify "$(printf $"Notification: Battery low" "$2")" "$1" "battery-caution" 65 | } 66 | 67 | check_prereqs 68 | 69 | while read -r line; do 70 | #echo "### $line" 71 | 72 | device="$(echo "$line" | awk -F'|' '{print $1}')" 73 | event="$(echo "$line" | awk -F'|' '{print $2}')" 74 | #echo "event: $event, device: $device" 75 | if is_device_excluded "$device"; then 76 | continue 77 | fi 78 | 79 | if [[ "$event" == "removed" ]]; then 80 | LAST_STATUS["$device"]="" 81 | continue 82 | fi 83 | 84 | device_info="$(get_device_info "$device")" 85 | device_name="$(echo "$device_info" | get_device_name)" 86 | battery_status="$(echo "$device_info" | get_battery_status)" 87 | charging_state="$(echo "$device_info" | get_charging_status || echo "$device_info" | get_charging_status_sysfs)" 88 | device_status="$charging_state:$battery_status" 89 | #echo "<$device_name>: <$battery_status> / <$charging_state>" 90 | 91 | if [[ "${LAST_STATUS["$device"]}" == "$device_status" ]]; then 92 | # already notified 93 | continue; 94 | fi 95 | 96 | LAST_STATUS["$device"]="$device_status" 97 | 98 | if [[ "$battery_status" == "100" ]]; then 99 | notify_fully_charged "$device_name" 100 | elif (( battery_status > LOW_BATTERY_THRESHOLD )); then 101 | if [[ "$charging_state" == "discharging" ]]; then 102 | notify_discharging "$device_name" "$battery_status" 103 | else 104 | notify_charging "$device_name" "$battery_status" 105 | fi 106 | elif (( battery_status <= LOW_BATTERY_THRESHOLD )); then 107 | if [[ "$charging_state" == "discharging" ]]; then 108 | notify_low_battery "$device_name" "$battery_status" 109 | else 110 | notify_charging "$device_name" "$battery_status" 111 | fi 112 | fi 113 | done < <(monitor_events) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Game center tools 2 | 3 | This repository contains some opinionated scripts that I wrote for my specific purposes: 4 | 5 | * headless Linux gaming system streamed to my TV set via [Sunshine](https://app.lizardbyte.dev/Sunshine/)/[Moonlight](https://moonlight-stream.org/) 6 | * no keyboard, only a Bluetooth gamepad connected directly to the gaming system OR a gamepad connected to the client and 7 | forwarded via USB/IP 8 | * XFCE desktop environment 9 | * Steam Big Picture as main UI 10 | 11 | ## Gamepad battery notifications (`gamepad-power`) 12 | 13 | Dependencies: `notify-send`, `udev`, mounted sysfs on `/sys` 14 | 15 | A little systemd service that monitors battery-powered gamepads and sends notifications when the battery charge changes. 16 | Notifications are sent using `notify-send` which must be installed on the system. Battery status detection requires a 17 | mounted sysfs in `/sys` and `udev` running. 18 | 19 | Run `make && sudo make install` to install the shell scripts and a systemd unit for the user daemon. After that, enable it: 20 | 21 | ```shell 22 | systemctl --user enable --now gamepad-power.service 23 | ``` 24 | 25 | Note that in order to start the service automatically at login time you need a desktop environment that supports systemd 26 | and `graphical-session.target` (XFCE is **not** one of them, I had to make it work in some other way). You can also 27 | start `/usr/local/bin/gamepad-power` in whatever way you prefer. 28 | 29 | ## Steam compatibility tools updater (`steam-compat-updater`) 30 | 31 | Dependencies: `notify-send`, `curl`, `tar`, `gunzip` 32 | 33 | This is a script that will update Steam compatibility tools (currently only [GE-Proton](https://github.com/GloriousEggroll/proton-ge-custom)). 34 | Just run it under the same user you also run Steam with and it will use desktop notifications (using `notify-send`) to 35 | notify upgrades or errors. If no upgrades are found, no notifications are displayed. 36 | 37 | > Please note that if Steam was running during an upgrade, it needs to be restarted to detect the upgrade. 38 | 39 | Run `make && sudo make install` to install the shell script and a systemd unit for the user daemon. After that, enable it: 40 | 41 | ```shell 42 | systemctl --user enable --now steam-compat-updater.service 43 | ``` 44 | 45 | Note that in order to start the service automatically at login time you need a desktop environment that supports systemd 46 | and `graphical-session.target` (XFCE is **not** one of them, I had to make it work in some other way). You can also 47 | start `/usr/local/bin/steam-compat-updater` in whatever way you prefer. This script is not a daemon and just runs once, 48 | does the upgrades, then exits - it is intended to be run once at system startup. 49 | 50 | ## Moonlight USB/IP integration 51 | 52 | Dependencies: `notify-send` (client only), Polkit (client only), `usbip` 53 | 54 | This is a set of systemd units, configuration files and script that somewhat integrates Moonlight 55 | with [USB/IP](https://wiki.archlinux.org/title/USB/IP), which is basically a tool to "forward" USB devices through the network to another host. This has the 56 | advantage that **games will see the controller as if it's attached directly to the gaming system** and will be able to 57 | use all its features - something that can't be done with the built-in Sunshine/Moonlight emulation. 58 | 59 | > [!NOTE] 60 | > This integration will make your controller work only for your gaming (Sunshine) host **but not in Moonlight GUI itself**. 61 | > This is because of USB/IP taking exclusive access to the controller. You must use another device to control the Moonlight GUI (mouse/keyboard). 62 | 63 | Heavily inspired by the USB/IP Arch wiki page, I tried to streamline the process as much as I could, automating almost 64 | everything. The idea is this: 65 | 66 | * connect a controller to your Moonlight system 67 | * start Moonlight via a wrapper script 68 | * start a service on the server (Sunshine) system to attach the controller 69 | 70 | ### On the Moonlight system (streaming client) 71 | 72 | Configuration files exist for the DualShock 4 and DualSense controllers. 73 | 74 | Run `make && sudo make install` to install everything. After that, you can start Moonlight via the wrapper script 75 | `moonlight-usbip` or via application shortcut "Moonlight USB/IP". 76 | 77 | The wrapper script will: 78 | 79 | * start the `usbip` daemon and try to bind both the DS4 and the DS5 (whatever it will find connected) 80 | * start Moonlight 81 | * stop `usbip` when Moonlight quits 82 | 83 | Privileged operations (e.g. start `usbip`) will use `systemctl` and should work out-of-the-box if Polkit is installed. 84 | 85 | ### On the gaming system (streaming server, e.g. Sunshine) 86 | 87 | Configure the host of your streaming client in all files inside `moonlight-usbip/server/remote-devices`: set the 88 | `USBIP_HOST` variable. 89 | 90 | Run `make && sudo make install` to install everything. 91 | 92 | > [!NOTE] 93 | > The following command may be configured in Sunshine as preparation steps: the `start` command for the "Do" (mandatory), the `stop` command for the "Undo" (optional). 94 | > This way the system will be completely automated, as long as you connect the controller before starting Moonlight. 95 | > **Anyway for the moment it is not recommended to set the "Undo" command because Proton can't handle controller reconnections due to a bug: https://github.com/ValveSoftware/Proton/issues/7952** 96 | 97 | Execute this command **after starting Moonlight**: 98 | 99 | ```shell 100 | systemctl start usbip@CONTROLLER.service 101 | ``` 102 | 103 | Replace `CONTROLLER` with: 104 | 105 | * `ds4` if you have a DualShock 4 controller 106 | * `ds5` if you have a DualSense controller 107 | 108 | Privileged operations (i.e. systemctl) should work out-of-the-box if Polkit is installed. 109 | --------------------------------------------------------------------------------