ok-script 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autohelper/AutoHelper.py +116 -0
- autohelper/__init__.py +0 -0
- autohelper/capture/BaseCaptureMethod.py +30 -0
- autohelper/capture/HwndWindow.py +138 -0
- autohelper/capture/__init__.py +0 -0
- autohelper/capture/adb/ADBCaptureMethod.py +29 -0
- autohelper/capture/adb/DeviceManager.py +209 -0
- autohelper/capture/adb/__init__.py +0 -0
- autohelper/capture/adb/bin/__init__.py +0 -0
- autohelper/capture/adb/targets.py +61 -0
- autohelper/capture/adb/vbox.py +140 -0
- autohelper/capture/windows/WindowsGraphicsCaptureMethod.py +228 -0
- autohelper/capture/windows/__init__.py +0 -0
- autohelper/capture/windows/d3d11.py +74 -0
- autohelper/capture/windows/utils.py +149 -0
- autohelper/capture/windows/window.py +36 -0
- autohelper/color/Color.py +41 -0
- autohelper/color/__init__.py +0 -0
- autohelper/config/Config.py +79 -0
- autohelper/config/InfoDict.py +16 -0
- autohelper/config/__init__.py +0 -0
- autohelper/feature/Box.py +199 -0
- autohelper/feature/Feature.py +20 -0
- autohelper/feature/FeatureSet.py +207 -0
- autohelper/feature/FindFeature.py +52 -0
- autohelper/feature/__init__.py +0 -0
- autohelper/gui/App.py +120 -0
- autohelper/gui/Communicate.py +22 -0
- autohelper/gui/MainWindow.py +69 -0
- autohelper/gui/TabTitles.py +56 -0
- autohelper/gui/__init__.py +5 -0
- autohelper/gui/debug/AspectRatioWidget.py +31 -0
- autohelper/gui/debug/DebugTab.py +37 -0
- autohelper/gui/debug/FrameWidget.py +125 -0
- autohelper/gui/debug/InfoWidget.py +40 -0
- autohelper/gui/debug/LoggerWidget.py +74 -0
- autohelper/gui/debug/__init__.py +0 -0
- autohelper/gui/i18n/__init__.py +0 -0
- autohelper/gui/i18n/path.py +3 -0
- autohelper/gui/icon/__init__.py +0 -0
- autohelper/gui/icon/icon.py +6 -0
- autohelper/gui/loading/LoadingWindow.py +142 -0
- autohelper/gui/loading/__init__.py +0 -0
- autohelper/gui/overlay/OverlayWindow.py +34 -0
- autohelper/gui/overlay/__init__.py +0 -0
- autohelper/gui/resources.py +485 -0
- autohelper/gui/tasks/ConfigItemFactory.py +14 -0
- autohelper/gui/tasks/StartButton.py +47 -0
- autohelper/gui/tasks/TaskOpButton.py +33 -0
- autohelper/gui/tasks/TaskTab.py +121 -0
- autohelper/gui/tasks/TooltipTableWidget.py +39 -0
- autohelper/gui/tasks/__init__.py +0 -0
- autohelper/gui/util/Alert.py +20 -0
- autohelper/gui/util/InitWorker.py +17 -0
- autohelper/gui/util/__init__.py +0 -0
- autohelper/gui/widget/ListTableWidgetItem.py +27 -0
- autohelper/gui/widget/NumericTableWidgetItem.py +33 -0
- autohelper/gui/widget/RoundCornerContainer.py +59 -0
- autohelper/gui/widget/TabWidget.py +30 -0
- autohelper/gui/widget/UpdateConfigWidgetItem.py +16 -0
- autohelper/gui/widget/YesNonWidgetItem.py +23 -0
- autohelper/gui/widget/__init__.py +0 -0
- autohelper/interaction/ADBInteraction.py +33 -0
- autohelper/interaction/BaseInteraction.py +36 -0
- autohelper/interaction/Win32Interaction.py +56 -0
- autohelper/interaction/__init__.py +0 -0
- autohelper/logging/Logger.py +103 -0
- autohelper/logging/__init__.py +0 -0
- autohelper/ocr/OCR.py +50 -0
- autohelper/ocr/__init__.py +0 -0
- autohelper/predict/__init__.py +0 -0
- autohelper/predict/predict.py +45 -0
- autohelper/rotypes/Windows/Foundation/Collections.py +60 -0
- autohelper/rotypes/Windows/Foundation/__init__.py +153 -0
- autohelper/rotypes/Windows/Globalization/__init__.py +25 -0
- autohelper/rotypes/Windows/Graphics/Capture/__init__.py +110 -0
- autohelper/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +41 -0
- autohelper/rotypes/Windows/Graphics/DirectX/__init__.py +167 -0
- autohelper/rotypes/Windows/Graphics/Imaging/__init__.py +34 -0
- autohelper/rotypes/Windows/Graphics/__init__.py +5 -0
- autohelper/rotypes/Windows/Media/Ocr/__init__.py +76 -0
- autohelper/rotypes/Windows/Media/__init__.py +0 -0
- autohelper/rotypes/Windows/Security/Cryptography/__init__.py +18 -0
- autohelper/rotypes/Windows/Security/__init__.py +0 -0
- autohelper/rotypes/Windows/Storage/Streams/__init__.py +27 -0
- autohelper/rotypes/Windows/Storage/__init__.py +0 -0
- autohelper/rotypes/Windows/__init__.py +0 -0
- autohelper/rotypes/__init__.py +4 -0
- autohelper/rotypes/delegate.py +105 -0
- autohelper/rotypes/export.py +21 -0
- autohelper/rotypes/idldsl.py +272 -0
- autohelper/rotypes/inspectable.py +79 -0
- autohelper/rotypes/roapi.py +30 -0
- autohelper/rotypes/types.py +77 -0
- autohelper/rotypes/winstring.py +44 -0
- autohelper/save/BlackBarProcessor.py +25 -0
- autohelper/save/PostProcessor.py +6 -0
- autohelper/save/SaveByInterval.py +30 -0
- autohelper/save/SaveByKeyPress.py +27 -0
- autohelper/save/SaveMethodBase.py +46 -0
- autohelper/save/__init__.py +0 -0
- autohelper/scene/FeatureScene.py +14 -0
- autohelper/scene/Scene.py +21 -0
- autohelper/scene/__init__.py +0 -0
- autohelper/stats/StreamStats.py +34 -0
- autohelper/stats/__init__.py +0 -0
- autohelper/task/BaseTask.py +109 -0
- autohelper/task/ExecutorOperation.py +119 -0
- autohelper/task/FindFeatureTask.py +6 -0
- autohelper/task/TaskExecutor.py +249 -0
- autohelper/task/__init__.py +0 -0
- autohelper/util/__init__.py +0 -0
- autohelper/util/json.py +27 -0
- autohelper/util/list.py +2 -0
- autohelper/util/path.py +37 -0
- autohelper/util/thread.py +12 -0
- autohelper/util/win32_process.py +86 -0
- autohelper/util/yaml.py +28 -0
- ok_script-0.0.7.dist-info/METADATA +64 -0
- ok_script-0.0.7.dist-info/RECORD +122 -0
- ok_script-0.0.7.dist-info/WHEEL +5 -0
- ok_script-0.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# https://github.com/ArknightsAutoHelper/ArknightsAutoHelper/blob/5a9284773b68e12012c338cce50cb732f6a3542c/automator/control/adb/targets/vbox.py
|
|
2
|
+
|
|
3
|
+
import winreg
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import adbutils
|
|
7
|
+
import win32com.client
|
|
8
|
+
|
|
9
|
+
from autohelper.capture.adb.targets import ADBControllerTarget
|
|
10
|
+
from autohelper.logging.Logger import get_logger
|
|
11
|
+
from autohelper.util import win32_process
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class VBoxClass:
|
|
18
|
+
clsid: str
|
|
19
|
+
vendor: str
|
|
20
|
+
tag: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class VBoxSerever(VBoxClass):
|
|
25
|
+
path: str
|
|
26
|
+
pid: int = 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
import ctypes
|
|
30
|
+
|
|
31
|
+
vbox_clsids = [
|
|
32
|
+
VBoxClass('{20190809-47b9-4a1e-82b2-07ccd5323c3f}', 'LDPlayer', 'ld'),
|
|
33
|
+
VBoxClass('{20191216-47b9-4a1e-82b2-07ccd5323c3f}', 'LDPlayer9', 'ld9'),
|
|
34
|
+
VBoxClass('{88888888-47b9-4a1e-82b2-07ccd5323c3f}', 'Nox', 'nox'),
|
|
35
|
+
VBoxClass('{baf3f651-58d8-429d-97ad-2b5699b43567}', 'BlueStacks', 'bstk'),
|
|
36
|
+
VBoxClass('{b1a7a4f2-47b9-4a1e-82b2-07ccd5323c3a}', 'Memu', 'memu'),
|
|
37
|
+
VBoxClass('{81919390-a492-11e5-a837-0800200c9a66}', 'MuMu', 'mumu'),
|
|
38
|
+
VBoxClass('{23cd1535-edaa-4f21-a4ab-45d97fd1d58b}', 'MuMu 12',
|
|
39
|
+
'mumu 12'),
|
|
40
|
+
# ('{B1A7A4F2-47B9-4A1E-82B2-07CCD5323C3F}', 'Oracle', 'oracle'), # nobody uses this
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
OpenProcess = ctypes.windll.kernel32.OpenProcess
|
|
44
|
+
OpenProcess.argtypes = [ctypes.c_uint32, ctypes.c_bool, ctypes.c_uint32]
|
|
45
|
+
OpenProcess.restype = ctypes.c_void_p
|
|
46
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
|
47
|
+
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
|
48
|
+
CloseHandle.argtypes = [ctypes.c_void_p]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_server(clsid):
|
|
52
|
+
try:
|
|
53
|
+
with winreg.OpenKeyEx(winreg.HKEY_CLASSES_ROOT, 'CLSID\\%s\\LocalServer32' % clsid, 0,
|
|
54
|
+
winreg.KEY_READ) as hkey:
|
|
55
|
+
value = winreg.QueryValue(hkey, None)
|
|
56
|
+
filename = value.strip('"')
|
|
57
|
+
return win32_process.get_final_path(filename)
|
|
58
|
+
except OSError:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# current process's privilege must equal to the target process's privilege, can raise a error if not
|
|
63
|
+
def installed_emulator():
|
|
64
|
+
logger.debug('enumerating vbox targets')
|
|
65
|
+
vbox_servers: list[VBoxSerever] = []
|
|
66
|
+
for vboxcls in vbox_clsids:
|
|
67
|
+
server = get_server(vboxcls.clsid)
|
|
68
|
+
if server:
|
|
69
|
+
logger.debug(f"enumerating vbox {server}")
|
|
70
|
+
vbox_servers.append(VBoxSerever(vboxcls.clsid, vboxcls.vendor, vboxcls.tag, server))
|
|
71
|
+
|
|
72
|
+
logger.debug(f'installed VirtualBox servers: {vbox_servers}')
|
|
73
|
+
|
|
74
|
+
running_processes = {pid: win32_process.resolve_image_name(pid) for pid in win32_process.all_pids()}
|
|
75
|
+
# logger.debug(f'running processes: {running_processes}')
|
|
76
|
+
for pid, path in running_processes.items():
|
|
77
|
+
# logger.debug(f'running_processes: {pid, path}')
|
|
78
|
+
for vbox_server in vbox_servers:
|
|
79
|
+
if vbox_server.path == path:
|
|
80
|
+
vbox_server.pid = pid
|
|
81
|
+
logger.debug(f'found vbox server: {vbox_server} {pid, path}')
|
|
82
|
+
break
|
|
83
|
+
running_servers = [x for x in vbox_servers if x.pid]
|
|
84
|
+
# running_servers = [x for x in vbox_servers if x[3] in running_processes]
|
|
85
|
+
|
|
86
|
+
logger.debug(f'running VirtualBox servers: {running_servers}')
|
|
87
|
+
results = []
|
|
88
|
+
for server in running_servers:
|
|
89
|
+
logger.debug(f'checking {server}')
|
|
90
|
+
hproc = OpenProcess(PROCESS_QUERY_INFORMATION, False, server.pid)
|
|
91
|
+
if not hproc:
|
|
92
|
+
logger.debug(f'try OpenProcess failed for PID {server.pid}')
|
|
93
|
+
continue
|
|
94
|
+
CloseHandle(hproc)
|
|
95
|
+
logger.debug(f'checking hproc end {server}')
|
|
96
|
+
try:
|
|
97
|
+
client = win32com.client.Dispatch(server.clsid)
|
|
98
|
+
for machine in client.Machines:
|
|
99
|
+
machine_name = machine.Name
|
|
100
|
+
logger.debug(f'checking machine {machine_name}')
|
|
101
|
+
if not machine.State == 5:
|
|
102
|
+
# machine is not running
|
|
103
|
+
continue
|
|
104
|
+
for adapter_id in range(4):
|
|
105
|
+
adapter = machine.GetNetworkAdapter(adapter_id)
|
|
106
|
+
if adapter.Enabled and adapter.AttachmentType == 1:
|
|
107
|
+
# adapter is enabled and attached to per-machine NAT
|
|
108
|
+
forwards = adapter.NatEngine.Redirects
|
|
109
|
+
for forward in forwards:
|
|
110
|
+
rule_name, proto, host, port, guest_host, guest_port = forward.split(',', 5)
|
|
111
|
+
if proto == '1' and guest_port == '5555':
|
|
112
|
+
address = f'127.0.0.1:{port}'
|
|
113
|
+
identifier = f'vbox:{server.tag}:{machine_name}'
|
|
114
|
+
logger.debug(
|
|
115
|
+
f'adb_server, None, {server.vendor}: {machine_name}, {address}, 2, 1, override_identifier={identifier}')
|
|
116
|
+
results.append(
|
|
117
|
+
ADBControllerTarget(None, f'{server.vendor}',
|
|
118
|
+
address, 2, 1, override_identifier=identifier,
|
|
119
|
+
preload_device_info={'emulator_hypervisor': 'vbox'}))
|
|
120
|
+
logger.debug(f'try checking {server}')
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f'failed to check server :{server} {e}', e)
|
|
123
|
+
return results
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _main():
|
|
127
|
+
logger.config({'debug': True})
|
|
128
|
+
adb = adbutils.AdbClient(host="127.0.0.1", port=5037)
|
|
129
|
+
for x in installed_emulator():
|
|
130
|
+
logger.debug(f'installed emulators {x}')
|
|
131
|
+
adb.connect(x.adb_address)
|
|
132
|
+
|
|
133
|
+
logger.debug(adb.device_list())
|
|
134
|
+
for device in adb.device_list():
|
|
135
|
+
logger.debug(device.shell('settings get secure android_id'))
|
|
136
|
+
logger.debug(f'{device.info}')
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == '__main__':
|
|
140
|
+
_main()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# original https://github.com/dantmnf & https://github.com/hakaboom/winAuto
|
|
2
|
+
import ctypes
|
|
3
|
+
import ctypes.wintypes
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing_extensions import override
|
|
8
|
+
|
|
9
|
+
from autohelper.capture.BaseCaptureMethod import BaseCaptureMethod
|
|
10
|
+
from autohelper.capture.HwndWindow import HwndWindow
|
|
11
|
+
from autohelper.capture.windows import d3d11
|
|
12
|
+
from autohelper.capture.windows.utils import WINDOWS_BUILD_NUMBER
|
|
13
|
+
from autohelper.logging.Logger import get_logger
|
|
14
|
+
from autohelper.rotypes import IInspectable
|
|
15
|
+
from autohelper.rotypes.Windows.Foundation import TypedEventHandler
|
|
16
|
+
from autohelper.rotypes.Windows.Graphics.Capture import Direct3D11CaptureFramePool, IGraphicsCaptureItemInterop, \
|
|
17
|
+
IGraphicsCaptureItem, GraphicsCaptureItem
|
|
18
|
+
from autohelper.rotypes.Windows.Graphics.DirectX import DirectXPixelFormat
|
|
19
|
+
from autohelper.rotypes.Windows.Graphics.DirectX.Direct3D11 import IDirect3DDevice, \
|
|
20
|
+
CreateDirect3D11DeviceFromDXGIDevice, \
|
|
21
|
+
IDirect3DDxgiInterfaceAccess
|
|
22
|
+
from autohelper.rotypes.roapi import GetActivationFactory
|
|
23
|
+
|
|
24
|
+
PBYTE = ctypes.POINTER(ctypes.c_ubyte)
|
|
25
|
+
WGC_NO_BORDER_MIN_BUILD = 20348
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class WindowsGraphicsCaptureMethod(BaseCaptureMethod):
|
|
31
|
+
name = "Windows Graphics Capture"
|
|
32
|
+
description = "fast, most compatible, capped at 60fps"
|
|
33
|
+
last_frame = None
|
|
34
|
+
last_frame_time = 0
|
|
35
|
+
hwnd_window: HwndWindow = None
|
|
36
|
+
frame_pool = None
|
|
37
|
+
item = None
|
|
38
|
+
session = None
|
|
39
|
+
cputex = None
|
|
40
|
+
|
|
41
|
+
def __init__(self, hwnd_window: HwndWindow):
|
|
42
|
+
super().__init__()
|
|
43
|
+
self.hwnd_window = hwnd_window
|
|
44
|
+
|
|
45
|
+
def frame_arrived_callback(self, x, y):
|
|
46
|
+
try:
|
|
47
|
+
frame = self.frame_pool.TryGetNextFrame()
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"TryGetNextFrame error {e}")
|
|
50
|
+
self.close()
|
|
51
|
+
return
|
|
52
|
+
if not frame:
|
|
53
|
+
return
|
|
54
|
+
self.last_frame_time = time.time()
|
|
55
|
+
img = None
|
|
56
|
+
with frame:
|
|
57
|
+
need_reset_framepool = False
|
|
58
|
+
need_reset_device = False
|
|
59
|
+
if frame.ContentSize.Width != self.last_size.Width or frame.ContentSize.Height != self.last_size.Height:
|
|
60
|
+
# print('size changed')
|
|
61
|
+
need_reset_framepool = True
|
|
62
|
+
self.last_size = frame.ContentSize
|
|
63
|
+
|
|
64
|
+
if need_reset_framepool:
|
|
65
|
+
self.reset_framepool(frame.ContentSize)
|
|
66
|
+
return
|
|
67
|
+
tex = None
|
|
68
|
+
|
|
69
|
+
cputex = None
|
|
70
|
+
try:
|
|
71
|
+
tex = frame.Surface.astype(IDirect3DDxgiInterfaceAccess).GetInterface(
|
|
72
|
+
d3d11.ID3D11Texture2D.GUID).astype(d3d11.ID3D11Texture2D)
|
|
73
|
+
desc = tex.GetDesc()
|
|
74
|
+
desc2 = d3d11.D3D11_TEXTURE2D_DESC()
|
|
75
|
+
desc2.Width = desc.Width
|
|
76
|
+
desc2.Height = desc.Height
|
|
77
|
+
desc2.MipLevels = desc.MipLevels
|
|
78
|
+
desc2.ArraySize = desc.ArraySize
|
|
79
|
+
desc2.Format = desc.Format
|
|
80
|
+
desc2.SampleDesc = desc.SampleDesc
|
|
81
|
+
desc2.Usage = d3d11.D3D11_USAGE_STAGING
|
|
82
|
+
desc2.CPUAccessFlags = d3d11.D3D11_CPU_ACCESS_READ
|
|
83
|
+
desc2.BindFlags = 0
|
|
84
|
+
desc2.MiscFlags = 0
|
|
85
|
+
cputex = self.dxdevice.CreateTexture2D(ctypes.byref(desc2), None)
|
|
86
|
+
self.immediatedc.CopyResource(cputex, tex)
|
|
87
|
+
mapinfo = self.immediatedc.Map(cputex, 0, d3d11.D3D11_MAP_READ, 0)
|
|
88
|
+
img = np.ctypeslib.as_array(ctypes.cast(mapinfo.pData, PBYTE),
|
|
89
|
+
(desc.Height, mapinfo.RowPitch // 4, 4))[
|
|
90
|
+
:, :desc.Width].copy()
|
|
91
|
+
self.immediatedc.Unmap(cputex, 0)
|
|
92
|
+
except OSError as e:
|
|
93
|
+
if e.winerror == d3d11.DXGI_ERROR_DEVICE_REMOVED or e.winerror == d3d11.DXGI_ERROR_DEVICE_RESET:
|
|
94
|
+
need_reset_framepool = True
|
|
95
|
+
need_reset_device = True
|
|
96
|
+
else:
|
|
97
|
+
raise
|
|
98
|
+
finally:
|
|
99
|
+
if tex is not None:
|
|
100
|
+
tex.Release()
|
|
101
|
+
if cputex is not None:
|
|
102
|
+
cputex.Release()
|
|
103
|
+
if need_reset_framepool:
|
|
104
|
+
self.reset_framepool(frame.ContentSize, need_reset_device)
|
|
105
|
+
return self.get_frame()
|
|
106
|
+
self.last_frame = img
|
|
107
|
+
|
|
108
|
+
def start_or_stop(self, capture_cursor=False):
|
|
109
|
+
if self.hwnd_window.exists and self.frame_pool is None:
|
|
110
|
+
logger.info('init windows capture')
|
|
111
|
+
self.rtdevice = IDirect3DDevice()
|
|
112
|
+
self.dxdevice = d3d11.ID3D11Device()
|
|
113
|
+
self.immediatedc = d3d11.ID3D11DeviceContext()
|
|
114
|
+
self.create_device()
|
|
115
|
+
interop = GetActivationFactory('Windows.Graphics.Capture.GraphicsCaptureItem').astype(
|
|
116
|
+
IGraphicsCaptureItemInterop)
|
|
117
|
+
item = interop.CreateForWindow(self.hwnd_window.hwnd, IGraphicsCaptureItem.GUID)
|
|
118
|
+
self.item = item
|
|
119
|
+
self.last_size = item.Size
|
|
120
|
+
delegate = TypedEventHandler(GraphicsCaptureItem, IInspectable).delegate(
|
|
121
|
+
self.close)
|
|
122
|
+
self.evtoken = item.add_Closed(delegate)
|
|
123
|
+
self.frame_pool = Direct3D11CaptureFramePool.CreateFreeThreaded(self.rtdevice,
|
|
124
|
+
DirectXPixelFormat.B8G8R8A8UIntNormalized,
|
|
125
|
+
1, item.Size)
|
|
126
|
+
self.session = self.frame_pool.CreateCaptureSession(item)
|
|
127
|
+
pool = self.frame_pool
|
|
128
|
+
pool.add_FrameArrived(
|
|
129
|
+
TypedEventHandler(Direct3D11CaptureFramePool, IInspectable).delegate(
|
|
130
|
+
self.frame_arrived_callback))
|
|
131
|
+
self.session.IsCursorCaptureEnabled = capture_cursor
|
|
132
|
+
if WINDOWS_BUILD_NUMBER >= WGC_NO_BORDER_MIN_BUILD:
|
|
133
|
+
self.session.IsBorderRequired = False
|
|
134
|
+
self.session.StartCapture()
|
|
135
|
+
elif not self.hwnd_window.exists and self.frame_pool is not None:
|
|
136
|
+
self.close()
|
|
137
|
+
return self.hwnd_window.exists
|
|
138
|
+
|
|
139
|
+
def create_device(self):
|
|
140
|
+
d3d11.D3D11CreateDevice(
|
|
141
|
+
None,
|
|
142
|
+
d3d11.D3D_DRIVER_TYPE_HARDWARE,
|
|
143
|
+
None,
|
|
144
|
+
d3d11.D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
|
145
|
+
None,
|
|
146
|
+
0,
|
|
147
|
+
d3d11.D3D11_SDK_VERSION,
|
|
148
|
+
ctypes.byref(self.dxdevice),
|
|
149
|
+
None,
|
|
150
|
+
ctypes.byref(self.immediatedc)
|
|
151
|
+
)
|
|
152
|
+
self.rtdevice = CreateDirect3D11DeviceFromDXGIDevice(self.dxdevice)
|
|
153
|
+
self.evtoken = None
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
def close(self):
|
|
157
|
+
logger.info('destroy windows capture')
|
|
158
|
+
if self.frame_pool is not None:
|
|
159
|
+
self.frame_pool.Close()
|
|
160
|
+
self.frame_pool = None
|
|
161
|
+
if self.session is not None:
|
|
162
|
+
self.session.Close() # E_UNEXPECTED ???
|
|
163
|
+
self.session = None
|
|
164
|
+
self.item = None
|
|
165
|
+
self.rtdevice.Release()
|
|
166
|
+
self.dxdevice.Release()
|
|
167
|
+
if self.cputex:
|
|
168
|
+
self.cputex.Release()
|
|
169
|
+
|
|
170
|
+
def get_frame(self):
|
|
171
|
+
if self.start_or_stop():
|
|
172
|
+
frame = self.last_frame
|
|
173
|
+
self.last_frame = None
|
|
174
|
+
latency = time.time() - self.last_frame_time
|
|
175
|
+
self.last_frame_time = time.time()
|
|
176
|
+
if latency > 1:
|
|
177
|
+
logger.warning(f"latency too large return None frame: {latency}")
|
|
178
|
+
return None
|
|
179
|
+
if frame is not None:
|
|
180
|
+
x, y = self.hwnd_window.get_top_left_frame_offset()
|
|
181
|
+
if x > 0 or y > 0:
|
|
182
|
+
frame = crop_image(frame, x, y)
|
|
183
|
+
|
|
184
|
+
if frame is not None:
|
|
185
|
+
new_height, new_width = frame.shape[:2]
|
|
186
|
+
if new_width <= 0 or new_width <= 0:
|
|
187
|
+
logger.warning(f"get_frame size <=0 {new_width}x{new_height}")
|
|
188
|
+
frame = None
|
|
189
|
+
else:
|
|
190
|
+
self.width = new_width
|
|
191
|
+
self.height = new_height
|
|
192
|
+
if frame.shape[2] == 4:
|
|
193
|
+
frame = frame[:, :, :3]
|
|
194
|
+
return frame
|
|
195
|
+
|
|
196
|
+
def reset_framepool(self, size, reset_device=False):
|
|
197
|
+
if reset_device:
|
|
198
|
+
self.create_device()
|
|
199
|
+
self.frame_pool.Recreate(self.rtdevice,
|
|
200
|
+
DirectXPixelFormat.B8G8R8A8UIntNormalized, 1, size)
|
|
201
|
+
|
|
202
|
+
def get_abs_cords(self, x, y):
|
|
203
|
+
return self.hwnd_window.get_abs_cords(x, y)
|
|
204
|
+
|
|
205
|
+
def clickable(self):
|
|
206
|
+
return self.hwnd_window.visible
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def crop_image(image, border, title_height):
|
|
210
|
+
# Load the image
|
|
211
|
+
# Image dimensions
|
|
212
|
+
height, width = image.shape[:2]
|
|
213
|
+
|
|
214
|
+
# Calculate the coordinates for the bottom-right corner
|
|
215
|
+
x2 = width - border
|
|
216
|
+
y2 = height - border
|
|
217
|
+
|
|
218
|
+
# Crop the image
|
|
219
|
+
cropped_image = image[title_height:y2, border:x2]
|
|
220
|
+
|
|
221
|
+
# print(f"cropped image: {title_height}-{y2}, {border}-{x2} {cropped_image.shape}")
|
|
222
|
+
#
|
|
223
|
+
# cv2.imshow('Image Window', cropped_image)
|
|
224
|
+
#
|
|
225
|
+
# # Wait for any key to be pressed before closing the window
|
|
226
|
+
# cv2.waitKey(0)
|
|
227
|
+
|
|
228
|
+
return cropped_image
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import ctypes.wintypes
|
|
2
|
+
|
|
3
|
+
from autohelper.rotypes import IUnknown
|
|
4
|
+
from autohelper.rotypes import idldsl
|
|
5
|
+
|
|
6
|
+
D3D11_SDK_VERSION = 7
|
|
7
|
+
D3D_DRIVER_TYPE_HARDWARE = 1
|
|
8
|
+
D3D11_CREATE_DEVICE_BGRA_SUPPORT = 0x20
|
|
9
|
+
D3D11_USAGE_STAGING = 3
|
|
10
|
+
D3D11_CPU_ACCESS_READ = 0x20000
|
|
11
|
+
D3D11_MAP_READ = 1
|
|
12
|
+
DXGI_ERROR_DEVICE_REMOVED = 0x887A0005
|
|
13
|
+
DXGI_ERROR_DEVICE_RESET = 0x887A0007
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DXGI_SAMPLE_DESC(ctypes.Structure):
|
|
17
|
+
_fields_ = [
|
|
18
|
+
('Count', ctypes.wintypes.UINT),
|
|
19
|
+
('Quality', ctypes.wintypes.UINT)
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class D3D11_TEXTURE2D_DESC(ctypes.Structure):
|
|
24
|
+
_fields_ = [
|
|
25
|
+
('Width', ctypes.wintypes.UINT),
|
|
26
|
+
('Height', ctypes.wintypes.UINT),
|
|
27
|
+
('MipLevels', ctypes.wintypes.UINT),
|
|
28
|
+
('ArraySize', ctypes.wintypes.UINT),
|
|
29
|
+
('Format', ctypes.wintypes.UINT),
|
|
30
|
+
('SampleDesc', DXGI_SAMPLE_DESC),
|
|
31
|
+
('Usage', ctypes.wintypes.UINT),
|
|
32
|
+
('BindFlags', ctypes.wintypes.UINT),
|
|
33
|
+
('CPUAccessFlags', ctypes.wintypes.UINT),
|
|
34
|
+
('MiscFlags', ctypes.wintypes.UINT),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class D3D11_MAPPED_SUBRESOURCE(ctypes.Structure):
|
|
39
|
+
_fields_ = [
|
|
40
|
+
('pData', ctypes.c_void_p),
|
|
41
|
+
('RowPitch', ctypes.wintypes.UINT),
|
|
42
|
+
('DepthPitch', ctypes.wintypes.UINT)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@idldsl.GUID('db6f6ddb-ac77-4e88-8253-819df9bbf140')
|
|
47
|
+
class ID3D11Device(IUnknown):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@idldsl.GUID('6f15aaf2-d208-4e89-9ab4-489535d34f9c')
|
|
52
|
+
class ID3D11Texture2D(IUnknown):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@idldsl.GUID('c0bfa96c-e089-44fb-8eaf-26f8796190da')
|
|
57
|
+
class ID3D11DeviceContext(IUnknown):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
idldsl.define_winrt_com_method(ID3D11Device, 'CreateTexture2D', ctypes.POINTER(D3D11_TEXTURE2D_DESC), ctypes.c_void_p,
|
|
62
|
+
retval=ID3D11Texture2D, vtbl=5)
|
|
63
|
+
idldsl.define_winrt_com_method(ID3D11Device, 'GetImmediateContext', retval=ID3D11DeviceContext, vtbl=40)
|
|
64
|
+
# idldsl.define_winrt_com_method(ID3D11Texture2D, 'GetDevice', retval=ID3D11Texture2D, vtbl=3)
|
|
65
|
+
idldsl.define_winrt_com_method(ID3D11Texture2D, 'GetDesc', retval=D3D11_TEXTURE2D_DESC, vtbl=10)
|
|
66
|
+
idldsl.define_winrt_com_method(ID3D11DeviceContext, 'Map', IUnknown, ctypes.c_uint, ctypes.c_int, ctypes.c_uint,
|
|
67
|
+
retval=D3D11_MAPPED_SUBRESOURCE, vtbl=14)
|
|
68
|
+
idldsl.define_winrt_com_method(ID3D11DeviceContext, 'Unmap', IUnknown, ctypes.c_uint, vtbl=15)
|
|
69
|
+
idldsl.define_winrt_com_method(ID3D11DeviceContext, 'CopyResource', IUnknown, IUnknown, vtbl=47)
|
|
70
|
+
|
|
71
|
+
D3D11CreateDevice = ctypes.oledll.d3d11.D3D11CreateDevice
|
|
72
|
+
D3D11CreateDevice.argtypes = (
|
|
73
|
+
IUnknown, ctypes.c_int, ctypes.c_void_p, ctypes.wintypes.UINT, ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint,
|
|
74
|
+
ID3D11Device, ctypes.c_void_p, ID3D11DeviceContext)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import ctypes
|
|
3
|
+
import ctypes.wintypes
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from itertools import chain
|
|
9
|
+
from platform import version
|
|
10
|
+
from threading import Thread
|
|
11
|
+
from typing import TYPE_CHECKING, Any, TypeGuard, TypeVar
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import win32gui
|
|
15
|
+
import win32ui
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
# Source does not exist, keep this under TYPE_CHECKING
|
|
19
|
+
from _win32typing import PyCDC # pyright: ignore[reportMissingModuleSource]
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
ONE_SECOND = 1000
|
|
24
|
+
"""1000 milliseconds in 1 second"""
|
|
25
|
+
DWMWA_EXTENDED_FRAME_BOUNDS = 9
|
|
26
|
+
MAXBYTE = 255
|
|
27
|
+
BGR_CHANNEL_COUNT = 3
|
|
28
|
+
"""How many channels in a BGR image"""
|
|
29
|
+
BGRA_CHANNEL_COUNT = 4
|
|
30
|
+
"""How many channels in a BGRA image"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ImageShape(IntEnum):
|
|
34
|
+
Y = 0
|
|
35
|
+
X = 1
|
|
36
|
+
Channels = 2
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ColorChannel(IntEnum):
|
|
40
|
+
Blue = 0
|
|
41
|
+
Green = 1
|
|
42
|
+
Red = 2
|
|
43
|
+
Alpha = 3
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decimal(value: float):
|
|
47
|
+
# Using ljust instead of :2f because of python float rounding errors
|
|
48
|
+
return f"{int(value * 100) / 100}".ljust(4, "0")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_digit(value: str | int | None):
|
|
52
|
+
"""Checks if `value` is a single-digit string from 0-9."""
|
|
53
|
+
if value is None:
|
|
54
|
+
return False
|
|
55
|
+
try:
|
|
56
|
+
return 0 <= int(value) <= 9 # noqa: PLR2004
|
|
57
|
+
except (ValueError, TypeError):
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_valid_image(image: np.ndarray | None) -> TypeGuard[np.ndarray]:
|
|
62
|
+
return image is not None and bool(image.size)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_valid_hwnd(hwnd: int):
|
|
66
|
+
"""Validate the hwnd points to a valid window and not the desktop or whatever window obtained with `""`."""
|
|
67
|
+
if not hwnd:
|
|
68
|
+
return False
|
|
69
|
+
if sys.platform == "win32":
|
|
70
|
+
return bool(win32gui.IsWindow(hwnd) and win32gui.GetWindowText(hwnd))
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def first(iterable: Iterable[T]) -> T:
|
|
75
|
+
"""@return: The first element of a collection. Dictionaries will return the first key."""
|
|
76
|
+
return next(iter(iterable))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def try_delete_dc(dc: "PyCDC"):
|
|
80
|
+
try:
|
|
81
|
+
dc.DeleteDC()
|
|
82
|
+
except win32ui.error:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_window_bounds(hwnd: int) -> tuple[int, int, int, int]:
|
|
87
|
+
extended_frame_bounds = ctypes.wintypes.RECT()
|
|
88
|
+
ctypes.windll.dwmapi.DwmGetWindowAttribute(
|
|
89
|
+
hwnd,
|
|
90
|
+
DWMWA_EXTENDED_FRAME_BOUNDS,
|
|
91
|
+
ctypes.byref(extended_frame_bounds),
|
|
92
|
+
ctypes.sizeof(extended_frame_bounds),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
window_rect = win32gui.GetWindowRect(hwnd)
|
|
96
|
+
window_left_bounds = extended_frame_bounds.left - window_rect[0]
|
|
97
|
+
window_top_bounds = extended_frame_bounds.top - window_rect[1]
|
|
98
|
+
window_width = extended_frame_bounds.right - extended_frame_bounds.left
|
|
99
|
+
window_height = extended_frame_bounds.bottom - extended_frame_bounds.top
|
|
100
|
+
return window_left_bounds, window_top_bounds, window_width, window_height
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def open_file(file_path: str | bytes | os.PathLike[str] | os.PathLike[bytes]):
|
|
104
|
+
os.startfile(file_path) # noqa: S606
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_or_create_eventloop():
|
|
108
|
+
try:
|
|
109
|
+
return asyncio.get_event_loop()
|
|
110
|
+
except RuntimeError:
|
|
111
|
+
loop = asyncio.new_event_loop()
|
|
112
|
+
asyncio.set_event_loop(loop)
|
|
113
|
+
return asyncio.get_event_loop()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def fire_and_forget(func: Callable[..., Any]):
|
|
117
|
+
"""
|
|
118
|
+
Runs synchronous function asynchronously without waiting for a response.
|
|
119
|
+
|
|
120
|
+
Uses threads on Windows because ~~`RuntimeError: There is no current event loop in thread 'MainThread'.`~~
|
|
121
|
+
Because maybe asyncio has issues. Unsure. See alpha.5 and https://github.com/Avasam/AutoSplit/issues/36
|
|
122
|
+
|
|
123
|
+
Uses asyncio on Linux because of a `Segmentation fault (core dumped)`
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def wrapped(*args: Any, **kwargs: Any):
|
|
127
|
+
if sys.platform == "win32":
|
|
128
|
+
thread = Thread(target=func, args=args, kwargs=kwargs)
|
|
129
|
+
thread.start()
|
|
130
|
+
return thread
|
|
131
|
+
return get_or_create_eventloop().run_in_executor(None, func, *args, *kwargs)
|
|
132
|
+
|
|
133
|
+
return wrapped
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def flatten(nested_iterable: Iterable[Iterable[T]]) -> chain[T]:
|
|
137
|
+
return chain.from_iterable(nested_iterable)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Environment specifics
|
|
141
|
+
WINDOWS_BUILD_NUMBER = int(version().split(".")[-1]) if sys.platform == "win32" else -1
|
|
142
|
+
FIRST_WIN_11_BUILD = 22000
|
|
143
|
+
"""AutoSplit Version number"""
|
|
144
|
+
WGC_MIN_BUILD = 17134
|
|
145
|
+
"""https://docs.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturepicker#applies-to"""
|
|
146
|
+
FROZEN = hasattr(sys, "frozen")
|
|
147
|
+
"""Running from build made by PyInstaller"""
|
|
148
|
+
auto_split_directory = os.path.dirname(sys.executable if FROZEN else os.path.abspath(__file__))
|
|
149
|
+
"""The directory of either the AutoSplit executable or AutoSplit.py"""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import ctypes.wintypes
|
|
3
|
+
|
|
4
|
+
import win32gui
|
|
5
|
+
|
|
6
|
+
user32 = ctypes.WinDLL('user32', use_last_error=True)
|
|
7
|
+
|
|
8
|
+
DWMWA_EXTENDED_FRAME_BOUNDS = 9
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_window_minimized(hWnd):
|
|
12
|
+
return user32.IsIconic(hWnd) != 0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_window_bounds(hwnd):
|
|
16
|
+
extended_frame_bounds = ctypes.wintypes.RECT()
|
|
17
|
+
ctypes.windll.dwmapi.DwmGetWindowAttribute(
|
|
18
|
+
hwnd,
|
|
19
|
+
DWMWA_EXTENDED_FRAME_BOUNDS,
|
|
20
|
+
ctypes.byref(extended_frame_bounds),
|
|
21
|
+
ctypes.sizeof(extended_frame_bounds),
|
|
22
|
+
)
|
|
23
|
+
scaling = user32.GetDpiForWindow(hwnd) / 96
|
|
24
|
+
client_x, client_y, client_width, client_height = win32gui.GetClientRect(hwnd)
|
|
25
|
+
client_width = int(client_width / scaling)
|
|
26
|
+
client_height = int(client_height / scaling)
|
|
27
|
+
window_width = int((extended_frame_bounds.right - extended_frame_bounds.left) / scaling)
|
|
28
|
+
window_height = int((extended_frame_bounds.bottom - extended_frame_bounds.top) / scaling)
|
|
29
|
+
border = int((window_width - client_width) / 2)
|
|
30
|
+
title = window_height - client_height - border
|
|
31
|
+
return int(extended_frame_bounds.left / scaling), int(
|
|
32
|
+
extended_frame_bounds.top / scaling), border, title, client_width, client_height, scaling
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_foreground_window(hwnd):
|
|
36
|
+
return win32gui.IsWindowVisible(hwnd) and win32gui.GetForegroundWindow() == hwnd
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
|
|
3
|
+
black_color = {
|
|
4
|
+
'r': (0, 0), # Red range
|
|
5
|
+
'g': (0, 0), # Green range
|
|
6
|
+
'b': (0, 0) # Blue range
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
white_color = {
|
|
10
|
+
'r': (255, 255), # Red range
|
|
11
|
+
'g': (255, 255), # Green range
|
|
12
|
+
'b': (255, 255) # Blue range
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def calculate_color_percentage(image, color_ranges, box=None):
|
|
17
|
+
# Check if the ROI is within the image bounds
|
|
18
|
+
if box is not None:
|
|
19
|
+
if (box.x >= 0 and box.y >= 0 and
|
|
20
|
+
box.x + box.width <= image.shape[1] and # image.shape[1] is the width of the image
|
|
21
|
+
box.y + box.height <= image.shape[0]): # image.shape[0] is the height of the image
|
|
22
|
+
|
|
23
|
+
# Extract the region of interest (ROI) using slicing
|
|
24
|
+
|
|
25
|
+
image = image[box.y:box.y + box.height, box.x:box.x + box.width, :3]
|
|
26
|
+
else:
|
|
27
|
+
# Return some error value or raise an exception
|
|
28
|
+
# For example, return 0 or None
|
|
29
|
+
return 0 # or None, or raise an exception
|
|
30
|
+
else:
|
|
31
|
+
image = image[:, :, :3]
|
|
32
|
+
# Create a mask for the pixels within the desired color range
|
|
33
|
+
mask = cv2.inRange(image,
|
|
34
|
+
(color_ranges['b'][0], color_ranges['g'][0], color_ranges['r'][0]),
|
|
35
|
+
(color_ranges['b'][1], color_ranges['g'][1], color_ranges['r'][1]))
|
|
36
|
+
|
|
37
|
+
# Calculate the percentage of pixels within the color range
|
|
38
|
+
target_pixels = cv2.countNonZero(mask)
|
|
39
|
+
total_pixels = image.size / 3 # Divide by 3 for an RGB image
|
|
40
|
+
percentage = target_pixels / total_pixels
|
|
41
|
+
return percentage
|
|
File without changes
|