pyloid 0.20.1.dev1__py3-none-any.whl → 0.20.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyloid/browser_window.py CHANGED
@@ -1,1968 +1,1915 @@
1
- import sys
2
- import os
3
- from PySide6.QtWidgets import (
4
- QMainWindow,
5
- )
6
- from PySide6.QtWebEngineWidgets import QWebEngineView
7
- from PySide6.QtWebChannel import QWebChannel
8
- from PySide6.QtGui import (
9
- QKeySequence,
10
- QShortcut,
11
- QCursor,
12
- )
13
- from PySide6.QtCore import Qt, QPoint, QUrl, QEvent, QFile
14
- from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
15
- from .api import PyloidAPI
16
- import uuid
17
- from typing import List, Optional, Dict, Callable
18
- import json
19
- from PySide6.QtWidgets import (
20
- QWidget,
21
- QVBoxLayout,
22
- )
23
- from .custom.titlebar import CustomTitleBar
24
- from .js_api.window_api import WindowAPI
25
- from PySide6.QtGui import QPixmap, QMovie
26
- from PySide6.QtWidgets import QSplashScreen, QLabel
27
- from PySide6.QtCore import QSize
28
- from typing import TYPE_CHECKING
29
- from PySide6.QtWebEngineCore import QWebEngineSettings, QWebEngineUrlRequestInterceptor
30
- from .utils import get_production_path, is_production
31
-
32
- if TYPE_CHECKING:
33
- from ..pyloid import Pyloid
34
-
35
-
36
- # 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
37
- class CustomWebPage(QWebEnginePage):
38
- def __init__(self, profile=None):
39
- super().__init__(profile)
40
- self.featurePermissionRequested.connect(self._handlePermissionRequest)
41
- self.desktopMediaRequested.connect(self._handleDesktopMediaRequest)
42
- self._permission_handlers = {}
43
- self._desktop_media_handler = None
44
-
45
- def _handlePermissionRequest(self, origin: QUrl, feature: QWebEnginePage.Feature):
46
- print(origin, feature)
47
-
48
- """Default permission request handler"""
49
- if feature in self._permission_handlers:
50
- # Execute if a handler is registered
51
- handler = self._permission_handlers[feature]
52
- handler(origin, feature)
53
- else:
54
- # Allow all permissions by default
55
- self.setFeaturePermission(
56
- origin, feature, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
57
- )
58
-
59
- def setPermissionHandler(self, feature: QWebEnginePage.Feature, handler):
60
- """Register a handler for a specific permission"""
61
- self._permission_handlers[feature] = handler
62
-
63
- def _handleDesktopMediaRequest(self, *args, **kwargs):
64
- print("Desktop media request received:", args, kwargs)
65
-
66
- # def setDesktopMediaHandler(self, handler):
67
- # """desktop media handler"""
68
- # self._desktop_media_handler = handler
69
-
70
- class CustomInterceptor(QWebEngineUrlRequestInterceptor):
71
- def __init__(self, index_path=None):
72
- super().__init__()
73
- self.index_path = get_production_path()
74
- self.last_path = "/"
75
-
76
- def interceptRequest(self, info):
77
- url = info.requestUrl()
78
- navigation_type = info.navigationType()
79
-
80
- print("--------------------------------")
81
-
82
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeTyped:
83
- print("NavigationTypeTyped")
84
-
85
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeReload:
86
- print("NavigationTypeReload")
87
-
88
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeBackForward:
89
- print("NavigationTypeBackForward")
90
-
91
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeLinkClicked:
92
- print("NavigationTypeLinkClicked")
93
-
94
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeFormSubmitted:
95
- print("NavigationTypeFormSubmitted")
96
-
97
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeTyped:
98
- print("NavigationTypeTyped")
99
-
100
- if navigation_type == QWebEnginePage.NavigationType.NavigationTypeOther:
101
- print("NavigationTypeOther")
102
-
103
- print(url)
104
- print(url.scheme())
105
- print(url.host())
106
- print(url.url())
107
- print(self.last_path)
108
-
109
- self.last_path = url.path()
110
-
111
-
112
- class CustomWebEngineView(QWebEngineView):
113
- def __init__(self, parent: "BrowserWindow" = None):
114
- super().__init__(parent._window)
115
- self.parent: "BrowserWindow" = parent
116
-
117
- # Custom Web Page
118
- self.custom_page = CustomWebPage()
119
- self.setPage(self.custom_page)
120
-
121
- self.is_resizing = False
122
- self.resize_start_pos = None
123
- self.resize_direction = None
124
- self.screen_geometry = self.screen().virtualGeometry()
125
- self.is_resizing_enabled = True
126
-
127
- self.setAttribute(Qt.WA_SetCursor, False)
128
-
129
- self.is_in_resize_area = False
130
-
131
- def mouse_press_event(self, event):
132
- if self.parent.frame or not self.is_resizing_enabled:
133
- return
134
-
135
- if event.button() == Qt.LeftButton:
136
- self.resize_direction = self.get_resize_direction(event.pos())
137
- if self.resize_direction:
138
- self.is_resizing = True
139
- self.resize_start_pos = event.globalPos()
140
-
141
- def start_system_drag(self):
142
- """Start system window move"""
143
- if self.parent._window.windowHandle():
144
- self.parent._window.windowHandle().startSystemMove()
145
-
146
- def mouse_move_event(self, event):
147
- if self.parent.frame or not self.is_resizing_enabled:
148
- return
149
-
150
- # Check resize direction
151
- was_in_resize_area = self.is_in_resize_area
152
- resize_direction = self.get_resize_direction(event.pos())
153
- self.is_in_resize_area = bool(resize_direction)
154
-
155
- if resize_direction and not self.is_resizing:
156
- self.set_cursor_for_resize_direction(resize_direction)
157
-
158
- if self.is_resizing:
159
- self.resize_window(event.globalPos())
160
- return
161
-
162
- # Change cursor when entering/leaving resize area
163
- if self.is_in_resize_area != was_in_resize_area:
164
- if self.is_in_resize_area:
165
- # self.setAttribute(Qt.WA_SetCursor, True)
166
- pass
167
- else:
168
- self.unsetCursor()
169
- # self.setAttribute(Qt.WA_SetCursor, False)
170
-
171
- def mouse_release_event(self, event):
172
- if self.parent.frame or not self.is_resizing_enabled:
173
- return
174
-
175
- if event.button() == Qt.LeftButton:
176
- self.is_resizing = False
177
-
178
- if self.resize_direction:
179
- self.unsetCursor()
180
- self.resize_direction = None
181
-
182
- self.setAttribute(Qt.WA_SetCursor, False)
183
-
184
- def eventFilter(self, source, event):
185
- if self.focusProxy() is source:
186
- if event.type() == QEvent.MouseButtonPress:
187
- self.mouse_press_event(event)
188
- elif event.type() == QEvent.MouseMove:
189
- self.mouse_move_event(event)
190
- elif event.type() == QEvent.MouseButtonRelease:
191
- self.mouse_release_event(event)
192
- return super().eventFilter(source, event)
193
-
194
- def get_resize_direction(self, pos):
195
- if (
196
- not self.parent.frame and self.is_resizing_enabled
197
- ): # Check if frame is not present and resizing is enabled
198
- margin = 8 # Margin in pixels to detect edge
199
- rect = self.rect()
200
- direction = None
201
-
202
- if pos.x() <= margin:
203
- direction = "left"
204
- elif pos.x() >= rect.width() - margin:
205
- direction = "right"
206
-
207
- if pos.y() <= margin:
208
- direction = "top" if direction is None else direction + "-top"
209
- elif pos.y() >= rect.height() - margin:
210
- direction = "bottom" if direction is None else direction + "-bottom"
211
-
212
- return direction
213
- return None
214
-
215
- def set_cursor_for_resize_direction(self, direction):
216
- if not self.parent.frame and direction and self.is_resizing_enabled:
217
- cursor = None
218
- if direction in ["left", "right"]:
219
- cursor = Qt.SizeHorCursor
220
- elif direction in ["top", "bottom"]:
221
- cursor = Qt.SizeVerCursor
222
- elif direction in ["left-top", "right-bottom"]:
223
- cursor = Qt.SizeFDiagCursor
224
- elif direction in ["right-top", "left-bottom"]:
225
- cursor = Qt.SizeBDiagCursor
226
-
227
- if cursor:
228
- self.setCursor(cursor)
229
- self.setAttribute(Qt.WA_SetCursor, True)
230
-
231
- def resize_window(self, global_pos):
232
- if (
233
- not self.parent.frame
234
- and self.resize_start_pos
235
- and self.resize_direction
236
- and self.is_resizing_enabled
237
- ): # Check if frame is not present and resizing is enabled
238
- delta = global_pos - self.resize_start_pos
239
- new_geometry = self.parent._window.geometry()
240
-
241
- if "left" in self.resize_direction:
242
- new_geometry.setLeft(new_geometry.left() + delta.x())
243
- if "right" in self.resize_direction:
244
- new_geometry.setRight(new_geometry.right() + delta.x())
245
- if "top" in self.resize_direction:
246
- new_geometry.setTop(new_geometry.top() + delta.y())
247
- if "bottom" in self.resize_direction:
248
- new_geometry.setBottom(new_geometry.bottom() + delta.y())
249
-
250
- self.parent._window.setGeometry(new_geometry)
251
- self.resize_start_pos = global_pos
252
-
253
-
254
- class BrowserWindow:
255
- def __init__(
256
- self,
257
- app: "Pyloid",
258
- title: str = "pyloid app",
259
- width: int = 800,
260
- height: int = 600,
261
- x: int = 200,
262
- y: int = 200,
263
- frame: bool = True,
264
- context_menu: bool = False,
265
- dev_tools: bool = False,
266
- js_apis: List[PyloidAPI] = [],
267
- ):
268
- ###########################################################################################
269
- self.id = str(uuid.uuid4()) # Generate unique ID
270
- self._window = QMainWindow()
271
- self.web_view = CustomWebEngineView(self)
272
-
273
- self._window.closeEvent = self.closeEvent # Override closeEvent method
274
- ###########################################################################################
275
- self.app = app
276
- self.title = title
277
- self.width = width
278
- self.height = height
279
- self.x = x
280
- self.y = y
281
- self.frame = frame
282
- self.context_menu = context_menu
283
- self.dev_tools = dev_tools
284
- self.js_apis = [WindowAPI()]
285
- for js_api in js_apis:
286
- self.js_apis.append(js_api)
287
- self.shortcuts = {}
288
- self.close_on_load = True
289
- self.splash_screen = None
290
- ###########################################################################################
291
-
292
- def _set_custom_frame(
293
- self,
294
- use_custom: bool,
295
- title: str = "Custom Title",
296
- bg_color: str = "darkblue",
297
- text_color: str = "white",
298
- icon_path: str = None,
299
- ):
300
- """Sets or removes the custom frame."""
301
- if use_custom:
302
- self._window.setWindowFlags(Qt.FramelessWindowHint)
303
- self.custom_title_bar = CustomTitleBar(self._window)
304
- self.custom_title_bar.set_style(bg_color, text_color)
305
- self.custom_title_bar.set_title(title)
306
-
307
- if icon_path:
308
- self.custom_title_bar.set_icon(icon_path)
309
-
310
- layout = QVBoxLayout()
311
- layout.setContentsMargins(0, 0, 0, 0)
312
- layout.setSpacing(0)
313
- layout.addWidget(self.custom_title_bar)
314
- layout.addWidget(self.web_view)
315
-
316
- central_widget = QWidget()
317
- central_widget.setLayout(layout)
318
- self._window.setCentralWidget(central_widget)
319
-
320
- # Add properties for window movement
321
- self._window.moving = False
322
- self._window.offset = QPoint()
323
- else:
324
- self._window.setWindowFlags(Qt.Window)
325
- self._window.setCentralWidget(self.web_view)
326
- self.custom_title_bar = None
327
-
328
- self._window.show()
329
-
330
- def _load(self):
331
- self.set_title(self.title)
332
-
333
- self.set_size(self.width, self.height)
334
- self.set_position(self.x, self.y)
335
-
336
- # allow local file access to remote urls and screen capture
337
- self.web_view.settings().setAttribute(
338
- QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True
339
- )
340
- self.web_view.settings().setAttribute(
341
- QWebEngineSettings.WebAttribute.ScreenCaptureEnabled, True
342
- )
343
- self.web_view.settings().setAttribute(
344
- QWebEngineSettings.WebAttribute.AutoLoadImages, True
345
- )
346
- self.web_view.settings().setAttribute(
347
- QWebEngineSettings.WebAttribute.JavascriptEnabled, True
348
- )
349
- self.web_view.settings().setAttribute(
350
- QWebEngineSettings.WebAttribute.LocalStorageEnabled, True
351
- )
352
- self.web_view.settings().setAttribute(
353
- QWebEngineSettings.WebAttribute.ErrorPageEnabled, True
354
- )
355
- self.web_view.settings().setAttribute(
356
- QWebEngineSettings.WebAttribute.AutoLoadIconsForPage, True
357
- )
358
- self.web_view.settings().setAttribute(
359
- QWebEngineSettings.WebAttribute.ShowScrollBars, True
360
- )
361
- self.web_view.settings().setAttribute(
362
- QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True
363
- )
364
- self.web_view.settings().setAttribute(
365
- QWebEngineSettings.WebAttribute.PdfViewerEnabled, True
366
- )
367
- self.web_view.settings().setAttribute(
368
- QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True
369
- )
370
- self.web_view.settings().setAttribute(
371
- QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True
372
- )
373
- self.web_view.settings().setUnknownUrlSchemePolicy(
374
- QWebEngineSettings.UnknownUrlSchemePolicy.AllowAllUnknownUrlSchemes
375
- )
376
- self.web_view.settings().setAttribute(
377
- QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True
378
- )
379
- self.web_view.settings().setAttribute(
380
- QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True
381
- )
382
- self.web_view.settings().setAttribute(
383
- QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True
384
- )
385
- self.web_view.settings().setAttribute(
386
- QWebEngineSettings.WebAttribute.JavascriptCanPaste, True
387
- )
388
- self.web_view.settings().setAttribute(
389
- QWebEngineSettings.WebAttribute.WebRTCPublicInterfacesOnly, False
390
- )
391
-
392
- # Set icon
393
- if self.app.icon:
394
- self._window.setWindowIcon(self.app.icon)
395
- else:
396
- print("Icon is not set.")
397
-
398
- # Set Windows taskbar icon
399
- if sys.platform == "win32":
400
- import ctypes
401
-
402
- myappid = "mycompany.myproduct.subproduct.version"
403
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
404
-
405
- # Remove title bar and borders (if needed)
406
- if not self.frame:
407
- self._window.setWindowFlags(Qt.FramelessWindowHint)
408
-
409
- # Disable default context menu
410
- if not self.context_menu:
411
- self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
412
-
413
- # Set up QWebChannel
414
- self.channel = QWebChannel()
415
-
416
- # Register additional JS APIs
417
- if self.js_apis:
418
- for js_api in self.js_apis:
419
- # Define window_id, window, and app for each JS API
420
- js_api.window_id = self.id
421
- js_api.window = self
422
- js_api.app = self.app
423
-
424
- self.channel.registerObject(js_api.__class__.__name__, js_api)
425
-
426
- self.web_view.page().setWebChannel(self.channel)
427
-
428
- # Connect pylonjs bridge
429
- self.web_view.loadFinished.connect(self._on_load_finished)
430
-
431
- # Add QWebEngineView to main window
432
- self._window.setCentralWidget(self.web_view)
433
-
434
- # Set F12 shortcut
435
- self.set_dev_tools(self.dev_tools)
436
-
437
- # 프로필 가져오기 및 인터셉터 설정
438
- profile = self.web_view.page().profile()
439
-
440
- # # 기존 인터셉터가 있다면 제거
441
- # if self.interceptor:
442
- # profile.setUrlRequestInterceptor(None)
443
-
444
- # 인터셉터 설정
445
- self.interceptor = CustomInterceptor()
446
- profile.setUrlRequestInterceptor(self.interceptor)
447
-
448
- def _on_load_finished(self, ok):
449
- """Handles the event when the web page finishes loading."""
450
- if ok and self.js_apis:
451
- # Load qwebchannel.js
452
- qwebchannel_js = QFile("://qtwebchannel/qwebchannel.js")
453
- if qwebchannel_js.open(QFile.ReadOnly):
454
- source = bytes(qwebchannel_js.readAll()).decode("utf-8")
455
- self.web_view.page().runJavaScript(source)
456
- qwebchannel_js.close()
457
-
458
- js_code = """
459
- if (typeof QWebChannel !== 'undefined') {
460
- new QWebChannel(qt.webChannelTransport, function (channel) {
461
- window.pyloid = {
462
- EventAPI: {
463
- _listeners: {}, // 콜백 함수들을 저장할 객체
464
-
465
- listen: function(eventName, callback) {
466
- // 이벤트에 대한 콜백 배열이 없다면 생성
467
- if (!this._listeners[eventName]) {
468
- this._listeners[eventName] = [];
469
- }
470
-
471
- // 콜백 함수 저장
472
- this._listeners[eventName].push(callback);
473
-
474
- document.addEventListener(eventName, function(event) {
475
- let eventData;
476
- try {
477
- eventData = JSON.parse(event.detail);
478
- } catch (e) {
479
- eventData = event.detail;
480
- }
481
- callback(eventData);
482
- });
483
- },
484
-
485
- unlisten: function(eventName) {
486
- // 해당 이벤트의 모든 리스너 제거
487
- if (this._listeners[eventName]) {
488
- this._listeners[eventName].forEach(callback => {
489
- document.removeEventListener(eventName, callback);
490
- });
491
- // 저장된 콜백 제거
492
- delete this._listeners[eventName];
493
- }
494
- }
495
- }
496
- };
497
- console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
498
-
499
- %s
500
-
501
- document.addEventListener('mousedown', function (e) {
502
- if (e.target.hasAttribute('data-pyloid-drag-region')) {
503
- window.pyloid.WindowAPI.startSystemDrag();
504
- }
505
- });
506
-
507
- function updateTheme(theme) {
508
- document.documentElement.setAttribute(
509
- 'data-pyloid-theme',
510
- theme
511
- );
512
- }
513
-
514
- // 테마 변경 이벤트 리스너
515
- document.addEventListener('themeChange', (e) => {
516
- console.log('themeChange event received:', e);
517
- updateTheme(e.detail.theme);
518
- });
519
-
520
- updateTheme('%s');
521
-
522
-
523
- // Dispatch a custom event to signal that the initialization is ready
524
- const event = new CustomEvent('pyloidReady');
525
- document.dispatchEvent(event);
526
- });
527
- } else {
528
- console.error('QWebChannel is not defined.');
529
- }
530
- """
531
- js_api_init = "\n".join(
532
- [
533
- f"window.pyloid['{js_api.__class__.__name__}'] = channel.objects['{js_api.__class__.__name__}'];\n"
534
- f"console.log('pyloid.{js_api.__class__.__name__} object initialized:', window.pyloid['{js_api.__class__.__name__}']);"
535
- for js_api in self.js_apis
536
- ]
537
- )
538
- self.web_view.page().runJavaScript(js_code % (js_api_init, self.app.theme))
539
-
540
- # if splash screen is set, close it when the page is loaded
541
- if self.close_on_load and self.splash_screen:
542
- self.close_splash_screen()
543
- else:
544
- pass
545
-
546
- ###########################################################################################
547
- # Load
548
- ###########################################################################################
549
- def load_file(self, file_path: str):
550
- """
551
- Loads a local HTML file into the web view.
552
-
553
- Parameters
554
- ----------
555
- file_path : str
556
- The path to the local HTML file to be loaded.
557
-
558
- Examples
559
- --------
560
- >>> app = Pyloid(app_name="Pyloid-App")
561
- >>> window = app.create_window("pyloid-window")
562
- >>> window.load_file('/path/to/local/file.html')
563
- >>> window.show()
564
- """
565
- self._load()
566
- file_path = os.path.abspath(file_path) # absolute path
567
- self.web_view.setUrl(QUrl.fromLocalFile(file_path))
568
- self.web_view.focusProxy().installEventFilter(self.web_view)
569
-
570
- def load_url(self, url: str):
571
- """
572
- Sets the URL of the window.
573
-
574
- Parameters
575
- ----------
576
- url : str
577
- The URL to be loaded in the web view.
578
-
579
- Examples
580
- --------
581
- >>> app = Pyloid(app_name="Pyloid-App")
582
- >>> window = app.create_window("pyloid-window")
583
- >>> window.load_url('https://www.example.com')
584
- >>> window.show()
585
- """
586
- self._load()
587
- self.web_view.setUrl(QUrl(url))
588
- self.web_view.focusProxy().installEventFilter(self.web_view)
589
-
590
- def load_html(self, html_content: str, base_url: str = ""):
591
- """
592
- Loads HTML content directly into the web view.
593
-
594
- Parameters
595
- ----------
596
- html_content : str
597
- The HTML content to be loaded.
598
- base_url : str, optional
599
- The base URL to use for resolving relative URLs (default is "").
600
-
601
- Examples
602
- --------
603
- >>> app = Pyloid(app_name="Pyloid-App")
604
- >>> window = app.create_window("pyloid-window")
605
- >>> html_content = "<html><body><h1>Hello, Pyloid!</h1></body></html>"
606
- >>> window.load_html(html_content)
607
- >>> window.show()
608
- """
609
- self._load()
610
- self.web_view.setHtml(html_content, QUrl(base_url))
611
- self.web_view.focusProxy().installEventFilter(self.web_view)
612
-
613
- ###########################################################################################
614
- # Set Parameters
615
- ###########################################################################################
616
- def set_title(self, title: str):
617
- """
618
- Sets the title of the window.
619
-
620
- Parameters
621
- ----------
622
- title : str
623
- The title to be set for the window.
624
-
625
- Examples
626
- --------
627
- >>> app = Pyloid(app_name="Pyloid-App")
628
- >>> window = app.create_window("pyloid-window")
629
- >>> window.set_title('My Window Title')
630
- """
631
- self.title = title
632
- self._window.setWindowTitle(self.title)
633
-
634
- def set_size(self, width: int, height: int):
635
- """
636
- Sets the size of the window.
637
-
638
- Parameters
639
- ----------
640
- width : int
641
- The width of the window.
642
- height : int
643
- The height of the window.
644
-
645
- Examples
646
- --------
647
- >>> app = Pyloid(app_name="Pyloid-App")
648
- >>> window = app.create_window("pyloid-window")
649
- >>> window.set_size(800, 600)
650
- """
651
- self.width = width
652
- self.height = height
653
- self._window.setGeometry(self.x, self.y, self.width, self.height)
654
-
655
- def set_position(self, x: int, y: int):
656
- """
657
- Sets the position of the window.
658
-
659
- Parameters
660
- ----------
661
- x : int
662
- The x-coordinate of the window's position.
663
- y : int
664
- The y-coordinate of the window's position.
665
-
666
- Examples
667
- --------
668
- >>> app = Pyloid(app_name="Pyloid-App")
669
- >>> window = app.create_window("pyloid-window")
670
- >>> window.set_position(100, 100)
671
- """
672
- self.x = x
673
- self.y = y
674
- self._window.setGeometry(self.x, self.y, self.width, self.height)
675
-
676
- def set_position_by_anchor(self, anchor: str):
677
- """
678
- Positions the window at a specific location on the screen.
679
-
680
- Parameters
681
- ----------
682
- anchor : str
683
- The anchor point indicating where to position the window.
684
- Possible values: 'center', 'top', 'bottom', 'left', 'right',
685
- 'top-left', 'top-right', 'bottom-left', 'bottom-right'
686
-
687
- Examples
688
- --------
689
- >>> window.set_position_by_anchor('center')
690
- >>> window.set_position_by_anchor('top-right')
691
- """
692
- screen = self.app.primaryScreen().availableGeometry()
693
- window_size = self.get_size()
694
-
695
- if anchor == "center":
696
- x = (screen.width() - window_size["width"]) // 2
697
- y = (screen.height() - window_size["height"]) // 2
698
- elif anchor == "top":
699
- x = (screen.width() - window_size["width"]) // 2
700
- y = screen.top()
701
- elif anchor == "bottom":
702
- x = (screen.width() - window_size["width"]) // 2
703
- y = screen.bottom() - window_size["height"]
704
- elif anchor == "left":
705
- x = screen.left()
706
- y = (screen.height() - window_size["height"]) // 2
707
- elif anchor == "right":
708
- x = screen.right() - window_size["width"]
709
- y = (screen.height() - window_size["height"]) // 2
710
- elif anchor == "top-left":
711
- x = screen.left()
712
- y = screen.top()
713
- elif anchor == "top-right":
714
- x = screen.right() - window_size["width"]
715
- y = screen.top()
716
- elif anchor == "bottom-left":
717
- x = screen.left()
718
- y = screen.bottom() - window_size["height"]
719
- elif anchor == "bottom-right":
720
- x = screen.right() - window_size["width"]
721
- y = screen.bottom() - window_size["height"]
722
- else:
723
- raise ValueError("Invalid anchor point.")
724
-
725
- self.set_position(x, y)
726
-
727
- def set_frame(self, frame: bool):
728
- """
729
- Sets the frame of the window.
730
-
731
- Parameters
732
- ----------
733
- frame : bool
734
- If True, the window will have a frame. If False, the window will be frameless.
735
-
736
- Examples
737
- --------
738
- >>> app = Pyloid(app_name="Pyloid-App")
739
- >>> window = app.create_window("pyloid-window")
740
- >>> window.set_frame(True)
741
- >>> window.set_frame(False)
742
- """
743
- self.frame = frame
744
- was_visible = self._window.isVisible()
745
- if self.frame:
746
- self._window.setWindowFlags(Qt.Window)
747
- else:
748
- self._window.setWindowFlags(Qt.FramelessWindowHint)
749
- if was_visible:
750
- self._window.show()
751
-
752
- def set_context_menu(self, context_menu: bool):
753
- """
754
- Sets the context menu of the window.
755
-
756
- Parameters
757
- ----------
758
- context_menu : bool
759
- If True, the context menu will be disabled. If False, the default context menu will be enabled.
760
-
761
- Examples
762
- --------
763
- >>> app = Pyloid(app_name="Pyloid-App")
764
- >>> window = app.create_window("pyloid-window")
765
- >>> window.set_context_menu(True)
766
- >>> window.set_context_menu(False)
767
- """
768
- self.context_menu = context_menu
769
- if self.context_menu:
770
- self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
771
- else:
772
- self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
773
-
774
- def set_dev_tools(self, enable: bool):
775
- """
776
- Sets the developer tools of the window.
777
-
778
- If enabled, the developer tools can be opened using the F12 key.
779
-
780
- Parameters
781
- ----------
782
- enable : bool
783
- If True, the developer tools will be enabled. If False, the developer tools will be disabled.
784
-
785
- Examples
786
- --------
787
- >>> app = Pyloid(app_name="Pyloid-App")
788
- >>> window = app.create_window("pyloid-window")
789
- >>> window.set_dev_tools(True)
790
- >>> window.set_dev_tools(False)
791
- """
792
- self.dev_tools = enable
793
- if self.dev_tools:
794
- self.add_shortcut("F12", self.open_dev_tools)
795
- else:
796
- self.remove_shortcut("F12")
797
-
798
- def open_dev_tools(self):
799
- """
800
- Opens the developer tools window.
801
-
802
- Examples
803
- --------
804
- >>> app = Pyloid(app_name="Pyloid-App")
805
- >>> window = app.create_window("pyloid-window")
806
- >>> window.open_dev_tools()
807
- """
808
- self.web_view.page().setDevToolsPage(QWebEnginePage(self.web_view.page()))
809
- self.dev_tools_window = QMainWindow(self._window)
810
- dev_tools_view = QWebEngineView(self.dev_tools_window)
811
- dev_tools_view.setPage(self.web_view.page().devToolsPage())
812
- self.dev_tools_window.setCentralWidget(dev_tools_view)
813
- self.dev_tools_window.resize(800, 600)
814
- self.dev_tools_window.show()
815
-
816
- # Add this line to handle dev tools window closure
817
- self.dev_tools_window.closeEvent = lambda event: setattr(
818
- self, "dev_tools_window", None
819
- )
820
-
821
- def closeEvent(self, event):
822
- """Handles the event when the window is closed."""
823
- # Close developer tools if open
824
- if hasattr(self, "dev_tools_window") and self.dev_tools_window:
825
- self.dev_tools_window.close()
826
- self.dev_tools_window = None
827
-
828
- # Solve memory leak issue with web view engine
829
- self.web_view.page().deleteLater()
830
- self.web_view.deleteLater()
831
- self._remove_from_app_windows()
832
- event.accept() # Accept the event (allow the window to close)
833
-
834
- def _remove_from_app_windows(self):
835
- """Removes the window from the app's window list."""
836
- if self in self.app.windows:
837
- self.app.windows.remove(self)
838
- if not self.app.windows:
839
- self.app.quit() # Quit the app if all windows are closed
840
-
841
- ###########################################################################################
842
- # Window management (no ID required)
843
- ###########################################################################################
844
- def hide(self):
845
- """
846
- Hides the window.
847
-
848
- Examples
849
- --------
850
- >>> app = Pyloid(app_name="Pyloid-App")
851
- >>> window = app.create_window("pyloid-window")
852
- >>> window.hide()
853
- """
854
- self._window.hide()
855
-
856
- def show(self):
857
- """
858
- Shows the window.
859
-
860
- Examples
861
- --------
862
- >>> app = Pyloid(app_name="Pyloid-App")
863
- >>> window = app.create_window("pyloid-window")
864
- >>> window.show()
865
- """
866
- self._window.show()
867
-
868
- def focus(self):
869
- """
870
- Focuses the window.
871
-
872
- Examples
873
- --------
874
- >>> app = Pyloid(app_name="Pyloid-App")
875
- >>> window = app.create_window("pyloid-window")
876
- >>> window.focus()
877
- """
878
- self._window.activateWindow()
879
- self._window.raise_()
880
- self._window.setWindowState(
881
- self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
882
- )
883
-
884
- def show_and_focus(self):
885
- """
886
- Shows and focuses the window.
887
-
888
- Examples
889
- --------
890
- >>> app = Pyloid(app_name="Pyloid-App")
891
- >>> window = app.create_window("pyloid-window")
892
- >>> window.show_and_focus()
893
- """
894
- self._window.show()
895
- self._window.activateWindow()
896
- self._window.raise_()
897
- self._window.setWindowState(
898
- self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
899
- )
900
-
901
- def close(self):
902
- """
903
- Closes the window.
904
-
905
- Examples
906
- --------
907
- >>> app = Pyloid(app_name="Pyloid-App")
908
- >>> window = app.create_window("pyloid-window")
909
- >>> window.close()
910
- """
911
- self._window.close()
912
-
913
- def fullscreen(self):
914
- """
915
- Enters fullscreen mode.
916
-
917
- Examples
918
- --------
919
- >>> app = Pyloid(app_name="Pyloid-App")
920
- >>> window = app.create_window("pyloid-window")
921
- >>> window.fullscreen()
922
- """
923
- self._window.showFullScreen()
924
-
925
- def toggle_fullscreen(self):
926
- """
927
- Toggles the fullscreen mode of the window.
928
-
929
- Examples
930
- --------
931
- >>> app = Pyloid(app_name="Pyloid-App")
932
- >>> window = app.create_window("pyloid-window")
933
- >>> window.toggle_fullscreen()
934
- """
935
- if self._window.isFullScreen():
936
- self._window.showNormal()
937
- else:
938
- self._window.showFullScreen()
939
-
940
- def minimize(self):
941
- """
942
- Minimizes the window.
943
-
944
- Examples
945
- --------
946
- >>> app = Pyloid(app_name="Pyloid-App")
947
- >>> window = app.create_window("pyloid-window")
948
- >>> window.minimize()
949
- """
950
- self._window.showMinimized()
951
-
952
- def maximize(self):
953
- """
954
- Maximizes the window.
955
-
956
- Examples
957
- --------
958
- >>> app = Pyloid(app_name="Pyloid-App")
959
- >>> window = app.create_window("pyloid-window")
960
- >>> window.maximize()
961
- """
962
- self._window.showMaximized()
963
-
964
- def unmaximize(self):
965
- """
966
- Restores the window from maximized state.
967
-
968
- Examples
969
- --------
970
- >>> app = Pyloid(app_name="Pyloid-App")
971
- >>> window = app.create_window("pyloid-window")
972
- >>> window.unmaximize()
973
- """
974
- self._window.showNormal()
975
-
976
- def toggle_maximize(self):
977
- """
978
- Toggles the maximized state of the window.
979
-
980
- Examples
981
- --------
982
- >>> app = Pyloid(app_name="Pyloid-App")
983
- >>> window = app.create_window("pyloid-window")
984
- >>> window.toggle_maximize()
985
- """
986
- if self._window.isMaximized():
987
- self._window.showNormal()
988
- else:
989
- self._window.showMaximized()
990
-
991
- def is_fullscreen(self) -> bool:
992
- """
993
- Returns True if the window is fullscreen.
994
-
995
- Examples
996
- --------
997
- >>> app = Pyloid(app_name="Pyloid-App")
998
- >>> window = app.create_window("pyloid-window")
999
- >>> window.is_fullscreen()
1000
- """
1001
- return self._window.isFullScreen()
1002
-
1003
- def is_maximized(self) -> bool:
1004
- """
1005
- Returns True if the window is maximized.
1006
-
1007
- Examples
1008
- --------
1009
- >>> app = Pyloid(app_name="Pyloid-App")
1010
- >>> window = app.create_window("pyloid-window")
1011
- >>> window.is_maximized()
1012
- """
1013
- return self._window.isMaximized()
1014
-
1015
- def capture(self, save_path: str) -> Optional[str]:
1016
- """
1017
- Captures the current window.
1018
-
1019
- Parameters
1020
- ----------
1021
- save_path : str
1022
- Path to save the captured image. If not specified, it will be saved in the current directory.
1023
-
1024
- Returns
1025
- -------
1026
- Optional[str]
1027
- Returns the path of the saved image.
1028
-
1029
- Examples
1030
- --------
1031
- >>> app = Pyloid(app_name="Pyloid-App")
1032
- >>> window = app.create_window("pyloid-window")
1033
- >>> save_path = window.capture("screenshot.png")
1034
- >>> print(f"Image saved at: {save_path}")
1035
- Image saved at: screenshot.png
1036
- """
1037
- try:
1038
- # Capture the window
1039
- screenshot = self._window.grab()
1040
-
1041
- # Save the image
1042
- screenshot.save(save_path)
1043
- return save_path
1044
- except Exception as e:
1045
- print(f"An error occurred while capturing the window: {e}")
1046
- return None
1047
-
1048
- ###########################################################################################
1049
- # Shortcut
1050
- ###########################################################################################
1051
- def add_shortcut(self, key_sequence: str, callback: Callable):
1052
- """
1053
- Adds a keyboard shortcut to the window if it does not already exist.
1054
-
1055
- Parameters
1056
- ----------
1057
- key_sequence : str
1058
- Shortcut sequence (e.g., "Ctrl+C")
1059
- callback : Callable
1060
- Function to be executed when the shortcut is pressed
1061
-
1062
- Returns
1063
- -------
1064
- QShortcut or None
1065
- Created QShortcut object or None if the shortcut already exists
1066
-
1067
- Examples
1068
- --------
1069
- ```python
1070
- app = Pyloid(app_name="Pyloid-App")
1071
-
1072
- window = app.create_window("pyloid-window")
1073
-
1074
- def on_shortcut():
1075
- print("Shortcut activated!")
1076
- window.add_shortcut("Ctrl+C", on_shortcut)
1077
-
1078
- app.run()
1079
- ```
1080
- """
1081
- if key_sequence in self.shortcuts:
1082
- # print(f"Shortcut {key_sequence} already exists.")
1083
- return None
1084
-
1085
- shortcut = QShortcut(QKeySequence(key_sequence), self._window)
1086
- shortcut.activated.connect(callback)
1087
- self.shortcuts[key_sequence] = shortcut
1088
- return shortcut
1089
-
1090
- def remove_shortcut(self, key_sequence: str):
1091
- """
1092
- Removes a keyboard shortcut from the window.
1093
-
1094
- Parameters
1095
- ----------
1096
- key_sequence : str
1097
- Shortcut sequence to be removed
1098
-
1099
- Examples
1100
- --------
1101
- ```python
1102
- app = Pyloid(app_name="Pyloid-App")
1103
-
1104
- window = app.create_window("pyloid-window")
1105
- window.remove_shortcut("Ctrl+C")
1106
-
1107
- app.run()
1108
- ```
1109
- """
1110
- if key_sequence in self.shortcuts:
1111
- shortcut = self.shortcuts.pop(key_sequence)
1112
- shortcut.setEnabled(False)
1113
- shortcut.deleteLater()
1114
-
1115
- def get_all_shortcuts(self):
1116
- """
1117
- Returns all registered shortcuts in the window.
1118
-
1119
- Returns
1120
- -------
1121
- dict
1122
- Dictionary of shortcut sequences and QShortcut objects
1123
-
1124
- Examples
1125
- --------
1126
- ```python
1127
- app = Pyloid(app_name="Pyloid-App")
1128
-
1129
- window = app.create_window("pyloid-window")
1130
- shortcuts = window.get_all_shortcuts()
1131
- print(shortcuts)
1132
-
1133
- app.run()
1134
- ```
1135
- """
1136
- return self.shortcuts
1137
-
1138
- ###########################################################################################
1139
- # Event (Calling the JS from Python)
1140
- ###########################################################################################
1141
- def emit(self, event_name, data: Optional[Dict] = None):
1142
- """
1143
- Emits an event to the JavaScript side.
1144
-
1145
- Parameters
1146
- ----------
1147
- event_name : str
1148
- Name of the event
1149
- data : dict, optional
1150
- Data to be sent with the event (default is None)
1151
-
1152
- Examples
1153
- --------
1154
- (Python)
1155
- ```python
1156
- app = Pyloid(app_name="Pyloid-App")
1157
-
1158
- window = app.create_window("pyloid-window")
1159
- window.emit("customEvent", {"message": "Hello, Pyloid!"})
1160
-
1161
- app.run()
1162
- ```
1163
- ---
1164
-
1165
- (JavaScript)
1166
- ```javascript
1167
- document.addEventListener('customEvent', (data) => {
1168
- console.log(data.message);
1169
- });
1170
- ```
1171
- """
1172
- script = f"""
1173
- (function() {{
1174
- const eventData = {json.dumps(data)};
1175
- const customEvent = new CustomEvent('{event_name}', {{ detail: eventData }});
1176
- document.dispatchEvent(customEvent);
1177
- }})();
1178
- """
1179
- self.web_view.page().runJavaScript(script)
1180
-
1181
- ###########################################################################################
1182
- # Get Properties
1183
- ###########################################################################################
1184
- def get_window_properties(self):
1185
- """
1186
- Returns the properties of the window.
1187
-
1188
- Returns
1189
- -------
1190
- dict
1191
- Dictionary containing the properties of the window
1192
-
1193
- Examples
1194
- --------
1195
- ```python
1196
- app = Pyloid(app_name="Pyloid-App")
1197
-
1198
- window = app.create_window("pyloid-window")
1199
- properties = window.get_window_properties()
1200
- print(properties)
1201
-
1202
- app.run()
1203
- ```
1204
- """
1205
- return {
1206
- "id": self.id,
1207
- "title": self.title,
1208
- "width": self.width,
1209
- "height": self.height,
1210
- "x": self.x,
1211
- "y": self.y,
1212
- "frame": self.frame,
1213
- "context_menu": self.context_menu,
1214
- "dev_tools": self.dev_tools,
1215
- }
1216
-
1217
- def get_id(self):
1218
- """
1219
- Returns the ID of the window.
1220
-
1221
- Returns
1222
- -------
1223
- str
1224
- ID of the window
1225
-
1226
- Examples
1227
- --------
1228
- ```python
1229
- app = Pyloid(app_name="Pyloid-App")
1230
-
1231
- window = app.create_window("pyloid-window")
1232
- window_id = window.get_id()
1233
- print(window_id)
1234
-
1235
- app.run()
1236
- ```
1237
- """
1238
- return self.id
1239
-
1240
- def get_size(self) -> Dict[str, int]:
1241
- """
1242
- Returns the size of the window.
1243
-
1244
- Returns
1245
- -------
1246
- dict
1247
- Dictionary containing the width and height of the window
1248
-
1249
- Examples
1250
- --------
1251
- ```python
1252
- app = Pyloid(app_name="Pyloid-App")
1253
-
1254
- window = app.create_window("pyloid-window")
1255
- size = window.get_size()
1256
- print(size)
1257
-
1258
- app.run()
1259
- ```
1260
- """
1261
- return {"width": self.width, "height": self.height}
1262
-
1263
- def get_position(self) -> Dict[str, int]:
1264
- """
1265
- Returns the position of the window.
1266
-
1267
- Returns
1268
- -------
1269
- dict
1270
- Dictionary containing the x and y coordinates of the window
1271
-
1272
- Examples
1273
- --------
1274
- ```python
1275
- app = Pyloid(app_name="Pyloid-App")
1276
-
1277
- window = app.create_window("pyloid-window")
1278
- position = window.get_position()
1279
- print(position)
1280
-
1281
- app.run()
1282
- ```
1283
- """
1284
- return {"x": self.x, "y": self.y}
1285
-
1286
- def get_title(self) -> str:
1287
- """
1288
- Returns the title of the window.
1289
-
1290
- Returns
1291
- -------
1292
- str
1293
- Title of the window
1294
-
1295
- Examples
1296
- --------
1297
- ```python
1298
- app = Pyloid(app_name="Pyloid-App")
1299
-
1300
- window = app.create_window("pyloid-window")
1301
- title = window.get_title()
1302
- print(title)
1303
-
1304
- app.run()
1305
- ```
1306
- """
1307
- return self.title
1308
-
1309
- def get_url(self) -> str:
1310
- """
1311
- Returns the URL of the window.
1312
-
1313
- Returns
1314
- -------
1315
- str
1316
- URL of the window
1317
-
1318
- Examples
1319
- --------
1320
- ```python
1321
- app = Pyloid(app_name="Pyloid-App")
1322
-
1323
- window = app.create_window("pyloid-window")
1324
- url = window.get_url()
1325
- print(url)
1326
-
1327
- app.run()
1328
- ```
1329
- """
1330
- return self.web_view.url().toString()
1331
-
1332
- def get_visible(self) -> bool:
1333
- """
1334
- Returns the visibility of the window.
1335
-
1336
- Returns
1337
- -------
1338
- bool
1339
- True if the window is visible, False otherwise
1340
-
1341
- Examples
1342
- --------
1343
- ```python
1344
- app = Pyloid(app_name="Pyloid-App")
1345
-
1346
- window = app.create_window("pyloid-window")
1347
- visible = window.get_visible()
1348
- print(visible)
1349
-
1350
- app.run()
1351
- ```
1352
- """
1353
- return self._window.isVisible()
1354
-
1355
- def get_frame(self) -> bool:
1356
- """
1357
- Returns the frame enabled state of the window.
1358
-
1359
- Returns
1360
- -------
1361
- bool
1362
- True if the frame is enabled, False otherwise
1363
-
1364
- Examples
1365
- --------
1366
- ```python
1367
- app = Pyloid(app_name="Pyloid-App")
1368
-
1369
- window = app.create_window("pyloid-window")
1370
- frame = window.get_frame()
1371
- print(frame)
1372
-
1373
- app.run()
1374
- ```
1375
- """
1376
- return self.frame
1377
-
1378
- ###########################################################################################
1379
- # Resize
1380
- ###########################################################################################
1381
- def set_resizable(self, resizable: bool):
1382
- """
1383
- Sets the resizability of the window.
1384
-
1385
- Parameters
1386
- ----------
1387
- resizable : bool
1388
- True to make the window resizable, False to make it fixed size
1389
-
1390
- Examples
1391
- --------
1392
- ```python
1393
- app = Pyloid(app_name="Pyloid-App")
1394
-
1395
- window = app.create_window("pyloid-window")
1396
- window.set_resizable(True)
1397
-
1398
- app.run()
1399
- ```
1400
- """
1401
- self.resizable = resizable
1402
- if self.frame:
1403
- flags = self._window.windowFlags() | Qt.WindowCloseButtonHint
1404
- if resizable:
1405
- pass
1406
- else:
1407
- flags |= Qt.MSWindowsFixedSizeDialogHint
1408
- self._window.setWindowFlags(flags)
1409
- else:
1410
- # 프레임이 없는 경우 커스텀 리사이징 로직을 설정합니다.
1411
- self.web_view.is_resizing_enabled = resizable
1412
-
1413
- self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
1414
-
1415
- def set_minimum_size(self, min_width: int, min_height: int):
1416
- """
1417
- Sets the minimum size of the window.
1418
-
1419
- Parameters
1420
- ----------
1421
- min_width : int
1422
- Minimum width of the window
1423
- min_height : int
1424
- Minimum height of the window
1425
-
1426
- Examples
1427
- --------
1428
- ```python
1429
- app = Pyloid(app_name="Pyloid-App")
1430
-
1431
- window = app.create_window("pyloid-window")
1432
- window.set_minimum_size(400, 300)
1433
-
1434
- app.run()
1435
- ```
1436
- """
1437
- self._window.setMinimumSize(min_width, min_height)
1438
-
1439
- def set_maximum_size(self, max_width: int, max_height: int):
1440
- """
1441
- Sets the maximum size of the window.
1442
-
1443
- Parameters
1444
- ----------
1445
- max_width : int
1446
- Maximum width of the window
1447
- max_height : int
1448
- Maximum height of the window
1449
-
1450
- Examples
1451
- --------
1452
- ```python
1453
- app = Pyloid(app_name="Pyloid-App")
1454
-
1455
- window = app.create_window("pyloid-window")
1456
- window.set_maximum_size(1024, 768)
1457
-
1458
- app.run()
1459
- ```
1460
- """
1461
- self._window.setMaximumSize(max_width, max_height)
1462
-
1463
- def get_minimum_size(self) -> Dict[str, int]:
1464
- """
1465
- Returns the minimum size of the window.
1466
-
1467
- Returns
1468
- -------
1469
- dict
1470
- Dictionary containing the minimum width and height of the window
1471
-
1472
- Examples
1473
- --------
1474
- ```python
1475
- app = Pyloid(app_name="Pyloid-App")
1476
-
1477
- window = app.create_window("pyloid-window")
1478
- min_size = window.get_minimum_size()
1479
- print(min_size)
1480
-
1481
- app.run()
1482
- ```
1483
- """
1484
- return {
1485
- "width": self._window.minimumWidth(),
1486
- "height": self._window.minimumHeight(),
1487
- }
1488
-
1489
- def get_maximum_size(self) -> Dict[str, int]:
1490
- """
1491
- Returns the maximum size of the window.
1492
-
1493
- Returns
1494
- -------
1495
- dict
1496
- Dictionary containing the maximum width and height of the window
1497
-
1498
- Examples
1499
- --------
1500
- ```python
1501
- app = Pyloid(app_name="Pyloid-App")
1502
-
1503
- window = app.create_window("pyloid-window")
1504
- max_size = window.get_maximum_size()
1505
- print(max_size)
1506
-
1507
- app.run()
1508
- ```
1509
- """
1510
- return {
1511
- "width": self._window.maximumWidth(),
1512
- "height": self._window.maximumHeight(),
1513
- }
1514
-
1515
- def get_resizable(self) -> bool:
1516
- """
1517
- Returns the resizability of the window.
1518
-
1519
- Returns
1520
- -------
1521
- bool
1522
- True if the window is resizable, False otherwise
1523
-
1524
- Examples
1525
- --------
1526
- ```python
1527
- app = Pyloid(app_name="Pyloid-App")
1528
-
1529
- window = app.create_window("pyloid-window")
1530
- resizable = window.get_resizable()
1531
- print(resizable)
1532
-
1533
- app.run()
1534
- ```
1535
- """
1536
- return self.resizable
1537
-
1538
- ###########################################################################################
1539
- # For Custom Pyside6 Features
1540
- ###########################################################################################
1541
- def get_QMainWindow(self) -> QMainWindow:
1542
- """
1543
- Returns the QMainWindow object of the window.
1544
-
1545
- you can use all the features of QMainWindow for customizing the window.
1546
-
1547
- Returns
1548
- -------
1549
- QMainWindow
1550
- QMainWindow object of the window
1551
-
1552
- Examples
1553
- --------
1554
- ```python
1555
- from PySide6.QtCore import Qt
1556
- from pyloid import Pyloid
1557
-
1558
- app = Pyloid(app_name="Pyloid-App")
1559
-
1560
- window = app.create_window("pyloid-window")
1561
- qmain = window.get_QMainWindow()
1562
-
1563
- qmain.setWindowFlags(qmain.windowFlags() | Qt.WindowStaysOnTopHint) # window stays on top
1564
- ```
1565
- """
1566
- return self._window
1567
-
1568
- def get_QWebEngineView(self) -> CustomWebEngineView:
1569
- """
1570
- Returns the CustomWebEngineView object which inherits from QWebEngineView.
1571
-
1572
- Returns
1573
- -------
1574
- CustomWebEngineView
1575
- CustomWebEngineView object of the window
1576
-
1577
- Examples
1578
- --------
1579
- ```python
1580
- window = app.create_window("pyloid-window")
1581
- web_view = window.get_QWebEngineView()
1582
-
1583
- web_view.page().runJavaScript("console.log('Hello, Pyloid!')")
1584
- ```
1585
- """
1586
- return self.web_view
1587
-
1588
- ###########################################################################################
1589
- # QMainWindow flags
1590
- ###########################################################################################
1591
- def set_window_stay_on_top(self, on_top: bool):
1592
- """
1593
- Sets the window stay on top flag of the window.
1594
-
1595
- Parameters
1596
- ----------
1597
- on_top : bool
1598
- True to keep the window on top, False otherwise
1599
-
1600
- Examples
1601
- --------
1602
- ```python
1603
- window.set_window_stay_on_top(True)
1604
- window.set_window_stay_on_top(False)
1605
- ```
1606
- """
1607
- flags = self._window.windowFlags()
1608
- if on_top:
1609
- flags |= Qt.WindowStaysOnTopHint
1610
- else:
1611
- flags &= ~Qt.WindowStaysOnTopHint
1612
-
1613
- # Maintain existing flags while only changing WindowStaysOnTopHint
1614
- # Explicitly add the close button
1615
- flags |= Qt.WindowCloseButtonHint
1616
-
1617
- self._window.setWindowFlags(flags)
1618
-
1619
- # Show the window again to apply the changes
1620
- self._window.show()
1621
-
1622
- def set_window_stay_on_bottom(self, on_bottom: bool):
1623
- """
1624
- Sets the window stay on bottom flag of the window.
1625
-
1626
- Parameters
1627
- ----------
1628
- on_bottom : bool
1629
- True to keep the window on bottom, False otherwise
1630
-
1631
- Examples
1632
- --------
1633
- ```python
1634
- window.set_window_stay_on_bottom(True)
1635
- window.set_window_stay_on_bottom(False)
1636
- ```
1637
- """
1638
- flags = self._window.windowFlags()
1639
- if on_bottom:
1640
- flags |= Qt.WindowStaysOnBottomHint
1641
- else:
1642
- flags &= ~Qt.WindowStaysOnBottomHint
1643
-
1644
- # Maintain existing flags while only changing WindowStaysOnBottomHint
1645
- # Explicitly add the close button
1646
- flags |= Qt.WindowCloseButtonHint
1647
-
1648
- self._window.setWindowFlags(flags)
1649
-
1650
- # Show the window again to apply the changes
1651
- self._window.show()
1652
-
1653
- ###########################################################################################
1654
- # Splash Screen
1655
- ###########################################################################################
1656
- def set_static_image_splash_screen(
1657
- self,
1658
- image_path: str,
1659
- close_on_load: bool = True,
1660
- stay_on_top: bool = True,
1661
- clickable: bool = True,
1662
- position: str = "center",
1663
- ):
1664
- """
1665
- Sets the static image splash screen of the window.
1666
-
1667
- Parameters
1668
- ----------
1669
- image_path : str
1670
- Path to the image file
1671
- close_on_load : bool, optional
1672
- True to close the splash screen when the page is loaded, False otherwise (default is True)
1673
- stay_on_top : bool, optional
1674
- True to keep the splash screen on top, False otherwise (default is True)
1675
- clickable : bool, optional
1676
- True to make the splash screen clickable, False otherwise (default is True)
1677
- if clickable is True, you can click the splash screen to close it.
1678
- position : str, optional
1679
- Position of the splash screen. Options are 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right' (default is 'center')
1680
-
1681
- Examples
1682
- --------
1683
- ```python
1684
- window.set_image_splash_screen("./assets/loading.png", close_on_load=True, stay_on_top=True)
1685
- ```
1686
- """
1687
- pixmap = QPixmap(image_path)
1688
-
1689
- if not clickable:
1690
-
1691
- class NonClickableSplashScreen(QSplashScreen):
1692
- def mousePressEvent(self, event):
1693
- pass # Ignore click events
1694
-
1695
- splash = NonClickableSplashScreen(
1696
- pixmap, Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0)
1697
- )
1698
- else:
1699
- splash = QSplashScreen(
1700
- pixmap, Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0)
1701
- )
1702
-
1703
- self.close_on_load = close_on_load
1704
- self.splash_screen = splash
1705
- self._position_splash_screen(position)
1706
- self.splash_screen.show()
1707
-
1708
- def set_gif_splash_screen(
1709
- self,
1710
- gif_path: str,
1711
- close_on_load: bool = True,
1712
- stay_on_top: bool = True,
1713
- clickable: bool = True,
1714
- position: str = "center",
1715
- ):
1716
- """
1717
- Sets the gif splash screen of the window.
1718
-
1719
- Parameters
1720
- ----------
1721
- gif_path : str
1722
- Path to the gif file
1723
- close_on_load : bool, optional
1724
- True to close the splash screen when the page is loaded, False otherwise (default is True)
1725
- stay_on_top : bool, optional
1726
- True to keep the splash screen on top, False otherwise (default is True)
1727
- clickable : bool, optional
1728
- True to make the splash screen clickable, False otherwise (default is True)
1729
- if clickable is True, you can click the splash screen to close it.
1730
- position : str, optional
1731
- Position of the splash screen. Options are 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right' (default is 'center')
1732
-
1733
- Examples
1734
- --------
1735
- ```python
1736
- window.set_gif_splash_screen("./assets/loading.gif", close_on_load=True, stay_on_top=True)
1737
- ```
1738
- """
1739
-
1740
- if not clickable:
1741
-
1742
- class NonClickableSplashScreen(QSplashScreen):
1743
- def mousePressEvent(self, event):
1744
- pass # Ignore click events
1745
-
1746
- # Create splash screen (using animated GIF)
1747
- splash = NonClickableSplashScreen(
1748
- QPixmap(1, 1),
1749
- Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0),
1750
- ) # Start with 1x1 transparent pixmap
1751
- else:
1752
- splash = QSplashScreen(
1753
- QPixmap(1, 1),
1754
- Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0),
1755
- )
1756
-
1757
- splash.setAttribute(Qt.WA_TranslucentBackground)
1758
-
1759
- # Create QLabel for GIF animation
1760
- label = QLabel(splash)
1761
- movie = QMovie(gif_path)
1762
- label.setMovie(movie)
1763
-
1764
- # Adjust splash screen size to match GIF size
1765
- movie.frameChanged.connect(
1766
- lambda: splash.setFixedSize(movie.currentPixmap().size())
1767
- )
1768
-
1769
- # Start animation and show splash screen
1770
- movie.start()
1771
- self.close_on_load = close_on_load
1772
- self.splash_screen = splash
1773
- self._position_splash_screen(position)
1774
- splash.show()
1775
-
1776
- def _position_splash_screen(self, position: str):
1777
- if not self.splash_screen:
1778
- return
1779
-
1780
- screen = self.app.primaryScreen().geometry()
1781
- splash_size = self.splash_screen.size()
1782
-
1783
- if position == "center":
1784
- new_position = screen.center() - QPoint(
1785
- splash_size.width() // 2, splash_size.height() // 2
1786
- )
1787
- elif position == "top-left":
1788
- new_position = screen.topLeft()
1789
- elif position == "top-right":
1790
- new_position = screen.topRight() - QPoint(splash_size.width(), 0)
1791
- elif position == "bottom-left":
1792
- new_position = screen.bottomLeft() - QPoint(0, splash_size.height())
1793
- elif position == "bottom-right":
1794
- new_position = screen.bottomRight() - QPoint(
1795
- splash_size.width(), splash_size.height()
1796
- )
1797
- else:
1798
- new_position = screen.center() - QPoint(
1799
- splash_size.width() // 2, splash_size.height() // 2
1800
- )
1801
-
1802
- self.splash_screen.move(new_position)
1803
-
1804
- def close_splash_screen(self):
1805
- """
1806
- Closes the splash screen if it exists.
1807
-
1808
- Examples
1809
- --------
1810
- ```python
1811
- window.close_splash_screen()
1812
- ```
1813
- """
1814
- if hasattr(self, "splash_screen") and self.splash_screen:
1815
- self.splash_screen.close()
1816
- self.close_on_load = None
1817
- self.splash_screen = None
1818
-
1819
- ###########################################################################################
1820
- # WebEngineView Attribute setting
1821
- ###########################################################################################
1822
- def set_web_engine_view_attribute(self, attribute: QWebEngineSettings, on: bool):
1823
- """
1824
- Sets the attribute of the WebEngineView.
1825
-
1826
- Parameters
1827
- ----------
1828
- attribute : QWebEngineSettings
1829
- Attribute to set
1830
- on : bool
1831
- True to enable the attribute, False to disable it
1832
-
1833
- Examples
1834
- --------
1835
- ```python
1836
- window.set_web_engine_view_attribute(QWebEngineSettings.WebAttribute.ScreenCaptureEnabled, False)
1837
- ```
1838
- """
1839
- settings = self.web_view.settings()
1840
- settings.setAttribute(attribute, on)
1841
-
1842
- def is_web_engine_view_attribute(self, attribute: QWebEngineSettings) -> bool:
1843
- """
1844
- Returns the attribute of the WebEngineView.
1845
-
1846
- Parameters
1847
- ----------
1848
- attribute : QWebEngineSettings
1849
- Attribute to get
1850
-
1851
- Returns
1852
- -------
1853
- bool
1854
- True if the attribute is enabled, False otherwise
1855
-
1856
- Examples
1857
- --------
1858
- ```python
1859
- window.is_web_engine_view_attribute(QWebEngineSettings.WebAttribute.ScreenCaptureEnabled)
1860
- ```
1861
- """
1862
- settings = self.web_view.settings()
1863
- return settings.testAttribute(attribute)
1864
-
1865
- def set_permission_handler(self, feature: QWebEnginePage.Feature, handler):
1866
- """
1867
- Sets a handler for a specific permission.
1868
-
1869
- Parameters
1870
- ----------
1871
- feature : QWebEnginePage.Feature
1872
- The type of permission to set
1873
- handler : callable
1874
- The handler function to process the permission request
1875
-
1876
- Examples
1877
- --------
1878
- ```python
1879
- def handle_camera(origin, feature):
1880
- window.web_view.page().setFeaturePermission(
1881
- origin,
1882
- feature,
1883
- QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1884
- )
1885
-
1886
- window.set_permission_handler(
1887
- QWebEnginePage.Feature.MediaVideoCapture,
1888
- handle_camera
1889
- )
1890
- ```
1891
- """
1892
- self.web_view.custom_page.setPermissionHandler(feature, handler)
1893
-
1894
- def grant_permission(self, feature: QWebEnginePage.Feature):
1895
- """
1896
- Automatically grants a specific permission when a request is made.
1897
-
1898
- Parameters
1899
- ----------
1900
- feature : QWebEnginePage.Feature
1901
- The type of permission to automatically grant
1902
-
1903
- Examples
1904
- --------
1905
- ```python
1906
- window.grant_permission(QWebEnginePage.Feature.MediaVideoCapture)
1907
- ```
1908
- """
1909
-
1910
- def auto_grant(origin, feat):
1911
- self.web_view.page().setFeaturePermission(
1912
- origin, feat, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1913
- )
1914
-
1915
- self.set_permission_handler(feature, auto_grant)
1916
-
1917
- def deny_permission(self, feature: QWebEnginePage.Feature):
1918
- """
1919
- Automatically denies a specific permission when a request is made.
1920
-
1921
- Parameters
1922
- ----------
1923
- feature : QWebEnginePage.Feature
1924
- The type of permission to automatically deny
1925
-
1926
- Examples
1927
- --------
1928
- ```python
1929
- window.deny_permission(QWebEnginePage.Feature.Notifications)
1930
- ```
1931
- """
1932
-
1933
- def auto_deny(origin, feat):
1934
- self.web_view.page().setFeaturePermission(
1935
- origin, feat, QWebEnginePage.PermissionPolicy.PermissionDeniedByUser
1936
- )
1937
-
1938
- self.set_permission_handler(feature, auto_deny)
1939
-
1940
- def set_desktop_media_handler(self, handler):
1941
- """
1942
- 데스크톱 미디어(화면/윈도우) 선택 핸들러를 설정합니다.
1943
-
1944
- Parameters
1945
- ----------
1946
- handler : callable
1947
- 요청을 처리할 핸들러 함수. QWebEngineDesktopMediaRequest��� 인자로 받습니다.
1948
-
1949
- Examples
1950
- --------
1951
- ```python
1952
- def custom_media_handler(request):
1953
- # 사용 가능한 화면 목록 출력
1954
- for screen in request.screenList():
1955
- print(f"Screen: {screen.name}")
1956
-
1957
- # 사용 가능한 윈도우 목록 출력
1958
- for window in request.windowList():
1959
- print(f"Window: {window.name}")
1960
-
1961
- # 첫 번째 화면 선택
1962
- if request.screenList():
1963
- request.selectScreen(request.screenList()[0])
1964
-
1965
- window.set_desktop_media_handler(custom_media_handler)
1966
- ```
1967
- """
1968
- self.web_view.custom_page.setDesktopMediaHandler(handler)
1
+ import sys
2
+ import os
3
+ from PySide6.QtWidgets import (
4
+ QMainWindow,
5
+ )
6
+ from PySide6.QtWebEngineWidgets import QWebEngineView
7
+ from PySide6.QtWebChannel import QWebChannel
8
+ from PySide6.QtGui import (
9
+ QKeySequence,
10
+ QShortcut,
11
+ QCursor,
12
+ )
13
+ from PySide6.QtCore import Qt, QPoint, QUrl, QEvent, QFile
14
+ from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
15
+ from .api import PyloidAPI
16
+ import uuid
17
+ from typing import List, Optional, Dict, Callable
18
+ import json
19
+ from PySide6.QtWidgets import (
20
+ QWidget,
21
+ QVBoxLayout,
22
+ )
23
+ from .custom.titlebar import CustomTitleBar
24
+ from .js_api.window_api import WindowAPI
25
+ from PySide6.QtGui import QPixmap, QMovie
26
+ from PySide6.QtWidgets import QSplashScreen, QLabel
27
+ from PySide6.QtCore import QSize
28
+ from typing import TYPE_CHECKING
29
+ from PySide6.QtWebEngineCore import QWebEngineSettings
30
+
31
+ if TYPE_CHECKING:
32
+ from ..pyloid import Pyloid
33
+
34
+
35
+ # 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
36
+ class CustomWebPage(QWebEnginePage):
37
+ def __init__(self, profile=None):
38
+ super().__init__(profile)
39
+ self.featurePermissionRequested.connect(self._handlePermissionRequest)
40
+ self.desktopMediaRequested.connect(self._handleDesktopMediaRequest)
41
+ self._permission_handlers = {}
42
+ self._desktop_media_handler = None
43
+
44
+ def _handlePermissionRequest(self, origin: QUrl, feature: QWebEnginePage.Feature):
45
+ print(origin, feature)
46
+
47
+ """Default permission request handler"""
48
+ if feature in self._permission_handlers:
49
+ # Execute if a handler is registered
50
+ handler = self._permission_handlers[feature]
51
+ handler(origin, feature)
52
+ else:
53
+ # Allow all permissions by default
54
+ self.setFeaturePermission(
55
+ origin, feature, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
56
+ )
57
+
58
+ def setPermissionHandler(self, feature: QWebEnginePage.Feature, handler):
59
+ """Register a handler for a specific permission"""
60
+ self._permission_handlers[feature] = handler
61
+
62
+ def _handleDesktopMediaRequest(self, *args, **kwargs):
63
+ print("Desktop media request received:", args, kwargs)
64
+
65
+ # def setDesktopMediaHandler(self, handler):
66
+ # """desktop media handler"""
67
+ # self._desktop_media_handler = handler
68
+
69
+
70
+ class CustomWebEngineView(QWebEngineView):
71
+ def __init__(self, parent: "BrowserWindow" = None):
72
+ super().__init__(parent._window)
73
+ self.parent: "BrowserWindow" = parent
74
+
75
+ # Custom Web Page
76
+ self.custom_page = CustomWebPage()
77
+ self.setPage(self.custom_page)
78
+
79
+ self.is_resizing = False
80
+ self.resize_start_pos = None
81
+ self.resize_direction = None
82
+ self.screen_geometry = self.screen().virtualGeometry()
83
+ self.is_resizing_enabled = True
84
+
85
+ self.setAttribute(Qt.WA_SetCursor, False)
86
+
87
+ self.is_in_resize_area = False
88
+
89
+ def mouse_press_event(self, event):
90
+ if self.parent.frame or not self.is_resizing_enabled:
91
+ return
92
+
93
+ if event.button() == Qt.LeftButton:
94
+ self.resize_direction = self.get_resize_direction(event.pos())
95
+ if self.resize_direction:
96
+ self.is_resizing = True
97
+ self.resize_start_pos = event.globalPos()
98
+
99
+ def start_system_drag(self):
100
+ """Start system window move"""
101
+ if self.parent._window.windowHandle():
102
+ self.parent._window.windowHandle().startSystemMove()
103
+
104
+ def mouse_move_event(self, event):
105
+ if self.parent.frame or not self.is_resizing_enabled:
106
+ return
107
+
108
+ # Check resize direction
109
+ was_in_resize_area = self.is_in_resize_area
110
+ resize_direction = self.get_resize_direction(event.pos())
111
+ self.is_in_resize_area = bool(resize_direction)
112
+
113
+ if resize_direction and not self.is_resizing:
114
+ self.set_cursor_for_resize_direction(resize_direction)
115
+
116
+ if self.is_resizing:
117
+ self.resize_window(event.globalPos())
118
+ return
119
+
120
+ # Change cursor when entering/leaving resize area
121
+ if self.is_in_resize_area != was_in_resize_area:
122
+ if self.is_in_resize_area:
123
+ # self.setAttribute(Qt.WA_SetCursor, True)
124
+ pass
125
+ else:
126
+ self.unsetCursor()
127
+ # self.setAttribute(Qt.WA_SetCursor, False)
128
+
129
+ def mouse_release_event(self, event):
130
+ if self.parent.frame or not self.is_resizing_enabled:
131
+ return
132
+
133
+ if event.button() == Qt.LeftButton:
134
+ self.is_resizing = False
135
+
136
+ if self.resize_direction:
137
+ self.unsetCursor()
138
+ self.resize_direction = None
139
+
140
+ self.setAttribute(Qt.WA_SetCursor, False)
141
+
142
+ def eventFilter(self, source, event):
143
+ if self.focusProxy() is source:
144
+ if event.type() == QEvent.MouseButtonPress:
145
+ self.mouse_press_event(event)
146
+ elif event.type() == QEvent.MouseMove:
147
+ self.mouse_move_event(event)
148
+ elif event.type() == QEvent.MouseButtonRelease:
149
+ self.mouse_release_event(event)
150
+ return super().eventFilter(source, event)
151
+
152
+ def get_resize_direction(self, pos):
153
+ if (
154
+ not self.parent.frame and self.is_resizing_enabled
155
+ ): # Check if frame is not present and resizing is enabled
156
+ margin = 8 # Margin in pixels to detect edge
157
+ rect = self.rect()
158
+ direction = None
159
+
160
+ if pos.x() <= margin:
161
+ direction = "left"
162
+ elif pos.x() >= rect.width() - margin:
163
+ direction = "right"
164
+
165
+ if pos.y() <= margin:
166
+ direction = "top" if direction is None else direction + "-top"
167
+ elif pos.y() >= rect.height() - margin:
168
+ direction = "bottom" if direction is None else direction + "-bottom"
169
+
170
+ return direction
171
+ return None
172
+
173
+ def set_cursor_for_resize_direction(self, direction):
174
+ if not self.parent.frame and direction and self.is_resizing_enabled:
175
+ cursor = None
176
+ if direction in ["left", "right"]:
177
+ cursor = Qt.SizeHorCursor
178
+ elif direction in ["top", "bottom"]:
179
+ cursor = Qt.SizeVerCursor
180
+ elif direction in ["left-top", "right-bottom"]:
181
+ cursor = Qt.SizeFDiagCursor
182
+ elif direction in ["right-top", "left-bottom"]:
183
+ cursor = Qt.SizeBDiagCursor
184
+
185
+ if cursor:
186
+ self.setCursor(cursor)
187
+ self.setAttribute(Qt.WA_SetCursor, True)
188
+
189
+ def resize_window(self, global_pos):
190
+ if (
191
+ not self.parent.frame
192
+ and self.resize_start_pos
193
+ and self.resize_direction
194
+ and self.is_resizing_enabled
195
+ ): # Check if frame is not present and resizing is enabled
196
+ delta = global_pos - self.resize_start_pos
197
+ new_geometry = self.parent._window.geometry()
198
+
199
+ if "left" in self.resize_direction:
200
+ new_geometry.setLeft(new_geometry.left() + delta.x())
201
+ if "right" in self.resize_direction:
202
+ new_geometry.setRight(new_geometry.right() + delta.x())
203
+ if "top" in self.resize_direction:
204
+ new_geometry.setTop(new_geometry.top() + delta.y())
205
+ if "bottom" in self.resize_direction:
206
+ new_geometry.setBottom(new_geometry.bottom() + delta.y())
207
+
208
+ self.parent._window.setGeometry(new_geometry)
209
+ self.resize_start_pos = global_pos
210
+
211
+
212
+ class BrowserWindow:
213
+ def __init__(
214
+ self,
215
+ app: "Pyloid",
216
+ title: str = "pyloid app",
217
+ width: int = 800,
218
+ height: int = 600,
219
+ x: int = 200,
220
+ y: int = 200,
221
+ frame: bool = True,
222
+ context_menu: bool = False,
223
+ dev_tools: bool = False,
224
+ js_apis: List[PyloidAPI] = [],
225
+ ):
226
+ ###########################################################################################
227
+ self.id = str(uuid.uuid4()) # Generate unique ID
228
+ self._window = QMainWindow()
229
+ self.web_view = CustomWebEngineView(self)
230
+
231
+ self._window.closeEvent = self.closeEvent # Override closeEvent method
232
+ ###########################################################################################
233
+ self.app = app
234
+ self.title = title
235
+ self.width = width
236
+ self.height = height
237
+ self.x = x
238
+ self.y = y
239
+ self.frame = frame
240
+ self.context_menu = context_menu
241
+ self.dev_tools = dev_tools
242
+ self.js_apis = [WindowAPI()]
243
+ for js_api in js_apis:
244
+ self.js_apis.append(js_api)
245
+ self.shortcuts = {}
246
+ self.close_on_load = True
247
+ self.splash_screen = None
248
+ ###########################################################################################
249
+
250
+ def _set_custom_frame(
251
+ self,
252
+ use_custom: bool,
253
+ title: str = "Custom Title",
254
+ bg_color: str = "darkblue",
255
+ text_color: str = "white",
256
+ icon_path: str = None,
257
+ ):
258
+ """Sets or removes the custom frame."""
259
+ if use_custom:
260
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
261
+ self.custom_title_bar = CustomTitleBar(self._window)
262
+ self.custom_title_bar.set_style(bg_color, text_color)
263
+ self.custom_title_bar.set_title(title)
264
+
265
+ if icon_path:
266
+ self.custom_title_bar.set_icon(icon_path)
267
+
268
+ layout = QVBoxLayout()
269
+ layout.setContentsMargins(0, 0, 0, 0)
270
+ layout.setSpacing(0)
271
+ layout.addWidget(self.custom_title_bar)
272
+ layout.addWidget(self.web_view)
273
+
274
+ central_widget = QWidget()
275
+ central_widget.setLayout(layout)
276
+ self._window.setCentralWidget(central_widget)
277
+
278
+ # Add properties for window movement
279
+ self._window.moving = False
280
+ self._window.offset = QPoint()
281
+ else:
282
+ self._window.setWindowFlags(Qt.Window)
283
+ self._window.setCentralWidget(self.web_view)
284
+ self.custom_title_bar = None
285
+
286
+ self._window.show()
287
+
288
+ def _load(self):
289
+ self.set_title(self.title)
290
+
291
+ self.set_size(self.width, self.height)
292
+ self.set_position(self.x, self.y)
293
+
294
+ # allow local file access to remote urls and screen capture
295
+ self.web_view.settings().setAttribute(
296
+ QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True
297
+ )
298
+ self.web_view.settings().setAttribute(
299
+ QWebEngineSettings.WebAttribute.ScreenCaptureEnabled, True
300
+ )
301
+ self.web_view.settings().setAttribute(
302
+ QWebEngineSettings.WebAttribute.AutoLoadImages, True
303
+ )
304
+ self.web_view.settings().setAttribute(
305
+ QWebEngineSettings.WebAttribute.JavascriptEnabled, True
306
+ )
307
+ self.web_view.settings().setAttribute(
308
+ QWebEngineSettings.WebAttribute.LocalStorageEnabled, True
309
+ )
310
+ self.web_view.settings().setAttribute(
311
+ QWebEngineSettings.WebAttribute.ErrorPageEnabled, True
312
+ )
313
+ self.web_view.settings().setAttribute(
314
+ QWebEngineSettings.WebAttribute.AutoLoadIconsForPage, True
315
+ )
316
+ self.web_view.settings().setAttribute(
317
+ QWebEngineSettings.WebAttribute.ShowScrollBars, True
318
+ )
319
+ self.web_view.settings().setAttribute(
320
+ QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True
321
+ )
322
+ self.web_view.settings().setAttribute(
323
+ QWebEngineSettings.WebAttribute.PdfViewerEnabled, True
324
+ )
325
+ self.web_view.settings().setAttribute(
326
+ QWebEngineSettings.WebAttribute.FullScreenSupportEnabled, True
327
+ )
328
+ self.web_view.settings().setAttribute(
329
+ QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True
330
+ )
331
+ self.web_view.settings().setUnknownUrlSchemePolicy(
332
+ QWebEngineSettings.UnknownUrlSchemePolicy.AllowAllUnknownUrlSchemes
333
+ )
334
+ self.web_view.settings().setAttribute(
335
+ QWebEngineSettings.WebAttribute.AllowRunningInsecureContent, True
336
+ )
337
+ self.web_view.settings().setAttribute(
338
+ QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins, True
339
+ )
340
+ self.web_view.settings().setAttribute(
341
+ QWebEngineSettings.WebAttribute.AllowWindowActivationFromJavaScript, True
342
+ )
343
+ self.web_view.settings().setAttribute(
344
+ QWebEngineSettings.WebAttribute.JavascriptCanPaste, True
345
+ )
346
+ self.web_view.settings().setAttribute(
347
+ QWebEngineSettings.WebAttribute.WebRTCPublicInterfacesOnly, False
348
+ )
349
+
350
+ # Set icon
351
+ if self.app.icon:
352
+ self._window.setWindowIcon(self.app.icon)
353
+ else:
354
+ print("Icon is not set.")
355
+
356
+ # Set Windows taskbar icon
357
+ if sys.platform == "win32":
358
+ import ctypes
359
+
360
+ myappid = "mycompany.myproduct.subproduct.version"
361
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
362
+
363
+ # Remove title bar and borders (if needed)
364
+ if not self.frame:
365
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
366
+
367
+ # Disable default context menu
368
+ if not self.context_menu:
369
+ self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
370
+
371
+ # Set up QWebChannel
372
+ self.channel = QWebChannel()
373
+
374
+ # Register additional JS APIs
375
+ if self.js_apis:
376
+ for js_api in self.js_apis:
377
+ # Define window_id, window, and app for each JS API
378
+ js_api.window_id = self.id
379
+ js_api.window = self
380
+ js_api.app = self.app
381
+
382
+ self.channel.registerObject(js_api.__class__.__name__, js_api)
383
+
384
+ self.web_view.page().setWebChannel(self.channel)
385
+
386
+ # Connect pylonjs bridge
387
+ self.web_view.loadFinished.connect(self._on_load_finished)
388
+
389
+ # Add QWebEngineView to main window
390
+ self._window.setCentralWidget(self.web_view)
391
+
392
+ # Set F12 shortcut
393
+ self.set_dev_tools(self.dev_tools)
394
+
395
+ def _on_load_finished(self, ok):
396
+ """Handles the event when the web page finishes loading."""
397
+ if ok and self.js_apis:
398
+ # Load qwebchannel.js
399
+ qwebchannel_js = QFile("://qtwebchannel/qwebchannel.js")
400
+ if qwebchannel_js.open(QFile.ReadOnly):
401
+ source = bytes(qwebchannel_js.readAll()).decode("utf-8")
402
+ self.web_view.page().runJavaScript(source)
403
+ qwebchannel_js.close()
404
+
405
+ js_code = """
406
+ if (typeof QWebChannel !== 'undefined') {
407
+ new QWebChannel(qt.webChannelTransport, function (channel) {
408
+ window.pyloid = {
409
+ EventAPI: {
410
+ _listeners: {}, // 콜백 함수들을 저장할 객체
411
+
412
+ listen: function(eventName, callback) {
413
+ // 이벤트에 대한 콜백 배열이 없다면 생성
414
+ if (!this._listeners[eventName]) {
415
+ this._listeners[eventName] = [];
416
+ }
417
+
418
+ // 콜백 함수 저장
419
+ this._listeners[eventName].push(callback);
420
+
421
+ document.addEventListener(eventName, function(event) {
422
+ let eventData;
423
+ try {
424
+ eventData = JSON.parse(event.detail);
425
+ } catch (e) {
426
+ eventData = event.detail;
427
+ }
428
+ callback(eventData);
429
+ });
430
+ },
431
+
432
+ unlisten: function(eventName) {
433
+ // 해당 이벤트의 모든 리스너 제거
434
+ if (this._listeners[eventName]) {
435
+ this._listeners[eventName].forEach(callback => {
436
+ document.removeEventListener(eventName, callback);
437
+ });
438
+ // 저장된 콜백 제거
439
+ delete this._listeners[eventName];
440
+ }
441
+ }
442
+ }
443
+ };
444
+ console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
445
+
446
+ %s
447
+
448
+ document.addEventListener('mousedown', function (e) {
449
+ if (e.target.hasAttribute('data-pyloid-drag-region')) {
450
+ window.pyloid.WindowAPI.startSystemDrag();
451
+ }
452
+ });
453
+
454
+ function updateTheme(theme) {
455
+ document.documentElement.setAttribute(
456
+ 'data-pyloid-theme',
457
+ theme
458
+ );
459
+ }
460
+
461
+ // 테마 변경 이벤트 리스너
462
+ document.addEventListener('themeChange', (e) => {
463
+ console.log('themeChange event received:', e);
464
+ updateTheme(e.detail.theme);
465
+ });
466
+
467
+ updateTheme('%s');
468
+
469
+
470
+ // Dispatch a custom event to signal that the initialization is ready
471
+ const event = new CustomEvent('pyloidReady');
472
+ document.dispatchEvent(event);
473
+ });
474
+ } else {
475
+ console.error('QWebChannel is not defined.');
476
+ }
477
+ """
478
+ js_api_init = "\n".join(
479
+ [
480
+ f"window.pyloid['{js_api.__class__.__name__}'] = channel.objects['{js_api.__class__.__name__}'];\n"
481
+ f"console.log('pyloid.{js_api.__class__.__name__} object initialized:', window.pyloid['{js_api.__class__.__name__}']);"
482
+ for js_api in self.js_apis
483
+ ]
484
+ )
485
+ self.web_view.page().runJavaScript(js_code % (js_api_init, self.app.theme))
486
+
487
+ # if splash screen is set, close it when the page is loaded
488
+ if self.close_on_load and self.splash_screen:
489
+ self.close_splash_screen()
490
+ else:
491
+ pass
492
+
493
+ ###########################################################################################
494
+ # Load
495
+ ###########################################################################################
496
+ def load_file(self, file_path: str):
497
+ """
498
+ Loads a local HTML file into the web view.
499
+
500
+ Parameters
501
+ ----------
502
+ file_path : str
503
+ The path to the local HTML file to be loaded.
504
+
505
+ Examples
506
+ --------
507
+ >>> app = Pyloid(app_name="Pyloid-App")
508
+ >>> window = app.create_window("pyloid-window")
509
+ >>> window.load_file('/path/to/local/file.html')
510
+ >>> window.show()
511
+ """
512
+ self._load()
513
+ file_path = os.path.abspath(file_path) # absolute path
514
+ self.web_view.setUrl(QUrl.fromLocalFile(file_path))
515
+ self.web_view.focusProxy().installEventFilter(self.web_view)
516
+
517
+ def load_url(self, url: str):
518
+ """
519
+ Sets the URL of the window.
520
+
521
+ Parameters
522
+ ----------
523
+ url : str
524
+ The URL to be loaded in the web view.
525
+
526
+ Examples
527
+ --------
528
+ >>> app = Pyloid(app_name="Pyloid-App")
529
+ >>> window = app.create_window("pyloid-window")
530
+ >>> window.load_url('https://www.example.com')
531
+ >>> window.show()
532
+ """
533
+ self._load()
534
+ self.web_view.setUrl(QUrl(url))
535
+ self.web_view.focusProxy().installEventFilter(self.web_view)
536
+
537
+ def load_html(self, html_content: str, base_url: str = ""):
538
+ """
539
+ Loads HTML content directly into the web view.
540
+
541
+ Parameters
542
+ ----------
543
+ html_content : str
544
+ The HTML content to be loaded.
545
+ base_url : str, optional
546
+ The base URL to use for resolving relative URLs (default is "").
547
+
548
+ Examples
549
+ --------
550
+ >>> app = Pyloid(app_name="Pyloid-App")
551
+ >>> window = app.create_window("pyloid-window")
552
+ >>> html_content = "<html><body><h1>Hello, Pyloid!</h1></body></html>"
553
+ >>> window.load_html(html_content)
554
+ >>> window.show()
555
+ """
556
+ self._load()
557
+ self.web_view.setHtml(html_content, QUrl(base_url))
558
+ self.web_view.focusProxy().installEventFilter(self.web_view)
559
+
560
+ ###########################################################################################
561
+ # Set Parameters
562
+ ###########################################################################################
563
+ def set_title(self, title: str):
564
+ """
565
+ Sets the title of the window.
566
+
567
+ Parameters
568
+ ----------
569
+ title : str
570
+ The title to be set for the window.
571
+
572
+ Examples
573
+ --------
574
+ >>> app = Pyloid(app_name="Pyloid-App")
575
+ >>> window = app.create_window("pyloid-window")
576
+ >>> window.set_title('My Window Title')
577
+ """
578
+ self.title = title
579
+ self._window.setWindowTitle(self.title)
580
+
581
+ def set_size(self, width: int, height: int):
582
+ """
583
+ Sets the size of the window.
584
+
585
+ Parameters
586
+ ----------
587
+ width : int
588
+ The width of the window.
589
+ height : int
590
+ The height of the window.
591
+
592
+ Examples
593
+ --------
594
+ >>> app = Pyloid(app_name="Pyloid-App")
595
+ >>> window = app.create_window("pyloid-window")
596
+ >>> window.set_size(800, 600)
597
+ """
598
+ self.width = width
599
+ self.height = height
600
+ self._window.setGeometry(self.x, self.y, self.width, self.height)
601
+
602
+ def set_position(self, x: int, y: int):
603
+ """
604
+ Sets the position of the window.
605
+
606
+ Parameters
607
+ ----------
608
+ x : int
609
+ The x-coordinate of the window's position.
610
+ y : int
611
+ The y-coordinate of the window's position.
612
+
613
+ Examples
614
+ --------
615
+ >>> app = Pyloid(app_name="Pyloid-App")
616
+ >>> window = app.create_window("pyloid-window")
617
+ >>> window.set_position(100, 100)
618
+ """
619
+ self.x = x
620
+ self.y = y
621
+ self._window.setGeometry(self.x, self.y, self.width, self.height)
622
+
623
+ def set_position_by_anchor(self, anchor: str):
624
+ """
625
+ Positions the window at a specific location on the screen.
626
+
627
+ Parameters
628
+ ----------
629
+ anchor : str
630
+ The anchor point indicating where to position the window.
631
+ Possible values: 'center', 'top', 'bottom', 'left', 'right',
632
+ 'top-left', 'top-right', 'bottom-left', 'bottom-right'
633
+
634
+ Examples
635
+ --------
636
+ >>> window.set_position_by_anchor('center')
637
+ >>> window.set_position_by_anchor('top-right')
638
+ """
639
+ screen = self.app.primaryScreen().availableGeometry()
640
+ window_size = self.get_size()
641
+
642
+ if anchor == "center":
643
+ x = (screen.width() - window_size["width"]) // 2
644
+ y = (screen.height() - window_size["height"]) // 2
645
+ elif anchor == "top":
646
+ x = (screen.width() - window_size["width"]) // 2
647
+ y = screen.top()
648
+ elif anchor == "bottom":
649
+ x = (screen.width() - window_size["width"]) // 2
650
+ y = screen.bottom() - window_size["height"]
651
+ elif anchor == "left":
652
+ x = screen.left()
653
+ y = (screen.height() - window_size["height"]) // 2
654
+ elif anchor == "right":
655
+ x = screen.right() - window_size["width"]
656
+ y = (screen.height() - window_size["height"]) // 2
657
+ elif anchor == "top-left":
658
+ x = screen.left()
659
+ y = screen.top()
660
+ elif anchor == "top-right":
661
+ x = screen.right() - window_size["width"]
662
+ y = screen.top()
663
+ elif anchor == "bottom-left":
664
+ x = screen.left()
665
+ y = screen.bottom() - window_size["height"]
666
+ elif anchor == "bottom-right":
667
+ x = screen.right() - window_size["width"]
668
+ y = screen.bottom() - window_size["height"]
669
+ else:
670
+ raise ValueError("Invalid anchor point.")
671
+
672
+ self.set_position(x, y)
673
+
674
+ def set_frame(self, frame: bool):
675
+ """
676
+ Sets the frame of the window.
677
+
678
+ Parameters
679
+ ----------
680
+ frame : bool
681
+ If True, the window will have a frame. If False, the window will be frameless.
682
+
683
+ Examples
684
+ --------
685
+ >>> app = Pyloid(app_name="Pyloid-App")
686
+ >>> window = app.create_window("pyloid-window")
687
+ >>> window.set_frame(True)
688
+ >>> window.set_frame(False)
689
+ """
690
+ self.frame = frame
691
+ was_visible = self._window.isVisible()
692
+ if self.frame:
693
+ self._window.setWindowFlags(Qt.Window)
694
+ else:
695
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
696
+ if was_visible:
697
+ self._window.show()
698
+
699
+ def set_context_menu(self, context_menu: bool):
700
+ """
701
+ Sets the context menu of the window.
702
+
703
+ Parameters
704
+ ----------
705
+ context_menu : bool
706
+ If True, the context menu will be disabled. If False, the default context menu will be enabled.
707
+
708
+ Examples
709
+ --------
710
+ >>> app = Pyloid(app_name="Pyloid-App")
711
+ >>> window = app.create_window("pyloid-window")
712
+ >>> window.set_context_menu(True)
713
+ >>> window.set_context_menu(False)
714
+ """
715
+ self.context_menu = context_menu
716
+ if self.context_menu:
717
+ self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
718
+ else:
719
+ self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
720
+
721
+ def set_dev_tools(self, enable: bool):
722
+ """
723
+ Sets the developer tools of the window.
724
+
725
+ If enabled, the developer tools can be opened using the F12 key.
726
+
727
+ Parameters
728
+ ----------
729
+ enable : bool
730
+ If True, the developer tools will be enabled. If False, the developer tools will be disabled.
731
+
732
+ Examples
733
+ --------
734
+ >>> app = Pyloid(app_name="Pyloid-App")
735
+ >>> window = app.create_window("pyloid-window")
736
+ >>> window.set_dev_tools(True)
737
+ >>> window.set_dev_tools(False)
738
+ """
739
+ self.dev_tools = enable
740
+ if self.dev_tools:
741
+ self.add_shortcut("F12", self.open_dev_tools)
742
+ else:
743
+ self.remove_shortcut("F12")
744
+
745
+ def open_dev_tools(self):
746
+ """
747
+ Opens the developer tools window.
748
+
749
+ Examples
750
+ --------
751
+ >>> app = Pyloid(app_name="Pyloid-App")
752
+ >>> window = app.create_window("pyloid-window")
753
+ >>> window.open_dev_tools()
754
+ """
755
+ self.web_view.page().setDevToolsPage(QWebEnginePage(self.web_view.page()))
756
+ self.dev_tools_window = QMainWindow(self._window)
757
+ dev_tools_view = QWebEngineView(self.dev_tools_window)
758
+ dev_tools_view.setPage(self.web_view.page().devToolsPage())
759
+ self.dev_tools_window.setCentralWidget(dev_tools_view)
760
+ self.dev_tools_window.resize(800, 600)
761
+ self.dev_tools_window.show()
762
+
763
+ # Add this line to handle dev tools window closure
764
+ self.dev_tools_window.closeEvent = lambda event: setattr(
765
+ self, "dev_tools_window", None
766
+ )
767
+
768
+ def closeEvent(self, event):
769
+ """Handles the event when the window is closed."""
770
+ # Close developer tools if open
771
+ if hasattr(self, "dev_tools_window") and self.dev_tools_window:
772
+ self.dev_tools_window.close()
773
+ self.dev_tools_window = None
774
+
775
+ # Solve memory leak issue with web view engine
776
+ self.web_view.page().deleteLater()
777
+ self.web_view.deleteLater()
778
+ self._remove_from_app_windows()
779
+ event.accept() # Accept the event (allow the window to close)
780
+
781
+ def _remove_from_app_windows(self):
782
+ """Removes the window from the app's window list."""
783
+ if self in self.app.windows:
784
+ self.app.windows.remove(self)
785
+ if not self.app.windows:
786
+ self.app.quit() # Quit the app if all windows are closed
787
+
788
+ ###########################################################################################
789
+ # Window management (no ID required)
790
+ ###########################################################################################
791
+ def hide(self):
792
+ """
793
+ Hides the window.
794
+
795
+ Examples
796
+ --------
797
+ >>> app = Pyloid(app_name="Pyloid-App")
798
+ >>> window = app.create_window("pyloid-window")
799
+ >>> window.hide()
800
+ """
801
+ self._window.hide()
802
+
803
+ def show(self):
804
+ """
805
+ Shows the window.
806
+
807
+ Examples
808
+ --------
809
+ >>> app = Pyloid(app_name="Pyloid-App")
810
+ >>> window = app.create_window("pyloid-window")
811
+ >>> window.show()
812
+ """
813
+ self._window.show()
814
+
815
+ def focus(self):
816
+ """
817
+ Focuses the window.
818
+
819
+ Examples
820
+ --------
821
+ >>> app = Pyloid(app_name="Pyloid-App")
822
+ >>> window = app.create_window("pyloid-window")
823
+ >>> window.focus()
824
+ """
825
+ self._window.activateWindow()
826
+ self._window.raise_()
827
+ self._window.setWindowState(
828
+ self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
829
+ )
830
+
831
+ def show_and_focus(self):
832
+ """
833
+ Shows and focuses the window.
834
+
835
+ Examples
836
+ --------
837
+ >>> app = Pyloid(app_name="Pyloid-App")
838
+ >>> window = app.create_window("pyloid-window")
839
+ >>> window.show_and_focus()
840
+ """
841
+ self._window.show()
842
+ self._window.activateWindow()
843
+ self._window.raise_()
844
+ self._window.setWindowState(
845
+ self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
846
+ )
847
+
848
+ def close(self):
849
+ """
850
+ Closes the window.
851
+
852
+ Examples
853
+ --------
854
+ >>> app = Pyloid(app_name="Pyloid-App")
855
+ >>> window = app.create_window("pyloid-window")
856
+ >>> window.close()
857
+ """
858
+ self._window.close()
859
+
860
+ def fullscreen(self):
861
+ """
862
+ Enters fullscreen mode.
863
+
864
+ Examples
865
+ --------
866
+ >>> app = Pyloid(app_name="Pyloid-App")
867
+ >>> window = app.create_window("pyloid-window")
868
+ >>> window.fullscreen()
869
+ """
870
+ self._window.showFullScreen()
871
+
872
+ def toggle_fullscreen(self):
873
+ """
874
+ Toggles the fullscreen mode of the window.
875
+
876
+ Examples
877
+ --------
878
+ >>> app = Pyloid(app_name="Pyloid-App")
879
+ >>> window = app.create_window("pyloid-window")
880
+ >>> window.toggle_fullscreen()
881
+ """
882
+ if self._window.isFullScreen():
883
+ self._window.showNormal()
884
+ else:
885
+ self._window.showFullScreen()
886
+
887
+ def minimize(self):
888
+ """
889
+ Minimizes the window.
890
+
891
+ Examples
892
+ --------
893
+ >>> app = Pyloid(app_name="Pyloid-App")
894
+ >>> window = app.create_window("pyloid-window")
895
+ >>> window.minimize()
896
+ """
897
+ self._window.showMinimized()
898
+
899
+ def maximize(self):
900
+ """
901
+ Maximizes the window.
902
+
903
+ Examples
904
+ --------
905
+ >>> app = Pyloid(app_name="Pyloid-App")
906
+ >>> window = app.create_window("pyloid-window")
907
+ >>> window.maximize()
908
+ """
909
+ self._window.showMaximized()
910
+
911
+ def unmaximize(self):
912
+ """
913
+ Restores the window from maximized state.
914
+
915
+ Examples
916
+ --------
917
+ >>> app = Pyloid(app_name="Pyloid-App")
918
+ >>> window = app.create_window("pyloid-window")
919
+ >>> window.unmaximize()
920
+ """
921
+ self._window.showNormal()
922
+
923
+ def toggle_maximize(self):
924
+ """
925
+ Toggles the maximized state of the window.
926
+
927
+ Examples
928
+ --------
929
+ >>> app = Pyloid(app_name="Pyloid-App")
930
+ >>> window = app.create_window("pyloid-window")
931
+ >>> window.toggle_maximize()
932
+ """
933
+ if self._window.isMaximized():
934
+ self._window.showNormal()
935
+ else:
936
+ self._window.showMaximized()
937
+
938
+ def is_fullscreen(self) -> bool:
939
+ """
940
+ Returns True if the window is fullscreen.
941
+
942
+ Examples
943
+ --------
944
+ >>> app = Pyloid(app_name="Pyloid-App")
945
+ >>> window = app.create_window("pyloid-window")
946
+ >>> window.is_fullscreen()
947
+ """
948
+ return self._window.isFullScreen()
949
+
950
+ def is_maximized(self) -> bool:
951
+ """
952
+ Returns True if the window is maximized.
953
+
954
+ Examples
955
+ --------
956
+ >>> app = Pyloid(app_name="Pyloid-App")
957
+ >>> window = app.create_window("pyloid-window")
958
+ >>> window.is_maximized()
959
+ """
960
+ return self._window.isMaximized()
961
+
962
+ def capture(self, save_path: str) -> Optional[str]:
963
+ """
964
+ Captures the current window.
965
+
966
+ Parameters
967
+ ----------
968
+ save_path : str
969
+ Path to save the captured image. If not specified, it will be saved in the current directory.
970
+
971
+ Returns
972
+ -------
973
+ Optional[str]
974
+ Returns the path of the saved image.
975
+
976
+ Examples
977
+ --------
978
+ >>> app = Pyloid(app_name="Pyloid-App")
979
+ >>> window = app.create_window("pyloid-window")
980
+ >>> save_path = window.capture("screenshot.png")
981
+ >>> print(f"Image saved at: {save_path}")
982
+ Image saved at: screenshot.png
983
+ """
984
+ try:
985
+ # Capture the window
986
+ screenshot = self._window.grab()
987
+
988
+ # Save the image
989
+ screenshot.save(save_path)
990
+ return save_path
991
+ except Exception as e:
992
+ print(f"An error occurred while capturing the window: {e}")
993
+ return None
994
+
995
+ ###########################################################################################
996
+ # Shortcut
997
+ ###########################################################################################
998
+ def add_shortcut(self, key_sequence: str, callback: Callable):
999
+ """
1000
+ Adds a keyboard shortcut to the window if it does not already exist.
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ key_sequence : str
1005
+ Shortcut sequence (e.g., "Ctrl+C")
1006
+ callback : Callable
1007
+ Function to be executed when the shortcut is pressed
1008
+
1009
+ Returns
1010
+ -------
1011
+ QShortcut or None
1012
+ Created QShortcut object or None if the shortcut already exists
1013
+
1014
+ Examples
1015
+ --------
1016
+ ```python
1017
+ app = Pyloid(app_name="Pyloid-App")
1018
+
1019
+ window = app.create_window("pyloid-window")
1020
+
1021
+ def on_shortcut():
1022
+ print("Shortcut activated!")
1023
+ window.add_shortcut("Ctrl+C", on_shortcut)
1024
+
1025
+ app.run()
1026
+ ```
1027
+ """
1028
+ if key_sequence in self.shortcuts:
1029
+ # print(f"Shortcut {key_sequence} already exists.")
1030
+ return None
1031
+
1032
+ shortcut = QShortcut(QKeySequence(key_sequence), self._window)
1033
+ shortcut.activated.connect(callback)
1034
+ self.shortcuts[key_sequence] = shortcut
1035
+ return shortcut
1036
+
1037
+ def remove_shortcut(self, key_sequence: str):
1038
+ """
1039
+ Removes a keyboard shortcut from the window.
1040
+
1041
+ Parameters
1042
+ ----------
1043
+ key_sequence : str
1044
+ Shortcut sequence to be removed
1045
+
1046
+ Examples
1047
+ --------
1048
+ ```python
1049
+ app = Pyloid(app_name="Pyloid-App")
1050
+
1051
+ window = app.create_window("pyloid-window")
1052
+ window.remove_shortcut("Ctrl+C")
1053
+
1054
+ app.run()
1055
+ ```
1056
+ """
1057
+ if key_sequence in self.shortcuts:
1058
+ shortcut = self.shortcuts.pop(key_sequence)
1059
+ shortcut.setEnabled(False)
1060
+ shortcut.deleteLater()
1061
+
1062
+ def get_all_shortcuts(self):
1063
+ """
1064
+ Returns all registered shortcuts in the window.
1065
+
1066
+ Returns
1067
+ -------
1068
+ dict
1069
+ Dictionary of shortcut sequences and QShortcut objects
1070
+
1071
+ Examples
1072
+ --------
1073
+ ```python
1074
+ app = Pyloid(app_name="Pyloid-App")
1075
+
1076
+ window = app.create_window("pyloid-window")
1077
+ shortcuts = window.get_all_shortcuts()
1078
+ print(shortcuts)
1079
+
1080
+ app.run()
1081
+ ```
1082
+ """
1083
+ return self.shortcuts
1084
+
1085
+ ###########################################################################################
1086
+ # Event (Calling the JS from Python)
1087
+ ###########################################################################################
1088
+ def emit(self, event_name, data: Optional[Dict] = None):
1089
+ """
1090
+ Emits an event to the JavaScript side.
1091
+
1092
+ Parameters
1093
+ ----------
1094
+ event_name : str
1095
+ Name of the event
1096
+ data : dict, optional
1097
+ Data to be sent with the event (default is None)
1098
+
1099
+ Examples
1100
+ --------
1101
+ (Python)
1102
+ ```python
1103
+ app = Pyloid(app_name="Pyloid-App")
1104
+
1105
+ window = app.create_window("pyloid-window")
1106
+ window.emit("customEvent", {"message": "Hello, Pyloid!"})
1107
+
1108
+ app.run()
1109
+ ```
1110
+ ---
1111
+
1112
+ (JavaScript)
1113
+ ```javascript
1114
+ document.addEventListener('customEvent', (data) => {
1115
+ console.log(data.message);
1116
+ });
1117
+ ```
1118
+ """
1119
+ script = f"""
1120
+ (function() {{
1121
+ const eventData = {json.dumps(data)};
1122
+ const customEvent = new CustomEvent('{event_name}', {{ detail: eventData }});
1123
+ document.dispatchEvent(customEvent);
1124
+ }})();
1125
+ """
1126
+ self.web_view.page().runJavaScript(script)
1127
+
1128
+ ###########################################################################################
1129
+ # Get Properties
1130
+ ###########################################################################################
1131
+ def get_window_properties(self):
1132
+ """
1133
+ Returns the properties of the window.
1134
+
1135
+ Returns
1136
+ -------
1137
+ dict
1138
+ Dictionary containing the properties of the window
1139
+
1140
+ Examples
1141
+ --------
1142
+ ```python
1143
+ app = Pyloid(app_name="Pyloid-App")
1144
+
1145
+ window = app.create_window("pyloid-window")
1146
+ properties = window.get_window_properties()
1147
+ print(properties)
1148
+
1149
+ app.run()
1150
+ ```
1151
+ """
1152
+ return {
1153
+ "id": self.id,
1154
+ "title": self.title,
1155
+ "width": self.width,
1156
+ "height": self.height,
1157
+ "x": self.x,
1158
+ "y": self.y,
1159
+ "frame": self.frame,
1160
+ "context_menu": self.context_menu,
1161
+ "dev_tools": self.dev_tools,
1162
+ }
1163
+
1164
+ def get_id(self):
1165
+ """
1166
+ Returns the ID of the window.
1167
+
1168
+ Returns
1169
+ -------
1170
+ str
1171
+ ID of the window
1172
+
1173
+ Examples
1174
+ --------
1175
+ ```python
1176
+ app = Pyloid(app_name="Pyloid-App")
1177
+
1178
+ window = app.create_window("pyloid-window")
1179
+ window_id = window.get_id()
1180
+ print(window_id)
1181
+
1182
+ app.run()
1183
+ ```
1184
+ """
1185
+ return self.id
1186
+
1187
+ def get_size(self) -> Dict[str, int]:
1188
+ """
1189
+ Returns the size of the window.
1190
+
1191
+ Returns
1192
+ -------
1193
+ dict
1194
+ Dictionary containing the width and height of the window
1195
+
1196
+ Examples
1197
+ --------
1198
+ ```python
1199
+ app = Pyloid(app_name="Pyloid-App")
1200
+
1201
+ window = app.create_window("pyloid-window")
1202
+ size = window.get_size()
1203
+ print(size)
1204
+
1205
+ app.run()
1206
+ ```
1207
+ """
1208
+ return {"width": self.width, "height": self.height}
1209
+
1210
+ def get_position(self) -> Dict[str, int]:
1211
+ """
1212
+ Returns the position of the window.
1213
+
1214
+ Returns
1215
+ -------
1216
+ dict
1217
+ Dictionary containing the x and y coordinates of the window
1218
+
1219
+ Examples
1220
+ --------
1221
+ ```python
1222
+ app = Pyloid(app_name="Pyloid-App")
1223
+
1224
+ window = app.create_window("pyloid-window")
1225
+ position = window.get_position()
1226
+ print(position)
1227
+
1228
+ app.run()
1229
+ ```
1230
+ """
1231
+ return {"x": self.x, "y": self.y}
1232
+
1233
+ def get_title(self) -> str:
1234
+ """
1235
+ Returns the title of the window.
1236
+
1237
+ Returns
1238
+ -------
1239
+ str
1240
+ Title of the window
1241
+
1242
+ Examples
1243
+ --------
1244
+ ```python
1245
+ app = Pyloid(app_name="Pyloid-App")
1246
+
1247
+ window = app.create_window("pyloid-window")
1248
+ title = window.get_title()
1249
+ print(title)
1250
+
1251
+ app.run()
1252
+ ```
1253
+ """
1254
+ return self.title
1255
+
1256
+ def get_url(self) -> str:
1257
+ """
1258
+ Returns the URL of the window.
1259
+
1260
+ Returns
1261
+ -------
1262
+ str
1263
+ URL of the window
1264
+
1265
+ Examples
1266
+ --------
1267
+ ```python
1268
+ app = Pyloid(app_name="Pyloid-App")
1269
+
1270
+ window = app.create_window("pyloid-window")
1271
+ url = window.get_url()
1272
+ print(url)
1273
+
1274
+ app.run()
1275
+ ```
1276
+ """
1277
+ return self.web_view.url().toString()
1278
+
1279
+ def get_visible(self) -> bool:
1280
+ """
1281
+ Returns the visibility of the window.
1282
+
1283
+ Returns
1284
+ -------
1285
+ bool
1286
+ True if the window is visible, False otherwise
1287
+
1288
+ Examples
1289
+ --------
1290
+ ```python
1291
+ app = Pyloid(app_name="Pyloid-App")
1292
+
1293
+ window = app.create_window("pyloid-window")
1294
+ visible = window.get_visible()
1295
+ print(visible)
1296
+
1297
+ app.run()
1298
+ ```
1299
+ """
1300
+ return self._window.isVisible()
1301
+
1302
+ def get_frame(self) -> bool:
1303
+ """
1304
+ Returns the frame enabled state of the window.
1305
+
1306
+ Returns
1307
+ -------
1308
+ bool
1309
+ True if the frame is enabled, False otherwise
1310
+
1311
+ Examples
1312
+ --------
1313
+ ```python
1314
+ app = Pyloid(app_name="Pyloid-App")
1315
+
1316
+ window = app.create_window("pyloid-window")
1317
+ frame = window.get_frame()
1318
+ print(frame)
1319
+
1320
+ app.run()
1321
+ ```
1322
+ """
1323
+ return self.frame
1324
+
1325
+ ###########################################################################################
1326
+ # Resize
1327
+ ###########################################################################################
1328
+ def set_resizable(self, resizable: bool):
1329
+ """
1330
+ Sets the resizability of the window.
1331
+
1332
+ Parameters
1333
+ ----------
1334
+ resizable : bool
1335
+ True to make the window resizable, False to make it fixed size
1336
+
1337
+ Examples
1338
+ --------
1339
+ ```python
1340
+ app = Pyloid(app_name="Pyloid-App")
1341
+
1342
+ window = app.create_window("pyloid-window")
1343
+ window.set_resizable(True)
1344
+
1345
+ app.run()
1346
+ ```
1347
+ """
1348
+ self.resizable = resizable
1349
+ if self.frame:
1350
+ flags = self._window.windowFlags() | Qt.WindowCloseButtonHint
1351
+ if resizable:
1352
+ pass
1353
+ else:
1354
+ flags |= Qt.MSWindowsFixedSizeDialogHint
1355
+ self._window.setWindowFlags(flags)
1356
+ else:
1357
+ # 프레임이 없는 경우 커스텀 리사이징 로직을 설정합니다.
1358
+ self.web_view.is_resizing_enabled = resizable
1359
+
1360
+ self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
1361
+
1362
+ def set_minimum_size(self, min_width: int, min_height: int):
1363
+ """
1364
+ Sets the minimum size of the window.
1365
+
1366
+ Parameters
1367
+ ----------
1368
+ min_width : int
1369
+ Minimum width of the window
1370
+ min_height : int
1371
+ Minimum height of the window
1372
+
1373
+ Examples
1374
+ --------
1375
+ ```python
1376
+ app = Pyloid(app_name="Pyloid-App")
1377
+
1378
+ window = app.create_window("pyloid-window")
1379
+ window.set_minimum_size(400, 300)
1380
+
1381
+ app.run()
1382
+ ```
1383
+ """
1384
+ self._window.setMinimumSize(min_width, min_height)
1385
+
1386
+ def set_maximum_size(self, max_width: int, max_height: int):
1387
+ """
1388
+ Sets the maximum size of the window.
1389
+
1390
+ Parameters
1391
+ ----------
1392
+ max_width : int
1393
+ Maximum width of the window
1394
+ max_height : int
1395
+ Maximum height of the window
1396
+
1397
+ Examples
1398
+ --------
1399
+ ```python
1400
+ app = Pyloid(app_name="Pyloid-App")
1401
+
1402
+ window = app.create_window("pyloid-window")
1403
+ window.set_maximum_size(1024, 768)
1404
+
1405
+ app.run()
1406
+ ```
1407
+ """
1408
+ self._window.setMaximumSize(max_width, max_height)
1409
+
1410
+ def get_minimum_size(self) -> Dict[str, int]:
1411
+ """
1412
+ Returns the minimum size of the window.
1413
+
1414
+ Returns
1415
+ -------
1416
+ dict
1417
+ Dictionary containing the minimum width and height of the window
1418
+
1419
+ Examples
1420
+ --------
1421
+ ```python
1422
+ app = Pyloid(app_name="Pyloid-App")
1423
+
1424
+ window = app.create_window("pyloid-window")
1425
+ min_size = window.get_minimum_size()
1426
+ print(min_size)
1427
+
1428
+ app.run()
1429
+ ```
1430
+ """
1431
+ return {
1432
+ "width": self._window.minimumWidth(),
1433
+ "height": self._window.minimumHeight(),
1434
+ }
1435
+
1436
+ def get_maximum_size(self) -> Dict[str, int]:
1437
+ """
1438
+ Returns the maximum size of the window.
1439
+
1440
+ Returns
1441
+ -------
1442
+ dict
1443
+ Dictionary containing the maximum width and height of the window
1444
+
1445
+ Examples
1446
+ --------
1447
+ ```python
1448
+ app = Pyloid(app_name="Pyloid-App")
1449
+
1450
+ window = app.create_window("pyloid-window")
1451
+ max_size = window.get_maximum_size()
1452
+ print(max_size)
1453
+
1454
+ app.run()
1455
+ ```
1456
+ """
1457
+ return {
1458
+ "width": self._window.maximumWidth(),
1459
+ "height": self._window.maximumHeight(),
1460
+ }
1461
+
1462
+ def get_resizable(self) -> bool:
1463
+ """
1464
+ Returns the resizability of the window.
1465
+
1466
+ Returns
1467
+ -------
1468
+ bool
1469
+ True if the window is resizable, False otherwise
1470
+
1471
+ Examples
1472
+ --------
1473
+ ```python
1474
+ app = Pyloid(app_name="Pyloid-App")
1475
+
1476
+ window = app.create_window("pyloid-window")
1477
+ resizable = window.get_resizable()
1478
+ print(resizable)
1479
+
1480
+ app.run()
1481
+ ```
1482
+ """
1483
+ return self.resizable
1484
+
1485
+ ###########################################################################################
1486
+ # For Custom Pyside6 Features
1487
+ ###########################################################################################
1488
+ def get_QMainWindow(self) -> QMainWindow:
1489
+ """
1490
+ Returns the QMainWindow object of the window.
1491
+
1492
+ you can use all the features of QMainWindow for customizing the window.
1493
+
1494
+ Returns
1495
+ -------
1496
+ QMainWindow
1497
+ QMainWindow object of the window
1498
+
1499
+ Examples
1500
+ --------
1501
+ ```python
1502
+ from PySide6.QtCore import Qt
1503
+ from pyloid import Pyloid
1504
+
1505
+ app = Pyloid(app_name="Pyloid-App")
1506
+
1507
+ window = app.create_window("pyloid-window")
1508
+ qmain = window.get_QMainWindow()
1509
+
1510
+ qmain.setWindowFlags(qmain.windowFlags() | Qt.WindowStaysOnTopHint) # window stays on top
1511
+ ```
1512
+ """
1513
+ return self._window
1514
+
1515
+ def get_QWebEngineView(self) -> CustomWebEngineView:
1516
+ """
1517
+ Returns the CustomWebEngineView object which inherits from QWebEngineView.
1518
+
1519
+ Returns
1520
+ -------
1521
+ CustomWebEngineView
1522
+ CustomWebEngineView object of the window
1523
+
1524
+ Examples
1525
+ --------
1526
+ ```python
1527
+ window = app.create_window("pyloid-window")
1528
+ web_view = window.get_QWebEngineView()
1529
+
1530
+ web_view.page().runJavaScript("console.log('Hello, Pyloid!')")
1531
+ ```
1532
+ """
1533
+ return self.web_view
1534
+
1535
+ ###########################################################################################
1536
+ # QMainWindow flags
1537
+ ###########################################################################################
1538
+ def set_window_stay_on_top(self, on_top: bool):
1539
+ """
1540
+ Sets the window stay on top flag of the window.
1541
+
1542
+ Parameters
1543
+ ----------
1544
+ on_top : bool
1545
+ True to keep the window on top, False otherwise
1546
+
1547
+ Examples
1548
+ --------
1549
+ ```python
1550
+ window.set_window_stay_on_top(True)
1551
+ window.set_window_stay_on_top(False)
1552
+ ```
1553
+ """
1554
+ flags = self._window.windowFlags()
1555
+ if on_top:
1556
+ flags |= Qt.WindowStaysOnTopHint
1557
+ else:
1558
+ flags &= ~Qt.WindowStaysOnTopHint
1559
+
1560
+ # Maintain existing flags while only changing WindowStaysOnTopHint
1561
+ # Explicitly add the close button
1562
+ flags |= Qt.WindowCloseButtonHint
1563
+
1564
+ self._window.setWindowFlags(flags)
1565
+
1566
+ # Show the window again to apply the changes
1567
+ self._window.show()
1568
+
1569
+ def set_window_stay_on_bottom(self, on_bottom: bool):
1570
+ """
1571
+ Sets the window stay on bottom flag of the window.
1572
+
1573
+ Parameters
1574
+ ----------
1575
+ on_bottom : bool
1576
+ True to keep the window on bottom, False otherwise
1577
+
1578
+ Examples
1579
+ --------
1580
+ ```python
1581
+ window.set_window_stay_on_bottom(True)
1582
+ window.set_window_stay_on_bottom(False)
1583
+ ```
1584
+ """
1585
+ flags = self._window.windowFlags()
1586
+ if on_bottom:
1587
+ flags |= Qt.WindowStaysOnBottomHint
1588
+ else:
1589
+ flags &= ~Qt.WindowStaysOnBottomHint
1590
+
1591
+ # Maintain existing flags while only changing WindowStaysOnBottomHint
1592
+ # Explicitly add the close button
1593
+ flags |= Qt.WindowCloseButtonHint
1594
+
1595
+ self._window.setWindowFlags(flags)
1596
+
1597
+ # Show the window again to apply the changes
1598
+ self._window.show()
1599
+
1600
+ ###########################################################################################
1601
+ # Splash Screen
1602
+ ###########################################################################################
1603
+ def set_static_image_splash_screen(
1604
+ self,
1605
+ image_path: str,
1606
+ close_on_load: bool = True,
1607
+ stay_on_top: bool = True,
1608
+ clickable: bool = True,
1609
+ position: str = "center",
1610
+ ):
1611
+ """
1612
+ Sets the static image splash screen of the window.
1613
+
1614
+ Parameters
1615
+ ----------
1616
+ image_path : str
1617
+ Path to the image file
1618
+ close_on_load : bool, optional
1619
+ True to close the splash screen when the page is loaded, False otherwise (default is True)
1620
+ stay_on_top : bool, optional
1621
+ True to keep the splash screen on top, False otherwise (default is True)
1622
+ clickable : bool, optional
1623
+ True to make the splash screen clickable, False otherwise (default is True)
1624
+ if clickable is True, you can click the splash screen to close it.
1625
+ position : str, optional
1626
+ Position of the splash screen. Options are 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right' (default is 'center')
1627
+
1628
+ Examples
1629
+ --------
1630
+ ```python
1631
+ window.set_image_splash_screen("./assets/loading.png", close_on_load=True, stay_on_top=True)
1632
+ ```
1633
+ """
1634
+ pixmap = QPixmap(image_path)
1635
+
1636
+ if not clickable:
1637
+
1638
+ class NonClickableSplashScreen(QSplashScreen):
1639
+ def mousePressEvent(self, event):
1640
+ pass # Ignore click events
1641
+
1642
+ splash = NonClickableSplashScreen(
1643
+ pixmap, Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0)
1644
+ )
1645
+ else:
1646
+ splash = QSplashScreen(
1647
+ pixmap, Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0)
1648
+ )
1649
+
1650
+ self.close_on_load = close_on_load
1651
+ self.splash_screen = splash
1652
+ self._position_splash_screen(position)
1653
+ self.splash_screen.show()
1654
+
1655
+ def set_gif_splash_screen(
1656
+ self,
1657
+ gif_path: str,
1658
+ close_on_load: bool = True,
1659
+ stay_on_top: bool = True,
1660
+ clickable: bool = True,
1661
+ position: str = "center",
1662
+ ):
1663
+ """
1664
+ Sets the gif splash screen of the window.
1665
+
1666
+ Parameters
1667
+ ----------
1668
+ gif_path : str
1669
+ Path to the gif file
1670
+ close_on_load : bool, optional
1671
+ True to close the splash screen when the page is loaded, False otherwise (default is True)
1672
+ stay_on_top : bool, optional
1673
+ True to keep the splash screen on top, False otherwise (default is True)
1674
+ clickable : bool, optional
1675
+ True to make the splash screen clickable, False otherwise (default is True)
1676
+ if clickable is True, you can click the splash screen to close it.
1677
+ position : str, optional
1678
+ Position of the splash screen. Options are 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right' (default is 'center')
1679
+
1680
+ Examples
1681
+ --------
1682
+ ```python
1683
+ window.set_gif_splash_screen("./assets/loading.gif", close_on_load=True, stay_on_top=True)
1684
+ ```
1685
+ """
1686
+
1687
+ if not clickable:
1688
+
1689
+ class NonClickableSplashScreen(QSplashScreen):
1690
+ def mousePressEvent(self, event):
1691
+ pass # Ignore click events
1692
+
1693
+ # Create splash screen (using animated GIF)
1694
+ splash = NonClickableSplashScreen(
1695
+ QPixmap(1, 1),
1696
+ Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0),
1697
+ ) # Start with 1x1 transparent pixmap
1698
+ else:
1699
+ splash = QSplashScreen(
1700
+ QPixmap(1, 1),
1701
+ Qt.WindowStaysOnTopHint if stay_on_top else Qt.WindowType(0),
1702
+ )
1703
+
1704
+ splash.setAttribute(Qt.WA_TranslucentBackground)
1705
+
1706
+ # Create QLabel for GIF animation
1707
+ label = QLabel(splash)
1708
+ movie = QMovie(gif_path)
1709
+ label.setMovie(movie)
1710
+
1711
+ # Adjust splash screen size to match GIF size
1712
+ movie.frameChanged.connect(
1713
+ lambda: splash.setFixedSize(movie.currentPixmap().size())
1714
+ )
1715
+
1716
+ # Start animation and show splash screen
1717
+ movie.start()
1718
+ self.close_on_load = close_on_load
1719
+ self.splash_screen = splash
1720
+ self._position_splash_screen(position)
1721
+ splash.show()
1722
+
1723
+ def _position_splash_screen(self, position: str):
1724
+ if not self.splash_screen:
1725
+ return
1726
+
1727
+ screen = self.app.primaryScreen().geometry()
1728
+ splash_size = self.splash_screen.size()
1729
+
1730
+ if position == "center":
1731
+ new_position = screen.center() - QPoint(
1732
+ splash_size.width() // 2, splash_size.height() // 2
1733
+ )
1734
+ elif position == "top-left":
1735
+ new_position = screen.topLeft()
1736
+ elif position == "top-right":
1737
+ new_position = screen.topRight() - QPoint(splash_size.width(), 0)
1738
+ elif position == "bottom-left":
1739
+ new_position = screen.bottomLeft() - QPoint(0, splash_size.height())
1740
+ elif position == "bottom-right":
1741
+ new_position = screen.bottomRight() - QPoint(
1742
+ splash_size.width(), splash_size.height()
1743
+ )
1744
+ else:
1745
+ new_position = screen.center() - QPoint(
1746
+ splash_size.width() // 2, splash_size.height() // 2
1747
+ )
1748
+
1749
+ self.splash_screen.move(new_position)
1750
+
1751
+ def close_splash_screen(self):
1752
+ """
1753
+ Closes the splash screen if it exists.
1754
+
1755
+ Examples
1756
+ --------
1757
+ ```python
1758
+ window.close_splash_screen()
1759
+ ```
1760
+ """
1761
+ if hasattr(self, "splash_screen") and self.splash_screen:
1762
+ self.splash_screen.close()
1763
+ self.close_on_load = None
1764
+ self.splash_screen = None
1765
+
1766
+ ###########################################################################################
1767
+ # WebEngineView Attribute setting
1768
+ ###########################################################################################
1769
+ def set_web_engine_view_attribute(self, attribute: QWebEngineSettings, on: bool):
1770
+ """
1771
+ Sets the attribute of the WebEngineView.
1772
+
1773
+ Parameters
1774
+ ----------
1775
+ attribute : QWebEngineSettings
1776
+ Attribute to set
1777
+ on : bool
1778
+ True to enable the attribute, False to disable it
1779
+
1780
+ Examples
1781
+ --------
1782
+ ```python
1783
+ window.set_web_engine_view_attribute(QWebEngineSettings.WebAttribute.ScreenCaptureEnabled, False)
1784
+ ```
1785
+ """
1786
+ settings = self.web_view.settings()
1787
+ settings.setAttribute(attribute, on)
1788
+
1789
+ def is_web_engine_view_attribute(self, attribute: QWebEngineSettings) -> bool:
1790
+ """
1791
+ Returns the attribute of the WebEngineView.
1792
+
1793
+ Parameters
1794
+ ----------
1795
+ attribute : QWebEngineSettings
1796
+ Attribute to get
1797
+
1798
+ Returns
1799
+ -------
1800
+ bool
1801
+ True if the attribute is enabled, False otherwise
1802
+
1803
+ Examples
1804
+ --------
1805
+ ```python
1806
+ window.is_web_engine_view_attribute(QWebEngineSettings.WebAttribute.ScreenCaptureEnabled)
1807
+ ```
1808
+ """
1809
+ settings = self.web_view.settings()
1810
+ return settings.testAttribute(attribute)
1811
+
1812
+ def set_permission_handler(self, feature: QWebEnginePage.Feature, handler):
1813
+ """
1814
+ Sets a handler for a specific permission.
1815
+
1816
+ Parameters
1817
+ ----------
1818
+ feature : QWebEnginePage.Feature
1819
+ The type of permission to set
1820
+ handler : callable
1821
+ The handler function to process the permission request
1822
+
1823
+ Examples
1824
+ --------
1825
+ ```python
1826
+ def handle_camera(origin, feature):
1827
+ window.web_view.page().setFeaturePermission(
1828
+ origin,
1829
+ feature,
1830
+ QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1831
+ )
1832
+
1833
+ window.set_permission_handler(
1834
+ QWebEnginePage.Feature.MediaVideoCapture,
1835
+ handle_camera
1836
+ )
1837
+ ```
1838
+ """
1839
+ self.web_view.custom_page.setPermissionHandler(feature, handler)
1840
+
1841
+ def grant_permission(self, feature: QWebEnginePage.Feature):
1842
+ """
1843
+ Automatically grants a specific permission when a request is made.
1844
+
1845
+ Parameters
1846
+ ----------
1847
+ feature : QWebEnginePage.Feature
1848
+ The type of permission to automatically grant
1849
+
1850
+ Examples
1851
+ --------
1852
+ ```python
1853
+ window.grant_permission(QWebEnginePage.Feature.MediaVideoCapture)
1854
+ ```
1855
+ """
1856
+
1857
+ def auto_grant(origin, feat):
1858
+ self.web_view.page().setFeaturePermission(
1859
+ origin, feat, QWebEnginePage.PermissionPolicy.PermissionGrantedByUser
1860
+ )
1861
+
1862
+ self.set_permission_handler(feature, auto_grant)
1863
+
1864
+ def deny_permission(self, feature: QWebEnginePage.Feature):
1865
+ """
1866
+ Automatically denies a specific permission when a request is made.
1867
+
1868
+ Parameters
1869
+ ----------
1870
+ feature : QWebEnginePage.Feature
1871
+ The type of permission to automatically deny
1872
+
1873
+ Examples
1874
+ --------
1875
+ ```python
1876
+ window.deny_permission(QWebEnginePage.Feature.Notifications)
1877
+ ```
1878
+ """
1879
+
1880
+ def auto_deny(origin, feat):
1881
+ self.web_view.page().setFeaturePermission(
1882
+ origin, feat, QWebEnginePage.PermissionPolicy.PermissionDeniedByUser
1883
+ )
1884
+
1885
+ self.set_permission_handler(feature, auto_deny)
1886
+
1887
+ def set_desktop_media_handler(self, handler):
1888
+ """
1889
+ 데스크톱 미디어(화면/윈도우) 선택 핸들러를 설정합니다.
1890
+
1891
+ Parameters
1892
+ ----------
1893
+ handler : callable
1894
+ 요청을 처리할 핸들러 함수. QWebEngineDesktopMediaRequest��� 인자로 받습니다.
1895
+
1896
+ Examples
1897
+ --------
1898
+ ```python
1899
+ def custom_media_handler(request):
1900
+ # 사용 가능한 화면 목록 출력
1901
+ for screen in request.screenList():
1902
+ print(f"Screen: {screen.name}")
1903
+
1904
+ # 사용 가능한 윈도우 목록 출력
1905
+ for window in request.windowList():
1906
+ print(f"Window: {window.name}")
1907
+
1908
+ # 첫 번째 화면 선택
1909
+ if request.screenList():
1910
+ request.selectScreen(request.screenList()[0])
1911
+
1912
+ window.set_desktop_media_handler(custom_media_handler)
1913
+ ```
1914
+ """
1915
+ self.web_view.custom_page.setDesktopMediaHandler(handler)