zhmiscellany 5.8.6__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.
@@ -1,4 +1,8 @@
1
+ import sys
2
+
1
3
  def is_junction(entry):
4
+ if sys.platform != "win32":
5
+ return False
2
6
  try:
3
7
  st = entry.stat(follow_symlinks=False)
4
8
  # On Windows, st_file_attributes is available.
@@ -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 win32api, math
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
- patch_rhg()
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
- class INPUT(Structure):
100
- _fields_ = [("type", c_int),
101
- ("union", INPUT_UNION)]
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
- # Constants
105
- MOUSEEVENTF_ABSOLUTE = 0x8000
106
- MOUSEEVENTF_MOVE = 0x0001
107
- MOUSEEVENTF_LEFTDOWN = 0x0002
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
- # Screen metrics
158
+
159
+ # --- Screen Metrics ---
115
160
  def get_actual_screen_resolution():
116
- hdc = ctypes.windll.user32.GetDC(0)
117
- width = ctypes.windll.gdi32.GetDeviceCaps(hdc, 118) # HORZRES
118
- height = ctypes.windll.gdi32.GetDeviceCaps(hdc, 117) # VERTRES
119
- ctypes.windll.user32.ReleaseDC(0, hdc)
120
- return width, height
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 not relative:
129
- # Convert coordinates to normalized coordinates (0-65535)
130
- normalized_x = int(x * (65535 / SCREEN_WIDTH))
131
- normalized_y = int(y * (65535 / SCREEN_HEIGHT))
132
- else:
133
- calibrate()
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
- normalized_x = x
139
- normalized_y = y
140
-
141
- if not relative:
142
- dwflags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE
143
- else:
144
- dwflags = MOUSEEVENTF_MOVE
145
-
146
- input_struct = INPUT(
147
- type=0, # INPUT_MOUSE
148
- union=INPUT_UNION(
149
- mi=MOUSEINPUT(
150
- dx=normalized_x,
151
- dy=normalized_y,
152
- mouseData=0,
153
- dwFlags=dwflags,
154
- time=0,
155
- dwExtraInfo=None
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
- x, y = win32api.GetCursorPos()
164
- return x, y
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
- move_mouse(0,0)
177
- move_mouse(calibration_distance, calibration_distance, relative=True)
178
- moved_pos = get_mouse_xy()
179
- calibration_multiplier_x = calibration_distance/moved_pos[0]
180
- calibration_multiplier_y = calibration_distance/moved_pos[1]
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]+1, original_mouse_point[1]+1)
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
- flags = {
191
- 1: MOUSEEVENTF_LEFTDOWN,
192
- 2: MOUSEEVENTF_RIGHTDOWN,
193
- 3: MOUSEEVENTF_MIDDLEDOWN
194
- }
195
-
196
- input_struct = INPUT(
197
- type=0, # INPUT_MOUSE
198
- union=INPUT_UNION(
199
- mi=MOUSEINPUT(
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
- ctypes.windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
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
- flags = {
219
- 1: MOUSEEVENTF_LEFTUP,
220
- 2: MOUSEEVENTF_RIGHTUP,
221
- 3: MOUSEEVENTF_MIDDLEUP
222
- }
223
-
224
- input_struct = INPUT(
225
- type=0, # INPUT_MOUSE
226
- union=INPUT_UNION(
227
- mi=MOUSEINPUT(
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
- ctypes.windll.user32.SendInput(1, ctypes.byref(input_struct), sizeof(INPUT))
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
- import win32crypt
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 Crypto.Cipher.AES.new(win32crypt.CryptUnprotectData(master_key, None, None, None, 0)[1], Crypto.Cipher.AES.MODE_GCM, buff[3:15]).decrypt(buff[15:])[:-16].decode()
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 ctypes
4
- from ctypes import wintypes
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
zhmiscellany/macro.py CHANGED
@@ -1,16 +1,26 @@
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
@@ -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)
@@ -419,27 +471,29 @@ def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
419
471
  "m = zhmiscellany.macro.click_pixel",
420
472
  "k = zhmiscellany.macro.press_key_directinput",
421
473
  "s = zhmiscellany.macro.scroll",
422
- "",
474
+ "sleep = zhmiscellany.misc.high_precision_sleep",
423
475
  "click_down_time = 1/30",
424
476
  "click_release_time = 1/30",
425
477
  "mouse_move_dly = 1/60",
426
478
  "key_down_time = 1/30",
427
479
  "scroll_dly = 1/30",
480
+ "post_scroll_dly = 1/10",
481
+ "pre_click_duration = 1/30",
428
482
  "",
429
483
  "pre_click_wiggle = True",
430
484
  "",
431
485
  "animation_time = 0.1",
432
486
  "",
433
487
  'print("Replaying actions in 3 seconds...")',
434
- "zhmiscellany.misc.high_precision_sleep(3)",
488
+ "sleep(3)",
435
489
  ""
436
490
  ]
437
491
 
438
492
  last_time = start_time
439
- skip_next = False
493
+ skip_next = 0
440
494
  for i, event in enumerate(events):
441
495
  if skip_next:
442
- skip_next = False
496
+ skip_next -= 1
443
497
  continue
444
498
  current_time = event['time']
445
499
  try:
@@ -476,17 +530,31 @@ def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
476
530
 
477
531
  if next_event and next_event['action'] == 'click' and (next_event['x'], next_event['y']) == (x, y):
478
532
  action_str = ''
479
- skip_next = True
533
+ skip_next = 1
480
534
 
481
- 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)")
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)")
482
536
 
483
537
  elif action == 'move':
484
538
  x, y = event['x'], event['y']
485
539
  code_lines.append(f"m(({x}, {y}), click_end_duration=mouse_move_dly)")
486
540
 
487
541
  elif action == 'scroll':
488
- dx, dy = event['dx'], event['dy']
489
- code_lines.append(f"s({dy}, scroll_dly)")
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)")
490
558
 
491
559
  elif action in ('key_press', 'key_release'):
492
560
  key = event['key']
@@ -513,6 +581,9 @@ def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
513
581
  action_str = value
514
582
  break
515
583
 
584
+ if key_str == STOP_KEY:
585
+ break
586
+
516
587
  code_lines.append(f"k('{key_str}', {action_str}key_hold_time=key_down_time)")
517
588
 
518
589
  last_time = current_time
@@ -546,16 +617,19 @@ def record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9'):
546
617
  events.append({'action': 'click', 'x': x, 'y': y, 'button': button, 'pressed': pressed, 'time': time.time()})
547
618
 
548
619
  def on_scroll(x, y, dx, dy):
549
- # pynput scroll listener reports the mouse position at time of scroll, which we don't need for replay
550
- events.append({'action': 'scroll', 'dx': dx, 'dy': dy, 'time': time.time()})
620
+ events.append({'action': 'scroll', 'x': x, 'y': y, 'dx': dx, 'dy': dy, 'time': time.time()})
551
621
 
552
622
  def on_press(key):
553
623
  events.append({'action': 'key_press', 'key': key, 'time': time.time()})
554
624
 
555
625
  def on_release(key):
556
- if key.name.lower() == STOP_KEY:
557
- # Stop listeners
558
- return False
626
+ try:
627
+ print(key.name)
628
+ if key.name == STOP_KEY:
629
+ # Stop listeners
630
+ return False
631
+ except:
632
+ pass
559
633
  events.append({'action': 'key_release', 'key': key, 'time': time.time()})
560
634
 
561
635
  print(f"Press '{STOP_KEY}' to stop recording and generate the script.")
@@ -720,5 +794,7 @@ KEY_CODES = {
720
794
  'StartMailKey': 180,
721
795
  'SelectMedia': 181,
722
796
  'StartApplication1': 182,
723
- 'StartApplication2': 183
797
+ 'StartApplication2': 183,
798
+ 'Equals': 187,
799
+ 'Minus': 189,
724
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zhmiscellany
3
- Version: 5.8.6
3
+ Version: 6.0.9
4
4
  Summary: A collection of useful/interesting python libraries made by zh.
5
5
  Home-page: https://discord.gg/ThBBAuueVJ
6
6
  Author: zh
@@ -10,19 +10,20 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Operating System :: Microsoft :: Windows
11
11
  Requires-Python: >=3.6
12
12
  Description-Content-Type: text/markdown
13
- Requires-Dist: ray>=0
14
13
  Requires-Dist: pycryptodome>=0
15
- Requires-Dist: pywin32>=0
16
14
  Requires-Dist: discum==1.1.0
17
15
  Requires-Dist: requests>=0
18
- Requires-Dist: random-header-generator>=0
19
16
  Requires-Dist: dill>=0
20
17
  Requires-Dist: numpy>=0
21
18
  Requires-Dist: keyboard>=0
22
19
  Requires-Dist: psutil>=0
23
20
  Requires-Dist: kthread>=0
24
- Requires-Dist: pydirectinput>=0
25
21
  Requires-Dist: pillow>=0
22
+ Requires-Dist: fuzzywuzzy>=0
23
+ Requires-Dist: ray>=0; sys_platform == "win32"
24
+ Requires-Dist: pywin32>=0; sys_platform == "win32"
25
+ Requires-Dist: random-header-generator>=0; sys_platform == "win32"
26
+ Requires-Dist: pydirectinput>=0; sys_platform == "win32"
26
27
 
27
28
  `zhmiscellany`,
28
29
  =
@@ -41,7 +42,7 @@ Introduction
41
42
 
42
43
  Can be installed with `pip install zhmiscellany`
43
44
 
44
- Currently, the package stands at 141 functions/classes/bindings across 15 modules.
45
+ Currently, the package stands at 148 functions/classes/bindings across 15 modules.
45
46
 
46
47
  The git repository for this package can be found [here](https://github.com/zen-ham/zhmiscellany). The docs also look nicer on github.
47
48
 
@@ -1348,6 +1349,15 @@ Very efficiently deduplicates an iterable such as a list and returns a list.
1348
1349
 
1349
1350
  #
1350
1351
 
1352
+ `zhmiscellany.processing.thread_join_return()`
1353
+ ---
1354
+
1355
+ `zhmiscellany.processing.thread_join_return()`
1356
+
1357
+ Just a thread, but it returns whatever value was returned by the function inside it when .join() is called so you can use the data.
1358
+
1359
+ #
1360
+
1351
1361
  ---
1352
1362
  `zhmiscellany.string`
1353
1363
  ---
@@ -1527,6 +1537,24 @@ keyboard.wait() requires a clean press of the specified key, and concurrent key
1527
1537
 
1528
1538
  #
1529
1539
 
1540
+ `zhmiscellany.macro.record_actions_to_code()`
1541
+ ---
1542
+
1543
+ `zhmiscellany.macro.record_actions_to_code(RECORD_MOUSE_MOVEMENT=False, STOP_KEY='f9')`
1544
+
1545
+ Records keyboard and mouse events and generates zhmiscellany.macro code to emulate them, I'm so tired..
1546
+
1547
+ #
1548
+
1549
+ `zhmiscellany.macro.is_key_pressed_async()`
1550
+ ---
1551
+
1552
+ `zhmiscellany.macro.is_key_pressed_async(vk_code)`
1553
+
1554
+ See if a key is pressed.
1555
+
1556
+ #
1557
+
1530
1558
  ---
1531
1559
  `zhmiscellany.cpp`
1532
1560
  ---
@@ -1634,3 +1662,39 @@ Sends data to a named pipe asynchronously using a background thread.
1634
1662
  A lightweight Python class that displays a customizable, semi-transparent, click-through colored rectangle overlay on the screen for visual status indication, such as if you had a complex macro/automation script and wanted to be able to see what step it's currently on.
1635
1663
 
1636
1664
  #
1665
+
1666
+ `zhmiscellany.gui.get_focused_window()`
1667
+ ---
1668
+
1669
+ `zhmiscellany.gui.get_focused_window()`
1670
+
1671
+ Return the window that currently has the keyboard focus.
1672
+
1673
+ #
1674
+
1675
+ `zhmiscellany.gui.get_window_rect()`
1676
+ ---
1677
+
1678
+ `zhmiscellany.gui.get_window_rect(hwnd)`
1679
+
1680
+ Return the bounding rectangle of a window.
1681
+
1682
+ #
1683
+
1684
+ `zhmiscellany.gui.set_window_pos()`
1685
+ ---
1686
+
1687
+ `zhmiscellany.gui.set_window_pos(hwnd, x: int, y: int, w: int, h: int)`
1688
+
1689
+ Move (and optionally resize) a window.
1690
+
1691
+ #
1692
+
1693
+ `zhmiscellany.gui.find_window_by_title_fuzzy()`
1694
+ ---
1695
+
1696
+ `zhmiscellany.gui.find_window_by_title_fuzzy(title_query, threshold=70)`
1697
+
1698
+ Find a window HWND by rough name.
1699
+
1700
+ #
@@ -1,27 +1,27 @@
1
1
  zhmiscellany/__init__.py,sha256=Oh3gAJRWq2aUEgkolmQyiZOQ3iey5OC4NA2XaTHuVv4,1139
2
2
  zhmiscellany/_discord_supportfuncs.py,sha256=RotSpurqFtL5q6v_ST1e2Y_1qJMaHp1CDsF5i-gR4ec,6524
3
- zhmiscellany/_fileio_supportfuncs.py,sha256=OIboEBVahqbpG1wbtrhAjoFV-oPylFVmOENOByawaX0,365
4
- zhmiscellany/_misc_supportfuncs.py,sha256=y5hlFjenJTrEPjR9VH0F7X9JhUWSV-Gx-fahNzyMMU8,7003
5
- zhmiscellany/_processing_supportfuncs.py,sha256=aZKWIfJW_lVaQs_EyGGBhmoMc6btA38X2hY6A3kTxN8,10341
3
+ zhmiscellany/_fileio_supportfuncs.py,sha256=soibLv9nOR79sHQ2MGQH2O6MhnqdXqI7ybxHSN2Tfac,434
4
+ zhmiscellany/_misc_supportfuncs.py,sha256=zD7LjbRCUC3wMuCS90jum8m_mNw1ZmW7ejPl7IZ9Nbg,11486
5
+ zhmiscellany/_processing_supportfuncs.py,sha256=u5dzW-2OtyViC74r5fhqd5_2uQ92ZhieRJRTJMevqbg,11071
6
6
  zhmiscellany/_py_resources.py,sha256=HqJs5aRLymLZ2J5Io8g6bY58pGWhN9b8vCktC2DW4KQ,229009
7
7
  zhmiscellany/_resource_files_lookup.py,sha256=hsgPW0dngokgqWrAVAv5rUo-eobzJPjvWHt4xrOG5l0,856
8
8
  zhmiscellany/cpp.py,sha256=XEUEoIKCDCdY5VgwNWE5oXrGjtsmGdz_MnaVwQmi2dk,179
9
9
  zhmiscellany/dict.py,sha256=0BZJ5eK-MurAHYV1OPa0jdGTr-QEWos7ZM0npb-tN9I,81
10
- zhmiscellany/discord.py,sha256=bq4fBBIykQrwBKWeJ_Ia5ZtMjP9RfRheOa7fyWZBNjo,19338
10
+ zhmiscellany/discord.py,sha256=nzXjRnJbuYbuH4hScqqdsF61VeXx8xiYrwp_AKTWt9o,19774
11
11
  zhmiscellany/fileio.py,sha256=KmRnWWZJWBc5Or3_mKcRY2ehE_Fuk870iS-imtyERyg,18169
12
- zhmiscellany/gui.py,sha256=VgT5j5UIr-wWyL2P1qCHSNL7RrvZWDlm0TX00JGWmKo,7404
12
+ zhmiscellany/gui.py,sha256=8aOd4raEmaQJOOhr1CeVriYIOvy-Bw2FcoZMpKKlBng,10158
13
13
  zhmiscellany/image.py,sha256=qUjxiYpc2VVZp2vwr1vN36O2PVQ7YlMKzhegQ1u4c0M,8198
14
14
  zhmiscellany/list.py,sha256=S8Z85bLJEP9lk2JkGpzUcG6kpRB7a-NWDIHMPiF5bKo,1473
15
- zhmiscellany/macro.py,sha256=Vcm4AEKHdSMYj9FstdcGDMZGLYPnkOhAkgCacG52eMM,25206
15
+ zhmiscellany/macro.py,sha256=kKtKYoHLSnBBHyGl8FlnfMaAoraKKSK023VA-mOezO0,27861
16
16
  zhmiscellany/math.py,sha256=btOQTe_GvqP0A7Zz84tmN_c8j1NGe_mKnhmAt40lhLU,2482
17
- zhmiscellany/misc.py,sha256=BsTbRWlXI5LZBG7Bl2MgLzHESyCMJnr_KNZAf2wY_H4,29689
18
- zhmiscellany/netio.py,sha256=GTamo5cJn4P6u8V_kgZ9kL8qeMUE7OQAmYkmj9Sp_GA,2236
17
+ zhmiscellany/misc.py,sha256=i4ZZ4ivfSfzjqXPe0etrK9l9zXk-TK6I9HRtts63YHc,30482
18
+ zhmiscellany/netio.py,sha256=VCqlo3ev_iOTTu5sXTZQwsYU0WMbAlOTDRsJ5Dj5-fc,2519
19
19
  zhmiscellany/pastebin.py,sha256=TbZ3DqFYXo5qt5d95ugrofYoptlzKkjXUr7VnEqa6ks,6357
20
- zhmiscellany/pipes.py,sha256=PxO4aykFzC60xbTQuc66KzZYIxiW0KPebXZbncD2HcU,3795
21
- zhmiscellany/processing.py,sha256=srwlV8FZ--svF5e6rZZxhIs_6ZjOAwq2OcQEf6T2Le8,10410
20
+ zhmiscellany/pipes.py,sha256=zETvWP4PF-PuSzYwR1UCodY4ftNAgmCChb9DUMofXok,4657
21
+ zhmiscellany/processing.py,sha256=sDKIbzG9TNFyT6yJ4TJL59taG-59_v3CBLekVSLrwgE,10899
22
22
  zhmiscellany/rust.py,sha256=znN6DYNoa_p-braTuDZKvUnXX8reWLFu_dG4fv2vLR0,442
23
23
  zhmiscellany/string.py,sha256=xyqE6V5YF2nieZDcg5ZrXTIrH2D9oDRbZ5vQGz8rPys,4787
24
- zhmiscellany-5.8.6.dist-info/METADATA,sha256=EtSCgK1v6hzA4_ocYnR4-0JPb4lAuaXERt5UEExGyTQ,42153
25
- zhmiscellany-5.8.6.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
26
- zhmiscellany-5.8.6.dist-info/top_level.txt,sha256=ioDtsrevCI52rTxZntMPljRIBsZs73tD0hI00HektiE,13
27
- zhmiscellany-5.8.6.dist-info/RECORD,,
24
+ zhmiscellany-6.0.9.dist-info/METADATA,sha256=6XEuRXVoVBvCorsRSFU8Rm2ZVr81aYO8jVn8ZIAnwGY,43560
25
+ zhmiscellany-6.0.9.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
26
+ zhmiscellany-6.0.9.dist-info/top_level.txt,sha256=ioDtsrevCI52rTxZntMPljRIBsZs73tD0hI00HektiE,13
27
+ zhmiscellany-6.0.9.dist-info/RECORD,,