From fd325ef1cc1d3e78b5e7686819e05606cc79d963 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Wed, 22 Mar 2023 01:37:23 +0100 Subject: Add cross-platform support to decky (#387) * Import generic watchdog observer over platform specific import * Use os.path rather than genericpath * Split off socket management in plugin.py * Don't specify multiprocessing start type Default on linux is already fork * Move all platform-specific functions to seperate files TODO: make plugin.py platform agnostic * fix import * add backwards compat to helpers.py * add backwards compatibility to helpers.py harder * Testing autobuild for win * Testing autobuild for win, try 2 * Testing autobuild for win, try 3 * Testing autobuild for win, try 4 * Create the plugins folder before attempting to use it * Implement win get_username() * Create win install script * Fix branch guess from version * Create .loader.version in install script * Add .cmd shim to facilitate auto-restarts * Properly fix branch guess from version * Fix updater on windows * Try 2 of fixing updates for windows * Test * pain * Update install script * Powershell doesn't believe in utf8 * Powershell good * add ON_LINUX variable to localplatform * Fix more merge issues * test * Move custom imports to main.py * Move custom imports to after __main__ check Due to windows' default behaviour being spawn, it will spawn a new process and thus import into sys.path multiple times * Log errors in get_system_pythonpaths() and get_loader_version() + split get_system_pythonpaths() on newline * Remove whitespace in result of get_system_pythonpaths() * use python3 on linux and python on windows in get_system_pythonpaths() * Remove fork-specific urls * Fix MIME types not working on Windows --- backend/localsocket.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 backend/localsocket.py (limited to 'backend/localsocket.py') diff --git a/backend/localsocket.py b/backend/localsocket.py new file mode 100644 index 00000000..ef0e3933 --- /dev/null +++ b/backend/localsocket.py @@ -0,0 +1,132 @@ +import asyncio, time, random +from localplatform import ON_WINDOWS + +BUFFER_LIMIT = 2 ** 20 # 1 MiB + +class UnixSocket: + def __init__(self, on_new_message): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + self.socket_addr = f"/tmp/plugin_socket_{time.time()}" + self.on_new_message = on_new_message + self.socket = None + self.reader = None + self.writer = None + + async def setup_server(self): + self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + + async def get_socket_connection(self): + if not await self._open_socket_if_not_exists(): + return None, None + + return self.reader, self.writer + + async def close_socket_connection(self): + if self.writer != None: + self.writer.close() + + self.reader = None + + async def read_single_line(self) -> str|None: + reader, writer = await self.get_socket_connection() + + if self.reader == None: + return None + + return await self._read_single_line(reader) + + async def write_single_line(self, message : str): + reader, writer = await self.get_socket_connection() + + if self.writer == None: + return; + + await self._write_single_line(writer, message) + + async def _read_single_line(self, reader) -> str: + line = bytearray() + while True: + try: + line.extend(await reader.readuntil()) + except asyncio.LimitOverrunError: + line.extend(await reader.read(reader._limit)) + continue + except asyncio.IncompleteReadError as err: + line.extend(err.partial) + break + else: + break + + return line.decode("utf-8") + + async def _write_single_line(self, writer, message : str): + if not message.endswith("\n"): + message += "\n" + + writer.write(message.encode("utf-8")) + await writer.drain() + + async def _listen_for_method_call(self, reader, writer): + while True: + line = await self._read_single_line(reader) + + try: + res = await self.on_new_message(line) + except Exception as e: + return + + if res != None: + await self._write_single_line(writer, res) + +class PortSocket (UnixSocket): + def __init__(self, on_new_message): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + super().__init__(on_new_message) + self.host = "127.0.0.1" + self.port = random.sample(range(40000, 60000), 1)[0] + + async def setup_server(self): + self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + +if ON_WINDOWS: + class LocalSocket (PortSocket): + pass +else: + class LocalSocket (UnixSocket): + pass \ No newline at end of file -- cgit v1.2.3