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

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