Skip to content

Commit c96132e

Browse files
committed
Merge remote-tracking branch 'origin/dev' into winapiSp
2 parents b3d7fa4 + 85f19a2 commit c96132e

File tree

6 files changed

+102
-75
lines changed

6 files changed

+102
-75
lines changed

alas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ def loop(self):
563563
# Reboot emulator
564564
if not self.device.emulator_check():
565565
self.emurestart(task)
566+
self.device.config = self.config
566567
# Skip first restart
567568
if self.is_first_task and task == 'Restart':
568569
logger.info('Skip task `Restart` at scheduler start')

module/device/platform/api_windows.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
175175
'"E:\\Program Files\\Netease\\MuMu Player 12\\shell\\MuMuPlayer.exe" -v 1'
176176
177177
Returns:
178-
process: tuple(processhandle, threadhandle, processid, mainthreadid),
178+
process: PROCESS_INFORMATION,
179179
focusedwindow: tuple(hwnd, WINDOWPLACEMENT)
180180
181181
Raises:
@@ -206,9 +206,9 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
206206
lpStartupInfo.cb = sizeof(STARTUPINFOW)
207207
lpStartupInfo.dwFlags = STARTF_USESHOWWINDOW
208208
if start:
209-
lpStartupInfo.wShowWindow = SW_HIDE if silentstart else SW_MINIMIZE
209+
lpStartupInfo.wShowWindow = SW_FORCEMINIMIZE if silentstart else SW_MINIMIZE
210210
else:
211-
lpStartupInfo.wShowWindow = SW_HIDE
211+
lpStartupInfo.wShowWindow = SW_FORCEMINIMIZE
212212
lpProcessInformation = PROCESS_INFORMATION()
213213

