From 2c82ac0bd797d5a76abfbe362437e6aaf8ef0b6f Mon Sep 17 00:00:00 2001 From: winstxnhdw Date: Sun, 25 Oct 2026 03:13:43 +0800 Subject: [PATCH 1/6] fix: add support for `startupinfo` --- tests/test_process.py | 47 +++++++++++++++++++++++++++++++++++++ winloop/handles/process.pxd | 8 ++++--- winloop/handles/process.pyx | 39 ++++++++++++++++++++++++++---- winloop/includes/uv.pxd | 3 ++- winloop/loop.pyx | 5 ++-- 5 files changed, 90 insertions(+), 12 deletions(-) diff --git a/tests/test_process.py b/tests/test_process.py index c7be41e..81057ca 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -38,6 +38,30 @@ def __exit__(self, exc_type, exc_val, exc_tb): class _TestProcess: + @unittest.skipUnless(sys.platform == "win32", "Windows startupinfo") + def test_process_startupinfo(self): + async def test(): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE + + proc = await asyncio.create_subprocess_exec( + sys.executable, + "-c", + "import sys; sys.stdout.write(sys.stdin.read())", + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + startupinfo=startupinfo, + ) + + out, err = await proc.communicate(b"startupinfo") + self.assertEqual(out, b"startupinfo") + self.assertEqual(err, b"") + self.assertEqual(proc.returncode, 0) + + self.loop.run_until_complete(test()) + def get_num_fds(self): # Winloop comment: use num_handles() as a meaningful # substitute for num_fds() on Windows. @@ -940,6 +964,29 @@ async def test(): class Test_UV_Process(_TestProcess, tb.UVTestCase): + @unittest.skipUnless(sys.platform == "win32", "Windows startupinfo") + def test_process_startupinfo_unsupported_flags(self): + async def test(): + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESTDHANDLES + + with self.assertRaisesRegex(ValueError, "startupinfo.dwFlags"): + await asyncio.create_subprocess_exec( + sys.executable, "-c", "pass", startupinfo=startupinfo, + ) + + self.loop.run_until_complete(test()) + + @unittest.skipIf(sys.platform == "win32", "POSIX startupinfo validation") + def test_process_startupinfo_posix(self): + async def test(): + with self.assertRaisesRegex(ValueError, "startupinfo"): + await asyncio.create_subprocess_exec( + sys.executable, "-c", "pass", startupinfo=object(), + ) + + self.loop.run_until_complete(test()) + @unittest.skipIf(sys.platform != "win32", "problem is windows related only") def test_windows_issue_153(self): """Winloop issue 153 is related to bringing back the shlex parser diff --git a/winloop/handles/process.pxd b/winloop/handles/process.pxd index 970abcf..3e57c9e 100644 --- a/winloop/handles/process.pxd +++ b/winloop/handles/process.pxd @@ -25,7 +25,7 @@ cdef class UVProcess(UVHandle): cdef _init(self, Loop loop, list args, dict env, cwd, start_new_session, _stdin, _stdout, _stderr, pass_fds, - debug_flags, preexec_fn, restore_signals) + debug_flags, preexec_fn, restore_signals, startupinfo) cdef _after_fork(self) @@ -33,8 +33,10 @@ cdef class UVProcess(UVHandle): cdef _init_args(self, list args) cdef _init_env(self, dict env) cdef _init_files(self, _stdin, _stdout, _stderr) + cdef _init_startupinfo(self, startupinfo) cdef _init_options(self, list args, dict env, cwd, start_new_session, - _stdin, _stdout, _stderr, bint force_fork) + _stdin, _stdout, _stderr, bint force_fork, + startupinfo) cdef _close_after_spawn(self, int fd) @@ -77,4 +79,4 @@ cdef class UVProcessTransport(UVProcess): _stdin, _stdout, _stderr, pass_fds, waiter, debug_flags, - preexec_fn, restore_signals) + preexec_fn, restore_signals, startupinfo) diff --git a/winloop/handles/process.pyx b/winloop/handles/process.pyx index b82d186..5b8af8b 100644 --- a/winloop/handles/process.pyx +++ b/winloop/handles/process.pyx @@ -25,7 +25,8 @@ cdef class UVProcess(UVHandle): cdef _init(self, Loop loop, list args, dict env, cwd, start_new_session, _stdin, _stdout, _stderr, # std* can be defined as macros in C - pass_fds, debug_flags, preexec_fn, restore_signals): + pass_fds, debug_flags, preexec_fn, restore_signals, + startupinfo): global __forking @@ -58,7 +59,8 @@ cdef class UVProcess(UVHandle): try: self._init_options(args, env, cwd, start_new_session, - _stdin, _stdout, _stderr, force_fork) + _stdin, _stdout, _stderr, force_fork, + startupinfo) restore_inheritable = set() if pass_fds: @@ -254,10 +256,12 @@ cdef class UVProcess(UVHandle): return ret cdef _init_options(self, list args, dict env, cwd, start_new_session, - _stdin, _stdout, _stderr, bint force_fork): + _stdin, _stdout, _stderr, bint force_fork, + startupinfo): memset(&self.options, 0, sizeof(uv.uv_process_options_t)) + self._init_startupinfo(startupinfo) self._init_env(env) self.options.env = self.uv_opt_env @@ -308,6 +312,29 @@ cdef class UVProcess(UVHandle): self._init_files(_stdin, _stdout, _stderr) + cdef _init_startupinfo(self, startupinfo): + if startupinfo is None: + return + if not system.PLATFORM_IS_WINDOWS: + raise ValueError('startupinfo is not supported') + if startupinfo.dwFlags & ~subprocess.STARTF_USESHOWWINDOW: + raise ValueError( + 'startupinfo.dwFlags only supports STARTF_USESHOWWINDOW') + attribute_list = startupinfo.lpAttributeList + if attribute_list and ( + attribute_list.get('handle_list') + or set(attribute_list) != {'handle_list'} + ): + raise ValueError( + 'non-empty startupinfo.lpAttributeList is not supported') + if startupinfo.dwFlags & subprocess.STARTF_USESHOWWINDOW: + if startupinfo.wShowWindow != subprocess.SW_HIDE: + raise ValueError( + 'startupinfo.wShowWindow only supports SW_HIDE') + # Match STARTF_USESHOWWINDOW with SW_HIDE, without changing + # whether the child has a console. + self.options.flags |= uv.UV_PROCESS_WINDOWS_HIDE_GUI + cdef _init_args(self, list args): cdef: bytes path @@ -644,7 +671,8 @@ cdef class UVProcessTransport(UVProcess): waiter, debug_flags, preexec_fn, - restore_signals): + restore_signals, + startupinfo): cdef UVProcessTransport handle handle = UVProcessTransport.__new__(UVProcessTransport) @@ -656,7 +684,8 @@ cdef class UVProcessTransport(UVProcess): pass_fds, debug_flags, preexec_fn, - restore_signals) + restore_signals, + startupinfo) if handle._init_futs: handle._stdio_ready = 0 diff --git a/winloop/includes/uv.pxd b/winloop/includes/uv.pxd index 8e824ce..919f243 100644 --- a/winloop/includes/uv.pxd +++ b/winloop/includes/uv.pxd @@ -477,7 +477,8 @@ cdef extern from "uv.h" nogil: UV_PROCESS_SETGID = 2, UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = 4, UV_PROCESS_DETACHED = 8, - UV_PROCESS_WINDOWS_HIDE = 16 + UV_PROCESS_WINDOWS_HIDE = 16, + UV_PROCESS_WINDOWS_HIDE_GUI = 64 ctypedef enum uv_stdio_flags: UV_IGNORE = 0x00, diff --git a/winloop/loop.pyx b/winloop/loop.pyx index 47c1791..351fe71 100644 --- a/winloop/loop.pyx +++ b/winloop/loop.pyx @@ -2825,8 +2825,6 @@ cdef class Loop: raise ValueError("universal_newlines must be False") if bufsize != 0: raise ValueError("bufsize must be 0") - if startupinfo is not None: - raise ValueError('startupinfo is not supported') if creationflags != 0: raise ValueError('creationflags is not supported') @@ -2848,7 +2846,8 @@ cdef class Loop: waiter, debug_flags, preexec_fn, - restore_signals) + restore_signals, + startupinfo) try: await waiter From 8dc3190c6871acbbced649f9d9753e16f7fb6371 Mon Sep 17 00:00:00 2001 From: Vizonex <114684698+Vizonex@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:33:01 -0500 Subject: [PATCH 2/6] Add STARTF_USESHOWWINDOW as a flag. --- winloop/includes/stdlib.pxi | 1 + 1 file changed, 1 insertion(+) diff --git a/winloop/includes/stdlib.pxi b/winloop/includes/stdlib.pxi index 0f38931..52a41ae 100644 --- a/winloop/includes/stdlib.pxi +++ b/winloop/includes/stdlib.pxi @@ -165,6 +165,7 @@ cdef threading_main_thread = threading.main_thread cdef int subprocess_PIPE = subprocess.PIPE cdef int subprocess_STDOUT = subprocess.STDOUT cdef int subprocess_DEVNULL = subprocess.DEVNULL +cdef int subprocess_STARTF_USESHOWWINDOW = getattr(subprocess, "STARTF_USESHOWWINDOW", 0) cdef subprocess_SubprocessError = subprocess.SubprocessError cdef int signal_SIGABRT = signal.SIGABRT From 63f4102e09dd29c2d0ebb62e8c5d506780785695 Mon Sep 17 00:00:00 2001 From: Vizonex <114684698+Vizonex@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:35:32 -0500 Subject: [PATCH 3/6] Replace subprocess.STARTF_USESHOWWINDOW with subprocess_STARTF_USESHOWWINDOW integer flag. --- winloop/handles/process.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/winloop/handles/process.pyx b/winloop/handles/process.pyx index 5b8af8b..99b73db 100644 --- a/winloop/handles/process.pyx +++ b/winloop/handles/process.pyx @@ -317,7 +317,7 @@ cdef class UVProcess(UVHandle): return if not system.PLATFORM_IS_WINDOWS: raise ValueError('startupinfo is not supported') - if startupinfo.dwFlags & ~subprocess.STARTF_USESHOWWINDOW: + if startupinfo.dwFlags & ~subprocess_STARTF_USESHOWWINDOW: raise ValueError( 'startupinfo.dwFlags only supports STARTF_USESHOWWINDOW') attribute_list = startupinfo.lpAttributeList @@ -327,7 +327,7 @@ cdef class UVProcess(UVHandle): ): raise ValueError( 'non-empty startupinfo.lpAttributeList is not supported') - if startupinfo.dwFlags & subprocess.STARTF_USESHOWWINDOW: + if startupinfo.dwFlags & subprocess_STARTF_USESHOWWINDOW: if startupinfo.wShowWindow != subprocess.SW_HIDE: raise ValueError( 'startupinfo.wShowWindow only supports SW_HIDE') From e3b926f30048bf3402a776327a8626ad3e368045 Mon Sep 17 00:00:00 2001 From: Vizonex <114684698+Vizonex@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:40:24 -0500 Subject: [PATCH 4/6] Add SW_HIDE flag. --- winloop/includes/stdlib.pxi | 1 + 1 file changed, 1 insertion(+) diff --git a/winloop/includes/stdlib.pxi b/winloop/includes/stdlib.pxi index 52a41ae..eef6211 100644 --- a/winloop/includes/stdlib.pxi +++ b/winloop/includes/stdlib.pxi @@ -166,6 +166,7 @@ cdef int subprocess_PIPE = subprocess.PIPE cdef int subprocess_STDOUT = subprocess.STDOUT cdef int subprocess_DEVNULL = subprocess.DEVNULL cdef int subprocess_STARTF_USESHOWWINDOW = getattr(subprocess, "STARTF_USESHOWWINDOW", 0) +cdef int subprocess_SW_HIDE = getattr(subprocess, "SW_HIDE", 0) cdef subprocess_SubprocessError = subprocess.SubprocessError cdef int signal_SIGABRT = signal.SIGABRT From 4e4f7967ff35a9ba08537a425d5b4d61f8729353 Mon Sep 17 00:00:00 2001 From: Vizonex <114684698+Vizonex@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:41:28 -0500 Subject: [PATCH 5/6] Turn subprocess.SW_HIDE into an integer flag --- winloop/handles/process.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winloop/handles/process.pyx b/winloop/handles/process.pyx index 99b73db..722d652 100644 --- a/winloop/handles/process.pyx +++ b/winloop/handles/process.pyx @@ -328,7 +328,7 @@ cdef class UVProcess(UVHandle): raise ValueError( 'non-empty startupinfo.lpAttributeList is not supported') if startupinfo.dwFlags & subprocess_STARTF_USESHOWWINDOW: - if startupinfo.wShowWindow != subprocess.SW_HIDE: + if startupinfo.wShowWindow != subprocess_SW_HIDE: raise ValueError( 'startupinfo.wShowWindow only supports SW_HIDE') # Match STARTF_USESHOWWINDOW with SW_HIDE, without changing From 5885984fa282cb932484b33ad8d63b720323ef08 Mon Sep 17 00:00:00 2001 From: Vizonex <114684698+Vizonex@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:52:09 -0500 Subject: [PATCH 6/6] Update winloop/handles/process.pyx --- winloop/handles/process.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/winloop/handles/process.pyx b/winloop/handles/process.pyx index 722d652..0c70940 100644 --- a/winloop/handles/process.pyx +++ b/winloop/handles/process.pyx @@ -311,7 +311,8 @@ cdef class UVProcess(UVHandle): self.options.exit_cb = &__uvprocess_on_exit_callback self._init_files(_stdin, _stdout, _stderr) - +# Winloop needs startupinfo in order to work with playwright +# SEE: https://github.com/Vizonex/Winloop/issues/34 cdef _init_startupinfo(self, startupinfo): if startupinfo is None: return