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