214214
success = CreateProcessW(
@@ -231,7 +231,7 @@ def execute(command: str, silentstart: bool, start: bool) -> tuple:
231231
return lpProcessInformation, focusedwindow
232232
else:
233233
closehandle(lpProcessInformation.hProcess, lpProcessInformation.hThread)
234-
return (), focusedwindow
234+
return None, focusedwindow
235235

236236

237237
def terminate_process(pid: int) -> bool:
@@ -342,9 +342,8 @@ def kill_process_by_regex(regex: str) -> int:
342342
"""
343343
count = 0
344344

345-
processes = _enum_processes()
346345
try:
347-
for lppe32 in processes:
346+
for lppe32 in _enum_processes():
348347
pid = lppe32.th32ProcessID
349348
cmdline = get_cmdline(lppe32.th32ProcessID)
350349
if not re.search(regex, cmdline):
@@ -353,7 +352,6 @@ def kill_process_by_regex(regex: str) -> int:
353352
terminate_process(pid)
354353
count += 1
355354
except IterationFinished:
356-
processes.close()
357355
return count
358356

359357

@@ -430,9 +428,8 @@ def get_thread(pid: int) -> int:
430428
"""
431429
mainthreadid = 0
432430
minstarttime = MAXULONGLONG
433-
threads = _enum_threads()
434431
try:
435-
for lpte32 in threads:
432+
for lpte32 in _enum_threads():
436433
if lpte32.th32OwnerProcessID != pid:
437434
continue
438435

@@ -444,11 +441,10 @@ def get_thread(pid: int) -> int:
444441
minstarttime = threadstarttime
445442
mainthreadid = lpte32.th32ThreadID
446443
except IterationFinished:
447-
threads.close()
448444
return mainthreadid
449445

450446

451-
def _get_process(pid: int) -> tuple:
447+
def _get_process(pid: int) -> PROCESS_INFORMATION:
452448
"""
453449
Get emulator's handle.
454450
@@ -470,12 +466,12 @@ def _get_process(pid: int) -> tuple:
470466
CloseHandle(hProcess)
471467
report("OpenThread failed.", level=30)
472468

473-
return hProcess, hThread, pid, tid
469+
return PROCESS_INFORMATION(hProcess, hThread, pid, tid)
474470
except Exception as e:
475471
logger.warning(f"Failed to get process and thread handles: {e}")
476-
return None, None, pid, tid
472+
return PROCESS_INFORMATION(None, None, pid, tid)
477473

478-
def get_process(instance: EmulatorInstance) -> tuple:
474+
def get_process(instance: EmulatorInstance) -> PROCESS_INFORMATION:
479475
"""
480476
Get emulator's process.
481477
@@ -491,26 +487,22 @@ def get_process(instance: EmulatorInstance) -> tuple:
491487
OSError if any winapi failed.
492488
IterationFinished if enumeration completed.
493489
"""
494-
processes = _enum_processes()
495-
for lppe32 in processes:
490+
for lppe32 in _enum_processes():
496491
pid = lppe32.th32ProcessID
497492
cmdline = get_cmdline(pid)
498493
if not instance.path in cmdline:
499494
continue
500495
if instance == Emulator.MuMuPlayer12:
501496
match = re.search(r'-v\s*(\d+)', cmdline)
502497
if match and int(match.group(1)) == instance.MuMuPlayer12_id:
503-
processes.close()
504498
return _get_process(pid)
505499
elif instance == Emulator.LDPlayerFamily:
506500
match = re.search(r'index=\s*(\d+)', cmdline)
507501
if match and int(match.group(1)) == instance.LDPlayer_id:
508-
processes.close()
509502
return _get_process(pid)
510503
else:
511504
matchname = re.search(fr'{instance.name}(\s+|$)', cmdline)
512505
if matchname and matchname.group(0).strip() == instance.name:
513-
processes.close()
514506
return _get_process(pid)
515507

516508

@@ -526,13 +518,7 @@ def switch_window(hwnds: list, arg: int = SW_SHOWNORMAL) -> bool:
526518
bool:
527519
"""
528520
for hwnd in hwnds:
529-
if not IsWindow(hwnd):
530-
continue
531-
if GetParent(hwnd):
532-
continue
533-
rect = RECT()
534-
GetWindowRect(hwnd, byref(rect))
535-
if {rect.left, rect.top, rect.right, rect.bottom} == {0}:
521+
if not GetWindow(hwnd, GW_CHILD):
536522
continue
537523
ShowWindow(hwnd, arg)
538524
return True

module/device/platform/platform_windows.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class EmulatorUnknown(Exception):
1313

1414
class PlatformWindows(PlatformBase, EmulatorManager):
1515
# Quadruple, contains the kernel process object, kernel thread object, process ID and thread ID.
16-
process: tuple = ()
16+
process = None
1717
# Window handles of the target process.
1818
hwnds: list = []
1919
# Pair, contains the hwnd of the focused window and a WINDOWPLACEMENT object.
@@ -31,7 +31,7 @@ def __execute(self, command: str, start: bool) -> bool:
3131
if self.process:
3232
if not all(self.process[:2]):
3333
api_windows.closehandle(*self.process[:2])
34-
self.process = ()
34+
self.process = None
3535

3636
if self.hwnds:
3737
self.hwnds = []
@@ -62,7 +62,7 @@ def get_hwnds(pid: int) -> list:
6262
return api_windows.get_hwnds(pid)
6363

6464
@staticmethod
65-
def get_process(instance: api_windows.t.Optional[EmulatorInstance]) -> tuple:
65+
def get_process(instance: api_windows.t.Optional[EmulatorInstance]) -> api_windows.PROCESS_INFORMATION:
6666
return api_windows.get_process(instance)
6767

6868
@staticmethod
@@ -102,8 +102,8 @@ def _emulator_start(self, instance: EmulatorInstance):
102102
logger.warning(f'Cannot get MuMu instance index from name {instance.name}')
103103
self._start(f'"{exe}" -v {instance.MuMuPlayer12_id}')
104104
elif instance == Emulator.LDPlayerFamily:
105-
# LDPlayer.exe index=0
106-
self._start(f'"{exe}" index={instance.LDPlayer_id}')
105+
# ldconsole.exe launch --index 0
106+
self._start(f'"{Emulator.single_to_console(exe)}" launch --index {instance.LDPlayer_id}')
107107
elif instance == Emulator.NoxPlayerFamily:
108108
# Nox.exe -clone:Nox_1
109109
self._start(f'"{exe}" -clone:{instance.name}')
@@ -153,12 +153,12 @@ def _emulator_stop(self, instance: EmulatorInstance):
153153
rf')'
154154
)
155155
elif instance == Emulator.MuMuPlayer12:
156-
# E:\Program Files\Netease\MuMu Player 12\shell\MuMuManager.exe api -v 1 shutdown_player
156+
# MuMuManager.exe api -v 1 shutdown_player
157157
if instance.MuMuPlayer12_id is None:
158158
logger.warning(f'Cannot get MuMu instance index from name {instance.name}')
159159
self._stop(f'"{Emulator.single_to_console(exe)}" api -v {instance.MuMuPlayer12_id} shutdown_player')
160160
elif instance == Emulator.LDPlayerFamily:
161-
# E:\Program Files\leidian\LDPlayer9\dnconsole.exe quit --index 0
161+
# ldconsole.exe quit --index 0
162162
self._stop(f'"{Emulator.single_to_console(exe)}" quit --index {instance.LDPlayer_id}')
163163
elif instance == Emulator.NoxPlayerFamily:
164164
# Nox.exe -clone:Nox_1 -quit
@@ -173,10 +173,10 @@ def _emulator_stop(self, instance: EmulatorInstance):
173173
rf')'
174174
)
175175
elif instance == Emulator.BlueStacks4:
176-
# E:\Program Files (x86)\BluestacksCN\bsconsole.exe quit --name Android
176+
# bsconsole.exe quit --name Android
177177
self._stop(f'"{Emulator.single_to_console(exe)}" quit --name {instance.name}')
178178
elif instance == Emulator.MEmuPlayer:
179-
# F:\Program Files\Microvirt\MEmu\memuc.exe stop -n MEmu_0
179+
# memuc.exe stop -n MEmu_0
180180
self._stop(f'"{Emulator.single_to_console(exe)}" stop -n {instance.name}')
181181
else:
182182
raise EmulatorUnknown(f'Cannot stop an unknown emulator instance: {instance}')
@@ -241,7 +241,7 @@ def show_package(m):
241241
logger.info(f'Found azurlane packages: {m}')
242242

