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
zhmiscellany/macro.py
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import random
|
|
3
3
|
import threading
|
|
4
|
+
import sys
|
|
4
5
|
|
|
5
6
|
from ._misc_supportfuncs import move_mouse, mouse_down, mouse_up, get_mouse_xy
|
|
6
7
|
import zhmiscellany.misc
|
|
7
8
|
import zhmiscellany.math
|
|
8
9
|
import zhmiscellany.string
|
|
9
10
|
import zhmiscellany.processing
|
|
10
|
-
import win32api, win32con, ctypes
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# Windows-specific imports
|
|
13
|
+
if sys.platform == "win32":
|
|
14
|
+
try:
|
|
15
|
+
import win32api, win32con, ctypes
|
|
16
|
+
WIN32_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
WIN32_AVAILABLE = False
|
|
19
|
+
print("Warning: Windows modules not available - macro functionality disabled")
|
|
20
|
+
else:
|
|
21
|
+
WIN32_AVAILABLE = False
|
|
13
22
|
|
|
23
|
+
import keyboard, kthread
|
|
14
24
|
import time
|
|
15
25
|
|
|
16
26
|
get_mouse_xy = get_mouse_xy
|
|
17
27
|
|
|
18
28
|
|
|
19
|
-
def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_click=False, shift=False, ctrl=False, act_start=True, act_end=True, click_end_duration=None, double_click=False, animation_time=None, animation_fps=60, animation_easing=True, relative=False, ensure_movement=True, pre_click_duration=None, pre_click_wiggle=
|
|
29
|
+
def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_click=False, shift=False, ctrl=False, act_start=True, act_end=True, click_end_duration=None, double_click=False, animation_time=None, animation_fps=60, animation_easing=True, relative=False, ensure_movement=True, pre_click_duration=None, pre_click_wiggle=True, click=True):
|
|
20
30
|
if not click:
|
|
21
31
|
act_start=False;act_end=False
|
|
22
32
|
if right_click and middle_click:
|
|
@@ -28,9 +38,15 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
28
38
|
y = x[1]
|
|
29
39
|
x = x[0]
|
|
30
40
|
|
|
31
|
-
def stochastic_round(
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
def stochastic_round(val):
|
|
42
|
+
negative = val < 0
|
|
43
|
+
if negative:
|
|
44
|
+
val = val * -1
|
|
45
|
+
floor = int(val)
|
|
46
|
+
out = floor + (random.random() < (val - floor))
|
|
47
|
+
if negative:
|
|
48
|
+
out = out * -1
|
|
49
|
+
return out
|
|
34
50
|
|
|
35
51
|
if type(x) == float:
|
|
36
52
|
x = stochastic_round(x)
|
|
@@ -99,13 +115,17 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
99
115
|
for point in animation_points:
|
|
100
116
|
click_pixel((round(point[0]), round(point[1])), act_start=False, act_end=False, click_end_duration=1/animation_fps, relative=relative)
|
|
101
117
|
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
if WIN32_AVAILABLE:
|
|
119
|
+
if ctrl:
|
|
120
|
+
win32api.keybd_event(win32con.VK_CONTROL, 0, 0, 0)
|
|
121
|
+
keys_down.append(win32con.VK_CONTROL)
|
|
105
122
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
if shift:
|
|
124
|
+
win32api.keybd_event(win32con.VK_SHIFT, 0, 0, 0)
|
|
125
|
+
keys_down.append(win32con.VK_SHIFT)
|
|
126
|
+
else:
|
|
127
|
+
if ctrl or shift:
|
|
128
|
+
print("Warning: Modifier keys not supported on this platform")
|
|
109
129
|
|
|
110
130
|
if x is not None and y is not None:
|
|
111
131
|
if not relative:
|
|
@@ -129,12 +149,13 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
129
149
|
if not (animation_time and relative):
|
|
130
150
|
targ = tuple(a + b for a, b in zip(get_mouse_xy(), (cx, cy)))
|
|
131
151
|
move_mouse(cx, cy, relative=relative)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
152
|
+
if ensure_movement:
|
|
153
|
+
for i in range(limit):
|
|
154
|
+
cur_pos = get_mouse_xy()
|
|
155
|
+
if cur_pos != targ:
|
|
156
|
+
move_mouse(max(-1, min(1, (targ[0]-cur_pos[0]))), max(-1, min(1, (targ[1]-cur_pos[1]))), relative=relative)
|
|
157
|
+
else:
|
|
158
|
+
break
|
|
138
159
|
|
|
139
160
|
if pre_click_duration:
|
|
140
161
|
if pre_click_wiggle:
|
|
@@ -166,8 +187,9 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
166
187
|
if act_end:
|
|
167
188
|
mouse_up(1)
|
|
168
189
|
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
if WIN32_AVAILABLE:
|
|
191
|
+
for key in keys_down:
|
|
192
|
+
win32api.keybd_event(key, 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
171
193
|
|
|
172
194
|
if click_end_duration:
|
|
173
195
|
zhmiscellany.misc.high_precision_sleep(click_end_duration)
|
|
@@ -177,6 +199,10 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
177
199
|
|
|
178
200
|
|
|
179
201
|
def press_key_directinput(key, shift=False, act_start=True, act_end=True, key_hold_time=0):
|
|
202
|
+
if not WIN32_AVAILABLE:
|
|
203
|
+
print("press_key_directinput() only supports Windows! Functionality disabled")
|
|
204
|
+
return
|
|
205
|
+
|
|
180
206
|
import pydirectinput
|
|
181
207
|
pydirectinput.PAUSE = 0
|
|
182
208
|
pydirectinput.FAILSAFE = False
|
|
@@ -188,6 +214,10 @@ def press_key_directinput(key, shift=False, act_start=True, act_end=True, key_ho
|
|
|
188
214
|
|
|
189
215
|
|
|
190
216
|
def press_key(vk_code, shift=False, act_start=True, act_end=True, key_hold_time=0):
|
|
217
|
+
if not WIN32_AVAILABLE:
|
|
218
|
+
print("press_key() only supports Windows! Functionality disabled")
|
|
219
|
+
return
|
|
220
|
+
|
|
191
221
|
if shift:
|
|
192
222
|
win32api.keybd_event(win32con.VK_SHIFT, 0, 0, 0)
|
|
193
223
|
if act_start:
|
|
@@ -258,7 +288,23 @@ def type_string(text=None, delay=None, key_hold_time=None, vk_codes=None, combin
|
|
|
258
288
|
press_key(vk_code, False, act_start=False, act_end=True, key_hold_time=key_hold_time)
|
|
259
289
|
|
|
260
290
|
|
|
261
|
-
def
|
|
291
|
+
def is_key_pressed_async(vk_code):
|
|
292
|
+
"""
|
|
293
|
+
Async check if a key is currently pressed
|
|
294
|
+
vk_code: Virtual Key code (e.g., 0x41 for 'A', 0x1B for ESC)
|
|
295
|
+
Returns: True if pressed, False otherwise
|
|
296
|
+
"""
|
|
297
|
+
if not WIN32_AVAILABLE:
|
|
298
|
+
print("is_key_pressed_async() only supports Windows! Returning False")
|
|
299
|
+
return False
|
|
300
|
+
return win32api.GetAsyncKeyState(vk_code) & 0x8000 != 0
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def scroll(amount, delay=None, post_scroll_delay=None):
|
|
304
|
+
if not WIN32_AVAILABLE:
|
|
305
|
+
print("scroll() only supports Windows! Functionality disabled")
|
|
306
|
+
return
|
|
307
|
+
|
|
262
308
|
def raw_scroll(amount):
|
|
263
309
|
# Constants for mouse input
|
|
264
310
|
INPUT_MOUSE = 0
|
|
@@ -303,12 +349,18 @@ def scroll(amount, delay=None):
|
|
|
303
349
|
direction = 1 if amount > 0 else -1
|
|
304
350
|
amount = amount*direction
|
|
305
351
|
for _ in range(amount):
|
|
306
|
-
raw_scroll(
|
|
352
|
+
raw_scroll(direction)
|
|
307
353
|
zhmiscellany.misc.high_precision_sleep(delay/amount)
|
|
354
|
+
if post_scroll_delay:
|
|
355
|
+
zhmiscellany.misc.high_precision_sleep(post_scroll_delay)
|
|
308
356
|
|
|
309
357
|
|
|
310
358
|
def get_mouse_buttons():
|
|
311
359
|
"""Returns a list of booleans [M1, M2, M3] indicating which mouse buttons are held down."""
|
|
360
|
+
if not WIN32_AVAILABLE:
|
|
361
|
+
print("get_mouse_buttons() only supports Windows! Returning [False, False, False]")
|
|
362
|
+
return [False, False, False]
|
|
363
|
+
|
|
312
364
|
VK_LBUTTON = 0x01 # Left mouse button (M1)
|
|
313
365
|
VK_RBUTTON = 0x02 # Right mouse button (M2)
|
|
314
366
|
VK_MBUTTON = 0x04 # Middle mouse button (M3)
|
|
@@ -356,6 +408,252 @@ def toggle_function(func, key='f8', blocking=True):
|
|
|
356
408
|
return t
|
|
357
409
|
|
|
358
410
|
|
|
411
|
+
def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
|
|
412
|
+
import time
|
|
413
|
+
import pynput
|
|
414
|
+
import pyperclip
|
|
415
|
+
|
|
416
|
+
# --- Configuration ---
|
|
417
|
+
# Set to True to record every single mouse movement.
|
|
418
|
+
# Set to False to only record mouse position on clicks and drags.
|
|
419
|
+
|
|
420
|
+
# --- Global State ---
|
|
421
|
+
events = []
|
|
422
|
+
start_time = None
|
|
423
|
+
|
|
424
|
+
# --- Helper Functions ---
|
|
425
|
+
def format_key(key):
|
|
426
|
+
"""
|
|
427
|
+
Formats the pynput key object into a string for the generated script.
|
|
428
|
+
Handles cases where modifier keys (like Ctrl) are held down, which can
|
|
429
|
+
prevent the `key.char` attribute from being populated correctly.
|
|
430
|
+
"""
|
|
431
|
+
if isinstance(key, pynput.keyboard.Key):
|
|
432
|
+
# Special keys (e.g., Key.shift, Key.ctrl)
|
|
433
|
+
return f"Key.{key.name}"
|
|
434
|
+
|
|
435
|
+
if isinstance(key, pynput.keyboard.KeyCode):
|
|
436
|
+
# This is the robust way to handle alphanumeric keys, especially with modifiers.
|
|
437
|
+
# key.char can be None or a control character when Ctrl/Alt are held.
|
|
438
|
+
# key.vk is the virtual key code, which is more reliable.
|
|
439
|
+
|
|
440
|
+
# Heuristic for A-Z keys (VK codes on Windows/Linux are often in this range)
|
|
441
|
+
if 65 <= getattr(key, 'vk', 0) <= 90:
|
|
442
|
+
return f"'{chr(key.vk).lower()}'"
|
|
443
|
+
|
|
444
|
+
# For other keys, try to use the char if it exists
|
|
445
|
+
if key.char:
|
|
446
|
+
bs = '\\'
|
|
447
|
+
return f"'{key.char.replace(bs, bs + bs + bs)}'"
|
|
448
|
+
|
|
449
|
+
# As a fallback for other keys without a char, use the vk
|
|
450
|
+
if hasattr(key, 'vk'):
|
|
451
|
+
return f"pynput.keyboard.KeyCode.from_vk({key.vk})"
|
|
452
|
+
|
|
453
|
+
# If it's some other type of key object (less common)
|
|
454
|
+
return str(key)
|
|
455
|
+
|
|
456
|
+
def generate_code(events, start_time):
|
|
457
|
+
"""
|
|
458
|
+
Generates the Python script from the recorded events and copies it to the clipboard.
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
if not events:
|
|
462
|
+
print("No actions were recorded.")
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
# Code preamble
|
|
466
|
+
code_lines = [
|
|
467
|
+
"import zhmiscellany",
|
|
468
|
+
"",
|
|
469
|
+
"zhmiscellany.misc.die_on_key('f9')",
|
|
470
|
+
"",
|
|
471
|
+
"m = zhmiscellany.macro.click_pixel",
|
|
472
|
+
"k = zhmiscellany.macro.press_key_directinput",
|
|
473
|
+
"s = zhmiscellany.macro.scroll",
|
|
474
|
+
"sleep = zhmiscellany.misc.high_precision_sleep",
|
|
475
|
+
"click_down_time = 1/30",
|
|
476
|
+
"click_release_time = 1/30",
|
|
477
|
+
"mouse_move_dly = 1/60",
|
|
478
|
+
"key_down_time = 1/30",
|
|
479
|
+
"scroll_dly = 1/30",
|
|
480
|
+
"post_scroll_dly = 1/10",
|
|
481
|
+
"pre_click_duration = 1/30",
|
|
482
|
+
"",
|
|
483
|
+
"pre_click_wiggle = True",
|
|
484
|
+
"",
|
|
485
|
+
"animation_time = 0.1",
|
|
486
|
+
"",
|
|
487
|
+
'print("Replaying actions in 3 seconds...")',
|
|
488
|
+
"sleep(3)",
|
|
489
|
+
""
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
last_time = start_time
|
|
493
|
+
skip_next = 0
|
|
494
|
+
for i, event in enumerate(events):
|
|
495
|
+
if skip_next:
|
|
496
|
+
skip_next -= 1
|
|
497
|
+
continue
|
|
498
|
+
current_time = event['time']
|
|
499
|
+
try:
|
|
500
|
+
next_event = events[i+1]
|
|
501
|
+
except:
|
|
502
|
+
next_event = None
|
|
503
|
+
delay = current_time - last_time
|
|
504
|
+
|
|
505
|
+
action = event['action']
|
|
506
|
+
|
|
507
|
+
if action == 'click':
|
|
508
|
+
x, y, button, pressed = event['x'], event['y'], event['button'], event['pressed']
|
|
509
|
+
button_str = f"Button.{button.name}"
|
|
510
|
+
action_str = "press" if pressed else "release"
|
|
511
|
+
|
|
512
|
+
replacements = {
|
|
513
|
+
'right': 'right_click=True, ',
|
|
514
|
+
'middle': 'middle_click=True, ',
|
|
515
|
+
'left': '',
|
|
516
|
+
}
|
|
517
|
+
for key, value in replacements.items():
|
|
518
|
+
if key in button_str:
|
|
519
|
+
button_str = value
|
|
520
|
+
break
|
|
521
|
+
|
|
522
|
+
replacements = {
|
|
523
|
+
'press': 'act_end=False, ',
|
|
524
|
+
'release': 'act_start=False, ',
|
|
525
|
+
}
|
|
526
|
+
for key, value in replacements.items():
|
|
527
|
+
if key in action_str:
|
|
528
|
+
action_str = value
|
|
529
|
+
break
|
|
530
|
+
|
|
531
|
+
if next_event and next_event['action'] == 'click' and (next_event['x'], next_event['y']) == (x, y):
|
|
532
|
+
action_str = ''
|
|
533
|
+
skip_next = 1
|
|
534
|
+
|
|
535
|
+
code_lines.append(f"m(({x}, {y}), {button_str}{action_str}click_duration=click_down_time, click_end_duration=click_release_time, pre_click_duration=pre_click_duration, animation_time=animation_time)")
|
|
536
|
+
|
|
537
|
+
elif action == 'move':
|
|
538
|
+
x, y = event['x'], event['y']
|
|
539
|
+
code_lines.append(f"m(({x}, {y}), click_end_duration=mouse_move_dly)")
|
|
540
|
+
|
|
541
|
+
elif action == 'scroll':
|
|
542
|
+
x, y, dx, dy = event['x'], event['y'], event['dx'], event['dy']
|
|
543
|
+
j = i
|
|
544
|
+
count = 0
|
|
545
|
+
while True:
|
|
546
|
+
count += 1
|
|
547
|
+
j += 1
|
|
548
|
+
try:
|
|
549
|
+
t_event = events[j]
|
|
550
|
+
except:
|
|
551
|
+
t_event = None
|
|
552
|
+
if t_event and t_event['action'] == 'scroll' and t_event['dy'] == dy and (t_event['x'], t_event['y']) == (x, y):
|
|
553
|
+
skip_next += 1
|
|
554
|
+
else:
|
|
555
|
+
break
|
|
556
|
+
code_lines.append(f"m(({x}, {y}), click_end_duration=mouse_move_dly)")
|
|
557
|
+
code_lines.append(f"s({dy*count}, scroll_dly, post_scroll_dly)")
|
|
558
|
+
|
|
559
|
+
elif action in ('key_press', 'key_release'):
|
|
560
|
+
key = event['key']
|
|
561
|
+
key_str = format_key(key)
|
|
562
|
+
if '.' in key_str:
|
|
563
|
+
key_str = key_str.split('.')[1]
|
|
564
|
+
replacements = {
|
|
565
|
+
'ctrl': 'ctrl',
|
|
566
|
+
'shift': 'shift',
|
|
567
|
+
'alt': 'alt',
|
|
568
|
+
}
|
|
569
|
+
for key, value in replacements.items():
|
|
570
|
+
if key in key_str:
|
|
571
|
+
key_str = value
|
|
572
|
+
break
|
|
573
|
+
action_str = "press" if action == 'key_press' else "release"
|
|
574
|
+
|
|
575
|
+
replacements = {
|
|
576
|
+
'press': 'act_end=False, ',
|
|
577
|
+
'release': 'act_start=False, ',
|
|
578
|
+
}
|
|
579
|
+
for key, value in replacements.items():
|
|
580
|
+
if key in action_str:
|
|
581
|
+
action_str = value
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
if key_str == STOP_KEY:
|
|
585
|
+
break
|
|
586
|
+
|
|
587
|
+
code_lines.append(f"k('{key_str}', {action_str}key_hold_time=key_down_time)")
|
|
588
|
+
|
|
589
|
+
last_time = current_time
|
|
590
|
+
|
|
591
|
+
code_lines.append("\nprint('Replay finished.')")
|
|
592
|
+
|
|
593
|
+
# Join all lines into a single script
|
|
594
|
+
final_script = "\n".join(code_lines)
|
|
595
|
+
|
|
596
|
+
# Print and copy to clipboard
|
|
597
|
+
print("\n" + "=" * 50)
|
|
598
|
+
print(" RECORDING FINISHED - SCRIPT GENERATED")
|
|
599
|
+
print("=" * 50 + "\n")
|
|
600
|
+
print(final_script)
|
|
601
|
+
print("\n" + "=" * 50)
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
pyperclip.copy(final_script)
|
|
605
|
+
print("Script has been copied to your clipboard!")
|
|
606
|
+
except pyperclip.PyperclipException:
|
|
607
|
+
print("Could not copy to clipboard. Please install xclip or xsel on Linux.")
|
|
608
|
+
return final_script
|
|
609
|
+
|
|
610
|
+
# --- Pynput Listener Callbacks ---
|
|
611
|
+
|
|
612
|
+
def on_move(x, y):
|
|
613
|
+
if RECORD_MOUSE_MOVEMENT:
|
|
614
|
+
events.append({'action': 'move', 'x': x, 'y': y, 'time': time.time()})
|
|
615
|
+
|
|
616
|
+
def on_click(x, y, button, pressed):
|
|
617
|
+
events.append({'action': 'click', 'x': x, 'y': y, 'button': button, 'pressed': pressed, 'time': time.time()})
|
|
618
|
+
|
|
619
|
+
def on_scroll(x, y, dx, dy):
|
|
620
|
+
events.append({'action': 'scroll', 'x': x, 'y': y, 'dx': dx, 'dy': dy, 'time': time.time()})
|
|
621
|
+
|
|
622
|
+
def on_press(key):
|
|
623
|
+
events.append({'action': 'key_press', 'key': key, 'time': time.time()})
|
|
624
|
+
|
|
625
|
+
def on_release(key):
|
|
626
|
+
try:
|
|
627
|
+
print(key.name)
|
|
628
|
+
if key.name == STOP_KEY:
|
|
629
|
+
# Stop listeners
|
|
630
|
+
return False
|
|
631
|
+
except:
|
|
632
|
+
pass
|
|
633
|
+
events.append({'action': 'key_release', 'key': key, 'time': time.time()})
|
|
634
|
+
|
|
635
|
+
print(f"Press '{STOP_KEY}' to stop recording and generate the script.")
|
|
636
|
+
print("...")
|
|
637
|
+
|
|
638
|
+
start_time = time.time()
|
|
639
|
+
|
|
640
|
+
# Create and start listeners
|
|
641
|
+
mouse_listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll)
|
|
642
|
+
keyboard_listener = pynput.keyboard.Listener(on_press=on_press, on_release=on_release)
|
|
643
|
+
|
|
644
|
+
mouse_listener.start()
|
|
645
|
+
keyboard_listener.start()
|
|
646
|
+
|
|
647
|
+
# Wait for the keyboard listener to stop (on F9 press)
|
|
648
|
+
keyboard_listener.join()
|
|
649
|
+
|
|
650
|
+
# Stop the mouse listener explicitly once the keyboard one has finished
|
|
651
|
+
mouse_listener.stop()
|
|
652
|
+
|
|
653
|
+
# Generate the replay script
|
|
654
|
+
return generate_code(events, start_time)
|
|
655
|
+
|
|
656
|
+
|
|
359
657
|
KEY_CODES = {
|
|
360
658
|
'None': 0,
|
|
361
659
|
'LeftMouseButton': 1,
|
|
@@ -496,5 +794,7 @@ KEY_CODES = {
|
|
|
496
794
|
'StartMailKey': 180,
|
|
497
795
|
'SelectMedia': 181,
|
|
498
796
|
'StartApplication1': 182,
|
|
499
|
-
'StartApplication2': 183
|
|
797
|
+
'StartApplication2': 183,
|
|
798
|
+
'Equals': 187,
|
|
799
|
+
'Minus': 189,
|
|
500
800
|
}
|
zhmiscellany/misc.py
CHANGED
|
@@ -7,7 +7,14 @@ import zhmiscellany.fileio
|
|
|
7
7
|
import time, hashlib, ctypes
|
|
8
8
|
import random, string, copy
|
|
9
9
|
import builtins, inspect
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
WIN32_AVAILABLE = False
|
|
12
|
+
if sys.platform == "win32":
|
|
13
|
+
try:
|
|
14
|
+
import win32gui, win32con, win32process
|
|
15
|
+
WIN32_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
print("Warning: pywin32 not available, Windows-specific features disabled")
|
|
11
18
|
|
|
12
19
|
import psutil
|
|
13
20
|
|
|
@@ -22,6 +29,9 @@ KEY_CODES = zhmiscellany.macro.KEY_CODES
|
|
|
22
29
|
|
|
23
30
|
|
|
24
31
|
def get_actual_screen_resolution():
|
|
32
|
+
if not WIN32_AVAILABLE:
|
|
33
|
+
print("get_actual_screen_resolution only supports Windows! Returning (0, 0)")
|
|
34
|
+
return (0, 0)
|
|
25
35
|
hdc = ctypes.windll.user32.GetDC(0)
|
|
26
36
|
width = ctypes.windll.gdi32.GetDeviceCaps(hdc, 118) # HORZRES
|
|
27
37
|
height = ctypes.windll.gdi32.GetDeviceCaps(hdc, 117) # VERTRES
|
|
@@ -30,6 +40,10 @@ def get_actual_screen_resolution():
|
|
|
30
40
|
|
|
31
41
|
|
|
32
42
|
def focus_window(process_name: str, interval=0):
|
|
43
|
+
if not WIN32_AVAILABLE:
|
|
44
|
+
print("focus_window only supports Windows!")
|
|
45
|
+
return
|
|
46
|
+
|
|
33
47
|
# Import user32.dll for additional window handling
|
|
34
48
|
user32 = ctypes.windll.user32
|
|
35
49
|
kernel32 = ctypes.windll.kernel32
|
|
@@ -116,6 +130,10 @@ def focus_window(process_name: str, interval=0):
|
|
|
116
130
|
|
|
117
131
|
|
|
118
132
|
def setup_console_window(xy=(0, 0), wh=(400, 100), always_on_top=True):
|
|
133
|
+
if not WIN32_AVAILABLE:
|
|
134
|
+
print("setup_console_window only supports Windows!")
|
|
135
|
+
return
|
|
136
|
+
|
|
119
137
|
# Get the console window handle
|
|
120
138
|
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
|
121
139
|
ontop = win32con.HWND_NOTOPMOST
|
|
@@ -223,6 +241,9 @@ def high_precision_sleep(duration):
|
|
|
223
241
|
|
|
224
242
|
|
|
225
243
|
def is_admin():
|
|
244
|
+
if not WIN32_AVAILABLE:
|
|
245
|
+
print("is_admin only supports Windows! Returning False")
|
|
246
|
+
return False
|
|
226
247
|
try:
|
|
227
248
|
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
|
228
249
|
except Exception:
|
|
@@ -749,4 +770,7 @@ l = types.FunctionType(
|
|
|
749
770
|
|
|
750
771
|
|
|
751
772
|
def wait_for_vsync():
|
|
752
|
-
|
|
773
|
+
if not WIN32_AVAILABLE:
|
|
774
|
+
print("wait_for_vsync only supports Windows!")
|
|
775
|
+
return
|
|
776
|
+
ctypes.windll.dwmapi.DwmFlush()
|
zhmiscellany/netio.py
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import os, requests
|
|
2
2
|
import zhmiscellany.string
|
|
3
3
|
import urllib.parse
|
|
4
|
-
|
|
4
|
+
import sys
|
|
5
5
|
from urllib3.exceptions import InsecureRequestWarning
|
|
6
6
|
import urllib3
|
|
7
7
|
|
|
8
8
|
urllib3.disable_warnings(InsecureRequestWarning)
|
|
9
9
|
|
|
10
|
+
WIN32_AVAILABLE = False
|
|
11
|
+
if sys.platform == "win32":
|
|
12
|
+
try:
|
|
13
|
+
from zhmiscellany._misc_supportfuncs import patch_rhg
|
|
14
|
+
from random_header_generator import HeaderGenerator
|
|
15
|
+
WIN32_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
print("Warning: random_header_generator not available, Windows-specific features disabled")
|
|
18
|
+
|
|
10
19
|
|
|
11
20
|
def resolve_file(url, destination_folder="."):
|
|
12
21
|
file_name = urllib.parse.unquote(url.split("/")[-1])
|
|
@@ -45,13 +54,12 @@ def download_file(url, destination_folder=".", just_return_path=False, headers=N
|
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
def generate_headers(url):
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
headers[k] = v
|
|
57
|
+
headers = {}
|
|
58
|
+
|
|
59
|
+
if WIN32_AVAILABLE:
|
|
60
|
+
generator = HeaderGenerator()
|
|
61
|
+
for k, v in generator().items():
|
|
62
|
+
headers[k] = v
|
|
55
63
|
|
|
56
64
|
headers['Referer'] = url
|
|
57
65
|
headers['Host'] = urllib.parse.urlparse(url).netloc
|
zhmiscellany/pipes.py
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import threading
|
|
2
2
|
import queue
|
|
3
3
|
import time
|
|
4
|
-
|
|
5
|
-
import win32pipe
|
|
6
|
-
import win32file
|
|
4
|
+
import sys
|
|
7
5
|
import zhmiscellany.string
|
|
8
6
|
|
|
7
|
+
WIN32_AVAILABLE = False
|
|
8
|
+
if sys.platform == "win32":
|
|
9
|
+
try:
|
|
10
|
+
import win32pipe
|
|
11
|
+
import win32file
|
|
12
|
+
WIN32_AVAILABLE = True
|
|
13
|
+
except ImportError:
|
|
14
|
+
print("Warning: pywin32 not available, Windows pipe features disabled")
|
|
15
|
+
|
|
9
16
|
|
|
10
17
|
class PipeTransmitter:
|
|
11
18
|
def __init__(self, pipe_name, close_pipes=False):
|
|
19
|
+
if not WIN32_AVAILABLE:
|
|
20
|
+
print("PipeTransmitter only supports Windows!")
|
|
21
|
+
return
|
|
22
|
+
|
|
12
23
|
self.pipe_name = r'\\.\pipe'+'\\'+pipe_name
|
|
13
24
|
self.close_pipes = close_pipes
|
|
14
25
|
self.send_queue = queue.Queue()
|
|
@@ -18,6 +29,9 @@ class PipeTransmitter:
|
|
|
18
29
|
self.send_thread.start()
|
|
19
30
|
|
|
20
31
|
def send_data_thread(self):
|
|
32
|
+
if not WIN32_AVAILABLE:
|
|
33
|
+
return
|
|
34
|
+
|
|
21
35
|
pipe_handle = win32pipe.CreateNamedPipe(
|
|
22
36
|
self.pipe_name,
|
|
23
37
|
win32pipe.PIPE_ACCESS_OUTBOUND,
|
|
@@ -43,6 +57,10 @@ class PipeTransmitter:
|
|
|
43
57
|
|
|
44
58
|
class PipeReceiver:
|
|
45
59
|
def __init__(self, pipe_name):
|
|
60
|
+
if not WIN32_AVAILABLE:
|
|
61
|
+
print("PipeReceiver only supports Windows!")
|
|
62
|
+
return
|
|
63
|
+
|
|
46
64
|
self.pipe_name = r'\\.\pipe'+'\\'+pipe_name
|
|
47
65
|
self.receive_queue = queue.Queue()
|
|
48
66
|
self.callback_function = None
|
|
@@ -52,6 +70,9 @@ class PipeReceiver:
|
|
|
52
70
|
self.receive_thread.start()
|
|
53
71
|
|
|
54
72
|
def receive_data_thread(self):
|
|
73
|
+
if not WIN32_AVAILABLE:
|
|
74
|
+
return
|
|
75
|
+
|
|
55
76
|
pipe_handle = win32file.CreateFile(
|
|
56
77
|
self.pipe_name,
|
|
57
78
|
win32file.GENERIC_READ,
|
|
@@ -79,6 +100,10 @@ class PipeReceiver:
|
|
|
79
100
|
|
|
80
101
|
|
|
81
102
|
def raw_receive_data(pipe_name):
|
|
103
|
+
if not WIN32_AVAILABLE:
|
|
104
|
+
print("raw_receive_data only supports Windows! Returning None")
|
|
105
|
+
return None
|
|
106
|
+
|
|
82
107
|
try:
|
|
83
108
|
pipe_name = r'\\.\pipe' + '\\' + pipe_name
|
|
84
109
|
pipe_handle = win32file.CreateFile(
|
|
@@ -96,6 +121,10 @@ def raw_receive_data(pipe_name):
|
|
|
96
121
|
|
|
97
122
|
|
|
98
123
|
def raw_send_data(data, pipe_name):
|
|
124
|
+
if not WIN32_AVAILABLE:
|
|
125
|
+
print("raw_send_data only supports Windows!")
|
|
126
|
+
return
|
|
127
|
+
|
|
99
128
|
def _raw_send_data(data, pipe_name):
|
|
100
129
|
sent = False
|
|
101
130
|
while not sent:
|
|
@@ -117,4 +146,4 @@ def raw_send_data(data, pipe_name):
|
|
|
117
146
|
except:
|
|
118
147
|
time.sleep(1)
|
|
119
148
|
|
|
120
|
-
threading.Thread(target=_raw_send_data, args=(data, pipe_name)).start()
|
|
149
|
+
threading.Thread(target=_raw_send_data, args=(data, pipe_name)).start()
|
zhmiscellany/processing.py
CHANGED
|
@@ -279,4 +279,19 @@ if __name__=="__main__":
|
|
|
279
279
|
yield var[1]
|
|
280
280
|
proc.stdout.close()
|
|
281
281
|
proc.wait()
|
|
282
|
-
cleanup()
|
|
282
|
+
cleanup()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class thread_join_return(threading.Thread):
|
|
286
|
+
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
|
287
|
+
super().__init__(group, target, name, args, kwargs)
|
|
288
|
+
self._return = None
|
|
289
|
+
|
|
290
|
+
def run(self):
|
|
291
|
+
if self._target is not None:
|
|
292
|
+
self._return = self._target(*self._args, **self._kwargs)
|
|
293
|
+
|
|
294
|
+
# A custom 'join' that also returns the value
|
|
295
|
+
def join(self, *args):
|
|
296
|
+
super().join(*args)
|
|
297
|
+
return self._return
|