diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-login1 b/tools/linuxfs/overlay/usr/local/bin/droiddeck-login1 index 7ce786740..0e538a8f6 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-login1 +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-login1 @@ -5,6 +5,8 @@ Android owns device power. Suspend here pauses the guest through SessionService; the app's Resume action thaws it and completes PrepareForSleep(false). Steam must see this service before startup or it can stay black after its idle timer. """ +import importlib.machinery +import importlib.util import os from pathlib import Path import re @@ -193,6 +195,15 @@ class LoginManager: self.notify(False) +def load_netmanager(): + path = Path(__file__).resolve().with_name("droiddeck-netmanager") + loader = importlib.machinery.SourceFileLoader("droiddeck_netmanager", str(path)) + spec = importlib.util.spec_from_loader(loader.name, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + def main(): directory = os.environ.get("BL_LAUNCH_DIR") if not directory or not Path(directory).is_dir(): @@ -218,6 +229,8 @@ def main(): owners = [Gio.bus_own_name(Gio.BusType.SYSTEM, name, Gio.BusNameOwnerFlags.NONE, connected, acquired, lost) for name in (NAME, UPOWER)] + if "--with-netmanager" in sys.argv[1:]: + owners.append(load_netmanager().own(loop)) try: loop.run() finally: diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-netmanager b/tools/linuxfs/overlay/usr/local/bin/droiddeck-netmanager index f8df451bb..814b110c9 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-netmanager +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-netmanager @@ -408,8 +408,7 @@ class NetworkManager: self.bus.emit_signal(None, path, OBJECT_MANAGER, signal, body) -def main(): - loop = GLib.MainLoop() +def own(loop): state = {} def acquired(bus, name): @@ -419,7 +418,12 @@ def main(): print("== netmanager: the system bus is gone", file=sys.stderr) loop.quit() - Gio.bus_own_name(Gio.BusType.SYSTEM, NAME, Gio.BusNameOwnerFlags.NONE, acquired, None, lost) + return Gio.bus_own_name(Gio.BusType.SYSTEM, NAME, Gio.BusNameOwnerFlags.NONE, acquired, None, lost) + + +def main(): + loop = GLib.MainLoop() + own(loop) loop.run() diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session index 0f134b9b6..6b4f1dca8 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session @@ -328,23 +328,6 @@ case $mode in fi if [ -n "${BL_CLIENT_CPUS:-}" ]; then step "client cores: $BL_CLIENT_CPUS" - ( - declare -A _bl_pinned=() - for ((_i = 0; _i < 720; _i++)); do - for _d in /proc/[0-9]*; do - IFS= read -r -d '' _c < "$_d/cmdline" 2>/dev/null || continue - case $_c in *steamwebhelper*|*steamrtarm64/steam|*gamescope*) ;; *) continue ;; esac - _t=("$_d"/task/*) - _p=${_d#/proc/} - if [ "${_bl_pinned[$_p]:-}" = "${#_t[@]}" ] && client_single_core_matches "$BL_CLIENT_CPUS" "${_t[@]}"; then - continue - fi - taskset -a -cp "$BL_CLIENT_CPUS" "$_p" >/dev/null 2>&1 || true - _bl_pinned[$_p]=${#_t[@]} - done - nap 5 - done - ) & fi if [ "${BL_ZRAM:-0}" = 1 ]; then printf 0 > /tmp/.bl-zram @@ -459,105 +442,50 @@ case $mode in ) & fi fi - [ -z "${BL_DESKTOP:-}" ] && [ "${BL_STRETCHED:-0}" = 1 ] && ( - while :; do - nap 1 - cur=$(xprop -root GAMESCOPE_NEW_SCALING_SCALER 2>/dev/null) || continue - case $cur in *" = "*) cur=${cur##* = } ;; *) cur=0 ;; esac - [ "$cur" = 0 ] || continue - xprop -root -f GAMESCOPE_NEW_SCALING_SCALER 32c -set GAMESCOPE_NEW_SCALING_SCALER 4 2>/dev/null \ - && echo "== stretch 16:9 to panel: the client's scaler was Auto, set to Stretch" - done - ) & - [ -z "${BL_DESKTOP:-}" ] && [ "${BL_RESTORE_ON_RESUME:-1}" = 1 ] && ( - prev= - xprop -spy -root GAMESCOPE_FOCUSED_APP 2>/dev/null | while read -r line; do - app=${line##*= } - case $app in ''|*[!0-9]*) prev=; continue ;; esac - if [ "$prev" = 769 ] && [ "$app" != 769 ]; then - nap 1 - bl_xprop_set_all GAMESCOPE_RESTORE_FOCUS_WINDOW 1 \ - && echo "== resume: asked gamescope to restore the game window (app $app)" - fi - prev=$app - done - ) & [ -z "${BL_DESKTOP:-}" ] && ( - xprop_value() { - case $1 in *'not found'*|*=) echo "$2"; return ;; esac - local v=${1#*=} - v=${v# } - echo "${v:-$2}" + stretch=0 restore=0 + [ "${BL_STRETCHED:-0}" = 1 ] && stretch=1 + [ "${BL_RESTORE_ON_RESUME:-1}" = 1 ] && restore=1 + atoms=(STEAM_TOUCH_CLICK_MODE GAMESCOPE_FOCUSED_APP) + [ "$stretch" = 1 ] && atoms+=(GAMESCOPE_NEW_SCALING_SCALER) + prop_value() { + case $1 in *'not found'*|*=) pv=$2; return ;; esac + pv=${1#*=} + pv=${pv# } + pv=${pv:-$2} } - tmode= app= - xprop -spy -root STEAM_TOUCH_CLICK_MODE GAMESCOPE_FOCUSED_APP 2>/dev/null | while read -r line; do + tmode= app= prev= + while read -r line; do case $line in STEAM_TOUCH_CLICK_MODE*) - v=$(xprop_value "$line" unset) - [ "$v" = "$tmode" ] && continue - tmode=$v + prop_value "$line" unset + [ "$pv" = "$tmode" ] && continue + tmode=$pv echo "== touch mode: $tmode (focused app ${app:-?}) at $(date +%T)" ;; GAMESCOPE_FOCUSED_APP*) - v=$(xprop_value "$line" none) - [ "$v" = "$app" ] && continue - app=$v - echo "== focused app: $app (touch mode ${tmode:-unset}) at $(date +%T)" + prop_value "$line" none + if [ "$pv" != "$app" ]; then + app=$pv + echo "== focused app: $app (touch mode ${tmode:-unset}) at $(date +%T)" + fi + [ "$restore" = 1 ] || continue + case $pv in ''|*[!0-9]*) prev=; continue ;; esac + if [ "$prev" = 769 ] && [ "$pv" != 769 ]; then + nap 1 + bl_xprop_set_all GAMESCOPE_RESTORE_FOCUS_WINDOW 1 \ + && echo "== resume: asked gamescope to restore the game window (app $pv)" + fi + prev=$pv + ;; + GAMESCOPE_NEW_SCALING_SCALER*) + prop_value "$line" 0 + [ "$pv" = 0 ] || continue + xprop -root -f GAMESCOPE_NEW_SCALING_SCALER 32c -set GAMESCOPE_NEW_SCALING_SCALER 4 2>/dev/null \ + && echo "== stretch 16:9 to panel: the client's scaler was Auto, set to Stretch" ;; esac - done - ) & - ( - beat=0 - compat_stamp=$HOME/.bl-compat-beat - seed_stamp=$HOME/.bl-seed-beat - rm -f "$compat_stamp" "$seed_stamp" - changed_since() { - local d - for d in "${@:2}"; do - [ "$d" -nt "$1" ] && return 0 - done - return 1 - } - compat_due() { - [ -e "$compat_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0 - changed_since "$compat_stamp" "${libraries[@]}" "$steam_root/steamapps/common" "$steam_root/config" \ - "$steam_root/compatibilitytools.d" - } - seed_due() { - local reg - [ -e "$seed_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0 - changed_since "$seed_stamp" "${libraries[@]}" && return 0 - for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ - /mnt/droiddeck-sd/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ - /root/Games/*/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ - "$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg \ - "$steam_root"/steamapps/compatdata/*/pfx/system.reg \ - /mnt/droiddeck-sd/steamapps/compatdata/*/pfx/system.reg \ - /root/Games/*/steamapps/compatdata/*/pfx/system.reg; do - [ -f "$reg" ] && [ ! -f "${reg%/*}/.droiddeck-redists" ] && return 0 - done - return 1 - } - for ((_i = 0; _i < 720; _i++)); do - nap 5 - beat=$((beat + 1)) - [ "$((beat % 3))" -eq 0 ] || continue - libraries=("$steam_root/steamapps" /mnt/droiddeck/steamapps /mnt/droiddeck-sd/steamapps /root/Games/*/steamapps) - seed=; compat= - seed_due && seed=1 - compat_due && compat=1 - [ -n "$seed$compat" ] || continue - pgrep -x wineserver >/dev/null 2>&1 && continue - if [ -n "$seed" ]; then - : > "$seed_stamp" - droiddeck-seed-redists "$steam_root" >/dev/null 2>&1 || true - fi - if [ -n "$compat" ]; then - : > "$compat_stamp" - steam-compatibility "$steam_root" >/dev/null 2>&1 || true - fi - done + done < <(xprop -spy -root "${atoms[@]}" 2>/dev/null) ) & if [ -n "${BL_LAUNCH_DIR:-}" ] && [ -z "${BL_DESKTOP:-}" ]; then _bl_held= @@ -630,11 +558,9 @@ BUSCONF sleep 0.1 done if [ -S /run/dbus/system_bus_socket ]; then - droiddeck-netmanager & - netmanager=$! echo "== network: system bus up, NetworkManager stand-in running" rm -f "$BL_LAUNCH_DIR/steam-sleep-ready" - droiddeck-login1 & + droiddeck-login1 --with-netmanager & login_manager=$! for ((_i = 0; _i < 50; _i++)); do [ -f "$BL_LAUNCH_DIR/steam-sleep-ready" ] && break @@ -739,22 +665,6 @@ BUSCONF droiddeck-steam-games watch "$steam_root" "$BL_ADDED_GAMES" & games_watcher=$! fi - if [ "${BL_LOSSLESS_OWNED:-0}" != 1 ]; then - ( - owned=$HOME/.config/droiddeck/lossless-owned - for ((_i = 0; _i < 60; _i++)); do - nap 30 - [ -e "$owned" ] && exit 0 - pgrep -x wineserver >/dev/null 2>&1 && continue - answer=$(droiddeck-steam-games probe "$steam_root" 993090 2>/dev/null) || continue - case $answer in - *'"993090": true'*) mkdir -p "${owned%/*}" && : > "$owned" && echo "== Lossless Scaling: owned" ;; - *) echo "== Lossless Scaling: not owned by this account" ;; - esac - exit 0 - done - ) & - fi echo "== steam args: ${_bl_ui[*]} ${_bl_cdp[*]} ${_bl_lang[*]} -clientbeta $steam_channel -no-cef-sandbox -cef-ozone-platform=x11 ${_bl_cef[*]} $*" steam_updates=0 rm -f "$HOME/.bl-stop-requested" "$STEAMOS_STEAM_SHUTDOWN_SENTINEL" "$STEAMOS_STEAM_REBOOT_SENTINEL" @@ -781,10 +691,54 @@ BUSCONF return 0 done } + requests= if [ -n "${BL_LAUNCH_DIR:-}" ] && [ -d "$BL_LAUNCH_DIR" ]; then rm -f "$BL_LAUNCH_DIR/steam-stop" "$BL_LAUNCH_DIR/steam-game" "$BL_LAUNCH_DIR/steam-game.tmp" - ( - while :; do + requests=1 + fi + ( + tick=0 + beat=0 + library_job= + lossless_job= + lossless_left=0 + lossless_owned=$HOME/.config/droiddeck/lossless-owned + lossless_asked=$HOME/.bl-lossless-asked + rm -f "$lossless_asked" + [ "${BL_LOSSLESS_OWNED:-0}" != 1 ] && lossless_left=60 + declare -A _bl_pinned=() + compat_stamp=$HOME/.bl-compat-beat + seed_stamp=$HOME/.bl-seed-beat + rm -f "$compat_stamp" "$seed_stamp" + changed_since() { + local d + for d in "${@:2}"; do + [ "$d" -nt "$1" ] && return 0 + done + return 1 + } + compat_due() { + [ -e "$compat_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0 + changed_since "$compat_stamp" "${libraries[@]}" "$steam_root/steamapps/common" "$steam_root/config" \ + "$steam_root/compatibilitytools.d" + } + seed_due() { + local reg + [ -e "$seed_stamp" ] && [ "$((beat % 60))" -ne 0 ] || return 0 + changed_since "$seed_stamp" "${libraries[@]}" && return 0 + for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ + /mnt/droiddeck-sd/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ + /root/Games/*/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ + "$steam_root"/compatibilitytools.d/*/files/share/default_pfx*/system.reg \ + "$steam_root"/steamapps/compatdata/*/pfx/system.reg \ + /mnt/droiddeck-sd/steamapps/compatdata/*/pfx/system.reg \ + /root/Games/*/steamapps/compatdata/*/pfx/system.reg; do + [ -f "$reg" ] && [ ! -f "${reg%/*}/.droiddeck-redists" ] && return 0 + done + return 1 + } + while :; do + if [ -n "$requests" ]; then if [ -e "$BL_LAUNCH_DIR/steam-stop" ]; then rm -f "$BL_LAUNCH_DIR/steam-stop" : > "$HOME/.bl-stop-requested" @@ -813,11 +767,56 @@ BUSCONF ;; esac fi - nap 0.5 - done - ) & - stop_watcher=$! - fi + fi + nap 0.5 + tick=$((tick + 1)) + [ "$((tick % 10))" -eq 0 ] || continue + beat=$((beat + 1)) + if [ "$beat" -le 720 ] && [ -n "${BL_CLIENT_CPUS:-}" ]; then + for _d in /proc/[0-9]*; do + IFS= read -r -d '' _c < "$_d/cmdline" 2>/dev/null || continue + case $_c in *steamwebhelper*|*steamrtarm64/steam|*gamescope*) ;; *) continue ;; esac + _t=("$_d"/task/*) + _p=${_d#/proc/} + [ "${_bl_pinned[$_p]:-}" = "${#_t[@]}" ] && client_single_core_matches "$BL_CLIENT_CPUS" "${_t[@]}" && continue + taskset -a -cp "$BL_CLIENT_CPUS" "$_p" >/dev/null 2>&1 || true + _bl_pinned[$_p]=${#_t[@]} + done + fi + if [ "$beat" -le 720 ] && [ "$((beat % 3))" -eq 0 ] && { [ -z "$library_job" ] || ! kill -0 "$library_job" 2>/dev/null; }; then + libraries=("$steam_root/steamapps" /mnt/droiddeck/steamapps /mnt/droiddeck-sd/steamapps /root/Games/*/steamapps) + seed=; compat= + seed_due && seed=1 + compat_due && compat=1 + if [ -n "$seed$compat" ] && ! pgrep -x wineserver >/dev/null 2>&1; then + [ -n "$seed" ] && : > "$seed_stamp" + [ -n "$compat" ] && : > "$compat_stamp" + ( + [ -z "$seed" ] || droiddeck-seed-redists "$steam_root" >/dev/null 2>&1 || true + [ -z "$compat" ] || steam-compatibility "$steam_root" >/dev/null 2>&1 || true + ) & + library_job=$! + fi + fi + if [ "$lossless_left" -gt 0 ] && [ "$((beat % 6))" -eq 0 ] && { [ -z "$lossless_job" ] || ! kill -0 "$lossless_job" 2>/dev/null; }; then + lossless_left=$((lossless_left - 1)) + if [ -e "$lossless_owned" ] || [ -e "$lossless_asked" ]; then + lossless_left=0 + elif ! pgrep -x wineserver >/dev/null 2>&1; then + ( + answer=$(droiddeck-steam-games probe "$steam_root" 993090 2>/dev/null) || exit 0 + case $answer in + *'"993090": true'*) mkdir -p "${lossless_owned%/*}" && : > "$lossless_owned" && echo "== Lossless Scaling: owned" ;; + *) echo "== Lossless Scaling: not owned by this account" ;; + esac + : > "$lossless_asked" + ) & + lossless_job=$! + fi + fi + done + ) & + stop_watcher=$! while :; do rm -f "$HOME/.bl-restart-once" if [ -f "$steam_beta" ] && [ "$(cat "$steam_beta" 2>/dev/null)" != "$steam_channel" ]; then diff --git a/tools/tests/test_login1.py b/tools/tests/test_login1.py index abfdc70a2..a2fb42121 100644 --- a/tools/tests/test_login1.py +++ b/tools/tests/test_login1.py @@ -22,6 +22,8 @@ IFACE = NAME + ".Manager" @unittest.skipUnless(Gio and os.environ.get("DBUS_SESSION_BUS_ADDRESS"), "requires dbus-run-session and PyGObject") class SleepHandshakeTest(unittest.TestCase): + ARGS = [] + def setUp(self): self.directory = tempfile.TemporaryDirectory() self.dir = Path(self.directory.name) @@ -35,7 +37,7 @@ class SleepHandshakeTest(unittest.TestCase): lambda bus, sender, path, interface, name, body: self.signals.append(body.unpack()[0])) env = dict(os.environ, BL_LAUNCH_DIR=str(self.dir), DBUS_SYSTEM_BUS_ADDRESS=os.environ["DBUS_SESSION_BUS_ADDRESS"]) - self.process = subprocess.Popen(["/usr/bin/python3", str(SCRIPT)], env=env, + self.process = subprocess.Popen(["/usr/bin/python3", str(SCRIPT), *self.ARGS], env=env, stdout=subprocess.DEVNULL) self.wait_for(lambda: (self.dir / "steam-sleep-ready").exists()) @@ -218,5 +220,22 @@ class SleepHandshakeTest(unittest.TestCase): self.call(IFACE, "PowerOff", GLib.Variant("(b)", (True,))) +class WithNetworkManagerTest(SleepHandshakeTest): + ARGS = ["--with-netmanager"] + + def owner(self, name): + return self.bus.call_sync("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", + "GetConnectionUnixProcessID", GLib.Variant("(s)", (name,)), None, + Gio.DBusCallFlags.NONE, 2000, None).unpack()[0] + + def test_one_process_owns_login1_and_networkmanager(self): + self.wait_for(lambda: self.bus.call_sync( + "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameHasOwner", + GLib.Variant("(s)", ("org.freedesktop.NetworkManager",)), None, + Gio.DBusCallFlags.NONE, 2000, None).unpack()[0]) + self.assertEqual(self.owner("org.freedesktop.NetworkManager"), self.process.pid) + self.assertEqual(self.owner(NAME), self.process.pid) + + if __name__ == "__main__": unittest.main()