screenoverlay 0.4.2__py3-none-any.whl → 0.6.0__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.
screenoverlay/__init__.py CHANGED
@@ -5,7 +5,7 @@ Provides blur, black, white, and custom color overlays with minimal latency
5
5
 
6
6
  from .overlay import NativeBlurOverlay as Overlay
7
7
 
8
- __version__ = '0.4.0'
8
+ __version__ = '0.6.0'
9
9
  __author__ = 'Pekay'
10
10
  __all__ = ['Overlay']
11
11
 
screenoverlay/overlay.py CHANGED
@@ -2,17 +2,14 @@
2
2
  """
3
3
  Native Blur Overlay - Uses OS-native blur effects
4
4
  No screen capture, no permissions needed, instant appearance
5
+
6
+ Single-process architecture using Tkinter's update() for non-blocking operation.
5
7
  """
6
8
 
7
9
  import tkinter as tk
8
10
  import platform
9
11
  import sys
10
12
  import os
11
- import threading
12
- from multiprocessing import Process, Queue
13
- import time
14
- import atexit
15
- import signal
16
13
 
17
14
  # Try to import screeninfo for multi-monitor support
18
15
  try:
@@ -65,136 +62,102 @@ class NativeBlurOverlay:
65
62
 
66
63
  self.root = None
67
64
  self.windows = [] # List to hold multiple windows for multi-monitor
68
- self._timer_id = None
69
- self._process = None
70
- self._command_queue = None
71
-
72
- # Register cleanup on exit to prevent orphaned processes
73
- atexit.register(self._cleanup_on_exit)
74
-
75
- def _cleanup_on_exit(self):
76
- """Cleanup overlay process on program exit"""
77
- if self._process is not None and self._process.is_alive():
78
- try:
79
- # Try graceful stop first
80
- if self._command_queue is not None:
81
- try:
82
- self._command_queue.put('stop')
83
- except:
84
- pass
85
-
86
- # Wait briefly
87
- self._process.join(timeout=0.5)
88
-
89
- # Force kill if still alive
90
- if self._process.is_alive():
91
- self._process.terminate()
92
- self._process.join(timeout=0.5)
93
-
94
- # Last resort - force kill
95
- if self._process.is_alive():
96
- self._process.kill()
97
- except:
98
- pass
65
+ self._is_visible = False
99
66
 
100
67
  def start(self):
101
68
  """
102
- Start the overlay process with show/hide control.
69
+ Initialize the overlay windows.
103
70
  Call this once at app startup.
104
71
 
105
- After calling start(), use show() and hide() to control visibility instantly.
72
+ After calling start(), use show() and hide() to control visibility instantly,
73
+ and call update() regularly in your main loop to keep the overlay responsive.
106
74
 
107
- Example for ScreenStop:
75
+ Example:
108
76
  overlay = Overlay(mode='blur', blur_strength=4)
109
77
  overlay.start() # Initialize (call once)
110
78
 
111
- overlay.show() # Show overlay (instant)
112
- time.sleep(2)
113
- overlay.hide() # Hide overlay (instant)
114
- overlay.show() # Show again
79
+ while True:
80
+ overlay.show() # Show overlay (instant)
81
+ time.sleep(2)
82
+ overlay.hide() # Hide overlay (instant)
83
+ overlay.update() # Keep overlay responsive (call regularly!)
115
84
 
116
85
  overlay.stop() # Cleanup when done
117
86
  """
118
- if self._process is not None:
119
- return
87
+ if self.root is not None:
88
+ return # Already started
120
89
 
121
- self._command_queue = Queue()
122
- self._process = Process(target=self._run_process, args=(self._command_queue,), daemon=True)
123
- self._process.start()
90
+ # Create windows for all monitors
91
+ self._create_windows()
124
92
 
125
- # Wait a bit for process to initialize
126
- time.sleep(0.3)
93
+ # Hide all windows initially
94
+ for win in self.windows:
95
+ win.withdraw()
96
+
97
+ self._is_visible = False
127
98
 
128
99
  def show(self):
129
- """Show the overlay (instant, ~1ms)"""
130
- if self._command_queue is not None:
131
- self._command_queue.put('show')
100
+ """Show the overlay (instant, <1ms)"""
101
+ if self.root is None:
102
+ # Auto-start if not started yet
103
+ self.start()
104
+
105
+ if not self._is_visible:
106
+ for win in self.windows:
107
+ try:
108
+ win.deiconify()
109
+ win.lift()
110
+ except Exception as e:
111
+ print(f"Warning: Failed to show window: {e}")
112
+ self._is_visible = True
132
113
 
