screenoverlay 0.5.0__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.5.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,154 +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
89
+
90
+ # Create windows for all monitors
91
+ self._create_windows()
120
92
 
121
- self._command_queue = Queue()
122
- self._process = Process(target=self._run_process, args=(self._command_queue,), daemon=True)
123
- self._process.start()
93
+ # Hide all windows initially
94
+ for win in self.windows:
95
+ win.withdraw()
124
96
 
125
- # Wait a bit for process to initialize
126
- time.sleep(0.3)
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
- """
135
- Hide the overlay and restart for next use (100% reliable, prevents ghost windows)
136
-
137
- This method:
138
- 1. Hides the overlay instantly (user sees clear screen ~1ms)
139
- 2. Stops the overlay process completely (kills any ghost windows)
140
- 3. Starts a fresh overlay ready for next show() (happens in background ~300ms)
115
+ """Hide the overlay (instant, <1ms)"""
116
+ if self.root is None:
117
+ return # Not started yet
141
118
 
142
- This "restart on hide" approach guarantees zero ghost windows by giving
143
- each detection cycle a fresh overlay process, at the cost of ~300ms
144
- startup time that happens during safe periods (when screen is clear).
119
+ if self._is_visible:
120
+ for win in self.windows:
121
+ try:
122
+ win.withdraw()
123
+ except Exception as e:
124
+ print(f"Warning: Failed to hide window: {e}")
125
+ self._is_visible = False
126
+
127
+ def update(self):
145
128
  """
146
- if self._command_queue is not None:
147
- # Hide first (instant for user)
148
- self._command_queue.put('hide')
149
-
150
- # Stop the process completely to prevent ghost windows
151
- self.stop()
152
-
153
- # Start fresh overlay for next show() (happens in background)
154
- self.start()
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}")
155
149
 
156
150
  def stop(self):
157
151
  """Stop and cleanup the overlay completely"""
158
- if self._command_queue is not None:
159
- self._command_queue.put('stop')
160
-
161
- if self._process is not None:
162
- self._process.join(timeout=2.0)
163
- if self._process.is_alive():
164
- self._process.terminate()
165
- self._process = None
166
-
167
- self._command_queue = None
168
-
169
- def _run_process(self, command_queue):
170
- """Run overlay in separate process with command queue"""
171
- try:
172
- # Create windows for all monitors
173
- self._create_windows()
174
-
175
- # Hide all windows initially
176
- for win in self.windows:
177
- win.withdraw()
178
-
179
- # Process commands from queue
180
- def check_commands():
181
- try:
182
- while not command_queue.empty():
183
- cmd = command_queue.get_nowait()
184
- if cmd == 'show':
185
- for win in self.windows:
186
- try:
187
- win.deiconify()
188
- win.lift()
189
- except Exception as e:
190
- print(f"Warning: Failed to show window: {e}")
191
- elif cmd == 'hide':
192
- for win in self.windows:
193
- try:
194
- win.withdraw()
195
- except Exception as e:
196
- print(f"Warning: Failed to hide window: {e}")
197
- elif cmd == 'stop':
198
- self.root.quit()
199
- return
200
- except Exception as e:
201
- print(f"Warning: Command queue error: {e}")
202
-
203
- # Check again in 10ms
204
- self.root.after(10, check_commands)
205
-
206
- # Start command checker
207
- check_commands()
208
-
209
- # Run mainloop
210
- self.root.mainloop()
211
-
212
- except Exception as e:
213
- print(f"Overlay process error: {e}")
214
- finally:
215
- os._exit(0)
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
216
161
 
217
162
  def _get_monitors(self):
218
163
  """Get information about all monitors"""
@@ -274,52 +219,9 @@ class NativeBlurOverlay:
274
219
  if self.apply_blur:
275
220
  self._apply_native_blur_to_window(window)
276
221
 
277
- # Bind escape key to exit (only on primary window)
222
+ # Bind escape key to hide (only on primary window)
278
223
  if window == self.root:
279
- window.bind('<Escape>', lambda e: self.kill_completely())
280
- window.focus_set()
281
-
282
- def _create_window(self):
283
- """Internal method to create and configure the Tkinter window"""
284
- self.root = tk.Tk()
285
-
286
- # Remove window decorations
287
- self.root.overrideredirect(True)
288
- self.root.attributes('-topmost', True)
289
-
290
- # Set background color (tint)
291
- bg_color = f'#{self.color_tint[0]:02x}{self.color_tint[1]:02x}{self.color_tint[2]:02x}'
292
- self.root.configure(bg=bg_color)
293
-
294
- # Set opacity
295
- self.root.attributes('-alpha', self.opacity)
296
-
297
- # Full screen
298
- screen_width = self.root.winfo_screenwidth()
299
- screen_height = self.root.winfo_screenheight()
300
- self.root.geometry(f"{screen_width}x{screen_height}+0+0")
301
-
302
- # Apply native blur effect based on OS (only if mode is 'blur')
303
- if self.apply_blur:
304
- self._apply_native_blur()
305
-
306
- # Bind escape key to exit
307
- self.root.bind('<Escape>', lambda e: self.kill_completely())
308
- self.root.focus_set()
309
-
310
- def activate(self, duration=5):
311
- """Show native blur overlay and exit after duration"""
312
- self._create_windows() # Use multi-monitor aware method
313
-
314
- # Auto-exit timer
315
- self._timer_id = self.root.after(int(duration * 1000), self.kill_completely)
316
-
317
- # Show window
318
- self.root.mainloop()
319
-
320
- def _apply_native_blur(self):
321
- """Apply OS-native backdrop blur effect to root window (legacy method)"""
322
- self._apply_native_blur_to_window(self.root)
224
+ window.bind('<Escape>', lambda e: self.hide())
323
225
 
324
226
  def _apply_native_blur_to_window(self, window):
325
227
  """Apply OS-native backdrop blur effect to a specific window"""
@@ -331,10 +233,6 @@ class NativeBlurOverlay:
331
233
  self._apply_windows_blur_to_window(window)
332
234
  elif system == 'Linux':
333
235
  self._apply_linux_blur_to_window(window)
334
-
335
- def _apply_macos_blur(self):
336
- """Apply macOS NSVisualEffectView blur (legacy method)"""
337
- self._apply_macos_blur_to_window(self.root)
338
236
 
339
237
  def _apply_macos_blur_to_window(self, window):
340
238
  """Apply macOS NSVisualEffectView blur to a specific window"""
@@ -359,17 +257,17 @@ class NativeBlurOverlay:
359
257
  from Cocoa import NSMakeRect
360
258
 
361
259
  # Get all windows and find ours
362
- for window in NSApp.windows():
363
- if window.isVisible():
260
+ for ns_window in NSApp.windows():
261
+ if ns_window.isVisible():
364
262
  # Create visual effect view
365
- frame = window.contentView().frame()
263
+ frame = ns_window.contentView().frame()
366
264
  effect_view = NSVisualEffectView.alloc().initWithFrame_(frame)
367
265
  effect_view.setBlendingMode_(NSVisualEffectBlendingModeBehindWindow)
368
266
  effect_view.setMaterial_(NSVisualEffectMaterialDark)
369
267
  effect_view.setState_(1) # Active state
370
268
 
371
269
  # Add as subview
