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

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