pyloid 0.9.7__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 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['QTWEBENGINE_DICTIONARIES_PATH'] = '/'
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, 'vulkan_warning_shown') and (('Failed to load vulkan' in message) or ('No Vulkan library available' in message) or ('Failed to create platform Vulkan instance' in message)):
32
- print('\033[93mPyloid Warning: Vulkan GPU API issue detected. Switching to software backend.\033[0m')
33
- os.environ['QT_QUICK_BACKEND'] = 'software'
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 'vulkan' not in message.lower():
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="pylon app",
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(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
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) # absolute 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(self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
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(self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
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__(self, app_name, single_instance=True, icon_path: str=None, tray_icon_path: str=None):
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(self._create_window_signal_function)
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
- """Sets the icon for the application."""
568
- self.icon = QIcon(icon_path)
591
+ """
592
+ Dynamically sets the application's icon.
569
593
 
570
- def set_tray_icon(self, tray_icon_path: str):
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
- def set_tray_menu_items(self, tray_menu_items: Dict[str, Callable]):
575
- """Sets the menu items for the tray icon."""
576
- self.tray_menu_items = tray_menu_items
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="pylon app",
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(main_window._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
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(main_window._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
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(window._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
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 run_tray(self):
780
- """Sets up the system tray icon and menu."""
781
- if not hasattr(self, 'tray'):
782
- self.tray = QSystemTrayIcon(self)
783
- if self.tray_icon:
784
- self.tray.setIcon(self.tray_icon)
785
- else:
786
- if self.icon:
787
- self.tray.setIcon(self.icon)
788
- else:
789
- print("Icon and Tray icon are not set.")
790
-
791
- tray_menu = QMenu()
792
-
793
- # Add menu items from external source
794
- if self.tray_menu_items:
795
- for item in self.tray_menu_items:
796
- action = tray_menu.addAction(item["label"])
797
- action.triggered.connect(item["callback"])
798
-
799
- self.tray.setContextMenu(tray_menu)
800
- self.tray.activated.connect(self._tray_activated)
801
- self.tray.show()
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 the event when the tray icon is activated."""
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
- Sets the actions for tray icon activation.
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 where keys are TrayEvent enum values,
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
- """Displays a notification in the system tray."""
821
- if not hasattr(self, 'tray'):
822
- self.run_tray() # Ensure the tray is initialized
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 = [Monitor(index, screen) for index, screen in enumerate(self.screens())]
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("\033[93mset_auto_start(True) is not supported in non-production environment\033[0m")
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyloid
3
- Version: 0.9.7
3
+ Version: 0.10.0
4
4
  Summary:
5
5
  Author: aesthetics-of-record
6
6
  Author-email: 111675679+aesthetics-of-record@users.noreply.github.com
@@ -42,13 +42,13 @@ With Pyloid, you can leverage the full power of Python in your desktop applicati
42
42
 
43
43
  ### Create Project 📦
44
44
 
45
- #### Creating a HTML/CSS/JS + Pylon Project 🌐
45
+ #### Creating a HTML/CSS/JS + Pyloid Project 🌐
46
46
 
47
- [https://github.com/pylonic/Pyloid_html_boilerplate](https://github.com/Pyloid/Pyloid_html_boilerplate)
47
+ [https://github.com/pylonic/pyloid_html_boilerplate](https://github.com/Pyloid/pyloid_html_boilerplate)
48
48
 
49
- #### Creating a React + Vite + Pylon Project ⚛️
49
+ #### Creating a React + Vite + Pyloid Project ⚛️
50
50
 
51
- [https://github.com/pylonic/Pyloid_react_boilerplate](https://github.com/Pyloid/Pyloid_react_boilerplate)
51
+ [https://github.com/pylonic/pyloid_react_boilerplate](https://github.com/Pyloid/yloid_react_boilerplate)
52
52
 
53
53
  ### Custom Your Boilerplate 🔨
54
54
 
@@ -75,7 +75,7 @@ else:
75
75
 
76
76
  # create window
77
77
  window = app.create_window(
78
- title="Pyloid Browser1",
78
+ title="Pyloid Browser",
79
79
  js_apis=[CustomAPI()],
80
80
  dev_tools=True
81
81
  )
@@ -164,7 +164,7 @@ document.addEventListener('pyloidReady', function () {
164
164
  });
165
165
 
166
166
  function App() {
167
- console.log('Pylon is ready');
167
+ console.log('Pyloid is ready');
168
168
 
169
169
  window.pyloid.CustomAPI.getAppVersion().then((version) => {
170
170
  console.log('App version:', version); // "App version: 1.0.0"
@@ -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,,
@@ -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.7.dist-info/LICENSE,sha256=F96EzotgWhhpnQTW2TcdoqrMDir1jyEo6H915tGQ-QE,11524
9
- pyloid-0.9.7.dist-info/METADATA,sha256=lXWpbeBLh6UQRip1xQa2MY7MJex7fNAQhMEzZOlDhcc,6066
10
- pyloid-0.9.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
11
- pyloid-0.9.7.dist-info/RECORD,,