87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""Own Windows capture processes even when the task worker is forcibly terminated."""
|
|
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
|
|
|
|
class CaptureProcessJob:
|
|
"""A non-inheritable job handle: Windows kills captures when its owner exits."""
|
|
|
|
def __init__(self):
|
|
class BasicLimits(ctypes.Structure):
|
|
_fields_ = [
|
|
("process_time", ctypes.c_int64),
|
|
("job_time", ctypes.c_int64),
|
|
("flags", wintypes.DWORD),
|
|
("minimum_working_set", ctypes.c_size_t),
|
|
("maximum_working_set", ctypes.c_size_t),
|
|
("active_process_limit", wintypes.DWORD),
|
|
("affinity", ctypes.c_size_t),
|
|
("priority", wintypes.DWORD),
|
|
("scheduling", wintypes.DWORD),
|
|
]
|
|
|
|
class IoCounters(ctypes.Structure):
|
|
_fields_ = [
|
|
(name, ctypes.c_uint64)
|
|
for name in (
|
|
"read_ops",
|
|
"write_ops",
|
|
"other_ops",
|
|
"read_bytes",
|
|
"write_bytes",
|
|
"other_bytes",
|
|
)
|
|
]
|
|
|
|
class ExtendedLimits(ctypes.Structure):
|
|
_fields_ = [
|
|
("basic", BasicLimits),
|
|
("io", IoCounters),
|
|
("process_memory", ctypes.c_size_t),
|
|
("job_memory", ctypes.c_size_t),
|
|
("peak_process_memory", ctypes.c_size_t),
|
|
("peak_job_memory", ctypes.c_size_t),
|
|
]
|
|
|
|
self._api = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
for name, arguments, result in (
|
|
("CreateJobObjectW", [wintypes.LPVOID, wintypes.LPCWSTR], wintypes.HANDLE),
|
|
(
|
|
"SetInformationJobObject",
|
|
[wintypes.HANDLE, ctypes.c_int, wintypes.LPVOID, wintypes.DWORD],
|
|
wintypes.BOOL,
|
|
),
|
|
("OpenProcess", [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD], wintypes.HANDLE),
|
|
("AssignProcessToJobObject", [wintypes.HANDLE, wintypes.HANDLE], wintypes.BOOL),
|
|
("CloseHandle", [wintypes.HANDLE], wintypes.BOOL),
|
|
):
|
|
function = getattr(self._api, name)
|
|
function.argtypes, function.restype = arguments, result
|
|
self._handle = self._api.CreateJobObjectW(None, None)
|
|
if not self._handle:
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
limits = ExtendedLimits()
|
|
limits.basic.flags = 0x00002000 # JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
|
if not self._api.SetInformationJobObject(
|
|
self._handle, 9, ctypes.byref(limits), ctypes.sizeof(limits)
|
|
):
|
|
error = ctypes.get_last_error()
|
|
self.close()
|
|
raise ctypes.WinError(error)
|
|
|
|
def assign(self, pid: int):
|
|
process = self._api.OpenProcess(0x0100 | 0x0001, False, pid)
|
|
if not process:
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
try:
|
|
if not self._api.AssignProcessToJobObject(self._handle, process):
|
|
raise ctypes.WinError(ctypes.get_last_error())
|
|
finally:
|
|
self._api.CloseHandle(process)
|
|
|
|
def close(self):
|
|
if self._handle:
|
|
self._api.CloseHandle(self._handle)
|
|
self._handle = None
|