133
114
  def hide(self):
134
- """Hide the overlay (instant, ~1ms)"""
135
- if self._command_queue is not None:
136
- self._command_queue.put('hide')
137
-
138
- def stop(self):
139
- """Stop and cleanup the overlay completely"""
140
- if self._command_queue is not None:
141
- self._command_queue.put('stop')
142
-
143
- if self._process is not None:
144
- self._process.join(timeout=2.0)
145
- if self._process.is_alive():
146
- self._process.terminate()
147
- self._process = None
115
+ """Hide the overlay (instant, <1ms)"""
116
+ if self.root is None:
117
+ return # Not started yet
148
118
 
149
- self._command_queue = None
150
-
151
- def _run_process(self, command_queue):
152
- """Run overlay in separate process with command queue"""
153
- try:
154
- # Create windows for all monitors
155
- self._create_windows()
156
-
157
- # Hide all windows initially
119
+ if self._is_visible:
158
120
  for win in self.windows:
159
- win.withdraw()
160
-
161
- # Process commands from queue
162
- def check_commands():
163
121
  try:
164
- while not command_queue.empty():
165
- cmd = command_queue.get_nowait()
166
- if cmd == 'show':
167
- for win in self.windows:
168
- try:
169
- win.deiconify()
170
- win.lift()
171
- except Exception as e:
172
- print(f"Warning: Failed to show window: {e}")
173
- elif cmd == 'hide':
174
- for win in self.windows:
175
- try:
176
- win.withdraw()
177
- except Exception as e:
178
- print(f"Warning: Failed to hide window: {e}")
179
- elif cmd == 'stop':
180
- self.root.quit()
181
- return
122
+ win.withdraw()
182
123
  except Exception as e:
183
- print(f"Warning: Command queue error: {e}")
184
-
185
- # Check again in 10ms
186
- self.root.after(10, check_commands)
187
-
188
- # Start command checker
189
- check_commands()
190
-
191
- # Run mainloop
192
- self.root.mainloop()
193
-
194
- except Exception as e:
195
- print(f"Overlay process error: {e}")
196
- finally:
197
- os._exit(0)
124
+ print(f"Warning: Failed to hide window: {e}")
125
+ self._is_visible = False
126
+
127
+ def update(self):
128
+ """
129
+ Keep overlay responsive - call this regularly in your main loop!
130
+
131
+ This processes Tkinter events and keeps the windows responsive.
132
+ Without calling this, the overlay will freeze.
133
+
134
+ Example:
135
+ while True:
136
+ detect_something()
137
+ if detected:
138
+ overlay.show()
139
+ else:
140
+ overlay.hide()
141
+ overlay.update() # ← Call this every loop iteration!
142
+ time.sleep(0.1)
143
+ """
144
+ if self.root is not None:
145
+ try:
146
+ self.root.update()
147
+ except Exception as e:
148
+ print(f"Warning: Update failed: {e}")
149
+
150
+ def stop(self):
151
+ """Stop and cleanup the overlay completely"""
152
+ if self.root is not None:
153
+ try:
154
+ self.root.quit()
155
+ self.root.destroy()
156
+ except:
157
+ pass
158
+ self.root = None
159
+ self.windows = []
160
+ self._is_visible = False
198
161
 
199
162
  def _get_monitors(self):
200
163
  """Get information about all monitors"""
@@ -256,52 +219,9 @@ class NativeBlurOverlay:
256
219
  if self.apply_blur:
257
220
  self._apply_native_blur_to_window(window)
258
221
 
259
- # Bind escape key to exit (only on primary window)
222
+ # Bind escape key to hide (only on primary window)
260
223
  if window == self.root:
