This commit is contained in:
2026-03-21 15:12:52 -07:00
parent 11384266e3
commit d9a6b1bfb4
6 changed files with 512 additions and 21 deletions

View File

@@ -48,6 +48,42 @@ def _windows_pipe_available(path: str) -> bool:
return False
def _windows_pipe_bytes_available(pipe: BinaryIO) -> Optional[int]:
"""Return the number of bytes ready to read from a Windows named pipe."""
if platform.system() != "Windows":
return None
try:
import msvcrt
handle = msvcrt.get_osfhandle(pipe.fileno())
kernel32 = ctypes.windll.kernel32
PeekNamedPipe = kernel32.PeekNamedPipe
PeekNamedPipe.argtypes = [
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.c_void_p,
ctypes.POINTER(ctypes.c_uint32),
ctypes.c_void_p,
]
PeekNamedPipe.restype = ctypes.c_bool
total_available = ctypes.c_uint32(0)
ok = PeekNamedPipe(
ctypes.c_void_p(handle),
None,
0,
None,
ctypes.byref(total_available),
None,
)
if not ok:
return None
return int(total_available.value)
except Exception:
return None
def _windows_pythonw_exe(python_exe: Optional[str]) -> Optional[str]:
"""Return a pythonw.exe adjacent to python.exe if available (Windows only)."""
if platform.system() != "Windows":
@@ -921,15 +957,46 @@ class MPVIPCClient:
deadline = _time.time() + max(0.0, effective_timeout)
if self.is_windows:
try:
pipe = cast(BinaryIO, self.sock)
return pipe.readline()
except (OSError, IOError, BrokenPipeError, ConnectionResetError) as exc:
# Pipe error; try to reconnect once
if not self.silent:
debug(f"Pipe readline failed: {exc}")
self.disconnect()
return None
pipe = cast(BinaryIO, self.sock)
while True:
nl = self._recv_buffer.find(b"\n")
if nl != -1:
line = self._recv_buffer[:nl + 1]
self._recv_buffer = self._recv_buffer[nl + 1:]
return line
remaining = deadline - _time.time()
if remaining <= 0:
return None
try:
available = _windows_pipe_bytes_available(pipe)
except Exception as exc:
if not self.silent:
debug(f"Pipe availability probe failed: {exc}")
self.disconnect()
return None
if available is None:
self.disconnect()
return None
if available <= 0:
_time.sleep(min(0.01, max(0.001, remaining)))
continue
try:
chunk = pipe.read(min(available, 4096))
except (OSError, IOError, BrokenPipeError, ConnectionResetError) as exc:
if not self.silent:
debug(f"Pipe readline failed: {exc}")
self.disconnect()
return None
if not chunk:
return b""
self._recv_buffer += chunk
# Unix: buffer until newline.
sock_obj = cast(socket.socket, self.sock)
@@ -1034,6 +1101,7 @@ class MPVIPCClient:
try:
# Try to open the named pipe
self.sock = open(self.socket_path, "r+b", buffering=0)
self._recv_buffer = b""
return True
except (OSError, IOError) as exc:
if not self.silent:
@@ -1055,6 +1123,7 @@ class MPVIPCClient:
self.sock = socket.socket(af_unix, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.connect(self.socket_path)
self._recv_buffer = b""
return True
except Exception as exc:
if not self.silent:
@@ -1167,6 +1236,7 @@ class MPVIPCClient:
except Exception:
pass
self.sock = None
self._recv_buffer = b""
def __del__(self) -> None:
"""Cleanup on object destruction."""