pyloid 0.16.3__tar.gz → 0.16.6__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.16.3
3
+ Version: 0.16.6
4
4
  Summary:
5
5
  Author: aesthetics-of-record
6
6
  Author-email: 111675679+aesthetics-of-record@users.noreply.github.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyloid"
3
- version = "0.16.3"
3
+ version = "0.16.6"
4
4
  description = ""
5
5
  authors = ["aesthetics-of-record <111675679+aesthetics-of-record@users.noreply.github.com>"]
6
6
  readme = "README.md"
@@ -26,16 +26,46 @@ from PySide6.QtGui import QPixmap, QMovie
26
26
  from PySide6.QtWidgets import QSplashScreen, QLabel
27
27
  from PySide6.QtCore import QSize
28
28
  from typing import TYPE_CHECKING
29
+ from PySide6.QtWebEngineCore import QWebEngineSettings
29
30
 
30
31
  if TYPE_CHECKING:
31
32
  from ..pyloid import Pyloid
32
33
 
33
34
 
34
35
  # 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
36
+ class CustomWebPage(QWebEnginePage):
37
+ def __init__(self, profile=None):
38
+ super().__init__(profile)
39
+ # 권한 요청 시그널 연결
40
+ self.featurePermissionRequested.connect(self._handlePermissionRequest)
41
+ self._permission_handlers = {}
42
+
43
+ def _handlePermissionRequest(self, origin: QUrl, feature: QWebEnginePage.Feature):
44
+ """기본 권한 요청 핸들러"""
45
+ if feature in self._permission_handlers:
46
+ # 등록된 핸들러가 있으면 실행
47
+ handler = self._permission_handlers[feature]
48
+ handler(origin, feature)
49
+ else:
50
+ # 기본적으로 모든 권한 허용
51
+ self.setFeaturePermission(
52
+ origin, feature, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
53
+ )
54
+
55
+ def setPermissionHandler(self, feature: QWebEnginePage.Feature, handler):
56
+ """특정 권한에 대한 핸들러 등록"""
57
+ self._permission_handlers[feature] = handler
58
+
59
+
35
60
  class CustomWebEngineView(QWebEngineView):
36
61
  def __init__(self, parent=None):
37
62
  super().__init__(parent._window)
38
63
  self.parent = parent
64
+
65
+ # 커스텀 웹 페이지 설정
66
+ self.custom_page = CustomWebPage()
67
+ self.setPage(self.custom_page)
68
+
39
69
  self.drag_relative_position = None
40
70
  self.is_dragging = False
41
71
  self.is_resizing = False
@@ -199,6 +229,8 @@ class BrowserWindow:
199
229
  for js_api in js_apis:
200
230
  self.js_apis.append(js_api)
201
231
  self.shortcuts = {}
232
+ self.close_on_load = True
233
+ self.splash_screen = None
202
234
  ###########################################################################################
203
235
 
