pyloid 0.9.8__py3-none-any.whl → 0.10.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.
- pyloid/filewatcher.py +35 -0
- pyloid/pyloid.py +343 -116
- {pyloid-0.9.8.dist-info → pyloid-0.10.0.dist-info}/LICENSE +1 -1
- {pyloid-0.9.8.dist-info → pyloid-0.10.0.dist-info}/METADATA +1 -1
- pyloid-0.10.0.dist-info/RECORD +12 -0
- pyloid-0.9.8.dist-info/RECORD +0 -11
- {pyloid-0.9.8.dist-info → pyloid-0.10.0.dist-info}/WHEEL +0 -0
pyloid/filewatcher.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
from PySide6.QtCore import QFileSystemWatcher, QObject, Signal
|
2
|
+
|
3
|
+
class FileWatcher(QObject):
|
4
|
+
file_changed = Signal(str)
|
5
|
+
directory_changed = Signal(str)
|
6
|
+
|
7
|
+
def __init__(self):
|
8
|
+
super().__init__()
|
9
|
+
self.watcher = QFileSystemWatcher()
|
10
|
+
self.watcher.fileChanged.connect(self.file_changed)
|
11
|
+
self.watcher.directoryChanged.connect(self.directory_changed)
|
12
|
+
|
13
|
+
def add_path(self, path):
|
14
|
+
"""Add a file or directory to the watch list."""
|
15
|
+
return self.watcher.addPath(path)
|
16
|
+
|
17
|
+
def remove_path(self, path):
|
18
|
+
"""Remove a file or directory from the watch list."""
|
19
|
+
return self.watcher.removePath(path)
|
20
|
+
|
21
|
+
def get_watched_paths(self):
|
22
|
+
"""Return all currently watched paths (files + directories)."""
|
23
|
+
return self.watcher.files() + self.watcher.directories()
|
24
|
+
|
25
|
+
def get_watched_files(self):
|
26
|
+
"""Return all currently watched files."""
|
27
|
+
return self.watcher.files()
|
28
|
+
|
29
|
+
def get_watched_directories(self):
|
30
|
+
"""Return all currently watched directories."""
|
31
|
+
return self.watcher.directories()
|
32
|
+
|
33
|
+
def remove_all_paths(self):
|
34
|
+
"""Remove all paths from the watch list."""
|
35
|
+
return self.watcher.removePaths(self.get_watched_paths())
|
pyloid/pyloid.py
CHANGED
@@ -8,8 +8,8 @@ from PySide6.QtWidgets import (
|
|
8
8
|
)
|
9
9
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
10
10
|
from PySide6.QtWebChannel import QWebChannel
|
11
|
-
from PySide6.QtGui import QIcon, QKeySequence, QShortcut, QClipboard, QImage
|
12
|
-
from PySide6.QtCore import Qt, Signal, QUrl, QObject
|
11
|
+
from PySide6.QtGui import QIcon, QKeySequence, QShortcut, QClipboard, QImage, QAction
|
12
|
+
from PySide6.QtCore import Qt, Signal, QUrl, QObject, QTimer
|
13
13
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
14
14
|
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
|
15
15
|
from .api import PyloidAPI, Bridge
|
@@ -21,22 +21,32 @@ from .utils import is_production
|
|
21
21
|
from .monitor import Monitor
|
22
22
|
import json
|
23
23
|
from .autostart import AutoStart
|
24
|
+
from .filewatcher import FileWatcher
|
24
25
|
|
25
26
|
# for linux debug
|
26
|
-
os.environ[
|
27
|
+
os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
|
27
28
|
|
28
29
|
# for macos debug
|
29
30
|
|
31
|
+
|
30
32
|
def custom_message_handler(mode, context, message):
|
31
|
-
if not hasattr(custom_message_handler,
|
32
|
-
|
33
|
-
|
33
|
+
if not hasattr(custom_message_handler, "vulkan_warning_shown") and (
|
34
|
+
("Failed to load vulkan" in message)
|
35
|
+
or ("No Vulkan library available" in message)
|
36
|
+
or ("Failed to create platform Vulkan instance" in message)
|
37
|
+
):
|
38
|
+
print(
|
39
|
+
"\033[93mPyloid Warning: Vulkan GPU API issue detected. Switching to software backend.\033[0m"
|
40
|
+
)
|
41
|
+
os.environ["QT_QUICK_BACKEND"] = "software"
|
34
42
|
custom_message_handler.vulkan_warning_shown = True
|
35
|
-
if
|
43
|
+
if "vulkan" not in message.lower():
|
36
44
|
print(message)
|
37
45
|
|
46
|
+
|
38
47
|
qInstallMessageHandler(custom_message_handler)
|
39
48
|
|
49
|
+
|
40
50
|
class WindowAPI(PyloidAPI):
|
41
51
|
def __init__(self, window_id: str, app):
|
42
52
|
super().__init__()
|
@@ -147,6 +157,7 @@ class WindowAPI(PyloidAPI):
|
|
147
157
|
return window.capture(save_path)
|
148
158
|
return None
|
149
159
|
|
160
|
+
|
150
161
|
# class EventAPI(PylonAPI):
|
151
162
|
# def __init__(self, window_id: str, app):
|
152
163
|
# super().__init__()
|
@@ -168,21 +179,20 @@ class WindowAPI(PyloidAPI):
|
|
168
179
|
# for callback in self.subscribers[event_name]:
|
169
180
|
# callback(*args, **kwargs)
|
170
181
|
|
171
|
-
|
172
182
|
|
173
183
|
class BrowserWindow:
|
174
184
|
def __init__(
|
175
185
|
self,
|
176
186
|
app,
|
177
|
-
title: str="
|
178
|
-
width: int=800,
|
179
|
-
height: int=600,
|
180
|
-
x: int=200,
|
181
|
-
y: int=200,
|
182
|
-
frame: bool=True,
|
183
|
-
context_menu: bool=False,
|
184
|
-
dev_tools: bool=False,
|
185
|
-
js_apis: List[PyloidAPI]=[],
|
187
|
+
title: str = "pyloid app",
|
188
|
+
width: int = 800,
|
189
|
+
height: int = 600,
|
190
|
+
x: int = 200,
|
191
|
+
y: int = 200,
|
192
|
+
frame: bool = True,
|
193
|
+
context_menu: bool = False,
|
194
|
+
dev_tools: bool = False,
|
195
|
+
js_apis: List[PyloidAPI] = [],
|
186
196
|
):
|
187
197
|
###########################################################################################
|
188
198
|
self.id = str(uuid.uuid4()) # Generate unique ID
|
@@ -190,9 +200,9 @@ class BrowserWindow:
|
|
190
200
|
self._window = QMainWindow()
|
191
201
|
self.web_view = QWebEngineView()
|
192
202
|
|
193
|
-
self._window.closeEvent = self.closeEvent # Override closeEvent method
|
203
|
+
self._window.closeEvent = self.closeEvent # Override closeEvent method
|
194
204
|
###########################################################################################
|
195
|
-
self.app = app
|
205
|
+
self.app = app
|
196
206
|
self.title = title
|
197
207
|
self.width = width
|
198
208
|
self.height = height
|
@@ -206,14 +216,16 @@ class BrowserWindow:
|
|
206
216
|
self.js_apis.append(js_api)
|
207
217
|
self.shortcuts = {}
|
208
218
|
###########################################################################################
|
209
|
-
|
219
|
+
|
210
220
|
def _load(self):
|
211
221
|
self._window.setWindowTitle(self.title)
|
212
222
|
|
213
223
|
self._window.setGeometry(self.x, self.y, self.width, self.height)
|
214
224
|
|
215
225
|
# allow local file access to remote urls
|
216
|
-
self.web_view.settings().setAttribute(
|
226
|
+
self.web_view.settings().setAttribute(
|
227
|
+
QWebEngineSettings.LocalContentCanAccessRemoteUrls, True
|
228
|
+
)
|
217
229
|
|
218
230
|
# Set icon
|
219
231
|
if self.app.icon:
|
@@ -228,7 +240,6 @@ class BrowserWindow:
|
|
228
240
|
myappid = "mycompany.myproduct.subproduct.version"
|
229
241
|
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
230
242
|
|
231
|
-
|
232
243
|
# Remove title bar and borders (if needed)
|
233
244
|
if not self.frame:
|
234
245
|
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
@@ -244,7 +255,6 @@ class BrowserWindow:
|
|
244
255
|
if self.js_apis:
|
245
256
|
for js_api in self.js_apis:
|
246
257
|
self.channel.registerObject(js_api.__class__.__name__, js_api)
|
247
|
-
|
248
258
|
|
249
259
|
self.web_view.page().setWebChannel(self.channel)
|
250
260
|
|
@@ -253,7 +263,6 @@ class BrowserWindow:
|
|
253
263
|
|
254
264
|
# Add QWebEngineView to main window
|
255
265
|
self._window.setCentralWidget(self.web_view)
|
256
|
-
|
257
266
|
|
258
267
|
# Set F12 shortcut
|
259
268
|
self.set_dev_tools(self.dev_tools)
|
@@ -311,14 +320,14 @@ class BrowserWindow:
|
|
311
320
|
def load_file(self, file_path):
|
312
321
|
"""Loads a local HTML file into the web view."""
|
313
322
|
self._load()
|
314
|
-
file_path = os.path.abspath(file_path)
|
323
|
+
file_path = os.path.abspath(file_path) # absolute path
|
315
324
|
self.web_view.setUrl(QUrl.fromLocalFile(file_path))
|
316
|
-
|
317
325
|
|
318
326
|
def load_url(self, url):
|
319
327
|
"""Sets the URL of the window."""
|
320
328
|
self._load()
|
321
329
|
self.web_view.setUrl(QUrl(url))
|
330
|
+
|
322
331
|
###########################################################################################
|
323
332
|
# Set Parameters
|
324
333
|
###########################################################################################
|
@@ -354,10 +363,10 @@ class BrowserWindow:
|
|
354
363
|
self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
|
355
364
|
else:
|
356
365
|
self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
|
357
|
-
|
366
|
+
|
358
367
|
def set_dev_tools(self, enable: bool):
|
359
368
|
"""Sets the developer tools of the window.
|
360
|
-
|
369
|
+
|
361
370
|
If enabled, the developer tools can be opened using the F12 key.
|
362
371
|
"""
|
363
372
|
self.dev_tools = enable
|
@@ -390,7 +399,7 @@ class BrowserWindow:
|
|
390
399
|
"dev_tools": self.dev_tools,
|
391
400
|
"js_apis": self.js_apis,
|
392
401
|
}
|
393
|
-
|
402
|
+
|
394
403
|
def get_id(self):
|
395
404
|
"""Returns the ID of the window."""
|
396
405
|
return self.id
|
@@ -417,19 +426,23 @@ class BrowserWindow:
|
|
417
426
|
def show(self):
|
418
427
|
"""Shows the window."""
|
419
428
|
self._window.show()
|
420
|
-
|
429
|
+
|
421
430
|
def focus(self):
|
422
431
|
"""Focuses the window."""
|
423
432
|
self._window.activateWindow()
|
424
433
|
self._window.raise_()
|
425
|
-
self._window.setWindowState(
|
434
|
+
self._window.setWindowState(
|
435
|
+
self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
436
|
+
)
|
426
437
|
|
427
438
|
def show_and_focus(self):
|
428
439
|
"""Shows and focuses the window."""
|
429
440
|
self._window.show()
|
430
441
|
self._window.activateWindow()
|
431
442
|
self._window.raise_()
|
432
|
-
self._window.setWindowState(
|
443
|
+
self._window.setWindowState(
|
444
|
+
self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
445
|
+
)
|
433
446
|
|
434
447
|
def close(self):
|
435
448
|
"""Closes the window."""
|
@@ -457,14 +470,14 @@ class BrowserWindow:
|
|
457
470
|
def capture(self, save_path: str) -> Optional[str]:
|
458
471
|
"""
|
459
472
|
Captures the current window.
|
460
|
-
|
473
|
+
|
461
474
|
:param save_path: Path to save the captured image. If not specified, it will be saved in the current directory.
|
462
475
|
:return: Path of the saved image
|
463
476
|
"""
|
464
477
|
try:
|
465
478
|
# Capture window
|
466
479
|
screenshot = self._window.grab()
|
467
|
-
|
480
|
+
|
468
481
|
# Save image
|
469
482
|
screenshot.save(save_path)
|
470
483
|
return save_path
|
@@ -478,7 +491,7 @@ class BrowserWindow:
|
|
478
491
|
def add_shortcut(self, key_sequence: str, callback: Callable):
|
479
492
|
"""
|
480
493
|
Adds a keyboard shortcut to the window if it does not already exist.
|
481
|
-
|
494
|
+
|
482
495
|
:param key_sequence: Shortcut sequence (e.g., "Ctrl+C")
|
483
496
|
:param callback: Function to be executed when the shortcut is pressed
|
484
497
|
:return: Created QShortcut object or None if the shortcut already exists
|
@@ -486,7 +499,7 @@ class BrowserWindow:
|
|
486
499
|
if key_sequence in self.shortcuts:
|
487
500
|
# print(f"Shortcut {key_sequence} already exists.")
|
488
501
|
return None
|
489
|
-
|
502
|
+
|
490
503
|
shortcut = QShortcut(QKeySequence(key_sequence), self._window)
|
491
504
|
shortcut.activated.connect(callback)
|
492
505
|
self.shortcuts[key_sequence] = shortcut
|
@@ -495,7 +508,7 @@ class BrowserWindow:
|
|
495
508
|
def remove_shortcut(self, key_sequence: str):
|
496
509
|
"""
|
497
510
|
Removes a keyboard shortcut from the window.
|
498
|
-
|
511
|
+
|
499
512
|
:param key_sequence: Shortcut sequence to be removed
|
500
513
|
"""
|
501
514
|
if key_sequence in self.shortcuts:
|
@@ -506,18 +519,18 @@ class BrowserWindow:
|
|
506
519
|
def get_all_shortcuts(self):
|
507
520
|
"""
|
508
521
|
Returns all registered shortcuts in the window.
|
509
|
-
|
522
|
+
|
510
523
|
:return: Dictionary of shortcut sequences and QShortcut objects
|
511
524
|
"""
|
512
525
|
return self.shortcuts
|
513
|
-
|
526
|
+
|
514
527
|
###########################################################################################
|
515
528
|
# Event (Calling the JS from Python)
|
516
529
|
###########################################################################################
|
517
|
-
def emit(self, event_name, data: Optional[Dict]=None):
|
530
|
+
def emit(self, event_name, data: Optional[Dict] = None):
|
518
531
|
"""
|
519
532
|
Emits an event to the JavaScript side.
|
520
|
-
|
533
|
+
|
521
534
|
:param event_name: Name of the event
|
522
535
|
:param data: Data to be sent with the event (optional)
|
523
536
|
"""
|
@@ -536,8 +549,13 @@ class _WindowController(QObject):
|
|
536
549
|
QApplication, str, int, int, int, int, bool, bool, bool, list
|
537
550
|
)
|
538
551
|
|
552
|
+
|
539
553
|
class Pyloid(QApplication):
|
540
|
-
def __init__(
|
554
|
+
def __init__(
|
555
|
+
self,
|
556
|
+
app_name,
|
557
|
+
single_instance=True,
|
558
|
+
):
|
541
559
|
super().__init__(sys.argv)
|
542
560
|
|
543
561
|
self.windows = []
|
@@ -551,10 +569,12 @@ class Pyloid(QApplication):
|
|
551
569
|
self._init_single_instance()
|
552
570
|
|
553
571
|
self.controller = _WindowController()
|
554
|
-
self.controller.create_window_signal.connect(
|
572
|
+
self.controller.create_window_signal.connect(
|
573
|
+
self._create_window_signal_function
|
574
|
+
)
|
575
|
+
|
576
|
+
self.file_watcher = FileWatcher()
|
555
577
|
|
556
|
-
self.icon = QIcon(icon_path) if icon_path else None
|
557
|
-
self.tray_icon = QIcon(tray_icon_path) if tray_icon_path else None
|
558
578
|
self.tray_menu_items = []
|
559
579
|
self.tray_actions = {}
|
560
580
|
|
@@ -563,29 +583,36 @@ class Pyloid(QApplication):
|
|
563
583
|
|
564
584
|
self.auto_start = AutoStart(self.app_name, self.app_path)
|
565
585
|
|
586
|
+
self.animation_timer = None
|
587
|
+
self.icon_frames = []
|
588
|
+
self.current_frame = 0
|
589
|
+
|
566
590
|
def set_icon(self, icon_path: str):
|
567
|
-
"""
|
568
|
-
|
591
|
+
"""
|
592
|
+
Dynamically sets the application's icon.
|
569
593
|
|
570
|
-
|
571
|
-
"""Sets the path for the tray icon."""
|
572
|
-
self.tray_icon = QIcon(tray_icon_path)
|
594
|
+
:param icon_path: Path to the new icon file
|
573
595
|
|
574
|
-
|
575
|
-
|
576
|
-
|
596
|
+
This method can be called while the application is running.
|
597
|
+
The icon can be changed at any time and is applied immediately.
|
598
|
+
"""
|
599
|
+
self.icon = QIcon(icon_path)
|
600
|
+
|
601
|
+
# Immediately update the icon for all open windows
|
602
|
+
for window in self.windows:
|
603
|
+
window._window.setWindowIcon(self.icon)
|
577
604
|
|
578
605
|
def create_window(
|
579
606
|
self,
|
580
|
-
title: str="
|
581
|
-
width: int=800,
|
582
|
-
height: int=600,
|
583
|
-
x: int=200,
|
584
|
-
y: int=200,
|
585
|
-
frame: bool=True,
|
586
|
-
context_menu: bool=False,
|
587
|
-
dev_tools: bool=False,
|
588
|
-
js_apis: List[PyloidAPI]=[],
|
607
|
+
title: str = "pyloid app",
|
608
|
+
width: int = 800,
|
609
|
+
height: int = 600,
|
610
|
+
x: int = 200,
|
611
|
+
y: int = 200,
|
612
|
+
frame: bool = True,
|
613
|
+
context_menu: bool = False,
|
614
|
+
dev_tools: bool = False,
|
615
|
+
js_apis: List[PyloidAPI] = [],
|
589
616
|
) -> BrowserWindow:
|
590
617
|
"""Creates a new browser window."""
|
591
618
|
self.controller.create_window_signal.emit(
|
@@ -613,7 +640,7 @@ class Pyloid(QApplication):
|
|
613
640
|
frame: bool,
|
614
641
|
context_menu: bool,
|
615
642
|
dev_tools: bool,
|
616
|
-
js_apis: List[PyloidAPI]=[],
|
643
|
+
js_apis: List[PyloidAPI] = [],
|
617
644
|
) -> BrowserWindow:
|
618
645
|
"""Function to create a new browser window."""
|
619
646
|
window = BrowserWindow(
|
@@ -656,7 +683,6 @@ class Pyloid(QApplication):
|
|
656
683
|
"""Handles new connections for the single instance server."""
|
657
684
|
pass
|
658
685
|
|
659
|
-
|
660
686
|
###########################################################################################
|
661
687
|
# App window
|
662
688
|
###########################################################################################
|
@@ -676,8 +702,11 @@ class Pyloid(QApplication):
|
|
676
702
|
main_window = self.windows[0]
|
677
703
|
main_window._window.activateWindow()
|
678
704
|
main_window._window.raise_()
|
679
|
-
main_window._window.setWindowState(
|
680
|
-
|
705
|
+
main_window._window.setWindowState(
|
706
|
+
main_window._window.windowState() & ~Qt.WindowMinimized
|
707
|
+
| Qt.WindowActive
|
708
|
+
)
|
709
|
+
|
681
710
|
def show_and_focus_main_window(self):
|
682
711
|
"""Shows and focuses the first window."""
|
683
712
|
if self.windows:
|
@@ -685,8 +714,11 @@ class Pyloid(QApplication):
|
|
685
714
|
main_window._window.show()
|
686
715
|
main_window._window.activateWindow()
|
687
716
|
main_window._window.raise_()
|
688
|
-
main_window._window.setWindowState(
|
689
|
-
|
717
|
+
main_window._window.setWindowState(
|
718
|
+
main_window._window.windowState() & ~Qt.WindowMinimized
|
719
|
+
| Qt.WindowActive
|
720
|
+
)
|
721
|
+
|
690
722
|
def close_all_windows(self):
|
691
723
|
"""Closes all windows."""
|
692
724
|
for window in self.windows:
|
@@ -696,6 +728,7 @@ class Pyloid(QApplication):
|
|
696
728
|
"""Quits the application."""
|
697
729
|
self.close_all_windows()
|
698
730
|
QApplication.quit()
|
731
|
+
|
699
732
|
###########################################################################################
|
700
733
|
# Window management in the app (ID required)
|
701
734
|
###########################################################################################
|
@@ -719,7 +752,9 @@ class Pyloid(QApplication):
|
|
719
752
|
window._window.show()
|
720
753
|
window._window.activateWindow()
|
721
754
|
window._window.raise_()
|
722
|
-
window._window.setWindowState(
|
755
|
+
window._window.setWindowState(
|
756
|
+
window._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
757
|
+
)
|
723
758
|
|
724
759
|
def close_window_by_id(self, window_id: str):
|
725
760
|
"""Closes the window with the given ID."""
|
@@ -753,7 +788,7 @@ class Pyloid(QApplication):
|
|
753
788
|
def capture_window_by_id(self, window_id: str, save_path: str) -> Optional[str]:
|
754
789
|
"""
|
755
790
|
Captures a specific window.
|
756
|
-
|
791
|
+
|
757
792
|
:param window_id: ID of the window to capture
|
758
793
|
:param save_path: Path to save the captured image. If not specified, it will be saved in the current directory.
|
759
794
|
:return: Path of the saved image
|
@@ -763,45 +798,83 @@ class Pyloid(QApplication):
|
|
763
798
|
if not window:
|
764
799
|
print(f"Cannot find window with the specified ID: {window_id}")
|
765
800
|
return None
|
766
|
-
|
801
|
+
|
767
802
|
# Capture window
|
768
803
|
screenshot = window._window.grab()
|
769
|
-
|
804
|
+
|
770
805
|
# Save image
|
771
806
|
screenshot.save(save_path)
|
772
807
|
return save_path
|
773
808
|
except Exception as e:
|
774
809
|
print(f"Error occurred while capturing the window: {e}")
|
775
810
|
return None
|
811
|
+
|
776
812
|
###########################################################################################
|
777
813
|
# Tray
|
778
814
|
###########################################################################################
|
779
|
-
def
|
780
|
-
"""
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
self.
|
801
|
-
|
815
|
+
def set_tray_icon(self, tray_icon_path: str):
|
816
|
+
"""
|
817
|
+
Dynamically sets the tray icon.
|
818
|
+
Can be called while the application is running, and changes are applied immediately.
|
819
|
+
|
820
|
+
:param tray_icon_path: Path to the new tray icon file
|
821
|
+
"""
|
822
|
+
# Stop and remove existing animation timer if present
|
823
|
+
if hasattr(self, "animation_timer") and self.animation_timer is not None:
|
824
|
+
self.animation_timer.stop()
|
825
|
+
self.animation_timer.deleteLater()
|
826
|
+
self.animation_timer = None
|
827
|
+
|
828
|
+
# Remove existing icon frames
|
829
|
+
if hasattr(self, "icon_frames"):
|
830
|
+
self.icon_frames = []
|
831
|
+
|
832
|
+
# Set new icon
|
833
|
+
self.tray_icon = QIcon(tray_icon_path)
|
834
|
+
|
835
|
+
if not hasattr(self, "tray"):
|
836
|
+
self._init_tray()
|
837
|
+
else:
|
838
|
+
self.tray.setIcon(self.tray_icon)
|
839
|
+
|
840
|
+
def set_tray_menu_items(
|
841
|
+
self, tray_menu_items: List[Dict[str, Union[str, Callable]]]
|
842
|
+
):
|
843
|
+
"""
|
844
|
+
Dynamically sets the tray menu items.
|
845
|
+
Can be called while the application is running, and changes are applied immediately.
|
846
|
+
|
847
|
+
:param tray_menu_items: List of new tray menu items
|
848
|
+
"""
|
849
|
+
self.tray_menu_items = tray_menu_items
|
850
|
+
if not hasattr(self, "tray"):
|
851
|
+
self._init_tray()
|
852
|
+
self._update_tray_menu()
|
853
|
+
|
854
|
+
def _init_tray(self):
|
855
|
+
"""Initializes the tray icon."""
|
856
|
+
self.tray = QSystemTrayIcon(self)
|
857
|
+
if self.tray_icon:
|
858
|
+
self.tray.setIcon(self.tray_icon)
|
859
|
+
else:
|
860
|
+
print("Icon and tray icon have not been set.")
|
861
|
+
if self.tray_menu_items:
|
862
|
+
pass
|
863
|
+
else:
|
864
|
+
self.tray.setContextMenu(QMenu())
|
865
|
+
self.tray.show()
|
866
|
+
|
867
|
+
def _update_tray_menu(self):
|
868
|
+
"""Updates the tray menu."""
|
869
|
+
tray_menu = self.tray.contextMenu()
|
870
|
+
tray_menu.clear()
|
871
|
+
for item in self.tray_menu_items:
|
872
|
+
action = QAction(item["label"], self)
|
873
|
+
action.triggered.connect(item["callback"])
|
874
|
+
tray_menu.addAction(action)
|
802
875
|
|
803
876
|
def _tray_activated(self, reason):
|
804
|
-
"""Handles
|
877
|
+
"""Handles events when the tray icon is activated."""
|
805
878
|
reason_enum = QSystemTrayIcon.ActivationReason(reason)
|
806
879
|
|
807
880
|
if reason_enum in self.tray_actions:
|
@@ -809,48 +882,124 @@ class Pyloid(QApplication):
|
|
809
882
|
|
810
883
|
def set_tray_actions(self, actions):
|
811
884
|
"""
|
812
|
-
|
885
|
+
Dynamically sets actions for tray icon activation.
|
886
|
+
Can be called while the application is running, and changes are applied immediately.
|
813
887
|
|
814
|
-
actions: Dictionary
|
815
|
-
and values are callback functions for the respective activation reasons.
|
888
|
+
:param actions: Dictionary with TrayEvent enum values as keys and corresponding callback functions as values
|
816
889
|
"""
|
890
|
+
if self.tray_actions:
|
891
|
+
self.tray.activated.disconnect() # Disconnect existing connections
|
892
|
+
|
817
893
|
self.tray_actions = actions
|
894
|
+
if not hasattr(self, "tray"):
|
895
|
+
self._init_tray()
|
896
|
+
|
897
|
+
self.tray.activated.connect(lambda reason: self._tray_activated(reason))
|
818
898
|
|
819
899
|
def show_notification(self, title: str, message: str):
|
820
|
-
"""
|
821
|
-
|
822
|
-
|
900
|
+
"""
|
901
|
+
Displays a notification in the system tray.
|
902
|
+
Can be called while the application is running, and the notification is displayed immediately.
|
903
|
+
|
904
|
+
:param title: Notification title
|
905
|
+
:param message: Notification content
|
906
|
+
"""
|
907
|
+
if not hasattr(self, "tray"):
|
908
|
+
self._init_tray() # Ensure the tray is initialized
|
823
909
|
|
824
910
|
self.tray.showMessage(title, message, QIcon(self.icon), 5000)
|
825
911
|
|
912
|
+
def _update_tray_icon(self):
|
913
|
+
"""Updates the animation frames."""
|
914
|
+
if hasattr(self, "tray") and self.icon_frames:
|
915
|
+
self.tray.setIcon(self.icon_frames[self.current_frame])
|
916
|
+
self.current_frame = (self.current_frame + 1) % len(self.icon_frames)
|
917
|
+
|
918
|
+
def set_tray_icon_animation(self, icon_frames: List[str], interval: int = 200):
|
919
|
+
"""
|
920
|
+
Dynamically sets and starts an animation for the tray icon.
|
921
|
+
Can be called while the application is running, and changes are applied immediately.
|
922
|
+
|
923
|
+
:param icon_frames: List of paths to animation frame images
|
924
|
+
:param interval: Interval between frames (milliseconds)
|
925
|
+
"""
|
926
|
+
if not hasattr(self, "tray"):
|
927
|
+
self._init_tray()
|
928
|
+
|
929
|
+
# Remove existing icon
|
930
|
+
if hasattr(self, "tray_icon"):
|
931
|
+
del self.tray_icon
|
932
|
+
|
933
|
+
# Stop and remove existing animation timer
|
934
|
+
if hasattr(self, "animation_timer") and self.animation_timer is not None:
|
935
|
+
self.animation_timer.stop()
|
936
|
+
self.animation_timer.deleteLater()
|
937
|
+
self.animation_timer = None
|
938
|
+
|
939
|
+
self.icon_frames = [QIcon(frame) for frame in icon_frames]
|
940
|
+
self.animation_interval = interval
|
941
|
+
self.start_tray_icon_animation()
|
942
|
+
|
943
|
+
def start_tray_icon_animation(self):
|
944
|
+
"""Starts the tray icon animation."""
|
945
|
+
if self.icon_frames:
|
946
|
+
if self.animation_timer is None:
|
947
|
+
self.animation_timer = QTimer(self)
|
948
|
+
self.animation_timer.timeout.connect(lambda: self._update_tray_icon())
|
949
|
+
self.animation_timer.start(self.animation_interval)
|
950
|
+
self.current_frame = 0
|
951
|
+
|
952
|
+
def set_tray_tooltip(self, message: str):
|
953
|
+
"""
|
954
|
+
Dynamically sets the tooltip for the tray icon.
|
955
|
+
Can be called while the application is running, and changes are applied immediately.
|
956
|
+
|
957
|
+
:param message: New tooltip message
|
958
|
+
"""
|
959
|
+
if not hasattr(self, "tray"):
|
960
|
+
self._init_tray()
|
961
|
+
self.tray.setToolTip(message)
|
962
|
+
|
963
|
+
def set_notification_callback(self, callback: Callable[[str], None]):
|
964
|
+
"""
|
965
|
+
Sets the callback function to be called when a notification is clicked.
|
966
|
+
|
967
|
+
:param callback: Callback function to be called when a notification is clicked
|
968
|
+
"""
|
969
|
+
if not hasattr(self, "tray"):
|
970
|
+
self._init_tray()
|
971
|
+
self.tray.messageClicked.connect(callback)
|
972
|
+
|
826
973
|
###########################################################################################
|
827
974
|
# Monitor
|
828
975
|
###########################################################################################
|
829
976
|
def get_all_monitors(self) -> List[Monitor]:
|
830
977
|
"""
|
831
978
|
Returns a list of information for all connected monitors.
|
832
|
-
|
979
|
+
|
833
980
|
:return: List containing monitor information
|
834
981
|
"""
|
835
|
-
monitors = [
|
982
|
+
monitors = [
|
983
|
+
Monitor(index, screen) for index, screen in enumerate(self.screens())
|
984
|
+
]
|
836
985
|
return monitors
|
837
|
-
|
986
|
+
|
838
987
|
def get_primary_monitor(self) -> Monitor:
|
839
988
|
"""
|
840
989
|
Returns information for the primary monitor.
|
841
|
-
|
990
|
+
|
842
991
|
:return: Primary monitor information
|
843
992
|
"""
|
844
993
|
primary_monitor = self.screens()[0]
|
845
994
|
return Monitor(0, primary_monitor)
|
846
|
-
|
995
|
+
|
847
996
|
###########################################################################################
|
848
997
|
# Clipboard
|
849
998
|
###########################################################################################
|
850
999
|
def copy_to_clipboard(self, text):
|
851
1000
|
"""
|
852
1001
|
Copies text to the clipboard.
|
853
|
-
|
1002
|
+
|
854
1003
|
:param text: Text to be copied
|
855
1004
|
"""
|
856
1005
|
self.clipboard_class.setText(text, QClipboard.Clipboard)
|
@@ -858,7 +1007,7 @@ class Pyloid(QApplication):
|
|
858
1007
|
def get_clipboard_text(self):
|
859
1008
|
"""
|
860
1009
|
Retrieves text from the clipboard.
|
861
|
-
|
1010
|
+
|
862
1011
|
:return: Text from the clipboard
|
863
1012
|
"""
|
864
1013
|
return self.clipboard_class.text()
|
@@ -866,7 +1015,7 @@ class Pyloid(QApplication):
|
|
866
1015
|
def set_clipboard_image(self, image: Union[str, bytes, os.PathLike]):
|
867
1016
|
"""
|
868
1017
|
Copies an image to the clipboard.
|
869
|
-
|
1018
|
+
|
870
1019
|
:param image: Path to the image to be copied
|
871
1020
|
"""
|
872
1021
|
self.clipboard_class.setImage(QImage(image), QClipboard.Clipboard)
|
@@ -874,11 +1023,11 @@ class Pyloid(QApplication):
|
|
874
1023
|
def get_clipboard_image(self):
|
875
1024
|
"""
|
876
1025
|
Retrieves an image from the clipboard.
|
877
|
-
|
1026
|
+
|
878
1027
|
:return: QImage object from the clipboard (None if no image)
|
879
1028
|
"""
|
880
1029
|
return self.clipboard_class.image()
|
881
|
-
|
1030
|
+
|
882
1031
|
###########################################################################################
|
883
1032
|
# Atostart
|
884
1033
|
###########################################################################################
|
@@ -887,7 +1036,7 @@ class Pyloid(QApplication):
|
|
887
1036
|
Sets the application to start automatically with the system. (set_auto_start(True) only works in production)
|
888
1037
|
True only works in production.
|
889
1038
|
False works in both environments.
|
890
|
-
|
1039
|
+
|
891
1040
|
:param enable: True to enable auto-start, False to disable
|
892
1041
|
"""
|
893
1042
|
if not enable:
|
@@ -899,7 +1048,9 @@ class Pyloid(QApplication):
|
|
899
1048
|
self.auto_start.set_auto_start(True)
|
900
1049
|
return True
|
901
1050
|
else:
|
902
|
-
print(
|
1051
|
+
print(
|
1052
|
+
"\033[93mset_auto_start(True) is not supported in non-production environment\033[0m"
|
1053
|
+
)
|
903
1054
|
return None
|
904
1055
|
|
905
1056
|
def is_auto_start(self):
|
@@ -908,7 +1059,83 @@ class Pyloid(QApplication):
|
|
908
1059
|
|
909
1060
|
:return: True if auto-start is enabled, False otherwise
|
910
1061
|
"""
|
911
|
-
|
1062
|
+
|
912
1063
|
return self.auto_start.is_auto_start()
|
913
|
-
|
914
1064
|
|
1065
|
+
###########################################################################################
|
1066
|
+
# File watcher
|
1067
|
+
###########################################################################################
|
1068
|
+
def watch_file(self, file_path: str) -> bool:
|
1069
|
+
"""
|
1070
|
+
Adds a file to the watch list.
|
1071
|
+
|
1072
|
+
:param file_path: Path of the file to watch
|
1073
|
+
:return: True if the file was successfully added to the watch list, False otherwise
|
1074
|
+
"""
|
1075
|
+
return self.file_watcher.add_path(file_path)
|
1076
|
+
|
1077
|
+
def watch_directory(self, dir_path: str) -> bool:
|
1078
|
+
"""
|
1079
|
+
Adds a directory to the watch list.
|
1080
|
+
|
1081
|
+
:param dir_path: Path of the directory to watch
|
1082
|
+
:return: True if the directory was successfully added to the watch list, False otherwise
|
1083
|
+
"""
|
1084
|
+
return self.file_watcher.add_path(dir_path)
|
1085
|
+
|
1086
|
+
def stop_watching(self, path: str) -> bool:
|
1087
|
+
"""
|
1088
|
+
Removes a file or directory from the watch list.
|
1089
|
+
|
1090
|
+
:param path: Path of the file or directory to stop watching
|
1091
|
+
:return: True if the path was successfully removed from the watch list, False otherwise
|
1092
|
+
"""
|
1093
|
+
return self.file_watcher.remove_path(path)
|
1094
|
+
|
1095
|
+
def get_watched_paths(self) -> List[str]:
|
1096
|
+
"""
|
1097
|
+
Returns all currently watched paths.
|
1098
|
+
|
1099
|
+
:return: List of all watched paths
|
1100
|
+
"""
|
1101
|
+
return self.file_watcher.get_watched_paths()
|
1102
|
+
|
1103
|
+
def get_watched_files(self) -> List[str]:
|
1104
|
+
"""
|
1105
|
+
Returns all currently watched files.
|
1106
|
+
|
1107
|
+
:return: List of all watched files
|
1108
|
+
"""
|
1109
|
+
return self.file_watcher.get_watched_files()
|
1110
|
+
|
1111
|
+
def get_watched_directories(self) -> List[str]:
|
1112
|
+
"""
|
1113
|
+
Returns all currently watched directories.
|
1114
|
+
|
1115
|
+
:return: List of all watched directories
|
1116
|
+
"""
|
1117
|
+
return self.file_watcher.get_watched_directories()
|
1118
|
+
|
1119
|
+
def remove_all_watched_paths(self) -> None:
|
1120
|
+
"""
|
1121
|
+
Removes all paths from the watch list.
|
1122
|
+
|
1123
|
+
:return: None
|
1124
|
+
"""
|
1125
|
+
self.file_watcher.remove_all_paths()
|
1126
|
+
|
1127
|
+
def set_file_change_callback(self, callback: Callable[[str], None]) -> None:
|
1128
|
+
"""
|
1129
|
+
Sets the callback function to be called when a file changes.
|
1130
|
+
|
1131
|
+
:param callback: Function to be called when a file changes
|
1132
|
+
"""
|
1133
|
+
self.file_watcher.file_changed.connect(callback)
|
1134
|
+
|
1135
|
+
def set_directory_change_callback(self, callback: Callable[[str], None]) -> None:
|
1136
|
+
"""
|
1137
|
+
Sets the callback function to be called when a directory changes.
|
1138
|
+
|
1139
|
+
:param callback: Function to be called when a directory changes
|
1140
|
+
"""
|
1141
|
+
self.file_watcher.directory_changed.connect(callback)
|
@@ -198,4 +198,4 @@ Apache License
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
200
|
See the License for the specific language governing permissions and
|
201
|
-
limitations under the License.
|
201
|
+
limitations under the License.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
pyloid/__init__.py,sha256=98jn3uaX1hB0aZCmFFLYBIswGmauIBHNYX2ZXQC4KjA,246
|
2
|
+
pyloid/api.py,sha256=whgfvPr1A6iwZ1Ewo-0FnOUNnt1K58c-P7YjzuQHcUM,194
|
3
|
+
pyloid/autostart.py,sha256=K7DQYl4LHItvPp0bt1V9WwaaZmVSTeGvadkcwG-KKrI,3899
|
4
|
+
pyloid/filewatcher.py,sha256=n8N56D65le5TpsgxXb7z-FO_0lqv4UYD4yGq_UuMrAs,1285
|
5
|
+
pyloid/monitor.py,sha256=fqDnZ_7dpxVZLVJ5gCluDRY2USrQ5YL_fw1AnYivhsk,12741
|
6
|
+
pyloid/pyloid.py,sha256=XQ6Tht37P0W1io9B4bA163TYSsL41msYmurx6X7fHtI,40227
|
7
|
+
pyloid/tray.py,sha256=rXgdkvzGxtie_EIcTSA7fjuta4nJk5THhNkGFcfv5Ew,634
|
8
|
+
pyloid/utils.py,sha256=DQerZWU_0o8dHcJ5y3yXf9i5OXn7KQZqU-hVBq3uPUA,711
|
9
|
+
pyloid-0.10.0.dist-info/LICENSE,sha256=MTYF-6xpRekyTUglRweWtbfbwBL1I_3Bgfbm_SNOuI8,11525
|
10
|
+
pyloid-0.10.0.dist-info/METADATA,sha256=rEpRbZ71xxDl9X9NB7w6lIr70uAXOrzveQ_dPb4SO8s,6068
|
11
|
+
pyloid-0.10.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
12
|
+
pyloid-0.10.0.dist-info/RECORD,,
|
pyloid-0.9.8.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
pyloid/__init__.py,sha256=98jn3uaX1hB0aZCmFFLYBIswGmauIBHNYX2ZXQC4KjA,246
|
2
|
-
pyloid/api.py,sha256=whgfvPr1A6iwZ1Ewo-0FnOUNnt1K58c-P7YjzuQHcUM,194
|
3
|
-
pyloid/autostart.py,sha256=K7DQYl4LHItvPp0bt1V9WwaaZmVSTeGvadkcwG-KKrI,3899
|
4
|
-
pyloid/monitor.py,sha256=fqDnZ_7dpxVZLVJ5gCluDRY2USrQ5YL_fw1AnYivhsk,12741
|
5
|
-
pyloid/pyloid.py,sha256=vHkXROI6rb4KC_Ij17rkJNtntG7DXwhNH3eOt9fwrX0,32997
|
6
|
-
pyloid/tray.py,sha256=rXgdkvzGxtie_EIcTSA7fjuta4nJk5THhNkGFcfv5Ew,634
|
7
|
-
pyloid/utils.py,sha256=DQerZWU_0o8dHcJ5y3yXf9i5OXn7KQZqU-hVBq3uPUA,711
|
8
|
-
pyloid-0.9.8.dist-info/LICENSE,sha256=F96EzotgWhhpnQTW2TcdoqrMDir1jyEo6H915tGQ-QE,11524
|
9
|
-
pyloid-0.9.8.dist-info/METADATA,sha256=VWsL6TzglQbZ9_AZpnfZhOJXiRBKAy_TCxypE239PTI,6067
|
10
|
-
pyloid-0.9.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
11
|
-
pyloid-0.9.8.dist-info/RECORD,,
|
File without changes
|