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