204
236
  def _set_custom_frame(
@@ -293,13 +325,13 @@ class BrowserWindow:
293
325
  def _on_load_finished(self, ok):
294
326
  """Handles the event when the web page finishes loading."""
295
327
  if ok and self.js_apis:
296
-
297
328
  # Load qwebchannel.js
298
329
  qwebchannel_js = QFile("://qtwebchannel/qwebchannel.js")
299
330
  if qwebchannel_js.open(QFile.ReadOnly):
300
331
  source = bytes(qwebchannel_js.readAll()).decode("utf-8")
301
332
  self.web_view.page().runJavaScript(source)
302
333
  qwebchannel_js.close()
334
+
303
335
 
304
336
  js_code = """
305
337
  if (typeof QWebChannel !== 'undefined') {
@@ -331,6 +363,22 @@ class BrowserWindow:
331
363
  window.pyloid.WindowAPI.startSystemDrag();
332
364
  }
333
365
  });
366
+
367
+ function updateTheme(theme) {
368
+ document.documentElement.setAttribute(
369
+ 'data-pyloid-theme',
370
+ theme
371
+ );
372
+ }
373
+
374
+ // 테마 변경 이벤트 리스너
375
+ document.addEventListener('themeChange', (e) => {
376
+ console.log('themeChange event received:', e);
377
+ updateTheme(e.detail.theme);
378
+ });
379
+
380
+ updateTheme('%s');
381
+
334
382
 
335
383
  // Dispatch a custom event to signal that the initialization is ready
336
384
  const event = new CustomEvent('pyloidReady');
@@ -347,7 +395,7 @@ class BrowserWindow:
347
395
  for js_api in self.js_apis
348
396
  ]
349
397
  )
350
- self.web_view.page().runJavaScript(js_code % js_api_init)
398
+ self.web_view.page().runJavaScript(js_code % (js_api_init, self.app.theme))
351
399
 
352
400
  # if splash screen is set, close it when the page is loaded
353
401
  if self.close_on_load and self.splash_screen:
@@ -1377,6 +1425,26 @@ class BrowserWindow:
1377
1425
  """
1378
1426
  return self._window
1379
1427
 
1428
+ def get_QWebEngineView(self) -> CustomWebEngineView:
1429
+ """
1430
+ Returns the CustomWebEngineView object which inherits from QWebEngineView.
1431
+
1432
+ Returns
1433
+ -------
1434
+ CustomWebEngineView
1435
+ CustomWebEngineView object of the window
1436
+
1437
+ Examples
1438
+ --------
1439
+ ```python
1440
+ window = app.create_window("pyloid-window")
1441
+ web_view = window.get_QWebEngineView()
1442
+
1443
+ web_view.page().runJavaScript("console.log('Hello, Pyloid!')")
1444
+ ```
1445
+ """
1446
+ return self.web_view
1447
+
1380
1448
  ###########################################################################################
1381
1449
  # QMainWindow flags
1382
1450
  ###########################################################################################
@@ -1607,3 +1675,124 @@ class BrowserWindow:
1607
1675
  self.splash_screen.close()
1608
1676
  self.close_on_load = None
1609
1677
  self.splash_screen = None
1678
+
1679
+ ###########################################################################################
1680
+ # WebEngineView Attribute setting
1681
+ ###########################################################################################
1682
+ def set_web_engine_view_attribute(self, attribute: QWebEngineSettings, on: bool):
1683
+ """
1684
+ Sets the attribute of the WebEngineView.
1685
+
1686
+ Parameters
1687
+ ----------
1688
+ attribute : QWebEngineSettings
1689
+ Attribute to set
1690
+ on : bool
1691
+ True to enable the attribute, False to disable it
1692
+
1693
+ Examples
1694
+ --------
1695
+ ```python
1696
+ window.set_web_engine_view_attribute(QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, False)
1697
+ ```
1698
+ """
1699
+ settings = self.web_view.settings()
1700
+ settings.setAttribute(attribute, on)
1701
+
1702
+ def is_web_engine_view_attribute(self, attribute: QWebEngineSettings) -> bool:
1703
+ """
1704
+ Returns the attribute of the WebEngineView.
1705
+
1706
+ Parameters
1707
+ ----------
1708
+ attribute : QWebEngineSettings
1709
+ Attribute to get
1710
+
1711
+ Returns
1712
+ -------
1713
+ bool
1714
+ True if the attribute is enabled, False otherwise
1715
+
1716
+ Examples
1717
+ --------
1718
+ ```python
1719
+ window.is_web_engine_view_attribute(QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard)
1720
+ ```
1721
+ """
1722
+ settings = self.web_view.settings()
1723
+ return settings.testAttribute(attribute)
1724
+
1725
+ def set_permission_handler(self, feature: QWebEnginePage.Feature, handler):
1726
+ """
1727
+ Sets a handler for a specific permission.
1728
+
1729
+ Parameters
1730
+ ----------
1731
+ feature : QWebEnginePage.Feature
1732
+ The type of permission to set
1733
+ handler : callable
1734
+ The handler function to process the permission request
1735
+
1736
+ Examples
1737
+ --------
1738
+ ```python
1739
+ def handle_camera(origin, feature):
1740
+ window.web_view.page().setFeaturePermission(
1741
+ origin,
1742
+ feature,
1743
+ QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1744
+ )
1745
+
1746
+ window.set_permission_handler(
1747
+ QWebEnginePage.Feature.MediaVideoCapture,
1748
+ handle_camera
1749
+ )
1750
+ ```
1751
+ """
1752
+ self.web_view.custom_page.setPermissionHandler(feature, handler)
1753
+
1754
+ def grant_permission(self, feature: QWebEnginePage.Feature):
1755
+ """
1756
+ Automatically grants a specific permission when a request is made.
1757
+
1758
+ Parameters
1759
+ ----------
1760
+ feature : QWebEnginePage.Feature
1761
+ The type of permission to automatically grant
1762
+
1763
+ Examples
1764
+ --------
1765
+ ```python
1766
+ window.grant_permission(QWebEnginePage.Feature.MediaVideoCapture)
1767
+ ```
1768
+ """
1769
+
1770
+ def auto_grant(origin, feat):
1771
+ self.web_view.page().setFeaturePermission(
1772
+ origin, feat, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1773
+ )
1774
+
1775
+ self.set_permission_handler(feature, auto_grant)
1776
+
1777
+ def deny_permission(self, feature: QWebEnginePage.Feature):
1778
+ """
1779
+ Automatically denies a specific permission when a request is made.
1780
+
1781
+ Parameters
1782
+ ----------
1783
+ feature : QWebEnginePage.Feature
1784
+ The type of permission to automatically deny
1785
+
1786
+ Examples
1787
+ --------
1788
+ ```python
1789
+ window.deny_permission(QWebEnginePage.Feature.Notifications)
1790
+ ```
1791
+ """
1792
+
1793
+ def auto_deny(origin, feat):
1794
+ self.web_view.page().setFeaturePermission(
1795
+ origin, feat, QWebEnginePage.PermissionPolicy.PermissionDeniedByUser
1796
+ )
1797
+
1798
+ self.set_permission_handler(feature, auto_deny)
@@ -1,6 +1,7 @@
1
1
  from typing import TYPE_CHECKING, Optional
2
2
 
3
3
  from ..api import PyloidAPI, Bridge
4
+ from PySide6.QtCore import QByteArray, QBuffer, QIODeviceBase
4
5
 
5
6
  if TYPE_CHECKING:
6
7
  from ..pyloid import Pyloid
@@ -208,3 +209,38 @@ class WindowAPI(PyloidAPI):
208
209
  window = self.app.get_window_by_id(self.window_id)
209
210
  if window:
210
211
  window.web_view.start_system_drag()
212
+
213
+ ###############################################################
214
+ # Clipboard
215
+ ###############################################################
216
+
217
+ @Bridge(str)
218
+ def setClipboardText(self, text: str):
219
+ """Sets the text to the clipboard."""
220
+ self.app.set_clipboard_text(text)
221
+
222
+ @Bridge(result=str)
223
+ def getClipboardText(self):
224
+ """Gets the text from the clipboard."""
225
+ return self.app.get_clipboard_text()
226
+
227
+ @Bridge(str, str)
228
+ def setClipboardImage(self, image_path: str, format: str):
229
+ """Sets the image to the clipboard."""
230
+ self.app.set_clipboard_image(image_path, format)
231
+
232
+ @Bridge(result=str)
233
+ def getClipboardImage(self):
234
+ """클립보드의 이미지를 Base64 인코딩된 데이터 URL로 반환합니다."""
235
+ image = self.app.get_clipboard_image() # QImage 반환 가정
236
+ if image and not image.isNull():
237
+ # QImage를 바이트 배열로 변환
238
+ byte_array = QByteArray()
239
+ buffer = QBuffer(byte_array)
240
+ buffer.open(QIODeviceBase.WriteOnly)
241
+ image.save(buffer, "PNG") # PNG 형식으로 저장
242
+
243
+ # Base64로 인코딩
244
+ base64_data = byte_array.toBase64().data().decode()
245
+ return f"data:image/png;base64,{base64_data}"
246
+ return ""
@@ -15,7 +15,7 @@ from PySide6.QtGui import (
15
15
  from PySide6.QtCore import Qt, Signal, QObject, QTimer
16
16
  from PySide6.QtNetwork import QLocalServer, QLocalSocket
17
17
  from .api import PyloidAPI
18
- from typing import List, Optional, Dict, Callable, Union
18
+ from typing import List, Optional, Dict, Callable, Union, Literal
19
19
  from PySide6.QtCore import qInstallMessageHandler
20
20
  import signal
21
21
  from .utils import is_production
@@ -25,6 +25,7 @@ from .filewatcher import FileWatcher
25
25
  import logging
26
26
  from .browser_window import BrowserWindow
27
27
  from .tray import TrayEvent
28
+ from PySide6.QtCore import QCoreApplication
28
29
 
29
30
  # for linux debug
30
31
  os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
@@ -32,6 +33,11 @@ os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
32
33
  # for macos debug
33
34
  logging.getLogger("Qt").setLevel(logging.ERROR)
34
35
 
36
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
37
+ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = (
38
+ "--enable-features=WebRTCPipeWireCapturer --ignore-certificate-errors --allow-insecure-localhost"
39
+ )
40
+
35
41
 
36
42
  def custom_message_handler(mode, context, message):
37
43
  if not hasattr(custom_message_handler, "vulkan_warning_shown") and (
@@ -56,6 +62,7 @@ def custom_message_handler(mode, context, message):
56
62
 
57
63
  qInstallMessageHandler(custom_message_handler)
58
64
 
65
+
59
66
  class _WindowController(QObject):
60
67
  create_window_signal = Signal(
61
68
  QApplication, str, int, int, int, int, bool, bool, bool, list
@@ -123,6 +130,51 @@ class Pyloid(QApplication):
123
130
  self.icon_frames = []
124
131
  self.current_frame = 0
125
132
 
133
+ self.theme = (
134
+ "dark"
135
+ if self.styleHints().colorScheme() == Qt.ColorScheme.Dark
136
+ else "light"
137
+ )
138
+
139
+ # Add color scheme tracking
140
+ self.styleHints().colorSchemeChanged.connect(self._handle_color_scheme_change)
141
+
142
+ # def set_theme(self, theme: Literal["system", "dark", "light"]):
143
+ # """
144
+ # 시스템의 테마를 설정합니다.
145
+
146
+ # Parameters
147
+ # ----------
148
+ # theme : Literal["system", "dark", "light"]
149
+ # 설정할 테마 ("system", "dark", "light" 중 하나)
150
+
151
+ # Examples
152
+ # --------
153
+ # >>> app = Pyloid(app_name="Pyloid-App")
154
+ # >>> app.set_theme("dark") # 다크 테마로 설정
155
+ # >>> app.set_theme("light") # 라이트 테마로 설정
156
+ # >>> app.set_theme("system") # 시스템 테마를 따름
157
+ # """
158
+ # self.theme = theme
159
+
160
+ # if theme == "system":
161
+ # # 시스템 테마를 light/dark 문자열로 변환
162
+ # system_theme = (
163
+ # "dark"
164
+ # if self.styleHints().colorScheme() == Qt.ColorScheme.Dark
165
+ # else "light"
166
+ # )
167
+ # self._handle_color_scheme_change(system_theme)
168
+ # self.styleHints().colorSchemeChanged.connect(
169
+ # lambda: self._handle_color_scheme_change(system_theme)
170
+ # )
171
+ # else:
172
+ # # 기존 이벤트 연결 해제
173
+ # self.styleHints().colorSchemeChanged.disconnect(
174
+ # lambda: self._handle_color_scheme_change(self.theme)
175
+ # )
176
+ # self._handle_color_scheme_change(self.theme)
177
+
126
178
  def set_icon(self, icon_path: str):
127
179
  """
128
180
  Dynamically sets the application's icon.
@@ -407,7 +459,7 @@ class Pyloid(QApplication):
407
459
  app = Pyloid(app_name="Pyloid-App")
408
460
 
409
461
  window = app.get_window_by_id("123e4567-e89b-12d3-a456-426614174000")
410
-
462
+
411
463
  if window:
412
464
  print("Window found:", window)
413
465
  ```
@@ -1244,7 +1296,9 @@ class Pyloid(QApplication):
1244
1296
  ###########################################################################################
1245
1297
  # File dialog
1246
1298
  ###########################################################################################
1247
- def open_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
1299
+ def open_file_dialog(
1300
+ self, dir: Optional[str] = None, filter: Optional[str] = None
1301
+ ) -> Optional[str]:
1248
1302
  """
1249
1303
  Opens a file dialog to select a file to open.
1250
1304
 
@@ -1270,7 +1324,9 @@ class Pyloid(QApplication):
1270
1324
  file_path, _ = QFileDialog.getOpenFileName(None, dir=dir, filter=filter)
1271
1325
  return file_path if file_path else None
1272
1326
 
1273
- def save_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
1327
+ def save_file_dialog(
1328
+ self, dir: Optional[str] = None, filter: Optional[str] = None
1329
+ ) -> Optional[str]:
1274
1330
  """
1275
1331
  Opens a file dialog to select a file to save.
1276
1332
 
@@ -1320,4 +1376,22 @@ class Pyloid(QApplication):
1320
1376
  directory_path = QFileDialog.getExistingDirectory(None, dir=dir)
1321
1377
  return directory_path if directory_path else None
1322
1378
 
1379
+ def _handle_color_scheme_change(self):
1380
+ self.theme = (
1381
+ "dark"
1382
+ if self.styleHints().colorScheme() == Qt.ColorScheme.Dark
1383
+ else "light"
1384
+ )
1385
+
1386
+ js_code = f"""
1387
+ document.dispatchEvent(new CustomEvent('themeChange', {{
1388
+ detail: {{ theme: "{self.theme}" }}
1389
+ }}));
1390
+ """
1323
1391
 
1392
+ # 모든 윈도우에 변경사항 적용
1393
+ for window in self.windows:
1394
+ window.web_view.page().runJavaScript(js_code)
1395
+ window.web_view.page().setBackgroundColor(
1396
+ Qt.GlobalColor.black if self.theme == "dark" else Qt.GlobalColor.white
1397
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes