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 +1 -1
- screenoverlay/overlay.py +110 -208
- {screenoverlay-0.5.0.dist-info → screenoverlay-0.6.0.dist-info}/METADATA +1 -1
- screenoverlay-0.6.0.dist-info/RECORD +7 -0
- screenoverlay-0.5.0.dist-info/RECORD +0 -7
- {screenoverlay-0.5.0.dist-info → screenoverlay-0.6.0.dist-info}/WHEEL +0 -0
- {screenoverlay-0.5.0.dist-info → screenoverlay-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {screenoverlay-0.5.0.dist-info → screenoverlay-0.6.0.dist-info}/top_level.txt +0 -0
screenoverlay/__init__.py
CHANGED
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.
|
|
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
|
-
|
|
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
|
|
75
|
+
Example:
|
|
108
76
|
overlay = Overlay(mode='blur', blur_strength=4)
|
|
109
77
|
overlay.start() # Initialize (call once)
|
|
110
78
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
93
|
+
# Hide all windows initially
|
|
94
|
+
for win in self.windows:
|
|
95
|
+
win.withdraw()
|
|
124
96
|
|
|
125
|
-
|
|
126
|
-
time.sleep(0.3)
|
|
97
|
+
self._is_visible = False
|
|
127
98
|
|
|
128
99
|
def show(self):
|
|
129
|
-
"""Show the overlay (instant,
|
|
130
|
-
if self.
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
self.
|
|
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
|
|
222
|
+
# Bind escape key to hide (only on primary window)
|
|
278
223
|
if window == self.root:
|
|
279
|
-
window.bind('<Escape>', lambda e: self.
|
|
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
|
|
363
|
-
if
|
|
260
|
+
for ns_window in NSApp.windows():
|
|
261
|
+
if ns_window.isVisible():
|
|
364
262
|
# Create visual effect view
|
|
365
|
-
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
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
#
|
|
478
|
-
|
|
479
|
-
|
|
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
|
|
396
|
+
print("Usage: python overlay.py [mode]")
|
|
494
397
|
print()
|
|
495
398
|
|
|
496
399
|
if mode == 'blur':
|
|
497
|
-
overlay =
|
|
400
|
+
overlay = Overlay(mode='blur', blur_strength=4)
|
|
498
401
|
elif mode == 'black':
|
|
499
|
-
overlay =
|
|
402
|
+
overlay = Overlay(mode='black')
|
|
500
403
|
elif mode == 'white':
|
|
501
|
-
overlay =
|
|
404
|
+
overlay = Overlay(mode='white')
|
|
502
405
|
elif mode == 'custom':
|
|
503
|
-
overlay =
|
|
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
|
-
|
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|