pyloid 0.20.2__py3-none-any.whl → 0.20.2.dev0__py3-none-any.whl

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