261
- window.bind('<Escape>', lambda e: self.kill_completely())
262
- window.focus_set()
263
-
264
- def _create_window(self):
265
- """Internal method to create and configure the Tkinter window"""
266
- self.root = tk.Tk()
267
-
268
- # Remove window decorations
269
- self.root.overrideredirect(True)
270
- self.root.attributes('-topmost', True)
271
-
272
- # Set background color (tint)
273
- bg_color = f'#{self.color_tint[0]:02x}{self.color_tint[1]:02x}{self.color_tint[2]:02x}'
274
- self.root.configure(bg=bg_color)
275
-
276
- # Set opacity
277
- self.root.attributes('-alpha', self.opacity)
278
-
279
- # Full screen
280
- screen_width = self.root.winfo_screenwidth()
281
- screen_height = self.root.winfo_screenheight()
282
- self.root.geometry(f"{screen_width}x{screen_height}+0+0")
283
-
284
- # Apply native blur effect based on OS (only if mode is 'blur')
285
- if self.apply_blur:
286
- self._apply_native_blur()
287
-
288
- # Bind escape key to exit
289
- self.root.bind('<Escape>', lambda e: self.kill_completely())
290
- self.root.focus_set()
291
-
292
- def activate(self, duration=5):
293
- """Show native blur overlay and exit after duration"""
294
- self._create_windows() # Use multi-monitor aware method
295
-
296
- # Auto-exit timer
297
- self._timer_id = self.root.after(int(duration * 1000), self.kill_completely)
298
-
299
- # Show window
300
- self.root.mainloop()
301
-
302
- def _apply_native_blur(self):
303
- """Apply OS-native backdrop blur effect to root window (legacy method)"""
304
- self._apply_native_blur_to_window(self.root)
224
+ window.bind('<Escape>', lambda e: self.hide())
305
225
 
306
226
  def _apply_native_blur_to_window(self, window):
307
227
  """Apply OS-native backdrop blur effect to a specific window"""
@@ -313,10 +233,6 @@ class NativeBlurOverlay:
313
233
  self._apply_windows_blur_to_window(window)
314
234
  elif system == 'Linux':
315
235
  self._apply_linux_blur_to_window(window)
316
-
317
- def _apply_macos_blur(self):
318
- """Apply macOS NSVisualEffectView blur (legacy method)"""
319
- self._apply_macos_blur_to_window(self.root)
320
236
 
321
237
  def _apply_macos_blur_to_window(self, window):
322
238
  """Apply macOS NSVisualEffectView blur to a specific window"""
@@ -341,17 +257,17 @@ class NativeBlurOverlay:
341
257
  from Cocoa import NSMakeRect
342
258
 
343
259
  # Get all windows and find ours
344
- for window in NSApp.windows():
345
- if window.isVisible():
260
+ for ns_window in NSApp.windows():
261
+ if ns_window.isVisible():
346
262
  # Create visual effect view
347
- frame = window.contentView().frame()
263
+ frame = ns_window.contentView().frame()
348
264
  effect_view = NSVisualEffectView.alloc().initWithFrame_(frame)
349
265
  effect_view.setBlendingMode_(NSVisualEffectBlendingModeBehindWindow)
350
266
  effect_view.setMaterial_(NSVisualEffectMaterialDark)
351
267
  effect_view.setState_(1) # Active state
352
268
 
353
269
  # Add as subview