243243
interval = Timer(0.5).start()
244-
timeout = Timer(300).start()
244+
timeout = Timer(180).start()
245245
while 1:
246246
interval.wait()
247247
interval.reset()
@@ -340,7 +340,7 @@ def emulator_check(self) -> bool:
340340
else:
341341
if not all(self.process[:2]):
342342
api_windows.closehandle(*self.process[:2])
343-
self.process = ()
343+
self.process = None
344344
raise ProcessLookupError
345345
except api_windows.IterationFinished:
346346
return False

module/device/platform/winapi/const_windows.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@
113113
SW_FORCEMINIMIZE = 11
114114
SW_MAX = 11
115115

116+
GW_HWNDFIRST = 0
117+
GW_HWNDLAST = 1
118+
GW_HWNDNEXT = 2
119+
GW_HWNDPREV = 3
120+
GW_OWNER = 4
121+
GW_CHILD = 5
122+
GW_ENABLEDPOPUP = 6
123+
116124
# winbase.h line 377
117125
DEBUG_PROCESS = 0x00000001
118126
DEBUG_ONLY_THIS_PROCESS = 0x00000002

module/device/platform/winapi/functions_windows.py

Lines changed: 65 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from abc import ABCMeta, abstractmethod
22
import typing as t
3+
import threading
4+
import time
5+
from functools import wraps
36

47
from ctypes import POINTER, WINFUNCTYPE, WinDLL, c_size_t
58
from ctypes.wintypes import \
@@ -10,7 +13,9 @@
1013
from module.device.platform.winapi.structures_windows import \
1114
SECURITY_ATTRIBUTES, STARTUPINFOW, WINDOWPLACEMENT, \
1215
PROCESS_INFORMATION, PROCESSENTRY32W, THREADENTRY32, \
13-
FILETIME, RECT
16+
FILETIME
17+
18+
from module.logger import logger
1419

1520
user32 = WinDLL(name='user32', use_last_error=True)
1621
kernel32 = WinDLL(name='kernel32', use_last_error=True)
@@ -53,15 +58,9 @@
5358
ShowWindow.argtypes = [HWND, INT]
5459
ShowWindow.restype = BOOL
5560

56-
IsWindow = user32.IsWindow
57-
IsWindow.argtypes = [HWND]
58-
IsWindow.restype = BOOL
59-
GetParent = user32.GetParent
60-
GetParent.argtypes = [HWND]
61-
GetParent.restype = HWND
62-
GetWindowRect = user32.GetWindowRect
63-
GetWindowRect.argtypes = [HWND, POINTER(RECT)]
64-
GetWindowRect.restype = BOOL
61+
GetWindow = user32.GetWindow
62+
GetWindow.argtypes = [HWND, UINT]
63+
GetWindow.restype = HWND
6564

