pyloid 0.11.3__tar.gz → 0.11.5__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pyloid-0.11.3 → pyloid-0.11.5}/PKG-INFO +2 -2
- {pyloid-0.11.3 → pyloid-0.11.5}/README.md +1 -1
- {pyloid-0.11.3 → pyloid-0.11.5}/pyproject.toml +1 -1
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/pyloid.py +258 -35
- {pyloid-0.11.3 → pyloid-0.11.5}/LICENSE +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/__init__.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/api.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/autostart.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/filewatcher.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/monitor.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/timer.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/tray.py +0 -0
- {pyloid-0.11.3 → pyloid-0.11.5}/src/pyloid/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pyloid
|
3
|
-
Version: 0.11.
|
3
|
+
Version: 0.11.5
|
4
4
|
Summary:
|
5
5
|
Author: aesthetics-of-record
|
6
6
|
Author-email: 111675679+aesthetics-of-record@users.noreply.github.com
|
@@ -48,7 +48,7 @@ With Pyloid, you can leverage the full power of Python in your desktop applicati
|
|
48
48
|
|
49
49
|
#### Creating a React + Vite + Pyloid Project ⚛️
|
50
50
|
|
51
|
-
[https://github.com/pylonic/pyloid_react_boilerplate](https://github.com/Pyloid/
|
51
|
+
[https://github.com/pylonic/pyloid_react_boilerplate](https://github.com/Pyloid/pyloid_react_boilerplate)
|
52
52
|
|
53
53
|
### Custom Your Boilerplate 🔨
|
54
54
|
|
@@ -33,7 +33,7 @@ With Pyloid, you can leverage the full power of Python in your desktop applicati
|
|
33
33
|
|
34
34
|
#### Creating a React + Vite + Pyloid Project ⚛️
|
35
35
|
|
36
|
-
[https://github.com/pylonic/pyloid_react_boilerplate](https://github.com/Pyloid/
|
36
|
+
[https://github.com/pylonic/pyloid_react_boilerplate](https://github.com/Pyloid/pyloid_react_boilerplate)
|
37
37
|
|
38
38
|
### Custom Your Boilerplate 🔨
|
39
39
|
|
@@ -8,8 +8,17 @@ 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
|
12
|
-
|
11
|
+
from PySide6.QtGui import (
|
12
|
+
QIcon,
|
13
|
+
QKeySequence,
|
14
|
+
QShortcut,
|
15
|
+
QClipboard,
|
16
|
+
QImage,
|
17
|
+
QAction,
|
18
|
+
QPalette,
|
19
|
+
QColor,
|
20
|
+
)
|
21
|
+
from PySide6.QtCore import Qt, Signal, QPoint, QUrl, QObject, QTimer, QSize
|
13
22
|
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
14
23
|
from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
|
15
24
|
from .api import PyloidAPI, Bridge
|
@@ -23,13 +32,16 @@ import json
|
|
23
32
|
from .autostart import AutoStart
|
24
33
|
from .filewatcher import FileWatcher
|
25
34
|
import logging
|
26
|
-
from PySide6.
|
35
|
+
from PySide6.QtGui import QPalette, QColor
|
36
|
+
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QHBoxLayout, QSizePolicy
|
37
|
+
from PySide6.QtGui import QPixmap
|
27
38
|
|
28
39
|
# for linux debug
|
29
40
|
os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
|
30
41
|
|
31
42
|
# for macos debug
|
32
|
-
logging.getLogger(
|
43
|
+
logging.getLogger("Qt").setLevel(logging.ERROR)
|
44
|
+
|
33
45
|
|
34
46
|
def custom_message_handler(mode, context, message):
|
35
47
|
if not hasattr(custom_message_handler, "vulkan_warning_shown") and (
|
@@ -42,7 +54,13 @@ def custom_message_handler(mode, context, message):
|
|
42
54
|
)
|
43
55
|
os.environ["QT_QUICK_BACKEND"] = "software"
|
44
56
|
custom_message_handler.vulkan_warning_shown = True
|
45
|
-
|
57
|
+
|
58
|
+
if "Autofill.enable failed" in message:
|
59
|
+
print(
|
60
|
+
"\033[93mPyloid Warning: Autofill is not enabled in developer tools.\033[0m"
|
61
|
+
)
|
62
|
+
|
63
|
+
if "vulkan" not in message.lower() and "Autofill.enable failed" not in message:
|
46
64
|
print(message)
|
47
65
|
|
48
66
|
|
@@ -60,12 +78,12 @@ class WindowAPI(PyloidAPI):
|
|
60
78
|
"""Returns the current window ID."""
|
61
79
|
return self.window_id
|
62
80
|
|
63
|
-
@Bridge(result=
|
81
|
+
@Bridge(result=dict)
|
64
82
|
def getWindowProperties(self):
|
65
83
|
"""Returns the properties of the window."""
|
66
84
|
window = self.app.get_window_by_id(self.window_id)
|
67
85
|
window_properties = window.get_window_properties()
|
68
|
-
return
|
86
|
+
return window_properties
|
69
87
|
|
70
88
|
@Bridge()
|
71
89
|
def close(self):
|
@@ -166,6 +184,44 @@ class WindowAPI(PyloidAPI):
|
|
166
184
|
return window.capture(save_path)
|
167
185
|
return None
|
168
186
|
|
187
|
+
@Bridge(result=bool)
|
188
|
+
def getFrame(self):
|
189
|
+
"""Returns whether the window has a frame."""
|
190
|
+
window = self.app.get_window_by_id(self.window_id)
|
191
|
+
return window.frame if window else False
|
192
|
+
|
193
|
+
@Bridge(result=bool)
|
194
|
+
def getContextMenu(self):
|
195
|
+
"""Returns whether the window has a context menu."""
|
196
|
+
window = self.app.get_window_by_id(self.window_id)
|
197
|
+
return window.context_menu if window else False
|
198
|
+
|
199
|
+
@Bridge(result=bool)
|
200
|
+
def getDevTools(self):
|
201
|
+
"""Returns whether the window has developer tools."""
|
202
|
+
window = self.app.get_window_by_id(self.window_id)
|
203
|
+
return window.dev_tools if window else False
|
204
|
+
|
205
|
+
@Bridge(result=str)
|
206
|
+
def getTitle(self):
|
207
|
+
"""Returns the title of the window."""
|
208
|
+
window = self.app.get_window_by_id(self.window_id)
|
209
|
+
return window.title if window else ""
|
210
|
+
|
211
|
+
@Bridge(result=dict)
|
212
|
+
def getSize(self):
|
213
|
+
"""Returns the size of the window."""
|
214
|
+
window = self.app.get_window_by_id(self.window_id)
|
215
|
+
return {"width": window.width, "height": window.height} if window else {"width": 0, "height": 0}
|
216
|
+
|
217
|
+
@Bridge(result=dict)
|
218
|
+
def getPosition(self):
|
219
|
+
"""Returns the position of the window."""
|
220
|
+
window = self.app.get_window_by_id(self.window_id)
|
221
|
+
return {"x": window.x, "y": window.y} if window else {"x": 0, "y": 0}
|
222
|
+
|
223
|
+
|
224
|
+
|
169
225
|
|
170
226
|
# class EventAPI(PylonAPI):
|
171
227
|
# def __init__(self, window_id: str, app):
|
@@ -189,6 +245,106 @@ class WindowAPI(PyloidAPI):
|
|
189
245
|
# callback(*args, **kwargs)
|
190
246
|
|
191
247
|
|
248
|
+
class CustomTitleBar(QWidget):
|
249
|
+
def __init__(self, parent=None):
|
250
|
+
super().__init__(parent)
|
251
|
+
self.layout = QHBoxLayout(self)
|
252
|
+
self.layout.setContentsMargins(5, 0, 5, 0)
|
253
|
+
self.layout.setSpacing(0)
|
254
|
+
|
255
|
+
self.icon_label = QLabel()
|
256
|
+
self.icon_label.setFixedSize(20, 20)
|
257
|
+
self.title = QLabel("Custom Title")
|
258
|
+
|
259
|
+
self.minimize_button = QPushButton("-")
|
260
|
+
self.maximize_button = QPushButton("❐")
|
261
|
+
self.close_button = QPushButton("×")
|
262
|
+
|
263
|
+
for button in (self.minimize_button, self.maximize_button, self.close_button):
|
264
|
+
button.setFixedSize(45, 30)
|
265
|
+
button.setFlat(True)
|
266
|
+
|
267
|
+
self.layout.addWidget(self.icon_label)
|
268
|
+
self.layout.addSpacing(5)
|
269
|
+
self.layout.addWidget(self.title)
|
270
|
+
self.layout.addStretch(1)
|
271
|
+
self.layout.addWidget(self.minimize_button)
|
272
|
+
self.layout.addWidget(self.maximize_button)
|
273
|
+
self.layout.addWidget(self.close_button)
|
274
|
+
|
275
|
+
self.minimize_button.clicked.connect(self.window().showMinimized)
|
276
|
+
self.maximize_button.clicked.connect(self.toggle_maximize)
|
277
|
+
self.close_button.clicked.connect(self.window().close)
|
278
|
+
|
279
|
+
self.setFixedHeight(30)
|
280
|
+
self.set_style("darkblue", "white")
|
281
|
+
|
282
|
+
def set_style(self, bg_color, text_color):
|
283
|
+
self.setAutoFillBackground(True)
|
284
|
+
palette = self.palette()
|
285
|
+
bg_qcolor = QColor(bg_color)
|
286
|
+
text_qcolor = QColor(text_color)
|
287
|
+
palette.setColor(QPalette.Window, bg_qcolor)
|
288
|
+
palette.setColor(QPalette.WindowText, text_qcolor)
|
289
|
+
self.setPalette(palette)
|
290
|
+
|
291
|
+
self.title.setStyleSheet(f"color: {text_color}; font-weight: bold;")
|
292
|
+
|
293
|
+
button_style = f"""
|
294
|
+
QPushButton {{
|
295
|
+
background-color: {bg_color};
|
296
|
+
color: {text_color};
|
297
|
+
border: none;
|
298
|
+
font-family: Arial;
|
299
|
+
font-size: 14px;
|
300
|
+
padding: 0px;
|
301
|
+
text-align: center;
|
302
|
+
}}
|
303
|
+
QPushButton:hover {{
|
304
|
+
background-color: {bg_qcolor.lighter(120).name()};
|
305
|
+
}}
|
306
|
+
QPushButton:pressed {{
|
307
|
+
background-color: {bg_qcolor.darker(110).name()};
|
308
|
+
}}
|
309
|
+
"""
|
310
|
+
for button in (self.minimize_button, self.maximize_button, self.close_button):
|
311
|
+
button.setStyleSheet(button_style)
|
312
|
+
|
313
|
+
self.close_button.setStyleSheet(button_style + f"""
|
314
|
+
QPushButton:hover {{
|
315
|
+
background-color: #e81123;
|
316
|
+
color: white;
|
317
|
+
}}
|
318
|
+
""")
|
319
|
+
|
320
|
+
def mousePressEvent(self, event):
|
321
|
+
if event.button() == Qt.LeftButton:
|
322
|
+
self.window().moving = True
|
323
|
+
self.window().offset = event.pos()
|
324
|
+
|
325
|
+
def mouseMoveEvent(self, event):
|
326
|
+
if self.window().moving:
|
327
|
+
self.window().move(event.globalPos() - self.window().offset)
|
328
|
+
|
329
|
+
def mouseReleaseEvent(self, event):
|
330
|
+
if event.button() == Qt.LeftButton:
|
331
|
+
self.window().moving = False
|
332
|
+
|
333
|
+
def toggle_maximize(self):
|
334
|
+
if self.window().isMaximized():
|
335
|
+
self.window().showNormal()
|
336
|
+
self.maximize_button.setText("❐")
|
337
|
+
else:
|
338
|
+
self.window().showMaximized()
|
339
|
+
self.maximize_button.setText("❐")
|
340
|
+
|
341
|
+
def set_icon(self, icon_path):
|
342
|
+
pixmap = QPixmap(icon_path)
|
343
|
+
self.icon_label.setPixmap(pixmap.scaled(20, 20, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
344
|
+
|
345
|
+
def set_title(self, title):
|
346
|
+
self.title.setText(title)
|
347
|
+
|
192
348
|
class BrowserWindow:
|
193
349
|
def __init__(
|
194
350
|
self,
|
@@ -226,6 +382,37 @@ class BrowserWindow:
|
|
226
382
|
self.shortcuts = {}
|
227
383
|
###########################################################################################
|
228
384
|
|
385
|
+
def set_custom_frame(self, use_custom: bool, title: str = "Custom Title", bg_color: str = "darkblue", text_color: str = "white", icon_path: str = None):
|
386
|
+
"""커스텀 프레임을 설정하거나 제거합니다."""
|
387
|
+
if use_custom:
|
388
|
+
self._window.setWindowFlags(Qt.FramelessWindowHint)
|
389
|
+
self.custom_title_bar = CustomTitleBar(self._window)
|
390
|
+
self.custom_title_bar.set_style(bg_color, text_color)
|
391
|
+
self.custom_title_bar.set_title(title)
|
392
|
+
|
393
|
+
if icon_path:
|
394
|
+
self.custom_title_bar.set_icon(icon_path)
|
395
|
+
|
396
|
+
layout = QVBoxLayout()
|
397
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
398
|
+
layout.setSpacing(0)
|
399
|
+
layout.addWidget(self.custom_title_bar)
|
400
|
+
layout.addWidget(self.web_view)
|
401
|
+
|
402
|
+
central_widget = QWidget()
|
403
|
+
central_widget.setLayout(layout)
|
404
|
+
self._window.setCentralWidget(central_widget)
|
405
|
+
|
406
|
+
# 창 이동을 위한 속성 추가
|
407
|
+
self._window.moving = False
|
408
|
+
self._window.offset = QPoint()
|
409
|
+
else:
|
410
|
+
self._window.setWindowFlags(Qt.Window)
|
411
|
+
self._window.setCentralWidget(self.web_view)
|
412
|
+
self.custom_title_bar = None
|
413
|
+
|
414
|
+
self._window.show()
|
415
|
+
|
229
416
|
def _load(self):
|
230
417
|
self._window.setWindowTitle(self.title)
|
231
418
|
|
@@ -397,26 +584,21 @@ class BrowserWindow:
|
|
397
584
|
self.dev_tools_window.resize(800, 600)
|
398
585
|
self.dev_tools_window.show()
|
399
586
|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
"title": self.title,
|
405
|
-
"width": self.width,
|
406
|
-
"height": self.height,
|
407
|
-
"x": self.x,
|
408
|
-
"y": self.y,
|
409
|
-
"frame": self.frame,
|
410
|
-
"context_menu": self.context_menu,
|
411
|
-
"dev_tools": self.dev_tools,
|
412
|
-
}
|
413
|
-
|
414
|
-
def get_id(self):
|
415
|
-
"""Returns the ID of the window."""
|
416
|
-
return self.id
|
587
|
+
# Add this line to handle dev tools window closure
|
588
|
+
self.dev_tools_window.closeEvent = lambda event: setattr(
|
589
|
+
self, "dev_tools_window", None
|
590
|
+
)
|
417
591
|
|
418
592
|
def closeEvent(self, event):
|
419
593
|
"""Handles the event when the window is closed."""
|
594
|
+
# Close developer tools if open
|
595
|
+
if hasattr(self, "dev_tools_window") and self.dev_tools_window:
|
596
|
+
self.dev_tools_window.close()
|
597
|
+
self.dev_tools_window = None
|
598
|
+
|
599
|
+
# Solve memory leak issue with web view engine
|
600
|
+
self.web_view.page().deleteLater()
|
601
|
+
self.web_view.deleteLater()
|
420
602
|
self._remove_from_app_windows()
|
421
603
|
event.accept() # Accept the event (allow the window to close)
|
422
604
|
|
@@ -556,15 +738,53 @@ class BrowserWindow:
|
|
556
738
|
###########################################################################################
|
557
739
|
# Get Properties
|
558
740
|
###########################################################################################
|
741
|
+
def get_window_properties(self):
|
742
|
+
"""Returns the properties of the window."""
|
743
|
+
return {
|
744
|
+
"id": self.id,
|
745
|
+
"title": self.title,
|
746
|
+
"width": self.width,
|
747
|
+
"height": self.height,
|
748
|
+
"x": self.x,
|
749
|
+
"y": self.y,
|
750
|
+
"frame": self.frame,
|
751
|
+
"context_menu": self.context_menu,
|
752
|
+
"dev_tools": self.dev_tools,
|
753
|
+
}
|
559
754
|
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
755
|
+
def get_id(self):
|
756
|
+
"""Returns the ID of the window."""
|
757
|
+
return self.id
|
758
|
+
|
759
|
+
def get_size(self) -> Dict[str, int]:
|
760
|
+
"""Returns the size of the window."""
|
761
|
+
return {"width": self.width, "height": self.height}
|
762
|
+
|
763
|
+
def get_position(self) -> Dict[str, int]:
|
764
|
+
"""Returns the position of the window."""
|
765
|
+
return {"x": self.x, "y": self.y}
|
766
|
+
|
767
|
+
def get_title(self) -> str:
|
768
|
+
"""Returns the title of the window."""
|
769
|
+
return self.title
|
770
|
+
|
771
|
+
def get_url(self) -> str:
|
772
|
+
"""Returns the URL of the window."""
|
773
|
+
return self.web_view.url().toString()
|
774
|
+
|
775
|
+
def get_visible(self) -> bool:
|
776
|
+
"""Returns the visibility of the window."""
|
777
|
+
return self._window.isVisible()
|
778
|
+
|
779
|
+
def set_resizable(self, resizable: bool):
|
780
|
+
"""창의 크기 조절 가능 여부를 설정합니다."""
|
781
|
+
self.resizable = resizable
|
782
|
+
if resizable:
|
783
|
+
self._window.setWindowFlags(self._window.windowFlags() & ~Qt.MSWindowsFixedSizeDialogHint)
|
784
|
+
else:
|
785
|
+
self._window.setWindowFlags(self._window.windowFlags() | Qt.MSWindowsFixedSizeDialogHint)
|
786
|
+
self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
|
787
|
+
|
568
788
|
|
569
789
|
class _WindowController(QObject):
|
570
790
|
create_window_signal = Signal(
|
@@ -602,7 +822,7 @@ class Pyloid(QApplication):
|
|
602
822
|
|
603
823
|
self.app_name = app_name
|
604
824
|
self.app_path = sys.executable
|
605
|
-
|
825
|
+
|
606
826
|
self.auto_start = AutoStart(self.app_name, self.app_path)
|
607
827
|
|
608
828
|
self.animation_timer = None
|
@@ -747,8 +967,11 @@ class Pyloid(QApplication):
|
|
747
967
|
window._window.close()
|
748
968
|
|
749
969
|
def quit(self):
|
750
|
-
"""
|
751
|
-
self.
|
970
|
+
"""애플리케이션을 종료합니다."""
|
971
|
+
for window in self.windows:
|
972
|
+
window._window.close()
|
973
|
+
window.web_page.deleteLater()
|
974
|
+
window.web_view.deleteLater()
|
752
975
|
QApplication.quit()
|
753
976
|
|
754
977
|
###########################################################################################
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|