354
- window.contentView().addSubview_positioned_relativeTo_(
270
+ ns_window.contentView().addSubview_positioned_relativeTo_(
355
271
  effect_view, 0, None
356
272
  )
357
273
  break
@@ -362,10 +278,6 @@ class NativeBlurOverlay:
362
278
  print("pyobjc not available, install with: pip install pyobjc-framework-Cocoa")
363
279
  except Exception as e:
364
280
  print(f"macOS blur effect failed: {e}")
365
-
366
- def _apply_windows_blur(self):
367
- """Apply Windows Acrylic/Blur effect (legacy method)"""
368
- self._apply_windows_blur_to_window(self.root)
369
281
 
370
282
  def _apply_windows_blur_to_window(self, window):
371
283
  """Apply Windows Acrylic/Blur effect to a specific window"""
@@ -428,10 +340,6 @@ class NativeBlurOverlay:
428
340
  # Blur effect failed, but window will still work (just without blur)
429
341
  print(f"Note: Windows blur effect unavailable: {e}")
430
342
  print("Overlay will work but without native blur effect")
431
-
432
- def _apply_linux_blur(self):
433
- """Apply Linux compositor blur (X11/Wayland) (legacy method)"""
434
- self._apply_linux_blur_to_window(self.root)
435
343
 
436
344
  def _apply_linux_blur_to_window(self, window):
437
345
  """Apply Linux compositor blur (X11/Wayland) to a specific window"""
@@ -447,18 +355,31 @@ class NativeBlurOverlay:
447
355
  except Exception as e:
448
356
  print(f"Linux blur effect hint failed: {e}")
449
357
 
450
- def kill_completely(self):
451
- """Exit the overlay completely (for activate() backward compatibility)"""
452
- try:
453
- if self.root:
454
- self.root.quit()
455
- self.root.destroy()
456
- except:
457
- pass
358
+ # Backward compatibility methods
359
+ def activate(self, duration=5):
360
+ """
361
+ Show overlay for a fixed duration and then exit (blocking).
362
+
363
+ This is the legacy API for backward compatibility.
364
+ For new code, use start() + show()/hide() + update() instead.
365
+ """
366
+ self.start()
367
+ self.show()
458
368
 
459
- # Only call os._exit if we're in activate() mode (has timer)
460
- if self._timer_id is not None:
461
- os._exit(0)
369
+ # Schedule hide and cleanup
370
+ self.root.after(int(duration * 1000), self._deactivate_and_exit)
371
+
372
+ # Run mainloop (blocking)
373
+ self.root.mainloop()
374
+
375
+ def _deactivate_and_exit(self):
376
+ """Helper for activate() - hide and exit"""
377
+ self.hide()
378
+ self.stop()
379
+
380
+
381
+ # Alias for convenience
382
+ Overlay = NativeBlurOverlay
462
383
 
463
384
 
464
385
  if __name__ == "__main__":
@@ -472,20 +393,19 @@ if __name__ == "__main__":
472
393
 
473
394
  print(f"Testing mode='{mode}' for 3 seconds...")
474
395
  print("Available modes: blur, black, white, custom")
475
- print("Usage: python NativeBlurOverlay.py [mode]")
396
+ print("Usage: python overlay.py [mode]")
476
397
  print()
477
398
 
478
399
  if mode == 'blur':
479
- overlay = NativeBlurOverlay(mode='blur', blur_strength=4)
400
+ overlay = Overlay(mode='blur', blur_strength=4)
480
401
  elif mode == 'black':
481
- overlay = NativeBlurOverlay(mode='black')
402
+ overlay = Overlay(mode='black')
482
403
  elif mode == 'white':
483
- overlay = NativeBlurOverlay(mode='white')
404
+ overlay = Overlay(mode='white')
484
405
  elif mode == 'custom':
485
- overlay = NativeBlurOverlay(mode='custom', opacity=0.7, color_tint=(255, 0, 0)) # Red example
406
+ overlay = Overlay(mode='custom', opacity=0.7, color_tint=(255, 0, 0)) # Red example
486
407
  else:
487
408
  print(f"Unknown mode: {mode}")
488
409
  sys.exit(1)
489
410
 
490
411
  overlay.activate(duration=3)
491
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: screenoverlay
3
- Version: 0.4.2
3
+ Version: 0.6.0
4
4
  Summary: Cross-platform screen overlay with blur, black, white, and custom modes
5
5
  Home-page: https://github.com/pekay-ai/screenoverlay
6
6
  Author: Pekay
@@ -0,0 +1,7 @@
1
+ screenoverlay/__init__.py,sha256=t0XbgQ-zncGYdl9TANsZd3bK45dK2ghg0cndEA5rLKM,256
2
+ screenoverlay/overlay.py,sha256=3kVHpaHSE3-0ZA7WRpcS3WFsUIlMbLJUMGhcm2KdY3Y,15273
3
+ screenoverlay-0.6.0.dist-info/licenses/LICENSE,sha256=QlEjK4tuMjNEYVlvzaIhxfsCeU8hcGZyuT85cm1YChE,1084
4
+ screenoverlay-0.6.0.dist-info/METADATA,sha256=Ku92qyrE7X3SPi9j2yeZdbG1TlJccrgf43qkTbG21SE,17132
5
+ screenoverlay-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ screenoverlay-0.6.0.dist-info/top_level.txt,sha256=kfPL07o_kJ-mlb14Ps2zp_tIYnD8GfsSXlbDxDF6Eic,14
7
+ screenoverlay-0.6.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- screenoverlay/__init__.py,sha256=NX7qDYscRGygfi9p6HJC_gT876L8wNpX1f01sS1mxPc,256
2
- screenoverlay/overlay.py,sha256=LRlwYVFTIudri2Z-9cDLNsgpf6Itnl5cCO2M2cDu0qA,18690
3
- screenoverlay-0.4.2.dist-info/licenses/LICENSE,sha256=QlEjK4tuMjNEYVlvzaIhxfsCeU8hcGZyuT85cm1YChE,1084
4
- screenoverlay-0.4.2.dist-info/METADATA,sha256=LgLMK4MIXrDtMx4pFzGRXn6ups6N6jWJ2g1k2g4v90Q,17132
5
- screenoverlay-0.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- screenoverlay-0.4.2.dist-info/top_level.txt,sha256=kfPL07o_kJ-mlb14Ps2zp_tIYnD8GfsSXlbDxDF6Eic,14
7
- screenoverlay-0.4.2.dist-info/RECORD,,