zhmiscellany 5.8.0__py3-none-any.whl → 5.8.2__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/macro.py +214 -9
- {zhmiscellany-5.8.0.dist-info → zhmiscellany-5.8.2.dist-info}/METADATA +1 -1
- {zhmiscellany-5.8.0.dist-info → zhmiscellany-5.8.2.dist-info}/RECORD +5 -5
- {zhmiscellany-5.8.0.dist-info → zhmiscellany-5.8.2.dist-info}/WHEEL +0 -0
- {zhmiscellany-5.8.0.dist-info → zhmiscellany-5.8.2.dist-info}/top_level.txt +0 -0
zhmiscellany/macro.py
CHANGED
|
@@ -16,7 +16,7 @@ import time
|
|
|
16
16
|
get_mouse_xy = get_mouse_xy
|
|
17
17
|
|
|
18
18
|
|
|
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=True,
|
|
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=True, click=True):
|
|
20
20
|
if not click:
|
|
21
21
|
act_start=False;act_end=False
|
|
22
22
|
if right_click and middle_click:
|
|
@@ -169,14 +169,6 @@ def click_pixel(x=None, y=None, click_duration=None, right_click=False, middle_c
|
|
|
169
169
|
for key in keys_down:
|
|
170
170
|
win32api.keybd_event(key, 0, win32con.KEYEVENTF_KEYUP, 0)
|
|
171
171
|
|
|
172
|
-
if post_click_duration:
|
|
173
|
-
if post_click_wiggle:
|
|
174
|
-
num_wiggle = round(animation_fps * post_click_duration)
|
|
175
|
-
for i in range(num_wiggle):
|
|
176
|
-
click_pixel(cx+((random.randint(0, 1)*2)-1), cy+((random.randint(0, 1)*2)-1), act_start=False, act_end=False, click_end_duration=1 / animation_fps)
|
|
177
|
-
else:
|
|
178
|
-
zhmiscellany.misc.high_precision_sleep(post_click_duration)
|
|
179
|
-
|
|
180
172
|
if click_end_duration:
|
|
181
173
|
zhmiscellany.misc.high_precision_sleep(click_end_duration)
|
|
182
174
|
|
|
@@ -364,6 +356,219 @@ def toggle_function(func, key='f8', blocking=True):
|
|
|
364
356
|
return t
|
|
365
357
|
|
|
366
358
|
|
|
359
|
+
def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
|
|
360
|
+
import time
|
|
361
|
+
import pynput
|
|
362
|
+
import pyperclip
|
|
363
|
+
|
|
364
|
+
# --- Configuration ---
|
|
365
|
+
# Set to True to record every single mouse movement.
|
|
366
|
+
# Set to False to only record mouse position on clicks and drags.
|
|
367
|
+
|
|
368
|
+
# --- Global State ---
|
|
369
|
+
events = []
|
|
370
|
+
start_time = None
|
|
371
|
+
|
|
372
|
+
# --- Helper Functions ---
|
|
373
|
+
def format_key(key):
|
|
374
|
+
"""
|
|
375
|
+
Formats the pynput key object into a string for the generated script.
|
|
376
|
+
Handles cases where modifier keys (like Ctrl) are held down, which can
|
|
377
|
+
prevent the `key.char` attribute from being populated correctly.
|
|
378
|
+
"""
|
|
379
|
+
if isinstance(key, pynput.keyboard.Key):
|
|
380
|
+
# Special keys (e.g., Key.shift, Key.ctrl)
|
|
381
|
+
return f"Key.{key.name}"
|
|
382
|
+
|
|
383
|
+
if isinstance(key, pynput.keyboard.KeyCode):
|
|
384
|
+
# This is the robust way to handle alphanumeric keys, especially with modifiers.
|
|
385
|
+
# key.char can be None or a control character when Ctrl/Alt are held.
|
|
386
|
+
# key.vk is the virtual key code, which is more reliable.
|
|
387
|
+
|
|
388
|
+
# Heuristic for A-Z keys (VK codes on Windows/Linux are often in this range)
|
|
389
|
+
if 65 <= getattr(key, 'vk', 0) <= 90:
|
|
390
|
+
return f"'{chr(key.vk).lower()}'"
|
|
391
|
+
|
|
392
|
+
# For other keys, try to use the char if it exists
|
|
393
|
+
if key.char:
|
|
394
|
+
bs = '\\'
|
|
395
|
+
return f"'{key.char.replace(bs, bs + bs + bs)}'"
|
|
396
|
+
|
|
397
|
+
# As a fallback for other keys without a char, use the vk
|
|
398
|
+
if hasattr(key, 'vk'):
|
|
399
|
+
return f"pynput.keyboard.KeyCode.from_vk({key.vk})"
|
|
400
|
+
|
|
401
|
+
# If it's some other type of key object (less common)
|
|
402
|
+
return str(key)
|
|
403
|
+
|
|
404
|
+
def generate_code():
|
|
405
|
+
"""
|
|
406
|
+
Generates the Python script from the recorded events and copies it to the clipboard.
|
|
407
|
+
"""
|
|
408
|
+
global events, start_time
|
|
409
|
+
|
|
410
|
+
if not events:
|
|
411
|
+
print("No actions were recorded.")
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
# Code preamble
|
|
415
|
+
code_lines = [
|
|
416
|
+
"import zhmiscellany",
|
|
417
|
+
"",
|
|
418
|
+
"zhmiscellany.misc.die_on_key('f9')",
|
|
419
|
+
"",
|
|
420
|
+
"m = zhmiscellany.macro.click_pixel",
|
|
421
|
+
"k = zhmiscellany.macro.press_key_directinput",
|
|
422
|
+
"s = zhmiscellany.macro.scroll",
|
|
423
|
+
"",
|
|
424
|
+
"click_down_time = 1/30",
|
|
425
|
+
"click_release_time = 1/30",
|
|
426
|
+
"mouse_move_dly = 1/60",
|
|
427
|
+
"key_down_time = 1/30",
|
|
428
|
+
"scroll_dly = 1/30",
|
|
429
|
+
"",
|
|
430
|
+
"pre_click_wiggle = True",
|
|
431
|
+
"",
|
|
432
|
+
"animation_time = 1",
|
|
433
|
+
"",
|
|
434
|
+
'print("Replaying actions in 3 seconds...")',
|
|
435
|
+
"zhmiscellany.misc.high_precision_sleep(3)",
|
|
436
|
+
""
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
last_time = start_time
|
|
440
|
+
for event in events:
|
|
441
|
+
current_time = event['time']
|
|
442
|
+
delay = current_time - last_time
|
|
443
|
+
|
|
444
|
+
action = event['action']
|
|
445
|
+
|
|
446
|
+
if action == 'click':
|
|
447
|
+
x, y, button, pressed = event['x'], event['y'], event['button'], event['pressed']
|
|
448
|
+
button_str = f"Button.{button.name}"
|
|
449
|
+
action_str = "press" if pressed else "release"
|
|
450
|
+
|
|
451
|
+
replacements = {
|
|
452
|
+
'right': 'right_click=True, ',
|
|
453
|
+
'middle': 'middle_click=True, ',
|
|
454
|
+
'left': '',
|
|
455
|
+
}
|
|
456
|
+
for key, value in replacements.items():
|
|
457
|
+
if key in button_str:
|
|
458
|
+
button_str = value
|
|
459
|
+
break
|
|
460
|
+
|
|
461
|
+
replacements = {
|
|
462
|
+
'press': 'act_end=False, ',
|
|
463
|
+
'release': 'act_start=False, ',
|
|
464
|
+
}
|
|
465
|
+
for key, value in replacements.items():
|
|
466
|
+
if key in action_str:
|
|
467
|
+
action_str = value
|
|
468
|
+
break
|
|
469
|
+
|
|
470
|
+
code_lines.append(f"m(({x}, {y}), {button_str}{action_str}click_duration=click_down_time, click_end_duration=click_release_time, pre_click_wiggle=pre_click_wiggle, animation_time=animation_time)")
|
|
471
|
+
|
|
472
|
+
elif action == 'move':
|
|
473
|
+
x, y = event['x'], event['y']
|
|
474
|
+
code_lines.append(f"m(({x}, {y}), click_end_duration=mouse_move_dly)")
|
|
475
|
+
|
|
476
|
+
elif action == 'scroll':
|
|
477
|
+
dx, dy = event['dx'], event['dy']
|
|
478
|
+
code_lines.append(f"s({dy}, scroll_dly)")
|
|
479
|
+
|
|
480
|
+
elif action in ('key_press', 'key_release'):
|
|
481
|
+
key = event['key']
|
|
482
|
+
key_str = format_key(key)
|
|
483
|
+
if '.' in key_str:
|
|
484
|
+
key_str = key_str.split('.')[1]
|
|
485
|
+
replacements = {
|
|
486
|
+
'ctrl': 'ctrl',
|
|
487
|
+
'shift': 'shift',
|
|
488
|
+
'alt': 'alt',
|
|
489
|
+
}
|
|
490
|
+
for key, value in replacements.items():
|
|
491
|
+
if key in key_str:
|
|
492
|
+
key_str = value
|
|
493
|
+
break
|
|
494
|
+
action_str = "press" if action == 'key_press' else "release"
|
|
495
|
+
|
|
496
|
+
replacements = {
|
|
497
|
+
'press': 'act_end=False, ',
|
|
498
|
+
'release': 'act_start=False, ',
|
|
499
|
+
}
|
|
500
|
+
for key, value in replacements.items():
|
|
501
|
+
if key in action_str:
|
|
502
|
+
action_str = value
|
|
503
|
+
break
|
|
504
|
+
|
|
505
|
+
code_lines.append(f"k('{key_str}', {action_str}key_hold_time=key_down_time)")
|
|
506
|
+
|
|
507
|
+
last_time = current_time
|
|
508
|
+
|
|
509
|
+
code_lines.append("\nprint('Replay finished.')")
|
|
510
|
+
|
|
511
|
+
# Join all lines into a single script
|
|
512
|
+
final_script = "\n".join(code_lines)
|
|
513
|
+
|
|
514
|
+
# Print and copy to clipboard
|
|
515
|
+
print("\n" + "=" * 50)
|
|
516
|
+
print(" RECORDING FINISHED - SCRIPT GENERATED")
|
|
517
|
+
print("=" * 50 + "\n")
|
|
518
|
+
print(final_script)
|
|
519
|
+
print("\n" + "=" * 50)
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
pyperclip.copy(final_script)
|
|
523
|
+
print("Script has been copied to your clipboard!")
|
|
524
|
+
except pyperclip.PyperclipException:
|
|
525
|
+
print("Could not copy to clipboard. Please install xclip or xsel on Linux.")
|
|
526
|
+
return final_script
|
|
527
|
+
|
|
528
|
+
# --- Pynput Listener Callbacks ---
|
|
529
|
+
|
|
530
|
+
def on_move(x, y):
|
|
531
|
+
if RECORD_MOUSE_MOVEMENT:
|
|
532
|
+
events.append({'action': 'move', 'x': x, 'y': y, 'time': time.time()})
|
|
533
|
+
|
|
534
|
+
def on_click(x, y, button, pressed):
|
|
535
|
+
events.append({'action': 'click', 'x': x, 'y': y, 'button': button, 'pressed': pressed, 'time': time.time()})
|
|
536
|
+
|
|
537
|
+
def on_scroll(x, y, dx, dy):
|
|
538
|
+
# pynput scroll listener reports the mouse position at time of scroll, which we don't need for replay
|
|
539
|
+
events.append({'action': 'scroll', 'dx': dx, 'dy': dy, 'time': time.time()})
|
|
540
|
+
|
|
541
|
+
def on_press(key):
|
|
542
|
+
events.append({'action': 'key_press', 'key': key, 'time': time.time()})
|
|
543
|
+
|
|
544
|
+
def on_release(key):
|
|
545
|
+
if key.name.lower() == STOP_KEY:
|
|
546
|
+
# Stop listeners
|
|
547
|
+
return False
|
|
548
|
+
events.append({'action': 'key_release', 'key': key, 'time': time.time()})
|
|
549
|
+
|
|
550
|
+
print(f"Press '{STOP_KEY}' to stop recording and generate the script.")
|
|
551
|
+
print("...")
|
|
552
|
+
|
|
553
|
+
start_time = time.time()
|
|
554
|
+
|
|
555
|
+
# Create and start listeners
|
|
556
|
+
mouse_listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll)
|
|
557
|
+
keyboard_listener = pynput.keyboard.Listener(on_press=on_press, on_release=on_release)
|
|
558
|
+
|
|
559
|
+
mouse_listener.start()
|
|
560
|
+
keyboard_listener.start()
|
|
561
|
+
|
|
562
|
+
# Wait for the keyboard listener to stop (on F9 press)
|
|
563
|
+
keyboard_listener.join()
|
|
564
|
+
|
|
565
|
+
# Stop the mouse listener explicitly once the keyboard one has finished
|
|
566
|
+
mouse_listener.stop()
|
|
567
|
+
|
|
568
|
+
# Generate the replay script
|
|
569
|
+
return generate_code()
|
|
570
|
+
|
|
571
|
+
|
|
367
572
|
KEY_CODES = {
|
|
368
573
|
'None': 0,
|
|
369
574
|
'LeftMouseButton': 1,
|
|
@@ -12,7 +12,7 @@ zhmiscellany/fileio.py,sha256=KmRnWWZJWBc5Or3_mKcRY2ehE_Fuk870iS-imtyERyg,18169
|
|
|
12
12
|
zhmiscellany/gui.py,sha256=VgT5j5UIr-wWyL2P1qCHSNL7RrvZWDlm0TX00JGWmKo,7404
|
|
13
13
|
zhmiscellany/image.py,sha256=qUjxiYpc2VVZp2vwr1vN36O2PVQ7YlMKzhegQ1u4c0M,8198
|
|
14
14
|
zhmiscellany/list.py,sha256=S8Z85bLJEP9lk2JkGpzUcG6kpRB7a-NWDIHMPiF5bKo,1473
|
|
15
|
-
zhmiscellany/macro.py,sha256=
|
|
15
|
+
zhmiscellany/macro.py,sha256=8Fwp0--03L6MMtgUYTGySTEwlw-DlJo00gKoWIXdREo,24764
|
|
16
16
|
zhmiscellany/math.py,sha256=btOQTe_GvqP0A7Zz84tmN_c8j1NGe_mKnhmAt40lhLU,2482
|
|
17
17
|
zhmiscellany/misc.py,sha256=BsTbRWlXI5LZBG7Bl2MgLzHESyCMJnr_KNZAf2wY_H4,29689
|
|
18
18
|
zhmiscellany/netio.py,sha256=GTamo5cJn4P6u8V_kgZ9kL8qeMUE7OQAmYkmj9Sp_GA,2236
|
|
@@ -21,7 +21,7 @@ zhmiscellany/pipes.py,sha256=PxO4aykFzC60xbTQuc66KzZYIxiW0KPebXZbncD2HcU,3795
|
|
|
21
21
|
zhmiscellany/processing.py,sha256=srwlV8FZ--svF5e6rZZxhIs_6ZjOAwq2OcQEf6T2Le8,10410
|
|
22
22
|
zhmiscellany/rust.py,sha256=znN6DYNoa_p-braTuDZKvUnXX8reWLFu_dG4fv2vLR0,442
|
|
23
23
|
zhmiscellany/string.py,sha256=xyqE6V5YF2nieZDcg5ZrXTIrH2D9oDRbZ5vQGz8rPys,4787
|
|
24
|
-
zhmiscellany-5.8.
|
|
25
|
-
zhmiscellany-5.8.
|
|
26
|
-
zhmiscellany-5.8.
|
|
27
|
-
zhmiscellany-5.8.
|
|
24
|
+
zhmiscellany-5.8.2.dist-info/METADATA,sha256=qCEULsjGKmFPsHfnyve1eay_nnitpjk7DFxOwZEg2yQ,42153
|
|
25
|
+
zhmiscellany-5.8.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
26
|
+
zhmiscellany-5.8.2.dist-info/top_level.txt,sha256=ioDtsrevCI52rTxZntMPljRIBsZs73tD0hI00HektiE,13
|
|
27
|
+
zhmiscellany-5.8.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|