pyloid 0.13.1__py3-none-any.whl → 0.14.1__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/api.py +85 -2
- pyloid/browser_window.py +1190 -0
- pyloid/filewatcher.py +137 -9
- pyloid/js_api/event_api.py +25 -0
- pyloid/js_api/window_api.py +166 -0
- pyloid/monitor.py +602 -77
- pyloid/pyloid.py +742 -892
- pyloid/timer.py +219 -35
- pyloid/tray.py +30 -0
- pyloid/utils.py +55 -6
- {pyloid-0.13.1.dist-info → pyloid-0.14.1.dist-info}/METADATA +1 -1
- pyloid-0.14.1.dist-info/RECORD +17 -0
- pyloid-0.13.1.dist-info/RECORD +0 -14
- {pyloid-0.13.1.dist-info → pyloid-0.14.1.dist-info}/LICENSE +0 -0
- {pyloid-0.13.1.dist-info → pyloid-0.14.1.dist-info}/WHEEL +0 -0
pyloid/pyloid.py
CHANGED
@@ -2,40 +2,29 @@ import sys
|
|
2
2
|
import os
|
3
3
|
from PySide6.QtWidgets import (
|
4
4
|
QApplication,
|
5
|
-
QMainWindow,
|
6
5
|
QSystemTrayIcon,
|
7
6
|
QMenu,
|
7
|
+
QFileDialog,
|
8
8
|
)
|
9
|
-
from PySide6.QtWebEngineWidgets import QWebEngineView
|
10
|
-
from PySide6.QtWebChannel import QWebChannel
|
11
9
|
from PySide6.QtGui import (
|
12
10
|
QIcon,
|
13
|
-
QKeySequence,
|
14
|
-
QShortcut,
|
15
11
|
QClipboard,
|
16
12
|
QImage,
|
17
13
|
QAction,
|
18
|
-
QCursor,
|
19
14
|
)
|
20
|
-
from PySide6.QtCore import Qt, Signal,
|
15
|
+
from PySide6.QtCore import Qt, Signal, QObject, QTimer
|
21
16
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
22
|
-
from
|
23
|
-
from
|
24
|
-
import uuid
|
25
|
-
from typing import List, Optional, Dict, Callable, Union, Any
|
17
|
+
from .api import PyloidAPI
|
18
|
+
from typing import List, Optional, Dict, Callable, Union
|
26
19
|
from PySide6.QtCore import qInstallMessageHandler
|
27
20
|
import signal
|
28
21
|
from .utils import is_production
|
29
22
|
from .monitor import Monitor
|
30
|
-
import json
|
31
23
|
from .autostart import AutoStart
|
32
24
|
from .filewatcher import FileWatcher
|
33
25
|
import logging
|
34
|
-
from
|
35
|
-
|
36
|
-
QVBoxLayout,
|
37
|
-
)
|
38
|
-
from .custom.titlebar import CustomTitleBar
|
26
|
+
from .browser_window import BrowserWindow
|
27
|
+
from .tray import TrayEvent
|
39
28
|
|
40
29
|
# for linux debug
|
41
30
|
os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
|
@@ -67,809 +56,6 @@ def custom_message_handler(mode, context, message):
|
|
67
56
|
|
68
57
|
qInstallMessageHandler(custom_message_handler)
|
69
58
|
|
70
|
-
|
71
|
-
class WindowAPI(PyloidAPI):
|
72
|
-
def __init__(self, window_id: str, app):
|
73
|
-
super().__init__()
|
74
|
-
self.window_id: str = window_id
|
75
|
-
self.app: Pyloid = app
|
76
|
-
|
77
|
-
@Bridge(result=str)
|
78
|
-
def getWindowId(self):
|
79
|
-
"""Returns the current window ID."""
|
80
|
-
return self.window_id
|
81
|
-
|
82
|
-
@Bridge(result=dict)
|
83
|
-
def getWindowProperties(self):
|
84
|
-
"""Returns the properties of the window."""
|
85
|
-
window = self.app.get_window_by_id(self.window_id)
|
86
|
-
window_properties = window.get_window_properties()
|
87
|
-
return window_properties
|
88
|
-
|
89
|
-
@Bridge()
|
90
|
-
def close(self):
|
91
|
-
"""Closes the window."""
|
92
|
-
window = self.app.get_window_by_id(self.window_id)
|
93
|
-
if window:
|
94
|
-
window.close()
|
95
|
-
|
96
|
-
@Bridge()
|
97
|
-
def hide(self):
|
98
|
-
"""Hides the window."""
|
99
|
-
window = self.app.get_window_by_id(self.window_id)
|
100
|
-
if window:
|
101
|
-
window.hide()
|
102
|
-
|
103
|
-
@Bridge()
|
104
|
-
def show(self):
|
105
|
-
"""Shows and focuses the window."""
|
106
|
-
window = self.app.get_window_by_id(self.window_id)
|
107
|
-
if window:
|
108
|
-
window.show()
|
109
|
-
|
110
|
-
@Bridge()
|
111
|
-
def toggleFullscreen(self):
|
112
|
-
"""Toggles fullscreen mode for the window."""
|
113
|
-
window = self.app.get_window_by_id(self.window_id)
|
114
|
-
if window:
|
115
|
-
window.toggle_fullscreen()
|
116
|
-
|
117
|
-
@Bridge()
|
118
|
-
def minimize(self):
|
119
|
-
"""Minimizes the window."""
|
120
|
-
window = self.app.get_window_by_id(self.window_id)
|
121
|
-
if window:
|
122
|
-
window.minimize()
|
123
|
-
|
124
|
-
@Bridge()
|
125
|
-
def maximize(self):
|
126
|
-
"""Maximizes the window."""
|
127
|
-
window = self.app.get_window_by_id(self.window_id)
|
128
|
-
if window:
|
129
|
-
window.maximize()
|
130
|
-
|
131
|
-
@Bridge()
|
132
|
-
def unmaximize(self):
|
133
|
-
"""Restores the window to its normal state."""
|
134
|
-
window = self.app.get_window_by_id(self.window_id)
|
135
|
-
if window:
|
136
|
-
window.unmaximize()
|
137
|
-
|
138
|
-
@Bridge(str)
|
139
|
-
def setTitle(self, title: str):
|
140
|
-
"""Sets the title of the window."""
|
141
|
-
window = self.app.get_window_by_id(self.window_id)
|
142
|
-
if window:
|
143
|
-
window.set_title(title)
|
144
|
-
|
145
|
-
@Bridge(int, int)
|
146
|
-
def setSize(self, width: int, height: int):
|
147
|
-
"""Sets the size of the window."""
|
148
|
-
window = self.app.get_window_by_id(self.window_id)
|
149
|
-
if window:
|
150
|
-
window.set_size(width, height)
|
151
|
-
|
152
|
-
@Bridge(int, int)
|
153
|
-
def setPosition(self, x: int, y: int):
|
154
|
-
"""Sets the position of the window."""
|
155
|
-
window = self.app.get_window_by_id(self.window_id)
|
156
|
-
if window:
|
157
|
-
window.set_position(x, y)
|
158
|
-
|
159
|
-
@Bridge(bool)
|
160
|
-
def setFrame(self, frame: bool):
|
161
|
-
"""Sets the frame of the window."""
|
162
|
-
window = self.app.get_window_by_id(self.window_id)
|
163
|
-
if window:
|
164
|
-
window.set_frame(frame)
|
165
|
-
|
166
|
-
@Bridge(bool)
|
167
|
-
def setContextMenu(self, context_menu: bool):
|
168
|
-
"""Sets the context menu of the window."""
|
169
|
-
window = self.app.get_window_by_id(self.window_id)
|
170
|
-
if window:
|
171
|
-
window.set_context_menu(context_menu)
|
172
|
-
|
173
|
-
@Bridge(bool)
|
174
|
-
def setDevTools(self, enable: bool):
|
175
|
-
"""Sets the developer tools of the window."""
|
176
|
-
window = self.app.get_window_by_id(self.window_id)
|
177
|
-
if window:
|
178
|
-
window.set_dev_tools(enable)
|
179
|
-
|
180
|
-
@Bridge(str, result=Optional[str])
|
181
|
-
def capture(self, save_path: str) -> Optional[str]:
|
182
|
-
"""Captures the current window."""
|
183
|
-
window = self.app.get_window_by_id(self.window_id)
|
184
|
-
if window:
|
185
|
-
return window.capture(save_path)
|
186
|
-
return None
|
187
|
-
|
188
|
-
@Bridge(result=bool)
|
189
|
-
def getFrame(self):
|
190
|
-
"""Returns whether the window has a frame."""
|
191
|
-
window = self.app.get_window_by_id(self.window_id)
|
192
|
-
return window.frame if window else False
|
193
|
-
|
194
|
-
@Bridge(result=bool)
|
195
|
-
def getContextMenu(self):
|
196
|
-
"""Returns whether the window has a context menu."""
|
197
|
-
window = self.app.get_window_by_id(self.window_id)
|
198
|
-
return window.context_menu if window else False
|
199
|
-
|
200
|
-
@Bridge(result=bool)
|
201
|
-
def getDevTools(self):
|
202
|
-
"""Returns whether the window has developer tools."""
|
203
|
-
window = self.app.get_window_by_id(self.window_id)
|
204
|
-
return window.dev_tools if window else False
|
205
|
-
|
206
|
-
@Bridge(result=str)
|
207
|
-
def getTitle(self):
|
208
|
-
"""Returns the title of the window."""
|
209
|
-
window = self.app.get_window_by_id(self.window_id)
|
210
|
-
return window.title if window else ""
|
211
|
-
|
212
|
-
@Bridge(result=dict)
|
213
|
-
def getSize(self):
|
214
|
-
"""Returns the size of the window."""
|
215
|
-
window = self.app.get_window_by_id(self.window_id)
|
216
|
-
return (
|
217
|
-
{"width": window.width, "height": window.height}
|
218
|
-
if window
|
219
|
-
else {"width": 0, "height": 0}
|
220
|
-
)
|
221
|
-
|
222
|
-
@Bridge(result=dict)
|
223
|
-
def getPosition(self):
|
224
|
-
"""Returns the position of the window."""
|
225
|
-
window = self.app.get_window_by_id(self.window_id)
|
226
|
-
return {"x": window.x, "y": window.y} if window else {"x": 0, "y": 0}
|
227
|
-
|
228
|
-
@Bridge()
|
229
|
-
def startSystemDrag(self):
|
230
|
-
"""Starts the system drag."""
|
231
|
-
window = self.app.get_window_by_id(self.window_id)
|
232
|
-
if window:
|
233
|
-
window.web_view.start_system_drag()
|
234
|
-
|
235
|
-
|
236
|
-
# class EventAPI(PylonAPI):
|
237
|
-
# def __init__(self, window_id: str, app):
|
238
|
-
# super().__init__()
|
239
|
-
# self.window_id: str = window_id
|
240
|
-
# self.app: PylonApp = app
|
241
|
-
# self.subscribers = {}
|
242
|
-
|
243
|
-
# @Bridge(str, Callable)
|
244
|
-
# def on(self, event_name: str, callback: Callable):
|
245
|
-
# """특정 이벤트를 구독합니다."""
|
246
|
-
# if event_name not in self.subscribers:
|
247
|
-
# self.subscribers[event_name] = []
|
248
|
-
# self.subscribers[event_name].append(callback)
|
249
|
-
|
250
|
-
# @Bridge(str, result=Optional[str])
|
251
|
-
# def emit(self, event_name: str, *args, **kwargs):
|
252
|
-
# """다른 윈도우로 특정 이벤트를 보냅니다."""
|
253
|
-
# if event_name in self.subscribers:
|
254
|
-
# for callback in self.subscribers[event_name]:
|
255
|
-
# callback(*args, **kwargs)
|
256
|
-
|
257
|
-
|
258
|
-
# 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
|
259
|
-
class CustomWebEngineView(QWebEngineView):
|
260
|
-
def __init__(self, parent=None):
|
261
|
-
super().__init__(parent._window)
|
262
|
-
self.parent = parent
|
263
|
-
self.drag_relative_position = None
|
264
|
-
self.is_dragging = False
|
265
|
-
self.is_resizing = False
|
266
|
-
self.resize_start_pos = None
|
267
|
-
self.resize_direction = None
|
268
|
-
self.screen_geometry = self.screen().availableGeometry()
|
269
|
-
self.is_resizing_enabled = True
|
270
|
-
|
271
|
-
def mouse_press_event(self, event):
|
272
|
-
if event.button() == Qt.LeftButton:
|
273
|
-
self.drag_relative_position = event.pos()
|
274
|
-
if not self.parent.frame and self.is_resizing_enabled:
|
275
|
-
self.resize_direction = self.get_resize_direction(event.pos())
|
276
|
-
if self.resize_direction:
|
277
|
-
self.is_resizing = True
|
278
|
-
self.resize_start_pos = event.globalPos()
|
279
|
-
|
280
|
-
def start_system_drag(self):
|
281
|
-
self.is_dragging = True
|
282
|
-
|
283
|
-
def mouse_move_event(self, event):
|
284
|
-
if self.is_resizing and self.is_resizing_enabled:
|
285
|
-
self.resize_window(event.globalPos())
|
286
|
-
elif not self.parent.frame and self.is_dragging:
|
287
|
-
# 현재 마우스 위치를 전역 좌표로 가져옵니다
|
288
|
-
current_global_pos = event.globalPos()
|
289
|
-
|
290
|
-
# 화면 경계를 계산합니다
|
291
|
-
left_boundary = self.screen_geometry.left()
|
292
|
-
right_boundary = self.screen_geometry.right()
|
293
|
-
top_boundary = self.screen_geometry.top()
|
294
|
-
bottom_boundary = self.screen_geometry.bottom()
|
295
|
-
|
296
|
-
# 마우스 커서 위치를 제한합니다
|
297
|
-
new_cursor_pos = QPoint(
|
298
|
-
max(left_boundary, min(current_global_pos.x(), right_boundary)),
|
299
|
-
max(top_boundary, min(current_global_pos.y(), bottom_boundary)),
|
300
|
-
)
|
301
|
-
|
302
|
-
# 마우스 커서를 새 위치로 이동합니다
|
303
|
-
QCursor.setPos(new_cursor_pos)
|
304
|
-
|
305
|
-
# 창의 새 위치를 계산합니다
|
306
|
-
new_window_pos = new_cursor_pos - self.drag_relative_position
|
307
|
-
|
308
|
-
# 창을 새 위치로 이동합니다
|
309
|
-
self.parent._window.move(new_window_pos)
|
310
|
-
else:
|
311
|
-
# Change cursor based on resize direction
|
312
|
-
resize_direction = self.get_resize_direction(event.pos())
|
313
|
-
if resize_direction and self.is_resizing_enabled:
|
314
|
-
self.set_cursor_for_resize_direction(resize_direction)
|
315
|
-
else:
|
316
|
-
self.unsetCursor()
|
317
|
-
|
318
|
-
def mouse_release_event(self, event):
|
319
|
-
if event.button() == Qt.LeftButton:
|
320
|
-
self.is_dragging = False
|
321
|
-
self.is_resizing = False
|
322
|
-
self.resize_direction = None
|
323
|
-
self.unsetCursor()
|
324
|
-
|
325
|
-
def eventFilter(self, source, event):
|
326
|
-
if self.focusProxy() is source:
|
327
|
-
if event.type() == QEvent.MouseButtonPress:
|
328
|
-
self.mouse_press_event(event)
|
329
|
-
elif event.type() == QEvent.MouseMove:
|
330
|
-
self.mouse_move_event(event)
|
331
|
-
elif event.type() == QEvent.MouseButtonRelease:
|
332
|
-
self.mouse_release_event(event)
|
333
|
-
return super().eventFilter(source, event)
|
334
|
-
|
335
|
-
def get_resize_direction(self, pos):
|
336
|
-
if not self.parent.frame and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
|
337
|
-
margin = 5 # Margin in pixels to detect edge
|
338
|
-
rect = self.rect()
|
339
|
-
direction = None
|
340
|
-
|
341
|
-
if pos.x() <= margin:
|
342
|
-
direction = 'left'
|
343
|
-
elif pos.x() >= rect.width() - margin:
|
344
|
-
direction = 'right'
|
345
|
-
|
346
|
-
if pos.y() <= margin:
|
347
|
-
direction = 'top' if direction is None else direction + '-top'
|
348
|
-
elif pos.y() >= rect.height() - margin:
|
349
|
-
direction = 'bottom' if direction is None else direction + '-bottom'
|
350
|
-
|
351
|
-
return direction
|
352
|
-
return None
|
353
|
-
|
354
|
-
def set_cursor_for_resize_direction(self, direction):
|
355
|
-
if not self.parent.frame and direction and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
|
356
|
-
if direction in ['left', 'right']:
|
357
|
-
self.setCursor(Qt.SizeHorCursor)
|
358
|
-
elif direction in ['top', 'bottom']:
|
359
|
-
self.setCursor(Qt.SizeVerCursor)
|
360
|
-
elif direction in ['left-top', 'right-bottom']:
|
361
|
-
self.setCursor(Qt.SizeFDiagCursor)
|
362
|
-
elif direction in ['right-top', 'left-bottom']:
|
363
|
-
self.setCursor(Qt.SizeBDiagCursor)
|
364
|
-
|
365
|
-
def resize_window(self, global_pos):
|
366
|
-
if not self.parent.frame and self.resize_start_pos and self.resize_direction and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
|
367
|
-
delta = global_pos - self.resize_start_pos
|
368
|
-
new_geometry = self.parent._window.geometry()
|
369
|
-
|
370
|
-
if 'left' in self.resize_direction:
|
371
|
-
new_geometry.setLeft(new_geometry.left() + delta.x())
|
372
|
-
if 'right' in self.resize_direction:
|
373
|
-
new_geometry.setRight(new_geometry.right() + delta.x())
|
374
|
-
if 'top' in self.resize_direction:
|
375
|
-
new_geometry.setTop(new_geometry.top() + delta.y())
|
376
|
-
if 'bottom' in self.resize_direction:
|
377
|
-
new_geometry.setBottom(new_geometry.bottom() + delta.y())
|
378
|
-
|
379
|
-
self.parent._window.setGeometry(new_geometry)
|
380
|
-
self.resize_start_pos = global_pos
|
381
|
-
|
382
|
-
|
383
|
-
class BrowserWindow:
|
384
|
-
def __init__(
|
385
|
-
self,
|
386
|
-
app,
|
387
|
-
title: str = "pyloid app",
|
388
|
-
width: int = 800,
|
389
|
-
height: int = 600,
|
390
|
-
x: int = 200,
|
391
|
-
y: int = 200,
|
392
|
-
frame: bool = True,
|
393
|
-
context_menu: bool = False,
|
394
|
-
dev_tools: bool = False,
|
395
|
-
js_apis: List[PyloidAPI] = [],
|
396
|
-
):
|
397
|
-
###########################################################################################
|
398
|
-
self.id = str(uuid.uuid4()) # Generate unique ID
|
399
|
-
self._window = QMainWindow()
|
400
|
-
self.web_view = CustomWebEngineView(self)
|
401
|
-
|
402
|
-
self._window.closeEvent = self.closeEvent # Override closeEvent method
|
403
|
-
###########################################################################################
|
404
|
-
self.app = app
|
405
|
-
self.title = title
|
406
|
-
self.width = width
|
407
|
-
self.height = height
|
408
|
-
self.x = x
|
409
|
-
self.y = y
|
410
|
-
self.frame = frame
|
411
|
-
self.context_menu = context_menu
|
412
|
-
self.dev_tools = dev_tools
|
413
|
-
self.js_apis = [WindowAPI(self.id, self.app)]
|
414
|
-
for js_api in js_apis:
|
415
|
-
self.js_apis.append(js_api)
|
416
|
-
self.shortcuts = {}
|
417
|
-
###########################################################################################
|
418
|
-
|
419
|
-
def _set_custom_frame(
|
420
|
-
self,
|
421
|
-
use_custom: bool,
|
422
|
-
title: str = "Custom Title",
|
423
|
-
bg_color: str = "darkblue",
|
424
|
-
text_color: str = "white",
|
425
|
-
icon_path: str = None,
|
426
|
-
):
|
427
|
-
"""Sets or removes the custom frame."""
|
428
|
-
if use_custom:
|
429
|
-
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
430
|
-
self.custom_title_bar = CustomTitleBar(self._window)
|
431
|
-
self.custom_title_bar.set_style(bg_color, text_color)
|
432
|
-
self.custom_title_bar.set_title(title)
|
433
|
-
|
434
|
-
if icon_path:
|
435
|
-
self.custom_title_bar.set_icon(icon_path)
|
436
|
-
|
437
|
-
layout = QVBoxLayout()
|
438
|
-
layout.setContentsMargins(0, 0, 0, 0)
|
439
|
-
layout.setSpacing(0)
|
440
|
-
layout.addWidget(self.custom_title_bar)
|
441
|
-
layout.addWidget(self.web_view)
|
442
|
-
|
443
|
-
central_widget = QWidget()
|
444
|
-
central_widget.setLayout(layout)
|
445
|
-
self._window.setCentralWidget(central_widget)
|
446
|
-
|
447
|
-
# Add properties for window movement
|
448
|
-
self._window.moving = False
|
449
|
-
self._window.offset = QPoint()
|
450
|
-
else:
|
451
|
-
self._window.setWindowFlags(Qt.Window)
|
452
|
-
self._window.setCentralWidget(self.web_view)
|
453
|
-
self.custom_title_bar = None
|
454
|
-
|
455
|
-
self._window.show()
|
456
|
-
|
457
|
-
def _load(self):
|
458
|
-
self.set_title(self.title)
|
459
|
-
|
460
|
-
self.set_size(self.width, self.height)
|
461
|
-
self.set_position(self.x, self.y)
|
462
|
-
|
463
|
-
# allow local file access to remote urls
|
464
|
-
self.web_view.settings().setAttribute(
|
465
|
-
QWebEngineSettings.LocalContentCanAccessRemoteUrls, True
|
466
|
-
)
|
467
|
-
|
468
|
-
# Set icon
|
469
|
-
if self.app.icon:
|
470
|
-
self._window.setWindowIcon(self.app.icon)
|
471
|
-
else:
|
472
|
-
print("Icon is not set.")
|
473
|
-
|
474
|
-
# Set Windows taskbar icon
|
475
|
-
if sys.platform == "win32":
|
476
|
-
import ctypes
|
477
|
-
|
478
|
-
myappid = "mycompany.myproduct.subproduct.version"
|
479
|
-
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
480
|
-
|
481
|
-
# Remove title bar and borders (if needed)
|
482
|
-
if not self.frame:
|
483
|
-
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
484
|
-
|
485
|
-
# Disable default context menu
|
486
|
-
if not self.context_menu:
|
487
|
-
self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
|
488
|
-
|
489
|
-
# Set up QWebChannel
|
490
|
-
self.channel = QWebChannel()
|
491
|
-
|
492
|
-
# Register additional JS APIs
|
493
|
-
if self.js_apis:
|
494
|
-
for js_api in self.js_apis:
|
495
|
-
self.channel.registerObject(js_api.__class__.__name__, js_api)
|
496
|
-
|
497
|
-
self.web_view.page().setWebChannel(self.channel)
|
498
|
-
|
499
|
-
# Connect pylonjs bridge
|
500
|
-
self.web_view.loadFinished.connect(self._on_load_finished)
|
501
|
-
|
502
|
-
# Add QWebEngineView to main window
|
503
|
-
self._window.setCentralWidget(self.web_view)
|
504
|
-
|
505
|
-
# Set F12 shortcut
|
506
|
-
self.set_dev_tools(self.dev_tools)
|
507
|
-
|
508
|
-
def _on_load_finished(self, ok):
|
509
|
-
"""Handles the event when the web page finishes loading."""
|
510
|
-
if ok and self.js_apis:
|
511
|
-
js_code = """
|
512
|
-
if (typeof QWebChannel !== 'undefined') {
|
513
|
-
new QWebChannel(qt.webChannelTransport, function (channel) {
|
514
|
-
window.pyloid = {
|
515
|
-
EventAPI: {
|
516
|
-
listen: function(eventName, callback) {
|
517
|
-
document.addEventListener(eventName, function(event) {
|
518
|
-
let eventData;
|
519
|
-
try {
|
520
|
-
eventData = JSON.parse(event.detail);
|
521
|
-
} catch (e) {
|
522
|
-
eventData = event.detail;
|
523
|
-
}
|
524
|
-
callback(eventData);
|
525
|
-
});
|
526
|
-
},
|
527
|
-
unlisten: function(eventName) {
|
528
|
-
document.removeEventListener(eventName);
|
529
|
-
}
|
530
|
-
}
|
531
|
-
};
|
532
|
-
console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
|
533
|
-
|
534
|
-
%s
|
535
|
-
|
536
|
-
document.addEventListener('mousedown', function (e) {
|
537
|
-
if (e.target.hasAttribute('data-pyloid-drag-region')) {
|
538
|
-
window.pyloid.WindowAPI.startSystemDrag();
|
539
|
-
}
|
540
|
-
});
|
541
|
-
|
542
|
-
// Dispatch a custom event to signal that the initialization is ready
|
543
|
-
const event = new CustomEvent('pyloidReady');
|
544
|
-
document.dispatchEvent(event);
|
545
|
-
});
|
546
|
-
} else {
|
547
|
-
console.error('QWebChannel is not defined.');
|
548
|
-
}
|
549
|
-
"""
|
550
|
-
js_api_init = "\n".join(
|
551
|
-
[
|
552
|
-
f"window.pyloid['{js_api.__class__.__name__}'] = channel.objects['{js_api.__class__.__name__}'];\n"
|
553
|
-
f"console.log('pyloid.{js_api.__class__.__name__} object initialized:', window.pyloid['{js_api.__class__.__name__}']);"
|
554
|
-
for js_api in self.js_apis
|
555
|
-
]
|
556
|
-
)
|
557
|
-
self.web_view.page().runJavaScript(js_code % js_api_init)
|
558
|
-
else:
|
559
|
-
pass
|
560
|
-
|
561
|
-
###########################################################################################
|
562
|
-
# Load
|
563
|
-
###########################################################################################
|
564
|
-
def load_file(self, file_path):
|
565
|
-
"""Loads a local HTML file into the web view."""
|
566
|
-
self._load()
|
567
|
-
file_path = os.path.abspath(file_path) # absolute path
|
568
|
-
self.web_view.setUrl(QUrl.fromLocalFile(file_path))
|
569
|
-
self.web_view.focusProxy().installEventFilter(self.web_view)
|
570
|
-
|
571
|
-
def load_url(self, url):
|
572
|
-
"""Sets the URL of the window."""
|
573
|
-
self._load()
|
574
|
-
self.web_view.setUrl(QUrl(url))
|
575
|
-
self.web_view.focusProxy().installEventFilter(self.web_view)
|
576
|
-
|
577
|
-
###########################################################################################
|
578
|
-
# Set Parameters
|
579
|
-
###########################################################################################
|
580
|
-
def set_title(self, title: str):
|
581
|
-
"""Sets the title of the window."""
|
582
|
-
self.title = title
|
583
|
-
self._window.setWindowTitle(self.title)
|
584
|
-
|
585
|
-
def set_size(self, width: int, height: int):
|
586
|
-
"""Sets the size of the window."""
|
587
|
-
self.width = width
|
588
|
-
self.height = height
|
589
|
-
self._window.setGeometry(self.x, self.y, self.width, self.height)
|
590
|
-
|
591
|
-
def set_position(self, x: int, y: int):
|
592
|
-
"""Sets the position of the window."""
|
593
|
-
self.x = x
|
594
|
-
self.y = y
|
595
|
-
self._window.setGeometry(self.x, self.y, self.width, self.height)
|
596
|
-
|
597
|
-
def set_frame(self, frame: bool):
|
598
|
-
"""Sets the frame of the window."""
|
599
|
-
self.frame = frame
|
600
|
-
was_visible = self._window.isVisible()
|
601
|
-
if self.frame:
|
602
|
-
self._window.setWindowFlags(Qt.Window)
|
603
|
-
else:
|
604
|
-
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
605
|
-
if was_visible:
|
606
|
-
self._window.show()
|
607
|
-
|
608
|
-
def set_context_menu(self, context_menu: bool):
|
609
|
-
"""Sets the context menu of the window."""
|
610
|
-
self.context_menu = context_menu
|
611
|
-
if self.context_menu:
|
612
|
-
self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
|
613
|
-
else:
|
614
|
-
self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
|
615
|
-
|
616
|
-
def set_dev_tools(self, enable: bool):
|
617
|
-
"""Sets the developer tools of the window.
|
618
|
-
|
619
|
-
If enabled, the developer tools can be opened using the F12 key.
|
620
|
-
"""
|
621
|
-
self.dev_tools = enable
|
622
|
-
if self.dev_tools:
|
623
|
-
self.add_shortcut("F12", self.open_dev_tools)
|
624
|
-
else:
|
625
|
-
self.remove_shortcut("F12")
|
626
|
-
|
627
|
-
def open_dev_tools(self):
|
628
|
-
"""Opens the developer tools window."""
|
629
|
-
self.web_view.page().setDevToolsPage(QWebEnginePage(self.web_view.page()))
|
630
|
-
self.dev_tools_window = QMainWindow(self._window)
|
631
|
-
dev_tools_view = QWebEngineView(self.dev_tools_window)
|
632
|
-
dev_tools_view.setPage(self.web_view.page().devToolsPage())
|
633
|
-
self.dev_tools_window.setCentralWidget(dev_tools_view)
|
634
|
-
self.dev_tools_window.resize(800, 600)
|
635
|
-
self.dev_tools_window.show()
|
636
|
-
|
637
|
-
# Add this line to handle dev tools window closure
|
638
|
-
self.dev_tools_window.closeEvent = lambda event: setattr(
|
639
|
-
self, "dev_tools_window", None
|
640
|
-
)
|
641
|
-
|
642
|
-
def closeEvent(self, event):
|
643
|
-
"""Handles the event when the window is closed."""
|
644
|
-
# Close developer tools if open
|
645
|
-
if hasattr(self, "dev_tools_window") and self.dev_tools_window:
|
646
|
-
self.dev_tools_window.close()
|
647
|
-
self.dev_tools_window = None
|
648
|
-
|
649
|
-
# Solve memory leak issue with web view engine
|
650
|
-
self.web_view.page().deleteLater()
|
651
|
-
self.web_view.deleteLater()
|
652
|
-
self._remove_from_app_windows()
|
653
|
-
event.accept() # Accept the event (allow the window to close)
|
654
|
-
|
655
|
-
def _remove_from_app_windows(self):
|
656
|
-
"""Removes the window from the app's window list."""
|
657
|
-
if self in self.app.windows:
|
658
|
-
self.app.windows.remove(self)
|
659
|
-
if not self.app.windows:
|
660
|
-
self.app.quit() # Quit the app if all windows are closed
|
661
|
-
|
662
|
-
###########################################################################################
|
663
|
-
# Window management (no ID required)
|
664
|
-
###########################################################################################
|
665
|
-
def hide(self):
|
666
|
-
"""Hides the window."""
|
667
|
-
self._window.hide()
|
668
|
-
|
669
|
-
def show(self):
|
670
|
-
"""Shows the window."""
|
671
|
-
self._window.show()
|
672
|
-
|
673
|
-
def focus(self):
|
674
|
-
"""Focuses the window."""
|
675
|
-
self._window.activateWindow()
|
676
|
-
self._window.raise_()
|
677
|
-
self._window.setWindowState(
|
678
|
-
self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
679
|
-
)
|
680
|
-
|
681
|
-
def show_and_focus(self):
|
682
|
-
"""Shows and focuses the window."""
|
683
|
-
self._window.show()
|
684
|
-
self._window.activateWindow()
|
685
|
-
self._window.raise_()
|
686
|
-
self._window.setWindowState(
|
687
|
-
self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
688
|
-
)
|
689
|
-
|
690
|
-
def close(self):
|
691
|
-
"""Closes the window."""
|
692
|
-
self._window.close()
|
693
|
-
|
694
|
-
def toggle_fullscreen(self):
|
695
|
-
"""Toggles fullscreen mode for the window."""
|
696
|
-
if self._window.isFullScreen():
|
697
|
-
self._window.showNormal()
|
698
|
-
else:
|
699
|
-
self._window.showFullScreen()
|
700
|
-
|
701
|
-
def minimize(self):
|
702
|
-
"""Minimizes the window."""
|
703
|
-
self._window.showMinimized()
|
704
|
-
|
705
|
-
def maximize(self):
|
706
|
-
"""Maximizes the window."""
|
707
|
-
self._window.showMaximized()
|
708
|
-
|
709
|
-
def unmaximize(self):
|
710
|
-
"""Unmaximizes the window."""
|
711
|
-
self._window.showNormal()
|
712
|
-
|
713
|
-
def capture(self, save_path: str) -> Optional[str]:
|
714
|
-
"""
|
715
|
-
Captures the current window.
|
716
|
-
|
717
|
-
:param save_path: Path to save the captured image. If not specified, it will be saved in the current directory.
|
718
|
-
:return: Path of the saved image
|
719
|
-
"""
|
720
|
-
try:
|
721
|
-
# Capture window
|
722
|
-
screenshot = self._window.grab()
|
723
|
-
|
724
|
-
# Save image
|
725
|
-
screenshot.save(save_path)
|
726
|
-
return save_path
|
727
|
-
except Exception as e:
|
728
|
-
print(f"Error occurred while capturing the window: {e}")
|
729
|
-
return None
|
730
|
-
|
731
|
-
###########################################################################################
|
732
|
-
# Shortcut
|
733
|
-
###########################################################################################
|
734
|
-
def add_shortcut(self, key_sequence: str, callback: Callable):
|
735
|
-
"""
|
736
|
-
Adds a keyboard shortcut to the window if it does not already exist.
|
737
|
-
|
738
|
-
:param key_sequence: Shortcut sequence (e.g., "Ctrl+C")
|
739
|
-
:param callback: Function to be executed when the shortcut is pressed
|
740
|
-
:return: Created QShortcut object or None if the shortcut already exists
|
741
|
-
"""
|
742
|
-
if key_sequence in self.shortcuts:
|
743
|
-
# print(f"Shortcut {key_sequence} already exists.")
|
744
|
-
return None
|
745
|
-
|
746
|
-
shortcut = QShortcut(QKeySequence(key_sequence), self._window)
|
747
|
-
shortcut.activated.connect(callback)
|
748
|
-
self.shortcuts[key_sequence] = shortcut
|
749
|
-
return shortcut
|
750
|
-
|
751
|
-
def remove_shortcut(self, key_sequence: str):
|
752
|
-
"""
|
753
|
-
Removes a keyboard shortcut from the window.
|
754
|
-
|
755
|
-
:param key_sequence: Shortcut sequence to be removed
|
756
|
-
"""
|
757
|
-
if key_sequence in self.shortcuts:
|
758
|
-
shortcut = self.shortcuts.pop(key_sequence)
|
759
|
-
shortcut.setEnabled(False)
|
760
|
-
shortcut.deleteLater()
|
761
|
-
|
762
|
-
def get_all_shortcuts(self):
|
763
|
-
"""
|
764
|
-
Returns all registered shortcuts in the window.
|
765
|
-
|
766
|
-
:return: Dictionary of shortcut sequences and QShortcut objects
|
767
|
-
"""
|
768
|
-
return self.shortcuts
|
769
|
-
|
770
|
-
###########################################################################################
|
771
|
-
# Event (Calling the JS from Python)
|
772
|
-
###########################################################################################
|
773
|
-
def emit(self, event_name, data: Optional[Dict] = None):
|
774
|
-
"""
|
775
|
-
Emits an event to the JavaScript side.
|
776
|
-
|
777
|
-
:param event_name: Name of the event
|
778
|
-
:param data: Data to be sent with the event (optional)
|
779
|
-
"""
|
780
|
-
script = f"""
|
781
|
-
(function() {{
|
782
|
-
const eventData = {json.dumps(data)};
|
783
|
-
const customEvent = new CustomEvent('{event_name}', {{ detail: eventData }});
|
784
|
-
document.dispatchEvent(customEvent);
|
785
|
-
}})();
|
786
|
-
"""
|
787
|
-
self.web_view.page().runJavaScript(script)
|
788
|
-
|
789
|
-
###########################################################################################
|
790
|
-
# Get Properties
|
791
|
-
###########################################################################################
|
792
|
-
def get_window_properties(self):
|
793
|
-
"""Returns the properties of the window."""
|
794
|
-
return {
|
795
|
-
"id": self.id,
|
796
|
-
"title": self.title,
|
797
|
-
"width": self.width,
|
798
|
-
"height": self.height,
|
799
|
-
"x": self.x,
|
800
|
-
"y": self.y,
|
801
|
-
"frame": self.frame,
|
802
|
-
"context_menu": self.context_menu,
|
803
|
-
"dev_tools": self.dev_tools,
|
804
|
-
}
|
805
|
-
|
806
|
-
def get_id(self):
|
807
|
-
"""Returns the ID of the window."""
|
808
|
-
return self.id
|
809
|
-
|
810
|
-
def get_size(self) -> Dict[str, int]:
|
811
|
-
"""Returns the size of the window."""
|
812
|
-
return {"width": self.width, "height": self.height}
|
813
|
-
|
814
|
-
def get_position(self) -> Dict[str, int]:
|
815
|
-
"""Returns the position of the window."""
|
816
|
-
return {"x": self.x, "y": self.y}
|
817
|
-
|
818
|
-
def get_title(self) -> str:
|
819
|
-
"""Returns the title of the window."""
|
820
|
-
return self.title
|
821
|
-
|
822
|
-
def get_url(self) -> str:
|
823
|
-
"""Returns the URL of the window."""
|
824
|
-
return self.web_view.url().toString()
|
825
|
-
|
826
|
-
def get_visible(self) -> bool:
|
827
|
-
"""Returns the visibility of the window."""
|
828
|
-
return self._window.isVisible()
|
829
|
-
|
830
|
-
def get_frame(self) -> bool:
|
831
|
-
"""Returns the frame enabled state of the window."""
|
832
|
-
return self.frame
|
833
|
-
|
834
|
-
###########################################################################################
|
835
|
-
# Resize
|
836
|
-
###########################################################################################
|
837
|
-
def set_resizable(self, resizable: bool):
|
838
|
-
"""Sets the resizability of the window."""
|
839
|
-
self.resizable = resizable
|
840
|
-
if self.frame:
|
841
|
-
flags = self._window.windowFlags() | Qt.WindowCloseButtonHint
|
842
|
-
if resizable:
|
843
|
-
pass
|
844
|
-
else:
|
845
|
-
flags |= Qt.MSWindowsFixedSizeDialogHint
|
846
|
-
self._window.setWindowFlags(flags)
|
847
|
-
else:
|
848
|
-
# 프레임이 없는 경우 커스텀 리사이징 로직을 설정합니다.
|
849
|
-
self.web_view.is_resizing_enabled = resizable
|
850
|
-
|
851
|
-
self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
|
852
|
-
|
853
|
-
def set_minimum_size(self, min_width: int, min_height: int):
|
854
|
-
"""Sets the minimum size of the window."""
|
855
|
-
self._window.setMinimumSize(min_width, min_height)
|
856
|
-
|
857
|
-
def set_maximum_size(self, max_width: int, max_height: int):
|
858
|
-
"""Sets the maximum size of the window."""
|
859
|
-
self._window.setMaximumSize(max_width, max_height)
|
860
|
-
|
861
|
-
def get_minimum_size(self):
|
862
|
-
"""Returns the minimum size of the window."""
|
863
|
-
return {'width': self._window.minimumWidth(), 'height': self._window.minimumHeight()}
|
864
|
-
|
865
|
-
def get_maximum_size(self):
|
866
|
-
"""Returns the maximum size of the window."""
|
867
|
-
return {'width': self._window.maximumWidth(), 'height': self._window.maximumHeight()}
|
868
|
-
|
869
|
-
def get_resizable(self):
|
870
|
-
"""Returns the resizability of the window."""
|
871
|
-
return self.resizable
|
872
|
-
|
873
59
|
class _WindowController(QObject):
|
874
60
|
create_window_signal = Signal(
|
875
61
|
QApplication, str, int, int, int, int, bool, bool, bool, list
|
@@ -882,6 +68,27 @@ class Pyloid(QApplication):
|
|
882
68
|
app_name,
|
883
69
|
single_instance=True,
|
884
70
|
):
|
71
|
+
"""
|
72
|
+
Initializes the Pyloid application.
|
73
|
+
|
74
|
+
Parameters
|
75
|
+
----------
|
76
|
+
app_name : str, required
|
77
|
+
The name of the application
|
78
|
+
single_instance : bool, optional
|
79
|
+
Whether to run the application as a single instance (default is True)
|
80
|
+
|
81
|
+
Examples
|
82
|
+
--------
|
83
|
+
```python
|
84
|
+
app = Pyloid(app_name="Pyloid-App")
|
85
|
+
|
86
|
+
window = app.create_window(title="New Window", width=1024, height=768)
|
87
|
+
window.show()
|
88
|
+
|
89
|
+
app.run()
|
90
|
+
```
|
91
|
+
"""
|
885
92
|
super().__init__(sys.argv)
|
886
93
|
|
887
94
|
self.windows = []
|
@@ -920,20 +127,28 @@ class Pyloid(QApplication):
|
|
920
127
|
"""
|
921
128
|
Dynamically sets the application's icon.
|
922
129
|
|
923
|
-
:param icon_path: Path to the new icon file
|
924
|
-
|
925
130
|
This method can be called while the application is running.
|
926
|
-
The icon can be changed at any time and
|
131
|
+
The icon can be changed at any time and will be applied immediately.
|
132
|
+
|
133
|
+
Parameters
|
134
|
+
----------
|
135
|
+
icon_path : str
|
136
|
+
Path to the new icon file
|
137
|
+
|
138
|
+
Examples
|
139
|
+
--------
|
140
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
141
|
+
>>> app.set_icon("icons/icon.png")
|
927
142
|
"""
|
928
143
|
self.icon = QIcon(icon_path)
|
929
144
|
|
930
|
-
# Immediately update the icon for all open windows
|
145
|
+
# Immediately update the icon for all open windows.
|
931
146
|
for window in self.windows:
|
932
147
|
window._window.setWindowIcon(self.icon)
|
933
148
|
|
934
149
|
def create_window(
|
935
150
|
self,
|
936
|
-
title: str
|
151
|
+
title: str,
|
937
152
|
width: int = 800,
|
938
153
|
height: int = 600,
|
939
154
|
x: int = 200,
|
@@ -943,7 +158,41 @@ class Pyloid(QApplication):
|
|
943
158
|
dev_tools: bool = False,
|
944
159
|
js_apis: List[PyloidAPI] = [],
|
945
160
|
) -> BrowserWindow:
|
946
|
-
"""
|
161
|
+
"""
|
162
|
+
Creates a new browser window.
|
163
|
+
|
164
|
+
Parameters
|
165
|
+
----------
|
166
|
+
title : str, required
|
167
|
+
Title of the window
|
168
|
+
width : int, optional
|
169
|
+
Width of the window (default is 800)
|
170
|
+
height : int, optional
|
171
|
+
Height of the window (default is 600)
|
172
|
+
x : int, optional
|
173
|
+
X coordinate of the window (default is 200)
|
174
|
+
y : int, optional
|
175
|
+
Y coordinate of the window (default is 200)
|
176
|
+
frame : bool, optional
|
177
|
+
Whether the window has a frame (default is True)
|
178
|
+
context_menu : bool, optional
|
179
|
+
Whether to use the context menu (default is False)
|
180
|
+
dev_tools : bool, optional
|
181
|
+
Whether to use developer tools (default is False)
|
182
|
+
js_apis : list of PyloidAPI, optional
|
183
|
+
List of JavaScript APIs to add to the window (default is an empty list)
|
184
|
+
|
185
|
+
Returns
|
186
|
+
-------
|
187
|
+
BrowserWindow
|
188
|
+
The created browser window object
|
189
|
+
|
190
|
+
Examples
|
191
|
+
--------
|
192
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
193
|
+
>>> window = app.create_window(title="New Window", width=1024, height=768)
|
194
|
+
>>> window.show()
|
195
|
+
"""
|
947
196
|
self.controller.create_window_signal.emit(
|
948
197
|
self,
|
949
198
|
title,
|
@@ -988,7 +237,20 @@ class Pyloid(QApplication):
|
|
988
237
|
return window
|
989
238
|
|
990
239
|
def run(self):
|
991
|
-
"""
|
240
|
+
"""
|
241
|
+
Runs the application event loop.
|
242
|
+
|
243
|
+
This method starts the application's event loop, allowing the application to run.
|
244
|
+
|
245
|
+
This code should be written at the very end of the file.
|
246
|
+
|
247
|
+
Examples
|
248
|
+
--------
|
249
|
+
```python
|
250
|
+
app = Pyloid(app_name="Pyloid-App")
|
251
|
+
app.run()
|
252
|
+
```
|
253
|
+
"""
|
992
254
|
if is_production():
|
993
255
|
sys.exit(self.exec())
|
994
256
|
else:
|
@@ -1016,17 +278,51 @@ class Pyloid(QApplication):
|
|
1016
278
|
# App window
|
1017
279
|
###########################################################################################
|
1018
280
|
def get_windows(self) -> List[BrowserWindow]:
|
1019
|
-
"""
|
281
|
+
"""
|
282
|
+
Returns a list of all browser windows.
|
283
|
+
|
284
|
+
Returns
|
285
|
+
-------
|
286
|
+
List[BrowserWindow]
|
287
|
+
List of all browser windows
|
288
|
+
|
289
|
+
Examples
|
290
|
+
--------
|
291
|
+
```python
|
292
|
+
app = Pyloid(app_name="Pyloid-App")
|
293
|
+
windows = app.get_windows()
|
294
|
+
for window in windows:
|
295
|
+
print(window.get_id())
|
296
|
+
```
|
297
|
+
"""
|
1020
298
|
return self.windows
|
1021
299
|
|
1022
300
|
def show_main_window(self):
|
1023
|
-
"""
|
301
|
+
"""
|
302
|
+
Shows and focuses the first window.
|
303
|
+
|
304
|
+
Examples
|
305
|
+
--------
|
306
|
+
```python
|
307
|
+
app = Pyloid(app_name="Pyloid-App")
|
308
|
+
app.show_main_window()
|
309
|
+
```
|
310
|
+
"""
|
1024
311
|
if self.windows:
|
1025
312
|
main_window = self.windows[0]
|
1026
313
|
main_window._window.show()
|
1027
314
|
|
1028
315
|
def focus_main_window(self):
|
1029
|
-
"""
|
316
|
+
"""
|
317
|
+
Focuses the first window.
|
318
|
+
|
319
|
+
Examples
|
320
|
+
--------
|
321
|
+
```python
|
322
|
+
app = Pyloid(app_name="Pyloid-App")
|
323
|
+
app.focus_main_window()
|
324
|
+
```
|
325
|
+
"""
|
1030
326
|
if self.windows:
|
1031
327
|
main_window = self.windows[0]
|
1032
328
|
main_window._window.activateWindow()
|
@@ -1037,7 +333,16 @@ class Pyloid(QApplication):
|
|
1037
333
|
)
|
1038
334
|
|
1039
335
|
def show_and_focus_main_window(self):
|
1040
|
-
"""
|
336
|
+
"""
|
337
|
+
Shows and focuses the first window.
|
338
|
+
|
339
|
+
Examples
|
340
|
+
--------
|
341
|
+
```python
|
342
|
+
app = Pyloid(app_name="Pyloid-App")
|
343
|
+
app.show_and_focus_main_window()
|
344
|
+
```
|
345
|
+
"""
|
1041
346
|
if self.windows:
|
1042
347
|
main_window = self.windows[0]
|
1043
348
|
main_window._window.show()
|
@@ -1049,12 +354,30 @@ class Pyloid(QApplication):
|
|
1049
354
|
)
|
1050
355
|
|
1051
356
|
def close_all_windows(self):
|
1052
|
-
"""
|
357
|
+
"""
|
358
|
+
Closes all windows.
|
359
|
+
|
360
|
+
Examples
|
361
|
+
--------
|
362
|
+
```python
|
363
|
+
app = Pyloid(app_name="Pyloid-App")
|
364
|
+
app.close_all_windows()
|
365
|
+
```
|
366
|
+
"""
|
1053
367
|
for window in self.windows:
|
1054
368
|
window._window.close()
|
1055
369
|
|
1056
370
|
def quit(self):
|
1057
|
-
"""
|
371
|
+
"""
|
372
|
+
Quits the application.
|
373
|
+
|
374
|
+
Examples
|
375
|
+
--------
|
376
|
+
```python
|
377
|
+
app = Pyloid(app_name="Pyloid-App")
|
378
|
+
app.quit()
|
379
|
+
```
|
380
|
+
"""
|
1058
381
|
for window in self.windows:
|
1059
382
|
window._window.close()
|
1060
383
|
window.web_page.deleteLater()
|
@@ -1065,20 +388,77 @@ class Pyloid(QApplication):
|
|
1065
388
|
# Window management in the app (ID required)
|
1066
389
|
###########################################################################################
|
1067
390
|
def get_window_by_id(self, window_id: str) -> Optional[BrowserWindow]:
|
1068
|
-
"""
|
391
|
+
"""
|
392
|
+
Returns the window with the given ID.
|
393
|
+
|
394
|
+
Parameters
|
395
|
+
----------
|
396
|
+
window_id : str
|
397
|
+
The ID of the window to find
|
398
|
+
|
399
|
+
Returns
|
400
|
+
-------
|
401
|
+
Optional[BrowserWindow]
|
402
|
+
The window object with the given ID. Returns None if the window is not found.
|
403
|
+
|
404
|
+
Examples
|
405
|
+
--------
|
406
|
+
```python
|
407
|
+
app = Pyloid(app_name="Pyloid-App")
|
408
|
+
|
409
|
+
window = app.get_window_by_id("123e4567-e89b-12d3-a456-426614174000")
|
410
|
+
|
411
|
+
if window:
|
412
|
+
print("Window found:", window)
|
413
|
+
```
|
414
|
+
"""
|
1069
415
|
for window in self.windows:
|
1070
416
|
if window.id == window_id:
|
1071
417
|
return window
|
1072
418
|
return None
|
1073
419
|
|
1074
420
|
def hide_window_by_id(self, window_id: str):
|
1075
|
-
"""
|
421
|
+
"""
|
422
|
+
Hides the window with the given ID.
|
423
|
+
|
424
|
+
Parameters
|
425
|
+
----------
|
426
|
+
window_id : str
|
427
|
+
The ID of the window to hide
|
428
|
+
|
429
|
+
Examples
|
430
|
+
--------
|
431
|
+
```python
|
432
|
+
app = Pyloid(app_name="Pyloid-App")
|
433
|
+
|
434
|
+
window = app.create_window(title="pyloid-window")
|
435
|
+
|
436
|
+
app.hide_window_by_id(window.id)
|
437
|
+
```
|
438
|
+
"""
|
1076
439
|
window = self.get_window_by_id(window_id)
|
1077
440
|
if window:
|
1078
441
|
window.hide()
|
1079
442
|
|
1080
443
|
def show_window_by_id(self, window_id: str):
|
1081
|
-
"""
|
444
|
+
"""
|
445
|
+
Shows and focuses the window with the given ID.
|
446
|
+
|
447
|
+
Parameters
|
448
|
+
----------
|
449
|
+
window_id : str
|
450
|
+
The ID of the window to show
|
451
|
+
|
452
|
+
Examples
|
453
|
+
--------
|
454
|
+
```python
|
455
|
+
app = Pyloid(app_name="Pyloid-App")
|
456
|
+
|
457
|
+
window = app.create_window(title="pyloid-window")
|
458
|
+
|
459
|
+
app.show_window_by_id(window.id)
|
460
|
+
```
|
461
|
+
"""
|
1082
462
|
window = self.get_window_by_id(window_id)
|
1083
463
|
if window:
|
1084
464
|
window._window.show()
|
@@ -1089,41 +469,147 @@ class Pyloid(QApplication):
|
|
1089
469
|
)
|
1090
470
|
|
1091
471
|
def close_window_by_id(self, window_id: str):
|
1092
|
-
"""
|
472
|
+
"""
|
473
|
+
Closes the window with the given ID.
|
474
|
+
|
475
|
+
Parameters
|
476
|
+
----------
|
477
|
+
window_id : str
|
478
|
+
The ID of the window to close
|
479
|
+
|
480
|
+
Examples
|
481
|
+
--------
|
482
|
+
```python
|
483
|
+
app = Pyloid(app_name="Pyloid-App")
|
484
|
+
|
485
|
+
window = app.create_window(title="pyloid-window")
|
486
|
+
|
487
|
+
app.close_window_by_id(window.id)
|
488
|
+
```
|
489
|
+
"""
|
1093
490
|
window = self.get_window_by_id(window_id)
|
1094
491
|
if window:
|
1095
492
|
window._window.close()
|
1096
493
|
|
1097
494
|
def toggle_fullscreen_by_id(self, window_id: str):
|
1098
|
-
"""
|
495
|
+
"""
|
496
|
+
Toggles fullscreen mode for the window with the given ID.
|
497
|
+
|
498
|
+
Parameters
|
499
|
+
----------
|
500
|
+
window_id : str
|
501
|
+
The ID of the window to toggle fullscreen mode
|
502
|
+
|
503
|
+
Examples
|
504
|
+
--------
|
505
|
+
```python
|
506
|
+
app = Pyloid(app_name="Pyloid-App")
|
507
|
+
|
508
|
+
window = app.create_window(title="pyloid-window")
|
509
|
+
|
510
|
+
app.toggle_fullscreen_by_id(window.id)
|
511
|
+
```
|
512
|
+
"""
|
1099
513
|
window = self.get_window_by_id(window_id)
|
1100
514
|
window.toggle_fullscreen()
|
1101
515
|
|
1102
516
|
def minimize_window_by_id(self, window_id: str):
|
1103
|
-
"""
|
517
|
+
"""
|
518
|
+
Minimizes the window with the given ID.
|
519
|
+
|
520
|
+
Parameters
|
521
|
+
----------
|
522
|
+
window_id : str
|
523
|
+
The ID of the window to minimize
|
524
|
+
|
525
|
+
Examples
|
526
|
+
--------
|
527
|
+
```python
|
528
|
+
app = Pyloid(app_name="Pyloid-App")
|
529
|
+
|
530
|
+
window = app.create_window(title="pyloid-window")
|
531
|
+
|
532
|
+
app.minimize_window_by_id(window.id)
|
533
|
+
```
|
534
|
+
"""
|
1104
535
|
window = self.get_window_by_id(window_id)
|
1105
536
|
if window:
|
1106
537
|
window.minimize()
|
1107
538
|
|
1108
539
|
def maximize_window_by_id(self, window_id: str):
|
1109
|
-
"""
|
540
|
+
"""
|
541
|
+
Maximizes the window with the given ID.
|
542
|
+
|
543
|
+
Parameters
|
544
|
+
----------
|
545
|
+
window_id : str
|
546
|
+
The ID of the window to maximize
|
547
|
+
|
548
|
+
Examples
|
549
|
+
--------
|
550
|
+
```python
|
551
|
+
app = Pyloid(app_name="Pyloid-App")
|
552
|
+
|
553
|
+
window = app.create_window(title="pyloid-window")
|
554
|
+
|
555
|
+
app.maximize_window_by_id(window.id)
|
556
|
+
```
|
557
|
+
"""
|
1110
558
|
window = self.get_window_by_id(window_id)
|
1111
559
|
if window:
|
1112
560
|
window.maximize()
|
1113
561
|
|
1114
562
|
def unmaximize_window_by_id(self, window_id: str):
|
1115
|
-
"""
|
563
|
+
"""
|
564
|
+
Unmaximizes the window with the given ID.
|
565
|
+
|
566
|
+
Parameters
|
567
|
+
----------
|
568
|
+
window_id : str
|
569
|
+
The ID of the window to unmaximize
|
570
|
+
|
571
|
+
Examples
|
572
|
+
--------
|
573
|
+
```python
|
574
|
+
app = Pyloid(app_name="Pyloid-App")
|
575
|
+
|
576
|
+
window = app.create_window(title="pyloid-window")
|
577
|
+
|
578
|
+
app.unmaximize_window_by_id(window.id)
|
579
|
+
```
|
580
|
+
"""
|
1116
581
|
window = self.get_window_by_id(window_id)
|
1117
582
|
if window:
|
1118
583
|
window.unmaximize()
|
1119
584
|
|
1120
585
|
def capture_window_by_id(self, window_id: str, save_path: str) -> Optional[str]:
|
1121
586
|
"""
|
1122
|
-
Captures
|
587
|
+
Captures the specified window.
|
588
|
+
|
589
|
+
Parameters
|
590
|
+
----------
|
591
|
+
window_id : str
|
592
|
+
The ID of the window to capture
|
593
|
+
save_path : str
|
594
|
+
The path to save the captured image. If not specified, it will be saved in the current directory.
|
595
|
+
|
596
|
+
Returns
|
597
|
+
-------
|
598
|
+
Optional[str]
|
599
|
+
The path of the saved image. Returns None if the window is not found or an error occurs.
|
600
|
+
|
601
|
+
Examples
|
602
|
+
--------
|
603
|
+
```python
|
604
|
+
app = Pyloid(app_name="Pyloid-App")
|
1123
605
|
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
606
|
+
window = app.create_window(title="pyloid-window")
|
607
|
+
|
608
|
+
image_path = app.capture_window_by_id(window.id, "save/image.png")
|
609
|
+
|
610
|
+
if image_path:
|
611
|
+
print("Image saved at:", image_path)
|
612
|
+
```
|
1127
613
|
"""
|
1128
614
|
try:
|
1129
615
|
window = self.get_window_by_id(window_id)
|
@@ -1149,7 +635,15 @@ class Pyloid(QApplication):
|
|
1149
635
|
Dynamically sets the tray icon.
|
1150
636
|
Can be called while the application is running, and changes are applied immediately.
|
1151
637
|
|
1152
|
-
|
638
|
+
Parameters
|
639
|
+
----------
|
640
|
+
tray_icon_path : str
|
641
|
+
The path of the new tray icon file
|
642
|
+
|
643
|
+
Examples
|
644
|
+
--------
|
645
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
646
|
+
>>> app.set_tray_icon("icons/icon.png")
|
1153
647
|
"""
|
1154
648
|
# Stop and remove existing animation timer if present
|
1155
649
|
if hasattr(self, "animation_timer") and self.animation_timer is not None:
|
@@ -1176,7 +670,19 @@ class Pyloid(QApplication):
|
|
1176
670
|
Dynamically sets the tray menu items.
|
1177
671
|
Can be called while the application is running, and changes are applied immediately.
|
1178
672
|
|
1179
|
-
|
673
|
+
Parameters
|
674
|
+
----------
|
675
|
+
tray_menu_items : List[Dict[str, Union[str, Callable]]]
|
676
|
+
The list of new tray menu items
|
677
|
+
|
678
|
+
Examples
|
679
|
+
--------
|
680
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
681
|
+
>>> menu_items = [
|
682
|
+
>>> {"label": "Open", "callback": lambda: print("Open clicked")},
|
683
|
+
>>> {"label": "Exit", "callback": app.quit}
|
684
|
+
>>> ]
|
685
|
+
>>> app.set_tray_menu_items(menu_items)
|
1180
686
|
"""
|
1181
687
|
self.tray_menu_items = tray_menu_items
|
1182
688
|
if not hasattr(self, "tray"):
|
@@ -1212,12 +718,27 @@ class Pyloid(QApplication):
|
|
1212
718
|
if reason_enum in self.tray_actions:
|
1213
719
|
self.tray_actions[reason_enum]()
|
1214
720
|
|
1215
|
-
def set_tray_actions(self, actions):
|
721
|
+
def set_tray_actions(self, actions: Dict[TrayEvent, Callable]):
|
1216
722
|
"""
|
1217
|
-
Dynamically sets actions for tray icon activation.
|
723
|
+
Dynamically sets the actions for tray icon activation.
|
1218
724
|
Can be called while the application is running, and changes are applied immediately.
|
1219
725
|
|
1220
|
-
|
726
|
+
Parameters
|
727
|
+
----------
|
728
|
+
actions: Dict[TrayEvent, Callable]
|
729
|
+
Dictionary with TrayEvent enum values as keys and corresponding callback functions as values
|
730
|
+
|
731
|
+
Examples
|
732
|
+
--------
|
733
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
734
|
+
>>> app.set_tray_actions(
|
735
|
+
>>> {
|
736
|
+
>>> TrayEvent.DoubleClick: lambda: print("Tray icon was double-clicked."),
|
737
|
+
>>> TrayEvent.MiddleClick: lambda: print("Tray icon was middle-clicked."),
|
738
|
+
>>> TrayEvent.RightClick: lambda: print("Tray icon was right-clicked."),
|
739
|
+
>>> TrayEvent.LeftClick: lambda: print("Tray icon was left-clicked."),
|
740
|
+
>>> }
|
741
|
+
>>> )
|
1221
742
|
"""
|
1222
743
|
if self.tray_actions:
|
1223
744
|
self.tray.activated.disconnect() # Disconnect existing connections
|
@@ -1233,8 +754,17 @@ class Pyloid(QApplication):
|
|
1233
754
|
Displays a notification in the system tray.
|
1234
755
|
Can be called while the application is running, and the notification is displayed immediately.
|
1235
756
|
|
1236
|
-
|
1237
|
-
|
757
|
+
Parameters
|
758
|
+
----------
|
759
|
+
title : str
|
760
|
+
Notification title
|
761
|
+
message : str
|
762
|
+
Notification message
|
763
|
+
|
764
|
+
Examples
|
765
|
+
--------
|
766
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
767
|
+
>>> app.show_notification("Update Available", "A new update is available for download.")
|
1238
768
|
"""
|
1239
769
|
if not hasattr(self, "tray"):
|
1240
770
|
self._init_tray() # Ensure the tray is initialized
|
@@ -1242,18 +772,30 @@ class Pyloid(QApplication):
|
|
1242
772
|
self.tray.showMessage(title, message, QIcon(self.icon), 5000)
|
1243
773
|
|
1244
774
|
def _update_tray_icon(self):
|
1245
|
-
"""
|
775
|
+
"""
|
776
|
+
Updates the animation frame.
|
777
|
+
"""
|
1246
778
|
if hasattr(self, "tray") and self.icon_frames:
|
1247
779
|
self.tray.setIcon(self.icon_frames[self.current_frame])
|
1248
780
|
self.current_frame = (self.current_frame + 1) % len(self.icon_frames)
|
1249
781
|
|
1250
782
|
def set_tray_icon_animation(self, icon_frames: List[str], interval: int = 200):
|
1251
783
|
"""
|
1252
|
-
Dynamically sets and starts
|
784
|
+
Dynamically sets and starts the animation for the tray icon.
|
1253
785
|
Can be called while the application is running, and changes are applied immediately.
|
1254
786
|
|
1255
|
-
|
1256
|
-
|
787
|
+
Parameters
|
788
|
+
----------
|
789
|
+
icon_frames : list of str
|
790
|
+
List of animation frame image paths
|
791
|
+
interval : int, optional
|
792
|
+
Frame interval in milliseconds, default is 200
|
793
|
+
|
794
|
+
Examples
|
795
|
+
--------
|
796
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
797
|
+
>>> icon_frames = ["frame1.png", "frame2.png", "frame3.png"]
|
798
|
+
>>> app.set_tray_icon_animation(icon_frames, 100)
|
1257
799
|
"""
|
1258
800
|
if not hasattr(self, "tray"):
|
1259
801
|
self._init_tray()
|
@@ -1270,10 +812,12 @@ class Pyloid(QApplication):
|
|
1270
812
|
|
1271
813
|
self.icon_frames = [QIcon(frame) for frame in icon_frames]
|
1272
814
|
self.animation_interval = interval
|
1273
|
-
self.
|
815
|
+
self._start_tray_icon_animation()
|
1274
816
|
|
1275
|
-
def
|
1276
|
-
"""
|
817
|
+
def _start_tray_icon_animation(self):
|
818
|
+
"""
|
819
|
+
Starts the tray icon animation.
|
820
|
+
"""
|
1277
821
|
if self.icon_frames:
|
1278
822
|
if self.animation_timer is None:
|
1279
823
|
self.animation_timer = QTimer(self)
|
@@ -1286,7 +830,15 @@ class Pyloid(QApplication):
|
|
1286
830
|
Dynamically sets the tooltip for the tray icon.
|
1287
831
|
Can be called while the application is running, and changes are applied immediately.
|
1288
832
|
|
1289
|
-
|
833
|
+
Parameters
|
834
|
+
----------
|
835
|
+
message : str
|
836
|
+
New tooltip message
|
837
|
+
|
838
|
+
Examples
|
839
|
+
--------
|
840
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
841
|
+
>>> app.set_tray_tooltip("Pyloid is running")
|
1290
842
|
"""
|
1291
843
|
if not hasattr(self, "tray"):
|
1292
844
|
self._init_tray()
|
@@ -1296,7 +848,17 @@ class Pyloid(QApplication):
|
|
1296
848
|
"""
|
1297
849
|
Sets the callback function to be called when a notification is clicked.
|
1298
850
|
|
1299
|
-
|
851
|
+
Parameters
|
852
|
+
----------
|
853
|
+
callback : function
|
854
|
+
Callback function to be called when a notification is clicked
|
855
|
+
|
856
|
+
Examples
|
857
|
+
--------
|
858
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
859
|
+
>>> def on_notification_click():
|
860
|
+
>>> print("Notification clicked")
|
861
|
+
>>> app.set_notification_callback(on_notification_click)
|
1300
862
|
"""
|
1301
863
|
if not hasattr(self, "tray"):
|
1302
864
|
self._init_tray()
|
@@ -1307,9 +869,19 @@ class Pyloid(QApplication):
|
|
1307
869
|
###########################################################################################
|
1308
870
|
def get_all_monitors(self) -> List[Monitor]:
|
1309
871
|
"""
|
1310
|
-
Returns
|
872
|
+
Returns information about all connected monitors.
|
1311
873
|
|
1312
|
-
|
874
|
+
Returns
|
875
|
+
-------
|
876
|
+
list of Monitor
|
877
|
+
List containing monitor information
|
878
|
+
|
879
|
+
Examples
|
880
|
+
--------
|
881
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
882
|
+
>>> monitors = app.get_all_monitors()
|
883
|
+
>>> for monitor in monitors:
|
884
|
+
>>> print(monitor.info())
|
1313
885
|
"""
|
1314
886
|
monitors = [
|
1315
887
|
Monitor(index, screen) for index, screen in enumerate(self.screens())
|
@@ -1318,9 +890,18 @@ class Pyloid(QApplication):
|
|
1318
890
|
|
1319
891
|
def get_primary_monitor(self) -> Monitor:
|
1320
892
|
"""
|
1321
|
-
Returns information
|
893
|
+
Returns information about the primary monitor.
|
894
|
+
|
895
|
+
Returns
|
896
|
+
-------
|
897
|
+
Monitor
|
898
|
+
Primary monitor information
|
1322
899
|
|
1323
|
-
|
900
|
+
Examples
|
901
|
+
--------
|
902
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
903
|
+
>>> primary_monitor = app.get_primary_monitor()
|
904
|
+
>>> print(primary_monitor.info())
|
1324
905
|
"""
|
1325
906
|
primary_monitor = self.screens()[0]
|
1326
907
|
return Monitor(0, primary_monitor)
|
@@ -1328,11 +909,21 @@ class Pyloid(QApplication):
|
|
1328
909
|
###########################################################################################
|
1329
910
|
# Clipboard
|
1330
911
|
###########################################################################################
|
1331
|
-
def
|
912
|
+
def set_clipboard_text(self, text):
|
1332
913
|
"""
|
1333
914
|
Copies text to the clipboard.
|
1334
915
|
|
1335
|
-
|
916
|
+
This function copies the given text to the clipboard. The text copied to the clipboard can be pasted into other applications.
|
917
|
+
|
918
|
+
Parameters
|
919
|
+
----------
|
920
|
+
text : str
|
921
|
+
Text to copy to the clipboard
|
922
|
+
|
923
|
+
Examples
|
924
|
+
--------
|
925
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
926
|
+
>>> app.set_clipboard_text("Hello, World!")
|
1336
927
|
"""
|
1337
928
|
self.clipboard_class.setText(text, QClipboard.Clipboard)
|
1338
929
|
|
@@ -1340,7 +931,19 @@ class Pyloid(QApplication):
|
|
1340
931
|
"""
|
1341
932
|
Retrieves text from the clipboard.
|
1342
933
|
|
1343
|
-
|
934
|
+
This function returns the text stored in the clipboard. If there is no text in the clipboard, it may return an empty string.
|
935
|
+
|
936
|
+
Returns
|
937
|
+
-------
|
938
|
+
str
|
939
|
+
Text stored in the clipboard
|
940
|
+
|
941
|
+
Examples
|
942
|
+
--------
|
943
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
944
|
+
>>> text = app.get_clipboard_text()
|
945
|
+
>>> print(text)
|
946
|
+
Hello, World!
|
1344
947
|
"""
|
1345
948
|
return self.clipboard_class.text()
|
1346
949
|
|
@@ -1348,7 +951,17 @@ class Pyloid(QApplication):
|
|
1348
951
|
"""
|
1349
952
|
Copies an image to the clipboard.
|
1350
953
|
|
1351
|
-
|
954
|
+
This function copies the given image file to the clipboard. The image copied to the clipboard can be pasted into other applications.
|
955
|
+
|
956
|
+
Parameters
|
957
|
+
----------
|
958
|
+
image : Union[str, bytes, os.PathLike]
|
959
|
+
Path to the image file to copy to the clipboard
|
960
|
+
|
961
|
+
Examples
|
962
|
+
--------
|
963
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
964
|
+
>>> app.set_clipboard_image("/path/to/image.png")
|
1352
965
|
"""
|
1353
966
|
self.clipboard_class.setImage(QImage(image), QClipboard.Clipboard)
|
1354
967
|
|
@@ -1356,20 +969,46 @@ class Pyloid(QApplication):
|
|
1356
969
|
"""
|
1357
970
|
Retrieves an image from the clipboard.
|
1358
971
|
|
1359
|
-
|
972
|
+
This function returns the image stored in the clipboard. If there is no image in the clipboard, it may return None.
|
973
|
+
|
974
|
+
Returns
|
975
|
+
-------
|
976
|
+
QImage
|
977
|
+
QImage object stored in the clipboard (None if no image)
|
978
|
+
|
979
|
+
Examples
|
980
|
+
--------
|
981
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
982
|
+
>>> image = app.get_clipboard_image()
|
983
|
+
>>> if image is not None:
|
984
|
+
>>> image.save("/path/to/save/image.png")
|
1360
985
|
"""
|
1361
986
|
return self.clipboard_class.image()
|
1362
987
|
|
1363
988
|
###########################################################################################
|
1364
|
-
#
|
989
|
+
# Autostart
|
1365
990
|
###########################################################################################
|
1366
991
|
def set_auto_start(self, enable: bool):
|
1367
992
|
"""
|
1368
|
-
Sets the application to start automatically
|
1369
|
-
True only works in production.
|
1370
|
-
False works in
|
993
|
+
Sets the application to start automatically at system startup. (set_auto_start(True) only works in production environment)
|
994
|
+
True only works in production environment.
|
995
|
+
False works in all environments.
|
996
|
+
|
997
|
+
Parameters
|
998
|
+
----------
|
999
|
+
enable : bool
|
1000
|
+
True to enable auto start, False to disable
|
1001
|
+
|
1002
|
+
Returns
|
1003
|
+
-------
|
1004
|
+
bool or None
|
1005
|
+
True if auto start is successfully set, False if disabled, None if trying to enable in non-production environment
|
1371
1006
|
|
1372
|
-
|
1007
|
+
Examples
|
1008
|
+
--------
|
1009
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1010
|
+
>>> app.set_auto_start(True)
|
1011
|
+
True
|
1373
1012
|
"""
|
1374
1013
|
if not enable:
|
1375
1014
|
self.auto_start.set_auto_start(False)
|
@@ -1387,11 +1026,20 @@ class Pyloid(QApplication):
|
|
1387
1026
|
|
1388
1027
|
def is_auto_start(self):
|
1389
1028
|
"""
|
1390
|
-
Checks if the application is set to start automatically
|
1029
|
+
Checks if the application is set to start automatically at system startup.
|
1391
1030
|
|
1392
|
-
|
1393
|
-
|
1031
|
+
Returns
|
1032
|
+
-------
|
1033
|
+
bool
|
1034
|
+
True if auto start is enabled, False otherwise
|
1394
1035
|
|
1036
|
+
Examples
|
1037
|
+
--------
|
1038
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1039
|
+
>>> auto_start_enabled = app.is_auto_start()
|
1040
|
+
>>> print(auto_start_enabled)
|
1041
|
+
True
|
1042
|
+
"""
|
1395
1043
|
return self.auto_start.is_auto_start()
|
1396
1044
|
|
1397
1045
|
###########################################################################################
|
@@ -1401,8 +1049,23 @@ class Pyloid(QApplication):
|
|
1401
1049
|
"""
|
1402
1050
|
Adds a file to the watch list.
|
1403
1051
|
|
1404
|
-
|
1405
|
-
|
1052
|
+
This function adds the specified file to the watch list. When the file is changed, the set callback function is called.
|
1053
|
+
|
1054
|
+
Parameters
|
1055
|
+
----------
|
1056
|
+
file_path : str
|
1057
|
+
Path to the file to watch
|
1058
|
+
|
1059
|
+
Returns
|
1060
|
+
-------
|
1061
|
+
bool
|
1062
|
+
True if the file is successfully added to the watch list, False otherwise
|
1063
|
+
|
1064
|
+
Examples
|
1065
|
+
--------
|
1066
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1067
|
+
>>> app.watch_file("/path/to/file.txt")
|
1068
|
+
True
|
1406
1069
|
"""
|
1407
1070
|
return self.file_watcher.add_path(file_path)
|
1408
1071
|
|
@@ -1410,8 +1073,23 @@ class Pyloid(QApplication):
|
|
1410
1073
|
"""
|
1411
1074
|
Adds a directory to the watch list.
|
1412
1075
|
|
1413
|
-
|
1414
|
-
|
1076
|
+
This function adds the specified directory to the watch list. When a file in the directory is changed, the set callback function is called.
|
1077
|
+
|
1078
|
+
Parameters
|
1079
|
+
----------
|
1080
|
+
dir_path : str
|
1081
|
+
Path to the directory to watch
|
1082
|
+
|
1083
|
+
Returns
|
1084
|
+
-------
|
1085
|
+
bool
|
1086
|
+
True if the directory is successfully added to the watch list, False otherwise
|
1087
|
+
|
1088
|
+
Examples
|
1089
|
+
--------
|
1090
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1091
|
+
>>> app.watch_directory("/path/to/directory")
|
1092
|
+
True
|
1415
1093
|
"""
|
1416
1094
|
return self.file_watcher.add_path(dir_path)
|
1417
1095
|
|
@@ -1419,8 +1097,23 @@ class Pyloid(QApplication):
|
|
1419
1097
|
"""
|
1420
1098
|
Removes a file or directory from the watch list.
|
1421
1099
|
|
1422
|
-
|
1423
|
-
|
1100
|
+
This function removes the specified file or directory from the watch list.
|
1101
|
+
|
1102
|
+
Parameters
|
1103
|
+
----------
|
1104
|
+
path : str
|
1105
|
+
Path to the file or directory to stop watching
|
1106
|
+
|
1107
|
+
Returns
|
1108
|
+
-------
|
1109
|
+
bool
|
1110
|
+
True if the path is successfully removed from the watch list, False otherwise
|
1111
|
+
|
1112
|
+
Examples
|
1113
|
+
--------
|
1114
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1115
|
+
>>> app.stop_watching("/path/to/file_or_directory")
|
1116
|
+
True
|
1424
1117
|
"""
|
1425
1118
|
return self.file_watcher.remove_path(path)
|
1426
1119
|
|
@@ -1428,7 +1121,18 @@ class Pyloid(QApplication):
|
|
1428
1121
|
"""
|
1429
1122
|
Returns all currently watched paths.
|
1430
1123
|
|
1431
|
-
|
1124
|
+
This function returns the paths of all files and directories currently being watched.
|
1125
|
+
|
1126
|
+
Returns
|
1127
|
+
-------
|
1128
|
+
List[str]
|
1129
|
+
List of all watched paths
|
1130
|
+
|
1131
|
+
Examples
|
1132
|
+
--------
|
1133
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1134
|
+
>>> app.get_watched_paths()
|
1135
|
+
['/path/to/file1.txt', '/path/to/directory']
|
1432
1136
|
"""
|
1433
1137
|
return self.file_watcher.get_watched_paths()
|
1434
1138
|
|
@@ -1436,7 +1140,18 @@ class Pyloid(QApplication):
|
|
1436
1140
|
"""
|
1437
1141
|
Returns all currently watched files.
|
1438
1142
|
|
1439
|
-
|
1143
|
+
This function returns the paths of all files currently being watched.
|
1144
|
+
|
1145
|
+
Returns
|
1146
|
+
-------
|
1147
|
+
List[str]
|
1148
|
+
List of all watched files
|
1149
|
+
|
1150
|
+
Examples
|
1151
|
+
--------
|
1152
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1153
|
+
>>> app.get_watched_files()
|
1154
|
+
['/path/to/file1.txt', '/path/to/file2.txt']
|
1440
1155
|
"""
|
1441
1156
|
return self.file_watcher.get_watched_files()
|
1442
1157
|
|
@@ -1444,7 +1159,18 @@ class Pyloid(QApplication):
|
|
1444
1159
|
"""
|
1445
1160
|
Returns all currently watched directories.
|
1446
1161
|
|
1447
|
-
|
1162
|
+
This function returns the paths of all directories currently being watched.
|
1163
|
+
|
1164
|
+
Returns
|
1165
|
+
-------
|
1166
|
+
List[str]
|
1167
|
+
List of all watched directories
|
1168
|
+
|
1169
|
+
Examples
|
1170
|
+
--------
|
1171
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1172
|
+
>>> app.get_watched_directories()
|
1173
|
+
['/path/to/directory1', '/path/to/directory2']
|
1448
1174
|
"""
|
1449
1175
|
return self.file_watcher.get_watched_directories()
|
1450
1176
|
|
@@ -1452,22 +1178,146 @@ class Pyloid(QApplication):
|
|
1452
1178
|
"""
|
1453
1179
|
Removes all paths from the watch list.
|
1454
1180
|
|
1455
|
-
|
1181
|
+
This function removes the paths of all files and directories from the watch list.
|
1182
|
+
|
1183
|
+
Returns
|
1184
|
+
-------
|
1185
|
+
None
|
1186
|
+
|
1187
|
+
Examples
|
1188
|
+
--------
|
1189
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1190
|
+
>>> app.remove_all_watched_paths()
|
1456
1191
|
"""
|
1457
1192
|
self.file_watcher.remove_all_paths()
|
1458
1193
|
|
1459
1194
|
def set_file_change_callback(self, callback: Callable[[str], None]) -> None:
|
1460
1195
|
"""
|
1461
|
-
Sets the callback function to be called when a file
|
1196
|
+
Sets the callback function to be called when a file is changed.
|
1462
1197
|
|
1463
|
-
|
1198
|
+
This function sets the callback function to be called when a file is changed.
|
1199
|
+
|
1200
|
+
Parameters
|
1201
|
+
----------
|
1202
|
+
callback : Callable[[str], None]
|
1203
|
+
Function to be called when a file is changed
|
1204
|
+
|
1205
|
+
Returns
|
1206
|
+
-------
|
1207
|
+
None
|
1208
|
+
|
1209
|
+
Examples
|
1210
|
+
--------
|
1211
|
+
>>> def on_file_change(file_path):
|
1212
|
+
>>> print(f"File changed: {file_path}")
|
1213
|
+
>>>
|
1214
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1215
|
+
>>> app.set_file_change_callback(on_file_change)
|
1464
1216
|
"""
|
1465
1217
|
self.file_watcher.file_changed.connect(callback)
|
1466
1218
|
|
1467
1219
|
def set_directory_change_callback(self, callback: Callable[[str], None]) -> None:
|
1468
1220
|
"""
|
1469
|
-
Sets the callback function to be called when a directory
|
1221
|
+
Sets the callback function to be called when a directory is changed.
|
1222
|
+
|
1223
|
+
This function sets the callback function to be called when a directory is changed.
|
1224
|
+
|
1225
|
+
Parameters
|
1226
|
+
----------
|
1227
|
+
callback : Callable[[str], None]
|
1228
|
+
Function to be called when a directory is changed
|
1470
1229
|
|
1471
|
-
|
1230
|
+
Returns
|
1231
|
+
-------
|
1232
|
+
None
|
1233
|
+
|
1234
|
+
Examples
|
1235
|
+
--------
|
1236
|
+
>>> def on_directory_change(dir_path):
|
1237
|
+
>>> print(f"Directory changed: {dir_path}")
|
1238
|
+
>>>
|
1239
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1240
|
+
>>> app.set_directory_change_callback(on_directory_change)
|
1472
1241
|
"""
|
1473
1242
|
self.file_watcher.directory_changed.connect(callback)
|
1243
|
+
|
1244
|
+
###########################################################################################
|
1245
|
+
# File dialog
|
1246
|
+
###########################################################################################
|
1247
|
+
def open_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
|
1248
|
+
"""
|
1249
|
+
Opens a file dialog to select a file to open.
|
1250
|
+
|
1251
|
+
Parameters
|
1252
|
+
----------
|
1253
|
+
dir : str, optional
|
1254
|
+
The initial directory that the dialog will open in. If None, the dialog will open in the current working directory.
|
1255
|
+
filter : str, optional
|
1256
|
+
A string that specifies the file types that can be selected. For example, "Text Files (*.txt);;All Files (*)".
|
1257
|
+
|
1258
|
+
Returns
|
1259
|
+
-------
|
1260
|
+
Optional[str]
|
1261
|
+
The path of the selected file. Returns None if no file is selected.
|
1262
|
+
|
1263
|
+
Examples
|
1264
|
+
--------
|
1265
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1266
|
+
>>> file_path = app.open_file_dialog(dir="/home/user", filter="Text Files (*.txt)")
|
1267
|
+
>>> if file_path:
|
1268
|
+
>>> print("Selected file:", file_path)
|
1269
|
+
"""
|
1270
|
+
file_path, _ = QFileDialog.getOpenFileName(None, dir=dir, filter=filter)
|
1271
|
+
return file_path if file_path else None
|
1272
|
+
|
1273
|
+
def save_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
|
1274
|
+
"""
|
1275
|
+
Opens a file dialog to select a file to save.
|
1276
|
+
|
1277
|
+
Parameters
|
1278
|
+
----------
|
1279
|
+
dir : str, optional
|
1280
|
+
The initial directory that the dialog will open in. If None, the dialog will open in the current working directory.
|
1281
|
+
filter : str, optional
|
1282
|
+
A string that specifies the file types that can be saved. For example, "Text Files (*.txt);;All Files (*)".
|
1283
|
+
|
1284
|
+
Returns
|
1285
|
+
-------
|
1286
|
+
Optional[str]
|
1287
|
+
The path of the selected file. Returns None if no file is selected.
|
1288
|
+
|
1289
|
+
Examples
|
1290
|
+
--------
|
1291
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1292
|
+
>>> file_path = app.save_file_dialog(dir="/home/user", filter="Text Files (*.txt)")
|
1293
|
+
>>> if file_path:
|
1294
|
+
>>> print("File will be saved to:", file_path)
|
1295
|
+
"""
|
1296
|
+
file_path, _ = QFileDialog.getSaveFileName(None, dir=dir, filter=filter)
|
1297
|
+
return file_path if file_path else None
|
1298
|
+
|
1299
|
+
def select_directory_dialog(self, dir: Optional[str] = None) -> Optional[str]:
|
1300
|
+
"""
|
1301
|
+
Opens a dialog to select a directory.
|
1302
|
+
|
1303
|
+
Parameters
|
1304
|
+
----------
|
1305
|
+
dir : str, optional
|
1306
|
+
The initial directory that the dialog will open in. If None, the dialog will open in the current working directory.
|
1307
|
+
|
1308
|
+
Returns
|
1309
|
+
-------
|
1310
|
+
Optional[str]
|
1311
|
+
The path of the selected directory. Returns None if no directory is selected.
|
1312
|
+
|
1313
|
+
Examples
|
1314
|
+
--------
|
1315
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1316
|
+
>>> directory_path = app.select_directory_dialog(dir="/home/user")
|
1317
|
+
>>> if directory_path:
|
1318
|
+
>>> print("Selected directory:", directory_path)
|
1319
|
+
"""
|
1320
|
+
directory_path = QFileDialog.getExistingDirectory(None, dir=dir)
|
1321
|
+
return directory_path if directory_path else None
|
1322
|
+
|
1323
|
+
|