Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work around account switching failing to open the CEF debugger socket #668

Merged
merged 3 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/decky_loader/localplatform/localplatform.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ def get_live_reload() -> bool:
def get_keep_systemd_service() -> bool:
return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1"

def get_use_cef_close_workaround() -> bool:
return ON_LINUX and os.getenv("USE_CEF_CLOSE_WORKAROUND", "1") == "1"

def get_log_level() -> int:
return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
os.getenv("LOG_LEVEL", "INFO")
Expand Down
38 changes: 38 additions & 0 deletions backend/decky_loader/localplatform/localplatformlinux.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from re import compile
from asyncio import Lock
import os, pwd, grp, sys, logging
from subprocess import call, run, DEVNULL, PIPE, STDOUT
from ..enums import UserType
Expand Down Expand Up @@ -227,3 +229,39 @@ def get_unprivileged_user() -> str:
user = 'deck'

return user

# Works around the CEF debugger TCP socket not closing properly when Steam restarts
# Group 1 is PID, group 2 is FD. this also filters for "steamwebhelper" in the process name.
cef_socket_lsof_regex = compile(r"^p(\d+)(?:\s|.)+csteamwebhelper(?:\s|.)+f(\d+)(?:\s|.)+TST=LISTEN")
close_cef_socket_lock = Lock()

async def close_cef_socket():
async with close_cef_socket_lock:
if _get_effective_user_id() != 0:
logger.warn("Can't close CEF socket as Decky isn't running as root.")
return
# Look for anything listening TCP on port 8080
lsof = run(["lsof", "-F", "-iTCP:8080", "-sTCP:LISTEN"], capture_output=True, text=True)
if lsof.returncode != 0 or len(lsof.stdout) < 1:
logger.error(f"lsof call failed in close_cef_socket! return code: {str(lsof.returncode)}")
return

lsof_data = cef_socket_lsof_regex.match(lsof.stdout)

if not lsof_data:
logger.error("lsof regex match failed in close_cef_socket!")
return

pid = lsof_data.group(1)
fd = lsof_data.group(2)

logger.info(f"Closing CEF socket with PID {pid} and FD {fd}")

# Use gdb to inject a close() call for the socket fd into steamwebhelper
gdb_ret = run(["gdb", "--nx", "-p", pid, "--batch", "--eval-command", f"call (int)close({fd})"], env={"LD_LIBRARY_PATH": ""})

if gdb_ret.returncode != 0:
logger.error(f"Failed to close CEF socket with gdb! return code: {str(gdb_ret.returncode)}", exc_info=True)
return

logger.info("CEF socket closed")
5 changes: 4 additions & 1 deletion backend/decky_loader/localplatform/localplatformwin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ def get_unprivileged_user() -> str:
return os.getenv("UNPRIVILEGED_USER", os.getlogin())

async def restart_webhelper() -> bool:
return True # Stubbed
return True # Stubbed

async def close_cef_socket():
return # Stubbed
8 changes: 6 additions & 2 deletions backend/decky_loader/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
if TYPE_CHECKING:
from .main import PluginManager
from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab
from .localplatform.localplatform import ON_WINDOWS
from . import helpers
from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username
from .localplatform.localplatform import ON_WINDOWS, service_stop, service_start, get_home_path, get_username, get_use_cef_close_workaround, close_cef_socket

class FilePickerObj(TypedDict):
file: Path
Expand Down Expand Up @@ -78,6 +77,7 @@ def __init__(self, context: PluginManager) -> None:
context.ws.add_route("utilities/get_tab_id", self.get_tab_id)
context.ws.add_route("utilities/get_user_info", self.get_user_info)
context.ws.add_route("utilities/http_request", self.http_request_legacy)
context.ws.add_route("utilities/close_cef_socket", self.close_cef_socket)
context.ws.add_route("utilities/_call_legacy_utility", self._call_legacy_utility)

context.web_app.add_routes([
Expand Down Expand Up @@ -287,6 +287,10 @@ async def stop_ssh(self):
await service_stop(helpers.SSHD_UNIT)
return True

async def close_cef_socket(self):
if get_use_cef_close_workaround():
await close_cef_socket()

async def filepicker_ls(self,
path: str | None = None,
include_files: bool = True,
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/steamfixes/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// import reloadFix from './reload';
import restartFix from './restart';
// import restartFix from './restart';
import cefSocketFix from './socket';

let fixes: Function[] = [];

export function deinitSteamFixes() {
fixes.forEach((deinit) => deinit());
}

export async function initSteamFixes() {
// fixes.push(await reloadFix());
fixes.push(await restartFix());
fixes.push(cefSocketFix());
// fixes.push(await restartFix());
}
16 changes: 16 additions & 0 deletions frontend/src/steamfixes/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Logger from '../logger';

const logger = new Logger('CEFSocketFix');

const closeCEFSocket = DeckyBackend.callable<[], void>('utilities/close_cef_socket');

export default function cefSocketFix() {
const reg = window.SteamClient?.User?.RegisterForShutdownStart(async () => {
logger.log('Closing CEF socket before shutdown');
await closeCEFSocket();
});

if (reg) logger.debug('CEF shutdown handler ready');

return () => reg?.unregister();
}
Loading