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/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
- import keyboard, kthread
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=False, click=True):
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(x):
32
- floor = int(x)
33
- return floor + (random.random() < (x - floor))
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 ctrl:
103
- win32api.keybd_event(win32con.VK_CONTROL, 0, 0, 0)
104
- keys_down.append(win32con.VK_CONTROL)
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
- if shift:
107
- win32api.keybd_event(win32con.VK_SHIFT, 0, 0, 0)
108
- keys_down.append(win32con.VK_SHIFT)
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
- for i in range(limit):
133
- cur_pos = get_mouse_xy()
134
- if cur_pos != targ:
135
- move_mouse(max(-1, min(1, (targ[0]-cur_pos[0]))), max(-1, min(1, (targ[1]-cur_pos[1]))), relative=relative)
136
- else:
137
- break
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
- for key in keys_down:
170
- win32api.keybd_event(key, 0, win32con.KEYEVENTF_KEYUP, 0)
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 scroll(amount, delay=None):
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(amount)
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
- import win32gui, win32con, win32process
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
- ctypes.windll.dwmapi.DwmFlush()
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
- from zhmiscellany._misc_supportfuncs import patch_rhg
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
- from random_header_generator import HeaderGenerator
49
- generator = HeaderGenerator()
50
- headers = {
51
- }
52
-
53
- for k, v in generator().items():
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()
@@ -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