6665
EnumWindowsProc = WINFUNCTYPE(BOOL, HWND, LPARAM, use_last_error=True)
6766
EnumWindows = user32.EnumWindows
@@ -173,7 +172,7 @@ class ProcessHandle(Handle):
173172
_func = OpenProcess
174173
_exitfunc = CloseHandle
175174

176-
def __get_init_args__(self, access, pid) -> tuple:
175+
def __get_init_args__(self, access, pid, uselog, raiseexcept) -> tuple:
177176
return access, False, pid
178177

179178
def _is_invalid_handle(self) -> bool:
@@ -183,7 +182,7 @@ class ThreadHandle(Handle):
183182
_func = OpenThread
184183
_exitfunc = CloseHandle
185184

186-
def __get_init_args__(self, access, pid) -> tuple:
185+
def __get_init_args__(self, access, pid, uselog, raiseexcept) -> tuple:
187186
return access, False, pid
188187

189188
def _is_invalid_handle(self) -> bool:
@@ -193,8 +192,8 @@ class CreateSnapshot(Handle):
193192
_func = CreateToolhelp32Snapshot
194193
_exitfunc = CloseHandle
195194

196-
def __get_init_args__(self, arg) -> tuple:
197-
return arg, DWORD(0)
195+
def __get_init_args__(self, access) -> tuple:
196+
return access, DWORD(0)
198197

199198
def _is_invalid_handle(self) -> bool:
200199
from module.device.platform.winapi.const_windows import INVALID_HANDLE_VALUE
@@ -259,28 +258,60 @@ def open_thread(access, tid, uselog=False, raiseexcept=True) -> ThreadHandle:
259258
def create_snapshot(arg) -> CreateSnapshot:
260259
return CreateSnapshot(arg)
261260

262-
def time_it(func):
263-
from time import time
264-
from functools import wraps
265-
from module.logger import logger
266-
import logging
261+
def get_func_path(func):
262+
module = func.__module__
263+
if hasattr(func, '__qualname__'):
264+
qualname = func.__qualname__
265+
else:
266+
qualname = func.__name__
267+
return f"{module}::{qualname.replace('.', '::')}"
267268

268-
@wraps(func)
269-
def wrapper(*args, **kwargs):
270-
original_level = logger.level
271-
logger.setLevel(logging.DEBUG)
269+
class LogLevelManager:
270+
def __init__(self, new_level):
271+
self.new_level = new_level
272+
self.original_level = logger.level
272273

273-
logger.debug(f"Entering {func.__name__}")
274-
start_time = time()
274+
def __enter__(self):
275+
logger.setLevel(self.new_level)
275276

276-
try:
277-
result = func(*args, **kwargs)
278-
finally:
279-
end_time = time()
280-
logger.debug(f"Exiting {func.__name__}")
281-
logger.debug(f"{func.__name__} executed in {end_time - start_time:.4f} seconds")
277+
def __exit__(self, exc_type, exc_val, exc_tb):
278+
logger.setLevel(self.original_level)
282279

283-
logger.setLevel(original_level)
280+
def Timer(timeout=1):
281+
import logging
284282

285-
return result
286-
return wrapper
283+
def decorator(func):
284+
if not callable(func):
285+
raise TypeError(f"Expected a callable, but got {type(func).__name__}")
286+
287+
@wraps(func)
288+
def wrapper(self, *args, **kwargs):
289+
func_path = get_func_path(func)
290+
result = [TimeoutError(f"Function '{func_path}' timed out after {timeout} seconds")]
291+
stop_event = threading.Event()
292+
293+
with LogLevelManager(logging.DEBUG):
294+
logger.debug(f"Entering {func_path}")
295+
start_time = time.time()
296+
297+
def target():
298+
try:
299+
result[0] = func(self, *args, **kwargs)
300+
except Exception as e:
301+
result[0] = e
302+
finally:
303+
stop_event.set()
304+
305+
thread = threading.Thread(target=target, name=f"Thread-{func_path}")
306+
thread.start()
307+
if not stop_event.wait(timeout):
308+
raise TimeoutError(f"Function '{func_path}' timed out after {timeout} seconds")
309+
310+
end_time = time.time()
311+
if isinstance(result[0], Exception):
312+
raise result[0]
313+
logger.debug(f"Exiting {func_path}")
314+
logger.debug(f"{func_path} executed in {end_time - start_time:.4f} seconds")
315+
return result[0]
316+
return wrapper
317+
return decorator

0 commit comments

Comments
 (0)