372
- window.contentView().addSubview_positioned_relativeTo_(
270
+ ns_window.contentView().addSubview_positioned_relativeTo_(
373
271
  effect_view, 0, None
374
272
  )
375
273
  break
@@ -380,10 +278,6 @@ class NativeBlurOverlay:
380
278
  print("pyobjc not available, install with: pip install pyobjc-framework-Cocoa")
381
279
  except Exception as e:
382
280
  print(f"macOS blur effect failed: {e}")
383
-
384
- def _apply_windows_blur(self):
385
- """Apply Windows Acrylic/Blur effect (legacy method)"""
386
- self._apply_windows_blur_to_window(self.root)
387
281
 
388
282
  def _apply_windows_blur_to_window(self, window):
389
283
  """Apply Windows Acrylic/Blur effect to a specific window"""
@@ -446,10 +340,6 @@ class NativeBlurOverlay:
446
340
  # Blur effect failed, but window will still work (just without blur)
447
341
  print(f"Note: Windows blur effect unavailable: {e}")
448
342
  print("Overlay will work but without native blur effect")
449
-
450
- def _apply_linux_blur(self):
451
- """Apply Linux compositor blur (X11/Wayland) (legacy method)"""
452
- self._apply_linux_blur_to_window(self.root)
453
343
 
454
344
  def _apply_linux_blur_to_window(self, window):
455
345
  """Apply Linux compositor blur (X11/Wayland) to a specific window"""
@@ -465,18 +355,31 @@ class NativeBlurOverlay:
465
355
  except Exception as e:
466
356
  print(f"Linux blur effect hint failed: {e}")
467
357
 
468
- def kill_completely(self):
469
- """Exit the overlay completely (for activate() backward compatibility)"""
470
- try:
471
- if self.root:
472
- self.root.quit()
473
- self.root.destroy()
474
- except:
475
- 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()
368
+
369
+ # Schedule hide and cleanup
370
+ self.root.after(int(duration * 1000), self._deactivate_and_exit)
476
371
 
477
- # Only call os._exit if we're in activate() mode (has timer)
478
- if self._timer_id is not None:
479
- os._exit(0)
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
480
383
 
481
384
 
482
385
  if __name__ == "__main__":
@@ -490,20 +393,19 @@ if __name__ == "__main__":
490
393
 
491
394
  print(f"Testing mode='{mode}' for 3 seconds...")
492
395
  print("Available modes: blur, black, white, custom")
493
- print("Usage: python NativeBlurOverlay.py [mode]")
396
+ print("Usage: python overlay.py [mode]")
494
397
  print()
495
398
 
496
399
  if mode == 'blur':
497
- overlay = NativeBlurOverlay(mode='blur', blur_strength=4)
400
+ overlay = Overlay(mode='blur', blur_strength=4)
498
401
  elif mode == 'black':
499
- overlay = NativeBlurOverlay(mode='black')
402
+ overlay = Overlay(mode='black')
500
403
  elif mode == 'white':
501
- overlay = NativeBlurOverlay(mode='white')
404
+ overlay = Overlay(mode='white')
502
405
  elif mode == 'custom':
503
- 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
504
407
  else:
505
408
  print(f"Unknown mode: {mode}")
506
409
  sys.exit(1)
507
410
 
508
411
  overlay.activate(duration=3)
509
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: screenoverlay
3
- Version: 0.5.0
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=2v9h_ldhYCxy0VccYkUkDGL6FcngtfPsb9ItzUl5qOQ,256
2
- screenoverlay/overlay.py,sha256=zfrbTFfplWOilNdOobG7VYF3_YrS0Xga_0qxsXEMs4E,19521
3
- screenoverlay-0.5.0.dist-info/licenses/LICENSE,sha256=QlEjK4tuMjNEYVlvzaIhxfsCeU8hcGZyuT85cm1YChE,1084
4
- screenoverlay-0.5.0.dist-info/METADATA,sha256=z_xtjSBLk-98i4ZoInrWrizNmRhCbIRZiGJScXkr2F0,17132
5
- screenoverlay-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- screenoverlay-0.5.0.dist-info/top_level.txt,sha256=kfPL07o_kJ-mlb14Ps2zp_tIYnD8GfsSXlbDxDF6Eic,14
7
- screenoverlay-0.5.0.dist-info/RECORD,,