zhmiscellany 5.7.9__py3-none-any.whl → 6.0.9__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.
- zhmiscellany/_fileio_supportfuncs.py +4 -0
- zhmiscellany/_misc_supportfuncs.py +229 -107
- zhmiscellany/_processing_supportfuncs.py +26 -1
- zhmiscellany/discord.py +18 -4
- zhmiscellany/gui.py +84 -3
- zhmiscellany/macro.py +323 -23
- zhmiscellany/misc.py +26 -2
- zhmiscellany/netio.py +16 -8
- zhmiscellany/pipes.py +33 -4
- zhmiscellany/processing.py +16 -1
- {zhmiscellany-5.7.9.dist-info → zhmiscellany-6.0.9.dist-info}/METADATA +70 -6
- {zhmiscellany-5.7.9.dist-info → zhmiscellany-6.0.9.dist-info}/RECORD +14 -14
- {zhmiscellany-5.7.9.dist-info → zhmiscellany-6.0.9.dist-info}/WHEEL +0 -0
- {zhmiscellany-5.7.9.dist-info → zhmiscellany-6.0.9.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import threading, os, signal, time, sys, shutil, ctypes
|
|
1
|
+
import threading, os, signal, time, sys, shutil, ctypes, math
|
|
2
2
|
from ctypes import Structure, c_long, c_uint, c_int, POINTER, sizeof
|
|
3
3
|
import zhmiscellany.fileio
|
|
4
|
-
import
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
# Windows-specific imports
|
|
7
|
+
if sys.platform == "win32":
|
|
8
|
+
try:
|
|
9
|
+
import win32api
|
|
10
|
+
from ctypes import windll
|
|
11
|
+
WIN32_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
WIN32_AVAILABLE = False
|
|
14
|
+
print("Warning: Windows modules not available - Windows functionality disabled")
|
|
15
|
+
else:
|
|
16
|
+
WIN32_AVAILABLE = False
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
_misc_action = 0
|
|
@@ -48,7 +60,8 @@ def patch_rhg(): # patches random_header_generator library's missing files. thi
|
|
|
48
60
|
else:
|
|
49
61
|
# we are running in normal Python environment
|
|
50
62
|
pass
|
|
51
|
-
|
|
63
|
+
if WIN32_AVAILABLE:
|
|
64
|
+
patch_rhg()
|
|
52
65
|
|
|
53
66
|
|
|
54
67
|
def patch_cpp():
|
|
@@ -75,111 +88,219 @@ def patch_cpp():
|
|
|
75
88
|
gen()
|
|
76
89
|
shutil.copy2(os.path.join(base_path, 'resources', fn), tp)
|
|
77
90
|
os.chdir(cwd)
|
|
78
|
-
patch_cpp()
|
|
79
91
|
|
|
92
|
+
if WIN32_AVAILABLE:
|
|
93
|
+
patch_cpp()
|
|
94
|
+
|
|
95
|
+
import ctypes
|
|
96
|
+
import os
|
|
97
|
+
import math
|
|
98
|
+
import sys
|
|
99
|
+
from ctypes import c_int, c_uint, c_long, POINTER, Structure, sizeof, byref, c_ulong, c_void_p
|
|
100
|
+
|
|
101
|
+
# --- OS Detection ---
|
|
102
|
+
IS_LINUX = not WIN32_AVAILABLE
|
|
103
|
+
|
|
104
|
+
# --- Windows Imports & Structures ---
|
|
105
|
+
if WIN32_AVAILABLE:
|
|
106
|
+
windll = ctypes.windll
|
|
107
|
+
|
|
108
|
+
if WIN32_AVAILABLE:
|
|
109
|
+
class POINT(Structure):
|
|
110
|
+
_fields_ = [("x", c_long),
|
|
111
|
+
("y", c_long)]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class MOUSEINPUT(Structure):
|
|
115
|
+
_fields_ = [("dx", c_long),
|
|
116
|
+
("dy", c_long),
|
|
117
|
+
("mouseData", c_uint),
|
|
118
|
+
("dwFlags", c_uint),
|
|
119
|
+
("time", c_uint),
|
|
120
|
+
("dwExtraInfo", POINTER(c_uint))]
|
|
80
121
|
|
|
81
|
-
class POINT(Structure):
|
|
82
|
-
_fields_ = [("x", c_long),
|
|
83
|
-
("y", c_long)]
|
|
84
122
|
|
|
123
|
+
class INPUT_UNION(ctypes.Union):
|
|
124
|
+
_fields_ = [("mi", MOUSEINPUT)]
|
|
85
125
|
|
|
86
|
-
class MOUSEINPUT(Structure):
|
|
87
|
-
_fields_ = [("dx", c_long),
|
|
88
|
-
("dy", c_long),
|
|
89
|
-
("mouseData", c_uint),
|
|
90
|
-
("dwFlags", c_uint),
|
|
91
|
-
("time", c_uint),
|
|
92
|
-
("dwExtraInfo", POINTER(c_uint))]
|
|
93
126
|
|
|
127
|
+
class INPUT(Structure):
|
|
128
|
+
_fields_ = [("type", c_int),
|
|
129
|
+
("union", INPUT_UNION)]
|
|
94
130
|
|
|
95
|
-
class INPUT_UNION(ctypes.Union):
|
|
96
|
-
_fields_ = [("mi", MOUSEINPUT)]
|
|
97
131
|
|
|
132
|
+
# Windows Constants
|
|
133
|
+
MOUSEEVENTF_ABSOLUTE = 0x8000
|
|
134
|
+
MOUSEEVENTF_MOVE = 0x0001
|
|
135
|
+
MOUSEEVENTF_LEFTDOWN = 0x0002
|
|
136
|
+
MOUSEEVENTF_LEFTUP = 0x0004
|
|
137
|
+
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
|
138
|
+
MOUSEEVENTF_RIGHTUP = 0x0010
|
|
139
|
+
MOUSEEVENTF_MIDDLEDOWN = 0x0020
|
|
140
|
+
MOUSEEVENTF_MIDDLEUP = 0x0040
|
|
98
141
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
# --- Linux / X11 Setup ---
|
|
143
|
+
if IS_LINUX:
|
|
144
|
+
try:
|
|
145
|
+
# Load X11 and XTest (required for fake inputs)
|
|
146
|
+
x11 = ctypes.cdll.LoadLibrary("libX11.so.6")
|
|
147
|
+
xtst = ctypes.cdll.LoadLibrary("libXtst.so.6")
|
|
102
148
|
|
|
149
|
+
# Setup Display
|
|
150
|
+
display = x11.XOpenDisplay(None)
|
|
151
|
+
root_window = x11.XDefaultRootWindow(display)
|
|
103
152
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
MOUSEEVENTF_LEFTUP = 0x0004
|
|
109
|
-
MOUSEEVENTF_RIGHTDOWN = 0x0008
|
|
110
|
-
MOUSEEVENTF_RIGHTUP = 0x0010
|
|
111
|
-
MOUSEEVENTF_MIDDLEDOWN = 0x0020
|
|
112
|
-
MOUSEEVENTF_MIDDLEUP = 0x0040
|
|
153
|
+
except OSError:
|
|
154
|
+
print("Error: Could not load X11 or Xtst libraries. Ensure libx11-6 and libxtst6 are installed.")
|
|
155
|
+
x11 = None
|
|
156
|
+
xtst = None
|
|
113
157
|
|
|
114
|
-
|
|
158
|
+
|
|
159
|
+
# --- Screen Metrics ---
|
|
115
160
|
def get_actual_screen_resolution():
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
161
|
+
if WIN32_AVAILABLE:
|
|
162
|
+
if WIN32_AVAILABLE:
|
|
163
|
+
hdc = windll.user32.GetDC(0)
|
|
164
|
+
width = windll.gdi32.GetDeviceCaps(hdc, 118) # HORZRES
|
|
165
|
+
height = windll.gdi32.GetDeviceCaps(hdc, 117) # VERTRES
|
|
166
|
+
windll.user32.ReleaseDC(0, hdc)
|
|
167
|
+
return width, height
|
|
168
|
+
return 1920, 1080
|
|
169
|
+
elif IS_LINUX and x11:
|
|
170
|
+
screen_num = 0
|
|
171
|
+
width = x11.XDisplayWidth(display, screen_num)
|
|
172
|
+
height = x11.XDisplayHeight(display, screen_num)
|
|
173
|
+
return width, height
|
|
174
|
+
else:
|
|
175
|
+
return 1920, 1080
|
|
176
|
+
|
|
121
177
|
|
|
122
178
|
SCREEN_WIDTH, SCREEN_HEIGHT = get_actual_screen_resolution()
|
|
123
179
|
|
|
180
|
+
# --- Globals ---
|
|
124
181
|
calibrated = False
|
|
125
182
|
calipass = False
|
|
183
|
+
calibration_multiplier_x = 1.0
|
|
184
|
+
calibration_multiplier_y = 1.0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# --- Functions ---
|
|
126
188
|
|
|
127
189
|
def move_mouse(x: int, y: int, relative=False):
|
|
128
|
-
if
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if calibrated:
|
|
135
|
-
normalized_x = math.ceil(x * calibration_multiplier_x)
|
|
136
|
-
normalized_y = math.ceil(y * calibration_multiplier_y)
|
|
190
|
+
if WIN32_AVAILABLE:
|
|
191
|
+
if not relative:
|
|
192
|
+
# Convert coordinates to normalized coordinates (0-65535)
|
|
193
|
+
normalized_x = int(x * (65535 / SCREEN_WIDTH))
|
|
194
|
+
normalized_y = int(y * (65535 / SCREEN_HEIGHT))
|
|
195
|
+
dwflags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE
|
|
137
196
|
else:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
197
|
+
calibrate()
|
|
198
|
+
if calibrated:
|
|
199
|
+
normalized_x = math.ceil(x * calibration_multiplier_x)
|
|
200
|
+
normalized_y = math.ceil(y * calibration_multiplier_y)
|
|
201
|
+
else:
|
|
202
|
+
normalized_x = x
|
|
203
|
+
normalized_y = y
|
|
204
|
+
dwflags = MOUSEEVENTF_MOVE
|
|
205
|
+
|
|
206
|
+
input_struct = INPUT(
|
|
207
|
+
type=0,
|
|
208
|
+
union=INPUT_UNION(
|
|
209
|
+
mi=MOUSEINPUT(
|
|
210
|
+
dx=normalized_x,
|
|
211
|
+
dy=normalized_y,
|
|
212
|
+
mouseData=0,
|
|
213
|
+
dwFlags=dwflags,
|
|
214
|
+
time=0,
|
|
215
|
+
dwExtraInfo=None
|
|
216
|
+
)
|
|
156
217
|
)
|
|
157
218
|
)
|
|
158
|
-
|
|
219
|
+
windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
|
|
220
|
+
|
|
221
|
+
elif IS_LINUX and x11:
|
|
222
|
+
# XWarpPointer(display, src_w, dest_w, src_x, src_y, src_width, src_height, dest_x, dest_y)
|
|
223
|
+
if relative:
|
|
224
|
+
# Move relative to current position (None as dest_window usually means relative move,
|
|
225
|
+
# but standard practice is None src, None dest = move relative to current position)
|
|
226
|
+
x11.XWarpPointer(display, None, None, 0, 0, 0, 0, int(x), int(y))
|
|
227
|
+
else:
|
|
228
|
+
# Absolute move relative to Root Window
|
|
229
|
+
x11.XWarpPointer(display, None, root_window, 0, 0, 0, 0, int(x), int(y))
|
|
230
|
+
|
|
231
|
+
x11.XFlush(display)
|
|
159
232
|
|
|
160
|
-
ctypes.windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
|
|
161
233
|
|
|
162
234
|
def get_mouse_xy():
|
|
163
|
-
|
|
164
|
-
|
|
235
|
+
if WIN32_AVAILABLE:
|
|
236
|
+
if WIN32_AVAILABLE:
|
|
237
|
+
return win32api.GetCursorPos()
|
|
238
|
+
return 0, 0
|
|
239
|
+
elif IS_LINUX and x11:
|
|
240
|
+
root_id = c_ulong()
|
|
241
|
+
child_id = c_ulong()
|
|
242
|
+
root_x = c_int()
|
|
243
|
+
root_y = c_int()
|
|
244
|
+
win_x = c_int()
|
|
245
|
+
win_y = c_int()
|
|
246
|
+
mask = c_uint()
|
|
247
|
+
|
|
248
|
+
# XQueryPointer returns True if pointer is on the same screen
|
|
249
|
+
result = x11.XQueryPointer(display, root_window,
|
|
250
|
+
byref(root_id), byref(child_id),
|
|
251
|
+
byref(root_x), byref(root_y),
|
|
252
|
+
byref(win_x), byref(win_y),
|
|
253
|
+
byref(mask))
|
|
254
|
+
if result:
|
|
255
|
+
return root_x.value, root_y.value
|
|
256
|
+
return 0, 0
|
|
257
|
+
else:
|
|
258
|
+
return 0, 0
|
|
259
|
+
|
|
165
260
|
|
|
166
261
|
def calibrate():
|
|
167
262
|
global calibration_multiplier_x, calibration_multiplier_y, calibrated, calipass
|
|
263
|
+
|
|
264
|
+
# Linux (X11) relative movement is usually 1:1 pixel accurate via XWarpPointer
|
|
265
|
+
# so we skip the calibration routine.
|
|
266
|
+
if IS_LINUX:
|
|
267
|
+
calibrated = True
|
|
268
|
+
calipass = True
|
|
269
|
+
calibration_multiplier_x = 1.0
|
|
270
|
+
calibration_multiplier_y = 1.0
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
if not WIN32_AVAILABLE:
|
|
274
|
+
return
|
|
275
|
+
|
|
168
276
|
if calibrated:
|
|
169
277
|
return
|
|
170
278
|
if calipass:
|
|
171
279
|
return
|
|
280
|
+
|
|
172
281
|
calipass = True
|
|
173
282
|
# calibrate relative movement, required because windows is weird
|
|
174
283
|
original_mouse_point = get_mouse_xy()
|
|
175
284
|
calibration_distance = 128
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
285
|
+
moved_pos = (0, 0)
|
|
286
|
+
lim = 64
|
|
287
|
+
i = 0
|
|
288
|
+
while ((not moved_pos[0]) or (not moved_pos[1])) and i < lim:
|
|
289
|
+
i += 1
|
|
290
|
+
move_mouse(0, 0)
|
|
291
|
+
move_mouse(calibration_distance, calibration_distance, relative=True)
|
|
292
|
+
moved_pos = get_mouse_xy()
|
|
293
|
+
if not i < lim:
|
|
294
|
+
raise Exception('Relative mouse movement could not be calibrated.')
|
|
295
|
+
|
|
296
|
+
# Avoid division by zero
|
|
297
|
+
if moved_pos[0] != 0:
|
|
298
|
+
calibration_multiplier_x = calibration_distance / moved_pos[0]
|
|
299
|
+
if moved_pos[1] != 0:
|
|
300
|
+
calibration_multiplier_y = calibration_distance / moved_pos[1]
|
|
301
|
+
|
|
181
302
|
calibrated = True
|
|
182
|
-
move_mouse(original_mouse_point[0]
|
|
303
|
+
move_mouse(original_mouse_point[0], original_mouse_point[1])
|
|
183
304
|
|
|
184
305
|
|
|
185
306
|
def mouse_down(button: int):
|
|
@@ -187,27 +308,28 @@ def mouse_down(button: int):
|
|
|
187
308
|
if button not in [1, 2, 3]:
|
|
188
309
|
raise ValueError("Button must be 1 (left), 2 (right), or 3 (middle)")
|
|
189
310
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
dx=0,
|
|
201
|
-
dy=0,
|
|
202
|
-
mouseData=0,
|
|
203
|
-
dwFlags=flags[button],
|
|
204
|
-
time=0,
|
|
205
|
-
dwExtraInfo=None
|
|
311
|
+
if WIN32_AVAILABLE:
|
|
312
|
+
flags = {
|
|
313
|
+
1: MOUSEEVENTF_LEFTDOWN,
|
|
314
|
+
2: MOUSEEVENTF_RIGHTDOWN,
|
|
315
|
+
3: MOUSEEVENTF_MIDDLEDOWN
|
|
316
|
+
}
|
|
317
|
+
input_struct = INPUT(
|
|
318
|
+
type=0,
|
|
319
|
+
union=INPUT_UNION(
|
|
320
|
+
mi=MOUSEINPUT(dx=0, dy=0, mouseData=0, dwFlags=flags[button], time=0, dwExtraInfo=None)
|
|
206
321
|
)
|
|
207
322
|
)
|
|
208
|
-
|
|
323
|
+
windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
|
|
324
|
+
|
|
325
|
+
elif IS_LINUX and xtst:
|
|
326
|
+
# Linux X11 Button Mapping: 1=Left, 2=Middle, 3=Right
|
|
327
|
+
# Input Mapping: 1=Left, 2=Right, 3=Middle
|
|
328
|
+
linux_button_map = {1: 1, 2: 3, 3: 2}
|
|
209
329
|
|
|
210
|
-
|
|
330
|
+
# XTestFakeButtonEvent(display, button, is_press, delay)
|
|
331
|
+
xtst.XTestFakeButtonEvent(display, linux_button_map[button], True, 0)
|
|
332
|
+
x11.XFlush(display)
|
|
211
333
|
|
|
212
334
|
|
|
213
335
|
def mouse_up(button: int):
|
|
@@ -215,24 +337,24 @@ def mouse_up(button: int):
|
|
|
215
337
|
if button not in [1, 2, 3]:
|
|
216
338
|
raise ValueError("Button must be 1 (left), 2 (right), or 3 (middle)")
|
|
217
339
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
dx=0,
|
|
229
|
-
dy=0,
|
|
230
|
-
mouseData=0,
|
|
231
|
-
dwFlags=flags[button],
|
|
232
|
-
time=0,
|
|
233
|
-
dwExtraInfo=None
|
|
340
|
+
if WIN32_AVAILABLE:
|
|
341
|
+
flags = {
|
|
342
|
+
1: MOUSEEVENTF_LEFTUP,
|
|
343
|
+
2: MOUSEEVENTF_RIGHTUP,
|
|
344
|
+
3: MOUSEEVENTF_MIDDLEUP
|
|
345
|
+
}
|
|
346
|
+
input_struct = INPUT(
|
|
347
|
+
type=0,
|
|
348
|
+
union=INPUT_UNION(
|
|
349
|
+
mi=MOUSEINPUT(dx=0, dy=0, mouseData=0, dwFlags=flags[button], time=0, dwExtraInfo=None)
|
|
234
350
|
)
|
|
235
351
|
)
|
|
236
|
-
|
|
352
|
+
windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
|
|
353
|
+
|
|
354
|
+
elif IS_LINUX and xtst:
|
|
355
|
+
# Linux X11 Button Mapping: 1=Left, 2=Middle, 3=Right
|
|
356
|
+
linux_button_map = {1: 1, 2: 3, 3: 2}
|
|
237
357
|
|
|
238
|
-
|
|
358
|
+
# XTestFakeButtonEvent(display, button, is_press, delay)
|
|
359
|
+
xtst.XTestFakeButtonEvent(display, linux_button_map[button], False, 0)
|
|
360
|
+
x11.XFlush(display)
|
|
@@ -7,6 +7,16 @@ import sys
|
|
|
7
7
|
import io
|
|
8
8
|
from unittest.mock import patch
|
|
9
9
|
|
|
10
|
+
# Ray availability check
|
|
11
|
+
if sys.platform == "win32":
|
|
12
|
+
try:
|
|
13
|
+
import ray
|
|
14
|
+
RAY_AVAILABLE = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
RAY_AVAILABLE = False
|
|
17
|
+
else:
|
|
18
|
+
RAY_AVAILABLE = False
|
|
19
|
+
|
|
10
20
|
|
|
11
21
|
def clear_logs():
|
|
12
22
|
ray_dir = tempfile.gettempdir()
|
|
@@ -59,6 +69,10 @@ def safe_open_log(path, unbuffered=False, **kwargs):
|
|
|
59
69
|
|
|
60
70
|
|
|
61
71
|
def ray_init(auto=False):
|
|
72
|
+
if not RAY_AVAILABLE:
|
|
73
|
+
print("ray_init() only supports Windows! Functionality disabled")
|
|
74
|
+
return
|
|
75
|
+
|
|
62
76
|
if auto:
|
|
63
77
|
if 'in_ray_matrix' in os.environ:
|
|
64
78
|
return
|
|
@@ -184,6 +198,10 @@ class ThreadWithResult(threading.Thread):
|
|
|
184
198
|
|
|
185
199
|
|
|
186
200
|
def batch_multiprocess(targets_and_args, max_retries=0, expect_crashes=False, disable_warning=False, flatten=False):
|
|
201
|
+
if not RAY_AVAILABLE:
|
|
202
|
+
print("batch_multiprocess() only supports Windows! Returning empty list")
|
|
203
|
+
return []
|
|
204
|
+
|
|
187
205
|
if _ray_state == 'disabled':
|
|
188
206
|
if not disable_warning:
|
|
189
207
|
logging.warning("zhmiscellany didn't detect that you were going to be using multiprocessing functions, and ray was not initialized preemptively.\n\
|
|
@@ -231,6 +249,9 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
|
|
|
231
249
|
return results
|
|
232
250
|
|
|
233
251
|
def multiprocess(target, args=(), max_retries=0, disable_warning=False):
|
|
252
|
+
if not RAY_AVAILABLE:
|
|
253
|
+
print("multiprocess() only supports Windows! Returning None")
|
|
254
|
+
return None
|
|
234
255
|
return batch_multiprocess([(target, args)], disable_warning=disable_warning, max_retries=max_retries)[0]
|
|
235
256
|
|
|
236
257
|
|
|
@@ -257,6 +278,10 @@ class RayActorWrapper:
|
|
|
257
278
|
|
|
258
279
|
|
|
259
280
|
def synchronous_class_multiprocess(cls, *args, disable_warning=False, **kwargs):
|
|
281
|
+
if not RAY_AVAILABLE:
|
|
282
|
+
print("synchronous_class_multiprocess() only supports Windows! Returning None")
|
|
283
|
+
return None
|
|
284
|
+
|
|
260
285
|
if _ray_state == 'disabled':
|
|
261
286
|
if not disable_warning:
|
|
262
287
|
logging.warning("zhmiscellany didn't detect that you were going to be using multiprocessing functions, and ray was not initialized preemptively.\n\
|
|
@@ -278,4 +303,4 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
|
|
|
278
303
|
|
|
279
304
|
remote_cls = ray.remote(cls)
|
|
280
305
|
actor_instance = remote_cls.remote(*args, **kwargs)
|
|
281
|
-
return RayActorWrapper(actor_instance)
|
|
306
|
+
return RayActorWrapper(actor_instance)
|
zhmiscellany/discord.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import time
|
|
2
|
-
|
|
2
|
+
import sys
|
|
3
3
|
import requests
|
|
4
4
|
import copy
|
|
5
5
|
import zhmiscellany.fileio
|
|
@@ -8,11 +8,21 @@ import zhmiscellany.processing
|
|
|
8
8
|
from ._discord_supportfuncs import scrape_guild
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
|
-
import Crypto.Cipher.AES
|
|
12
11
|
import os
|
|
13
12
|
import json
|
|
14
13
|
import re
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
# Windows-specific imports
|
|
16
|
+
if sys.platform == "win32":
|
|
17
|
+
try:
|
|
18
|
+
import win32crypt
|
|
19
|
+
from Crypto.Cipher import AES
|
|
20
|
+
WIN32_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
WIN32_AVAILABLE = False
|
|
23
|
+
print("Warning: Windows modules not available - local Discord user detection disabled")
|
|
24
|
+
else:
|
|
25
|
+
WIN32_AVAILABLE = False
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
def add_reactions_to_message(user_token, emojis, channel_id, message_id):
|
|
@@ -108,6 +118,10 @@ def get_channel_messages(user_token, channel_id, limit=0, use_cache=True, show_p
|
|
|
108
118
|
|
|
109
119
|
|
|
110
120
|
def get_local_discord_user(show_output=False):
|
|
121
|
+
if not WIN32_AVAILABLE:
|
|
122
|
+
print("get_local_discord_user() only supports Windows! Returning None")
|
|
123
|
+
return None
|
|
124
|
+
|
|
111
125
|
global _cached_user_info
|
|
112
126
|
try:
|
|
113
127
|
a = _cached_user_info
|
|
@@ -121,7 +135,7 @@ def get_local_discord_user(show_output=False):
|
|
|
121
135
|
|
|
122
136
|
def decrypt(buff, master_key):
|
|
123
137
|
try:
|
|
124
|
-
return
|
|
138
|
+
return AES.new(win32crypt.CryptUnprotectData(master_key, None, None, None, 0)[1], AES.MODE_GCM, buff[3:15]).decrypt(buff[15:])[:-16].decode()
|
|
125
139
|
except:
|
|
126
140
|
return "Error"
|
|
127
141
|
|
zhmiscellany/gui.py
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import tkinter as tk
|
|
2
2
|
import threading
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Windows-specific imports
|
|
6
|
+
if sys.platform == "win32":
|
|
7
|
+
try:
|
|
8
|
+
import ctypes
|
|
9
|
+
from ctypes import wintypes
|
|
10
|
+
import win32gui
|
|
11
|
+
WIN32_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
WIN32_AVAILABLE = False
|
|
14
|
+
print("Warning: Windows modules not available - GUI functionality disabled")
|
|
15
|
+
else:
|
|
16
|
+
WIN32_AVAILABLE = False
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
class StateIndicator:
|
|
@@ -64,6 +76,10 @@ class StateIndicator:
|
|
|
64
76
|
|
|
65
77
|
def _make_click_through(self):
|
|
66
78
|
"""Make the window click-through using Windows API"""
|
|
79
|
+
if not WIN32_AVAILABLE:
|
|
80
|
+
print("Click-through only supported on Windows")
|
|
81
|
+
return
|
|
82
|
+
|
|
67
83
|
try:
|
|
68
84
|
# Get the window handle
|
|
69
85
|
hwnd = ctypes.windll.user32.GetParent(self._root.winfo_id())
|
|
@@ -125,6 +141,9 @@ class StateIndicator:
|
|
|
125
141
|
|
|
126
142
|
def _update_layered_attributes(self):
|
|
127
143
|
"""Update the layered window attributes for proper transparency with click-through"""
|
|
144
|
+
if not WIN32_AVAILABLE:
|
|
145
|
+
return
|
|
146
|
+
|
|
128
147
|
try:
|
|
129
148
|
hwnd = ctypes.windll.user32.GetParent(self._root.winfo_id())
|
|
130
149
|
if hwnd == 0:
|
|
@@ -176,4 +195,66 @@ class StateIndicator:
|
|
|
176
195
|
if isinstance(xy_tuple, tuple) and len(xy_tuple) == 2 and all(isinstance(n, (int, float)) and n > 0 for n in xy_tuple):
|
|
177
196
|
self._size = xy_tuple
|
|
178
197
|
if self._root and self._root.winfo_exists():
|
|
179
|
-
self._root.after(0, self._update_size_internal)
|
|
198
|
+
self._root.after(0, self._update_size_internal)
|
|
199
|
+
|
|
200
|
+
if WIN32_AVAILABLE:
|
|
201
|
+
user32 = ctypes.windll.user32
|
|
202
|
+
|
|
203
|
+
def get_focused_window():
|
|
204
|
+
"""Return the window that currently has the keyboard focus."""
|
|
205
|
+
return user32.GetForegroundWindow()
|
|
206
|
+
|
|
207
|
+
def get_window_rect(hwnd):
|
|
208
|
+
"""Return the bounding rectangle of a window."""
|
|
209
|
+
HWND = wintypes.HWND
|
|
210
|
+
RECT = wintypes.RECT
|
|
211
|
+
rect = RECT()
|
|
212
|
+
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
213
|
+
return rect
|
|
214
|
+
|
|
215
|
+
def set_window_pos(hwnd, x: int, y: int, w: int, h: int):
|
|
216
|
+
"""Move (and optionally resize) a window."""
|
|
217
|
+
# 0x0040 == SWP_NOACTIVATE | 0x0020 == SWP_SHOWWINDOW
|
|
218
|
+
user32.SetWindowPos(hwnd, 0, x, y, w, h, 0x0040 | 0x0020)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def find_window_by_title_fuzzy(title_query, threshold=70):
|
|
222
|
+
from fuzzywuzzy import process
|
|
223
|
+
from fuzzywuzzy import fuzz
|
|
224
|
+
def enum_windows_callback(hwnd, windows):
|
|
225
|
+
if win32gui.IsWindowVisible(hwnd):
|
|
226
|
+
window_title = win32gui.GetWindowText(hwnd)
|
|
227
|
+
if window_title:
|
|
228
|
+
windows.append((hwnd, window_title))
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
windows = []
|
|
232
|
+
win32gui.EnumWindows(enum_windows_callback, windows)
|
|
233
|
+
|
|
234
|
+
if not windows:
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
titles = [title for hwnd, title in windows]
|
|
238
|
+
best_match = process.extractOne(title_query, titles, scorer=fuzz.token_set_ratio)
|
|
239
|
+
|
|
240
|
+
if best_match[1] >= threshold:
|
|
241
|
+
matched_title = best_match[0]
|
|
242
|
+
for hwnd, title in windows:
|
|
243
|
+
if title == matched_title:
|
|
244
|
+
return hwnd
|
|
245
|
+
return None
|
|
246
|
+
else:
|
|
247
|
+
def get_focused_window():
|
|
248
|
+
print("get_focused_window() only supports Windows! Returning None")
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
def get_window_rect(hwnd):
|
|
252
|
+
print("get_window_rect() only supports Windows! Returning None")
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
def set_window_pos(hwnd, x: int, y: int, w: int, h: int):
|
|
256
|
+
print("set_window_pos() only supports Windows! Functionality disabled")
|
|
257
|
+
|
|
258
|
+
def find_window_by_title_fuzzy(title_query, threshold=70):
|
|
259
|
+
print("find_window_by_title_fuzzy() only supports Windows! Returning None")
|
|
260
|
+
return None
|