pyloid 0.20.2.dev2__py3-none-any.whl → 0.22.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyloid/__init__.py +2 -2
- pyloid/browser_window.py +1210 -205
- pyloid/js_api/base.py +29 -0
- pyloid/js_api/event_api.py +3 -3
- pyloid/js_api/window_api.py +254 -251
- pyloid/pyloid.py +901 -35
- pyloid/serve.py +56 -0
- pyloid/utils.py +36 -0
- {pyloid-0.20.2.dev2.dist-info → pyloid-0.22.0.dist-info}/LICENSE +1 -1
- {pyloid-0.20.2.dev2.dist-info → pyloid-0.22.0.dist-info}/METADATA +2 -3
- pyloid-0.22.0.dist-info/RECORD +20 -0
- pyloid/builder/__init__.py +0 -81
- pyloid/builder/build_config.schema.json +0 -73
- pyloid/builder/spec.py +0 -266
- pyloid-0.20.2.dev2.dist-info/RECORD +0 -21
- {pyloid-0.20.2.dev2.dist-info → pyloid-0.22.0.dist-info}/WHEEL +0 -0
pyloid/pyloid.py
CHANGED
@@ -26,9 +26,14 @@ import logging
|
|
26
26
|
from .browser_window import BrowserWindow
|
27
27
|
from .tray import TrayEvent
|
28
28
|
from PySide6.QtCore import QCoreApplication
|
29
|
-
from PySide6.QtCore import
|
30
|
-
import
|
31
|
-
from .
|
29
|
+
from PySide6.QtCore import Signal, QObject, Slot
|
30
|
+
import uuid
|
31
|
+
from PySide6.QtCore import QEventLoop
|
32
|
+
import socket
|
33
|
+
from typing import Any
|
34
|
+
|
35
|
+
# software backend
|
36
|
+
os.environ["QT_QUICK_BACKEND"] = "software"
|
32
37
|
|
33
38
|
# for linux debug
|
34
39
|
os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
|
@@ -73,11 +78,14 @@ class _WindowController(QObject):
|
|
73
78
|
)
|
74
79
|
|
75
80
|
|
76
|
-
|
81
|
+
|
82
|
+
# Only Work in Main Thread
|
83
|
+
class _Pyloid(QApplication):
|
77
84
|
def __init__(
|
78
85
|
self,
|
79
86
|
app_name,
|
80
87
|
single_instance=True,
|
88
|
+
data=None,
|
81
89
|
):
|
82
90
|
"""
|
83
91
|
Initializes the Pyloid application.
|
@@ -88,6 +96,8 @@ class Pyloid(QApplication):
|
|
88
96
|
The name of the application
|
89
97
|
single_instance : bool, optional
|
90
98
|
Whether to run the application as a single instance (default is True)
|
99
|
+
data : dict, optional
|
100
|
+
Data to be transmitted to the frontend web engine via IPC
|
91
101
|
|
92
102
|
Examples
|
93
103
|
--------
|
@@ -101,8 +111,10 @@ class Pyloid(QApplication):
|
|
101
111
|
```
|
102
112
|
"""
|
103
113
|
super().__init__(sys.argv)
|
114
|
+
|
115
|
+
self.data = data
|
104
116
|
|
105
|
-
self.
|
117
|
+
self.windows_dict = {} # 윈도우 ID를 키로 사용하는 딕셔너리
|
106
118
|
self.server = None
|
107
119
|
|
108
120
|
self.app_name = app_name
|
@@ -199,8 +211,8 @@ class Pyloid(QApplication):
|
|
199
211
|
self.icon = QIcon(icon_path)
|
200
212
|
|
201
213
|
# Immediately update the icon for all open windows.
|
202
|
-
for window in self.
|
203
|
-
window.
|
214
|
+
for window in self.windows_dict.values():
|
215
|
+
window.set_icon(self.icon)
|
204
216
|
|
205
217
|
def create_window(
|
206
218
|
self,
|
@@ -261,7 +273,8 @@ class Pyloid(QApplication):
|
|
261
273
|
dev_tools,
|
262
274
|
js_apis,
|
263
275
|
)
|
264
|
-
|
276
|
+
latest_window_id = list(self.windows_dict.keys())[-1]
|
277
|
+
return self.windows_dict[latest_window_id]
|
265
278
|
|
266
279
|
def _create_window_signal_function(
|
267
280
|
self,
|
@@ -289,7 +302,7 @@ class Pyloid(QApplication):
|
|
289
302
|
dev_tools,
|
290
303
|
js_apis,
|
291
304
|
)
|
292
|
-
self.
|
305
|
+
self.windows_dict[window._window.id] = window
|
293
306
|
return window
|
294
307
|
|
295
308
|
def run(self):
|
@@ -333,7 +346,7 @@ class Pyloid(QApplication):
|
|
333
346
|
###########################################################################################
|
334
347
|
# App window
|
335
348
|
###########################################################################################
|
336
|
-
def get_windows(self) ->
|
349
|
+
def get_windows(self) -> Dict[str, BrowserWindow]:
|
337
350
|
"""
|
338
351
|
Returns a list of all browser windows.
|
339
352
|
|
@@ -351,7 +364,7 @@ class Pyloid(QApplication):
|
|
351
364
|
print(window.get_id())
|
352
365
|
```
|
353
366
|
"""
|
354
|
-
return self.
|
367
|
+
return self.windows_dict
|
355
368
|
|
356
369
|
def show_main_window(self):
|
357
370
|
"""
|
@@ -364,8 +377,9 @@ class Pyloid(QApplication):
|
|
364
377
|
app.show_main_window()
|
365
378
|
```
|
366
379
|
"""
|
367
|
-
if self.
|
368
|
-
|
380
|
+
if self.windows_dict:
|
381
|
+
# 첫 번째 윈도우 가져오기
|
382
|
+
main_window = next(iter(self.windows_dict.values()))
|
369
383
|
main_window._window.show()
|
370
384
|
|
371
385
|
def focus_main_window(self):
|
@@ -379,8 +393,9 @@ class Pyloid(QApplication):
|
|
379
393
|
app.focus_main_window()
|
380
394
|
```
|
381
395
|
"""
|
382
|
-
if self.
|
383
|
-
|
396
|
+
if self.windows_dict:
|
397
|
+
# 첫 번째 윈도우 가져오기
|
398
|
+
main_window = next(iter(self.windows_dict.values()))
|
384
399
|
main_window._window.activateWindow()
|
385
400
|
main_window._window.raise_()
|
386
401
|
main_window._window.setWindowState(
|
@@ -399,8 +414,8 @@ class Pyloid(QApplication):
|
|
399
414
|
app.show_and_focus_main_window()
|
400
415
|
```
|
401
416
|
"""
|
402
|
-
if self.
|
403
|
-
main_window = self.
|
417
|
+
if self.windows_dict:
|
418
|
+
main_window = next(iter(self.windows_dict.values()))
|
404
419
|
main_window._window.show()
|
405
420
|
main_window._window.activateWindow()
|
406
421
|
main_window._window.raise_()
|
@@ -420,7 +435,7 @@ class Pyloid(QApplication):
|
|
420
435
|
app.close_all_windows()
|
421
436
|
```
|
422
437
|
"""
|
423
|
-
for window in self.
|
438
|
+
for window in self.windows_dict.values():
|
424
439
|
window._window.close()
|
425
440
|
|
426
441
|
def quit(self):
|
@@ -435,12 +450,12 @@ class Pyloid(QApplication):
|
|
435
450
|
```
|
436
451
|
"""
|
437
452
|
|
438
|
-
|
439
|
-
for window in self.windows:
|
453
|
+
for window in self.windows_dict.values():
|
440
454
|
window._window.close()
|
441
455
|
window.web_page.deleteLater()
|
442
456
|
window.web_view.deleteLater()
|
443
457
|
|
458
|
+
self.windows_dict.clear()
|
444
459
|
QApplication.quit()
|
445
460
|
|
446
461
|
###########################################################################################
|
@@ -449,7 +464,7 @@ class Pyloid(QApplication):
|
|
449
464
|
def get_window_by_id(self, window_id: str) -> Optional[BrowserWindow]:
|
450
465
|
"""
|
451
466
|
Returns the window with the given ID.
|
452
|
-
|
467
|
+
|
453
468
|
Parameters
|
454
469
|
----------
|
455
470
|
window_id : str
|
@@ -459,22 +474,19 @@ class Pyloid(QApplication):
|
|
459
474
|
-------
|
460
475
|
Optional[BrowserWindow]
|
461
476
|
The window object with the given ID. Returns None if the window is not found.
|
462
|
-
|
477
|
+
|
463
478
|
Examples
|
464
479
|
--------
|
465
480
|
```python
|
466
481
|
app = Pyloid(app_name="Pyloid-App")
|
467
|
-
|
482
|
+
|
468
483
|
window = app.get_window_by_id("123e4567-e89b-12d3-a456-426614174000")
|
469
|
-
|
484
|
+
|
470
485
|
if window:
|
471
486
|
print("Window found:", window)
|
472
487
|
```
|
473
488
|
"""
|
474
|
-
|
475
|
-
if window.id == window_id:
|
476
|
-
return window
|
477
|
-
return None
|
489
|
+
return self.windows_dict.get(window_id)
|
478
490
|
|
479
491
|
def hide_window_by_id(self, window_id: str):
|
480
492
|
"""
|
@@ -520,12 +532,7 @@ class Pyloid(QApplication):
|
|
520
532
|
"""
|
521
533
|
window = self.get_window_by_id(window_id)
|
522
534
|
if window:
|
523
|
-
window.
|
524
|
-
window._window.activateWindow()
|
525
|
-
window._window.raise_()
|
526
|
-
window._window.setWindowState(
|
527
|
-
window._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
|
528
|
-
)
|
535
|
+
window.show()
|
529
536
|
|
530
537
|
def close_window_by_id(self, window_id: str):
|
531
538
|
"""
|
@@ -1397,8 +1404,867 @@ class Pyloid(QApplication):
|
|
1397
1404
|
"""
|
1398
1405
|
|
1399
1406
|
# 모든 윈도우에 변경사항 적용
|
1400
|
-
for window in self.
|
1407
|
+
for window in self.windows_dict.values():
|
1401
1408
|
window.web_view.page().runJavaScript(js_code)
|
1402
1409
|
window.web_view.page().setBackgroundColor(
|
1403
1410
|
Qt.GlobalColor.black if self.theme == "dark" else Qt.GlobalColor.white
|
1404
1411
|
)
|
1412
|
+
|
1413
|
+
|
1414
|
+
class Pyloid(QObject):
|
1415
|
+
command_signal = Signal(str, str, object)
|
1416
|
+
result_signal = Signal(str, object)
|
1417
|
+
|
1418
|
+
def __init__(self, app_name: str, single_instance: bool = True, data: Optional[Dict[str, Any]] = None):
|
1419
|
+
"""
|
1420
|
+
Initialize the Pyloid application.
|
1421
|
+
|
1422
|
+
This application is designed to work in a multi-threaded environment.
|
1423
|
+
All Qt GUI related operations are executed in the main thread's event queue regardless of the calling thread,
|
1424
|
+
ensuring thread safety for GUI operations.
|
1425
|
+
|
1426
|
+
Parameters
|
1427
|
+
----------
|
1428
|
+
app_name : str, required
|
1429
|
+
The name of the application.
|
1430
|
+
single_instance : bool, optional
|
1431
|
+
Determines whether to run as a single instance. (Default is True)
|
1432
|
+
data : dict, optional
|
1433
|
+
Data to be transmitted to the frontend web engine via IPC
|
1434
|
+
|
1435
|
+
Notes
|
1436
|
+
-----
|
1437
|
+
The generated or passed `id` is transmitted to the frontend via IPC,
|
1438
|
+
and is used as an API key to connect to the integrated backend FastAPI server.
|
1439
|
+
"""
|
1440
|
+
super().__init__()
|
1441
|
+
|
1442
|
+
self.data = data
|
1443
|
+
|
1444
|
+
self.app = _Pyloid(app_name, single_instance, self.data)
|
1445
|
+
|
1446
|
+
self.command_signal.connect(self._handle_command)
|
1447
|
+
|
1448
|
+
@Slot(str, str, object)
|
1449
|
+
def _handle_command(self, command_id, command_type, params):
|
1450
|
+
result = None
|
1451
|
+
|
1452
|
+
if command_type == "set_icon":
|
1453
|
+
result = self.app.set_icon(params["icon_path"])
|
1454
|
+
|
1455
|
+
elif command_type == "create_window":
|
1456
|
+
window = self.app.create_window(
|
1457
|
+
title=params.get("title", ""),
|
1458
|
+
width=params.get("width", 800),
|
1459
|
+
height=params.get("height", 600),
|
1460
|
+
x=params.get("x", 200),
|
1461
|
+
y=params.get("y", 200),
|
1462
|
+
frame=params.get("frame", True),
|
1463
|
+
context_menu=params.get("context_menu", False),
|
1464
|
+
dev_tools=params.get("dev_tools", False)
|
1465
|
+
)
|
1466
|
+
result = window
|
1467
|
+
|
1468
|
+
elif command_type == "run":
|
1469
|
+
result = self.app.run()
|
1470
|
+
|
1471
|
+
elif command_type == "get_windows":
|
1472
|
+
result = self.app.get_windows()
|
1473
|
+
|
1474
|
+
elif command_type == "show_main_window":
|
1475
|
+
result = self.app.show_main_window()
|
1476
|
+
|
1477
|
+
elif command_type == "focus_main_window":
|
1478
|
+
result = self.app.focus_main_window()
|
1479
|
+
|
1480
|
+
elif command_type == "show_and_focus_main_window":
|
1481
|
+
result = self.app.show_and_focus_main_window()
|
1482
|
+
|
1483
|
+
elif command_type == "close_all_windows":
|
1484
|
+
result = self.app.close_all_windows()
|
1485
|
+
|
1486
|
+
elif command_type == "quit":
|
1487
|
+
result = self.app.quit()
|
1488
|
+
|
1489
|
+
elif command_type == "get_window_by_id":
|
1490
|
+
result = self.app.get_window_by_id(params["window_id"])
|
1491
|
+
|
1492
|
+
elif command_type == "set_tray_icon":
|
1493
|
+
result = self.app.set_tray_icon(params["tray_icon_path"])
|
1494
|
+
|
1495
|
+
elif command_type == "set_tray_menu_items":
|
1496
|
+
result = self.app.set_tray_menu_items(params["tray_menu_items"])
|
1497
|
+
|
1498
|
+
elif command_type == "set_tray_actions":
|
1499
|
+
result = self.app.set_tray_actions(params["actions"])
|
1500
|
+
|
1501
|
+
elif command_type == "show_notification":
|
1502
|
+
result = self.app.show_notification(params["title"], params["message"])
|
1503
|
+
|
1504
|
+
elif command_type == "set_tray_icon_animation":
|
1505
|
+
result = self.app.set_tray_icon_animation(params["icon_frames"], params.get("interval", 200))
|
1506
|
+
|
1507
|
+
elif command_type == "set_tray_tooltip":
|
1508
|
+
result = self.app.set_tray_tooltip(params["message"])
|
1509
|
+
|
1510
|
+
elif command_type == "set_notification_callback":
|
1511
|
+
result = self.app.set_notification_callback(params["callback"])
|
1512
|
+
|
1513
|
+
elif command_type == "get_all_monitors":
|
1514
|
+
result = self.app.get_all_monitors()
|
1515
|
+
|
1516
|
+
elif command_type == "get_primary_monitor":
|
1517
|
+
result = self.app.get_primary_monitor()
|
1518
|
+
|
1519
|
+
elif command_type == "set_clipboard_text":
|
1520
|
+
result = self.app.set_clipboard_text(params["text"])
|
1521
|
+
|
1522
|
+
elif command_type == "get_clipboard_text":
|
1523
|
+
result = self.app.get_clipboard_text()
|
1524
|
+
|
1525
|
+
elif command_type == "set_clipboard_image":
|
1526
|
+
result = self.app.set_clipboard_image(params["image"])
|
1527
|
+
|
1528
|
+
elif command_type == "get_clipboard_image":
|
1529
|
+
result = self.app.get_clipboard_image()
|
1530
|
+
|
1531
|
+
elif command_type == "set_auto_start":
|
1532
|
+
result = self.app.set_auto_start(params["enable"])
|
1533
|
+
|
1534
|
+
elif command_type == "is_auto_start":
|
1535
|
+
result = self.app.is_auto_start()
|
1536
|
+
|
1537
|
+
elif command_type == "watch_file":
|
1538
|
+
result = self.app.watch_file(params["file_path"])
|
1539
|
+
|
1540
|
+
elif command_type == "watch_directory":
|
1541
|
+
result = self.app.watch_directory(params["dir_path"])
|
1542
|
+
|
1543
|
+
elif command_type == "stop_watching":
|
1544
|
+
result = self.app.stop_watching(params["path"])
|
1545
|
+
|
1546
|
+
elif command_type == "get_watched_paths":
|
1547
|
+
result = self.app.get_watched_paths()
|
1548
|
+
|
1549
|
+
elif command_type == "get_watched_files":
|
1550
|
+
result = self.app.get_watched_files()
|
1551
|
+
|
1552
|
+
elif command_type == "get_watched_directories":
|
1553
|
+
result = self.app.get_watched_directories()
|
1554
|
+
|
1555
|
+
elif command_type == "remove_all_watched_paths":
|
1556
|
+
result = self.app.remove_all_watched_paths()
|
1557
|
+
|
1558
|
+
elif command_type == "set_file_change_callback":
|
1559
|
+
result = self.app.set_file_change_callback(params["callback"])
|
1560
|
+
|
1561
|
+
elif command_type == "set_directory_change_callback":
|
1562
|
+
result = self.app.set_directory_change_callback(params["callback"])
|
1563
|
+
|
1564
|
+
elif command_type == "open_file_dialog":
|
1565
|
+
result = self.app.open_file_dialog(params.get("dir"), params.get("filter"))
|
1566
|
+
|
1567
|
+
elif command_type == "save_file_dialog":
|
1568
|
+
result = self.app.save_file_dialog(params.get("dir"), params.get("filter"))
|
1569
|
+
|
1570
|
+
elif command_type == "select_directory_dialog":
|
1571
|
+
result = self.app.select_directory_dialog(params.get("dir"))
|
1572
|
+
|
1573
|
+
else:
|
1574
|
+
return None
|
1575
|
+
|
1576
|
+
self.result_signal.emit(command_id, result)
|
1577
|
+
|
1578
|
+
def execute_command(self, command_type: str, params: object, timeout: Optional[int] = None):
|
1579
|
+
command_id = str(uuid.uuid4())
|
1580
|
+
|
1581
|
+
result_data = [None]
|
1582
|
+
loop = QEventLoop()
|
1583
|
+
|
1584
|
+
if timeout:
|
1585
|
+
timer = QTimer()
|
1586
|
+
timer.setSingleShot(True)
|
1587
|
+
timer.timeout.connect(loop.quit)
|
1588
|
+
timer.start(timeout)
|
1589
|
+
|
1590
|
+
def on_result(received_id, result):
|
1591
|
+
if received_id == command_id:
|
1592
|
+
result_data[0] = result
|
1593
|
+
loop.quit()
|
1594
|
+
|
1595
|
+
|
1596
|
+
self.result_signal.connect(on_result, Qt.QueuedConnection)
|
1597
|
+
|
1598
|
+
self.command_signal.emit(command_id, command_type, params)
|
1599
|
+
|
1600
|
+
loop.exec()
|
1601
|
+
|
1602
|
+
self.result_signal.disconnect(on_result)
|
1603
|
+
|
1604
|
+
return result_data[0]
|
1605
|
+
|
1606
|
+
# -------------------------------------------------------------------
|
1607
|
+
# Execute_command 래퍼 (wrapper) 함수들
|
1608
|
+
# -------------------------------------------------------------------
|
1609
|
+
|
1610
|
+
def set_icon(self, icon_path: str) -> bool:
|
1611
|
+
"""
|
1612
|
+
Dynamically sets the application's icon.
|
1613
|
+
|
1614
|
+
Parameters
|
1615
|
+
----------
|
1616
|
+
icon_path : str
|
1617
|
+
Path to the new icon file
|
1618
|
+
|
1619
|
+
Examples
|
1620
|
+
--------
|
1621
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1622
|
+
>>> app.set_icon("icons/icon.png")
|
1623
|
+
"""
|
1624
|
+
return self.execute_command("set_icon", {"icon_path": icon_path})
|
1625
|
+
|
1626
|
+
def create_window(
|
1627
|
+
self,
|
1628
|
+
title: str,
|
1629
|
+
width: int = 800,
|
1630
|
+
height: int = 600,
|
1631
|
+
x: int = 200,
|
1632
|
+
y: int = 200,
|
1633
|
+
frame: bool = True,
|
1634
|
+
context_menu: bool = False,
|
1635
|
+
dev_tools: bool = False,
|
1636
|
+
) -> BrowserWindow:
|
1637
|
+
"""
|
1638
|
+
Creates a new browser window.
|
1639
|
+
|
1640
|
+
Parameters
|
1641
|
+
----------
|
1642
|
+
title : str, required
|
1643
|
+
Title of the window
|
1644
|
+
width : int, optional
|
1645
|
+
Width of the window (default is 800)
|
1646
|
+
height : int, optional
|
1647
|
+
Height of the window (default is 600)
|
1648
|
+
x : int, optional
|
1649
|
+
X coordinate of the window (default is 200)
|
1650
|
+
y : int, optional
|
1651
|
+
Y coordinate of the window (default is 200)
|
1652
|
+
frame : bool, optional
|
1653
|
+
Whether the window has a frame (default is True)
|
1654
|
+
context_menu : bool, optional
|
1655
|
+
Whether to use the context menu (default is False)
|
1656
|
+
dev_tools : bool, optional
|
1657
|
+
Whether to use developer tools (default is False)
|
1658
|
+
|
1659
|
+
Returns
|
1660
|
+
-------
|
1661
|
+
BrowserWindow
|
1662
|
+
The created browser window object
|
1663
|
+
|
1664
|
+
Examples
|
1665
|
+
--------
|
1666
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1667
|
+
>>> window_id = app.create_window(title="New Window", width=1024, height=768)
|
1668
|
+
"""
|
1669
|
+
params = {
|
1670
|
+
"title": title,
|
1671
|
+
"width": width,
|
1672
|
+
"height": height,
|
1673
|
+
"x": x,
|
1674
|
+
"y": y,
|
1675
|
+
"frame": frame,
|
1676
|
+
"context_menu": context_menu,
|
1677
|
+
"dev_tools": dev_tools,
|
1678
|
+
}
|
1679
|
+
return self.execute_command("create_window", params)
|
1680
|
+
|
1681
|
+
def run(self) -> None:
|
1682
|
+
"""
|
1683
|
+
Runs the application event loop.
|
1684
|
+
|
1685
|
+
Examples
|
1686
|
+
--------
|
1687
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1688
|
+
>>> app.run()
|
1689
|
+
"""
|
1690
|
+
return self.app.run()
|
1691
|
+
|
1692
|
+
def get_windows(self) -> Dict[str, BrowserWindow]:
|
1693
|
+
"""
|
1694
|
+
Returns a list of all browser windows.
|
1695
|
+
|
1696
|
+
Returns
|
1697
|
+
-------
|
1698
|
+
Dict[str, BrowserWindow]
|
1699
|
+
Dictionary of all browser windows
|
1700
|
+
|
1701
|
+
Examples
|
1702
|
+
--------
|
1703
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1704
|
+
>>> windows = app.get_windows()
|
1705
|
+
"""
|
1706
|
+
return self.execute_command("get_windows", {})
|
1707
|
+
|
1708
|
+
def show_main_window(self) -> None:
|
1709
|
+
"""
|
1710
|
+
Shows and focuses the first window.
|
1711
|
+
|
1712
|
+
Examples
|
1713
|
+
--------
|
1714
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1715
|
+
>>> app.show_main_window()
|
1716
|
+
"""
|
1717
|
+
return self.execute_command("show_main_window", {})
|
1718
|
+
|
1719
|
+
def focus_main_window(self) -> None:
|
1720
|
+
"""
|
1721
|
+
Focuses the first window.
|
1722
|
+
|
1723
|
+
Examples
|
1724
|
+
--------
|
1725
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1726
|
+
>>> app.focus_main_window()
|
1727
|
+
"""
|
1728
|
+
return self.execute_command("focus_main_window", {})
|
1729
|
+
|
1730
|
+
def show_and_focus_main_window(self) -> None:
|
1731
|
+
"""
|
1732
|
+
Shows and focuses the first window.
|
1733
|
+
|
1734
|
+
Examples
|
1735
|
+
--------
|
1736
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1737
|
+
>>> app.show_and_focus_main_window()
|
1738
|
+
"""
|
1739
|
+
return self.execute_command("show_and_focus_main_window", {})
|
1740
|
+
|
1741
|
+
def close_all_windows(self) -> None:
|
1742
|
+
"""
|
1743
|
+
Closes all windows.
|
1744
|
+
|
1745
|
+
Examples
|
1746
|
+
--------
|
1747
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1748
|
+
>>> app.close_all_windows()
|
1749
|
+
"""
|
1750
|
+
return self.execute_command("close_all_windows", {})
|
1751
|
+
|
1752
|
+
def quit(self) -> None:
|
1753
|
+
"""
|
1754
|
+
Quits the application.
|
1755
|
+
|
1756
|
+
Examples
|
1757
|
+
--------
|
1758
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1759
|
+
>>> app.quit()
|
1760
|
+
"""
|
1761
|
+
return self.execute_command("quit", {})
|
1762
|
+
|
1763
|
+
def get_window_by_id(self, window_id: str) -> Optional[BrowserWindow]:
|
1764
|
+
"""
|
1765
|
+
Returns the window with the given ID.
|
1766
|
+
|
1767
|
+
Parameters
|
1768
|
+
----------
|
1769
|
+
window_id : str
|
1770
|
+
The ID of the window to find
|
1771
|
+
|
1772
|
+
Returns
|
1773
|
+
-------
|
1774
|
+
Optional[BrowserWindow]
|
1775
|
+
The window object with the given ID. Returns None if the window is not found.
|
1776
|
+
|
1777
|
+
Examples
|
1778
|
+
--------
|
1779
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1780
|
+
>>> window = app.get_window_by_id("some-window-id")
|
1781
|
+
"""
|
1782
|
+
return self.execute_command("get_window_by_id", {"window_id": window_id})
|
1783
|
+
|
1784
|
+
def set_tray_icon(self, tray_icon_path: str) -> bool:
|
1785
|
+
"""
|
1786
|
+
Dynamically sets the tray icon.
|
1787
|
+
|
1788
|
+
Parameters
|
1789
|
+
----------
|
1790
|
+
tray_icon_path : str
|
1791
|
+
The path of the new tray icon file
|
1792
|
+
|
1793
|
+
Examples
|
1794
|
+
--------
|
1795
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1796
|
+
>>> app.set_tray_icon("icons/icon.png")
|
1797
|
+
"""
|
1798
|
+
return self.execute_command("set_tray_icon", {"tray_icon_path": tray_icon_path})
|
1799
|
+
|
1800
|
+
def set_tray_menu_items(self, tray_menu_items: List[Dict[str, Union[str, Callable]]]) -> bool:
|
1801
|
+
"""
|
1802
|
+
Dynamically sets the tray menu items.
|
1803
|
+
|
1804
|
+
Parameters
|
1805
|
+
----------
|
1806
|
+
tray_menu_items : List[Dict[str, Union[str, Callable]]]
|
1807
|
+
The list of new tray menu items
|
1808
|
+
|
1809
|
+
Examples
|
1810
|
+
--------
|
1811
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1812
|
+
>>> menu_items = [{"label": "Open", "callback": lambda: print("Open clicked")},
|
1813
|
+
>>> {"label": "Exit", "callback": app.quit}]
|
1814
|
+
>>> app.set_tray_menu_items(menu_items)
|
1815
|
+
"""
|
1816
|
+
return self.execute_command("set_tray_menu_items", {"tray_menu_items": tray_menu_items})
|
1817
|
+
|
1818
|
+
def set_tray_actions(self, actions: Dict[TrayEvent, Callable]) -> bool:
|
1819
|
+
"""
|
1820
|
+
Dynamically sets the actions for tray icon activation.
|
1821
|
+
|
1822
|
+
Parameters
|
1823
|
+
----------
|
1824
|
+
actions : Dict[TrayEvent, Callable]
|
1825
|
+
Dictionary with TrayEvent enum values as keys and corresponding callback functions as values
|
1826
|
+
|
1827
|
+
Examples
|
1828
|
+
--------
|
1829
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1830
|
+
>>> app.set_tray_actions({TrayEvent.DoubleClick: lambda: print("Double-clicked")})
|
1831
|
+
"""
|
1832
|
+
return self.execute_command("set_tray_actions", {"actions": actions})
|
1833
|
+
|
1834
|
+
def show_notification(self, title: str, message: str) -> bool:
|
1835
|
+
"""
|
1836
|
+
Displays a notification in the system tray.
|
1837
|
+
|
1838
|
+
Parameters
|
1839
|
+
----------
|
1840
|
+
title : str
|
1841
|
+
Notification title
|
1842
|
+
message : str
|
1843
|
+
Notification message
|
1844
|
+
|
1845
|
+
Examples
|
1846
|
+
--------
|
1847
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1848
|
+
>>> app.show_notification("Update Available", "A new update is available for download.")
|
1849
|
+
"""
|
1850
|
+
return self.execute_command("show_notification", {"title": title, "message": message})
|
1851
|
+
|
1852
|
+
def set_tray_icon_animation(self, icon_frames: List[str], interval: int = 200) -> bool:
|
1853
|
+
"""
|
1854
|
+
Dynamically sets and starts the animation for the tray icon.
|
1855
|
+
|
1856
|
+
Parameters
|
1857
|
+
----------
|
1858
|
+
icon_frames : list of str
|
1859
|
+
List of animation frame image paths
|
1860
|
+
interval : int, optional
|
1861
|
+
Frame interval in milliseconds, default is 200
|
1862
|
+
|
1863
|
+
Examples
|
1864
|
+
--------
|
1865
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1866
|
+
>>> app.set_tray_icon_animation(["frame1.png", "frame2.png", "frame3.png"], 100)
|
1867
|
+
"""
|
1868
|
+
return self.execute_command("set_tray_icon_animation", {"icon_frames": icon_frames, "interval": interval})
|
1869
|
+
|
1870
|
+
def set_tray_tooltip(self, message: str) -> bool:
|
1871
|
+
"""
|
1872
|
+
Dynamically sets the tooltip for the tray icon.
|
1873
|
+
|
1874
|
+
Parameters
|
1875
|
+
----------
|
1876
|
+
message : str
|
1877
|
+
New tooltip message
|
1878
|
+
|
1879
|
+
Examples
|
1880
|
+
--------
|
1881
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1882
|
+
>>> app.set_tray_tooltip("Pyloid is running")
|
1883
|
+
"""
|
1884
|
+
return self.execute_command("set_tray_tooltip", {"message": message})
|
1885
|
+
|
1886
|
+
def set_notification_callback(self, callback: Callable[[str], None]) -> bool:
|
1887
|
+
"""
|
1888
|
+
Sets the callback function to be called when a notification is clicked.
|
1889
|
+
|
1890
|
+
Parameters
|
1891
|
+
----------
|
1892
|
+
callback : function
|
1893
|
+
Callback function to be called when a notification is clicked
|
1894
|
+
|
1895
|
+
Examples
|
1896
|
+
--------
|
1897
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1898
|
+
>>> def on_notification_click():
|
1899
|
+
>>> print("Notification clicked")
|
1900
|
+
>>> app.set_notification_callback(on_notification_click)
|
1901
|
+
"""
|
1902
|
+
return self.execute_command("set_notification_callback", {"callback": callback})
|
1903
|
+
|
1904
|
+
def get_all_monitors(self) -> List[Monitor]:
|
1905
|
+
"""
|
1906
|
+
Returns information about all connected monitors.
|
1907
|
+
|
1908
|
+
Returns
|
1909
|
+
-------
|
1910
|
+
List[Monitor]
|
1911
|
+
List containing monitor information
|
1912
|
+
|
1913
|
+
Examples
|
1914
|
+
--------
|
1915
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1916
|
+
>>> monitors = app.get_all_monitors()
|
1917
|
+
>>> for monitor in monitors:
|
1918
|
+
>>> print(monitor.info())
|
1919
|
+
"""
|
1920
|
+
return self.execute_command("get_all_monitors", {})
|
1921
|
+
|
1922
|
+
def get_primary_monitor(self) -> Monitor:
|
1923
|
+
"""
|
1924
|
+
Returns information about the primary monitor.
|
1925
|
+
|
1926
|
+
Returns
|
1927
|
+
-------
|
1928
|
+
Monitor
|
1929
|
+
Primary monitor information
|
1930
|
+
|
1931
|
+
Examples
|
1932
|
+
--------
|
1933
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1934
|
+
>>> primary_monitor = app.get_primary_monitor()
|
1935
|
+
>>> print(primary_monitor.info())
|
1936
|
+
"""
|
1937
|
+
return self.execute_command("get_primary_monitor", {})
|
1938
|
+
|
1939
|
+
def set_clipboard_text(self, text: str) -> None:
|
1940
|
+
"""
|
1941
|
+
Copies text to the clipboard.
|
1942
|
+
|
1943
|
+
Parameters
|
1944
|
+
----------
|
1945
|
+
text : str
|
1946
|
+
Text to copy to the clipboard
|
1947
|
+
|
1948
|
+
Examples
|
1949
|
+
--------
|
1950
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1951
|
+
>>> app.set_clipboard_text("Hello, World!")
|
1952
|
+
"""
|
1953
|
+
return self.execute_command("set_clipboard_text", {"text": text})
|
1954
|
+
|
1955
|
+
def get_clipboard_text(self) -> str:
|
1956
|
+
"""
|
1957
|
+
Retrieves text from the clipboard.
|
1958
|
+
|
1959
|
+
Returns
|
1960
|
+
-------
|
1961
|
+
str
|
1962
|
+
Text stored in the clipboard
|
1963
|
+
|
1964
|
+
Examples
|
1965
|
+
--------
|
1966
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1967
|
+
>>> text = app.get_clipboard_text()
|
1968
|
+
>>> print(text)
|
1969
|
+
"""
|
1970
|
+
return self.execute_command("get_clipboard_text", {})
|
1971
|
+
|
1972
|
+
def set_clipboard_image(self, image: Union[str, bytes, os.PathLike]) -> None:
|
1973
|
+
"""
|
1974
|
+
Copies an image to the clipboard.
|
1975
|
+
|
1976
|
+
Parameters
|
1977
|
+
----------
|
1978
|
+
image : Union[str, bytes, os.PathLike]
|
1979
|
+
Path to the image file to copy to the clipboard
|
1980
|
+
|
1981
|
+
Examples
|
1982
|
+
--------
|
1983
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
1984
|
+
>>> app.set_clipboard_image("/path/to/image.png")
|
1985
|
+
"""
|
1986
|
+
return self.execute_command("set_clipboard_image", {"image": image})
|
1987
|
+
|
1988
|
+
def get_clipboard_image(self) -> QImage:
|
1989
|
+
"""
|
1990
|
+
Retrieves an image from the clipboard.
|
1991
|
+
|
1992
|
+
Returns
|
1993
|
+
-------
|
1994
|
+
QImage
|
1995
|
+
QImage object stored in the clipboard (None if no image)
|
1996
|
+
|
1997
|
+
Examples
|
1998
|
+
--------
|
1999
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2000
|
+
>>> image = app.get_clipboard_image()
|
2001
|
+
>>> if image is not None:
|
2002
|
+
>>> image.save("/path/to/save/image.png")
|
2003
|
+
"""
|
2004
|
+
return self.execute_command("get_clipboard_image", {})
|
2005
|
+
|
2006
|
+
def set_auto_start(self, enable: bool) -> Union[bool, None]:
|
2007
|
+
"""
|
2008
|
+
Sets the application to start automatically at system startup.
|
2009
|
+
|
2010
|
+
Parameters
|
2011
|
+
----------
|
2012
|
+
enable : bool
|
2013
|
+
True to enable auto start, False to disable
|
2014
|
+
|
2015
|
+
Returns
|
2016
|
+
-------
|
2017
|
+
bool or None
|
2018
|
+
True if auto start is successfully set, False if disabled, None if not supported in non-production environment
|
2019
|
+
|
2020
|
+
Examples
|
2021
|
+
--------
|
2022
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2023
|
+
>>> app.set_auto_start(True)
|
2024
|
+
"""
|
2025
|
+
return self.execute_command("set_auto_start", {"enable": enable})
|
2026
|
+
|
2027
|
+
def is_auto_start(self) -> bool:
|
2028
|
+
"""
|
2029
|
+
Checks if the application is set to start automatically at system startup.
|
2030
|
+
|
2031
|
+
Returns
|
2032
|
+
-------
|
2033
|
+
bool
|
2034
|
+
True if auto start is enabled, False otherwise
|
2035
|
+
|
2036
|
+
Examples
|
2037
|
+
--------
|
2038
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2039
|
+
>>> auto_start_enabled = app.is_auto_start()
|
2040
|
+
>>> print(auto_start_enabled)
|
2041
|
+
"""
|
2042
|
+
return self.execute_command("is_auto_start", {})
|
2043
|
+
|
2044
|
+
def watch_file(self, file_path: str) -> bool:
|
2045
|
+
"""
|
2046
|
+
Adds a file to the watch list.
|
2047
|
+
|
2048
|
+
Parameters
|
2049
|
+
----------
|
2050
|
+
file_path : str
|
2051
|
+
Path to the file to watch
|
2052
|
+
|
2053
|
+
Returns
|
2054
|
+
-------
|
2055
|
+
bool
|
2056
|
+
True if the file is successfully added to the watch list, False otherwise
|
2057
|
+
|
2058
|
+
Examples
|
2059
|
+
--------
|
2060
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2061
|
+
>>> app.watch_file("/path/to/file.txt")
|
2062
|
+
"""
|
2063
|
+
return self.execute_command("watch_file", {"file_path": file_path})
|
2064
|
+
|
2065
|
+
def watch_directory(self, dir_path: str) -> bool:
|
2066
|
+
"""
|
2067
|
+
Adds a directory to the watch list.
|
2068
|
+
|
2069
|
+
Parameters
|
2070
|
+
----------
|
2071
|
+
dir_path : str
|
2072
|
+
Path to the directory to watch
|
2073
|
+
|
2074
|
+
Returns
|
2075
|
+
-------
|
2076
|
+
bool
|
2077
|
+
True if the directory is successfully added to the watch list, False otherwise
|
2078
|
+
|
2079
|
+
Examples
|
2080
|
+
--------
|
2081
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2082
|
+
>>> app.watch_directory("/path/to/directory")
|
2083
|
+
"""
|
2084
|
+
return self.execute_command("watch_directory", {"dir_path": dir_path})
|
2085
|
+
|
2086
|
+
def stop_watching(self, path: str) -> bool:
|
2087
|
+
"""
|
2088
|
+
Removes a file or directory from the watch list.
|
2089
|
+
|
2090
|
+
Parameters
|
2091
|
+
----------
|
2092
|
+
path : str
|
2093
|
+
Path to the file or directory to stop watching
|
2094
|
+
|
2095
|
+
Returns
|
2096
|
+
-------
|
2097
|
+
bool
|
2098
|
+
True if the path is successfully removed from the watch list, False otherwise
|
2099
|
+
|
2100
|
+
Examples
|
2101
|
+
--------
|
2102
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2103
|
+
>>> app.stop_watching("/path/to/file_or_directory")
|
2104
|
+
"""
|
2105
|
+
return self.execute_command("stop_watching", {"path": path})
|
2106
|
+
|
2107
|
+
def get_watched_paths(self) -> List[str]:
|
2108
|
+
"""
|
2109
|
+
Returns all currently watched paths.
|
2110
|
+
|
2111
|
+
Returns
|
2112
|
+
-------
|
2113
|
+
List[str]
|
2114
|
+
List of all watched paths
|
2115
|
+
|
2116
|
+
Examples
|
2117
|
+
--------
|
2118
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2119
|
+
>>> app.get_watched_paths()
|
2120
|
+
['/path/to/file1.txt', '/path/to/directory']
|
2121
|
+
"""
|
2122
|
+
return self.execute_command("get_watched_paths", {})
|
2123
|
+
|
2124
|
+
def get_watched_files(self) -> List[str]:
|
2125
|
+
"""
|
2126
|
+
Returns all currently watched files.
|
2127
|
+
|
2128
|
+
Returns
|
2129
|
+
-------
|
2130
|
+
List[str]
|
2131
|
+
List of all watched files
|
2132
|
+
|
2133
|
+
Examples
|
2134
|
+
--------
|
2135
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2136
|
+
>>> app.get_watched_files()
|
2137
|
+
['/path/to/file1.txt', '/path/to/file2.txt']
|
2138
|
+
"""
|
2139
|
+
return self.execute_command("get_watched_files", {})
|
2140
|
+
|
2141
|
+
def get_watched_directories(self) -> List[str]:
|
2142
|
+
"""
|
2143
|
+
Returns all currently watched directories.
|
2144
|
+
|
2145
|
+
Returns
|
2146
|
+
-------
|
2147
|
+
List[str]
|
2148
|
+
List of all watched directories
|
2149
|
+
|
2150
|
+
Examples
|
2151
|
+
--------
|
2152
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2153
|
+
>>> app.get_watched_directories()
|
2154
|
+
['/path/to/directory1', '/path/to/directory2']
|
2155
|
+
"""
|
2156
|
+
return self.execute_command("get_watched_directories", {})
|
2157
|
+
|
2158
|
+
def remove_all_watched_paths(self) -> None:
|
2159
|
+
"""
|
2160
|
+
Removes all paths from the watch list.
|
2161
|
+
|
2162
|
+
Examples
|
2163
|
+
--------
|
2164
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2165
|
+
>>> app.remove_all_watched_paths()
|
2166
|
+
"""
|
2167
|
+
return self.execute_command("remove_all_watched_paths", {})
|
2168
|
+
|
2169
|
+
def set_file_change_callback(self, callback: Callable[[str], None]) -> None:
|
2170
|
+
"""
|
2171
|
+
Sets the callback function to be called when a file is changed.
|
2172
|
+
|
2173
|
+
Parameters
|
2174
|
+
----------
|
2175
|
+
callback : Callable[[str], None]
|
2176
|
+
Function to be called when a file is changed
|
2177
|
+
|
2178
|
+
Examples
|
2179
|
+
--------
|
2180
|
+
>>> def on_file_change(file_path):
|
2181
|
+
>>> print(f"File changed: {file_path}")
|
2182
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2183
|
+
>>> app.set_file_change_callback(on_file_change)
|
2184
|
+
"""
|
2185
|
+
return self.execute_command("set_file_change_callback", {"callback": callback})
|
2186
|
+
|
2187
|
+
def set_directory_change_callback(self, callback: Callable[[str], None]) -> None:
|
2188
|
+
"""
|
2189
|
+
Sets the callback function to be called when a directory is changed.
|
2190
|
+
|
2191
|
+
Parameters
|
2192
|
+
----------
|
2193
|
+
callback : Callable[[str], None]
|
2194
|
+
Function to be called when a directory is changed
|
2195
|
+
|
2196
|
+
Examples
|
2197
|
+
--------
|
2198
|
+
>>> def on_directory_change(dir_path):
|
2199
|
+
>>> print(f"Directory changed: {dir_path}")
|
2200
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2201
|
+
>>> app.set_directory_change_callback(on_directory_change)
|
2202
|
+
"""
|
2203
|
+
return self.execute_command("set_directory_change_callback", {"callback": callback})
|
2204
|
+
|
2205
|
+
def open_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
|
2206
|
+
"""
|
2207
|
+
Opens a file dialog to select a file to open.
|
2208
|
+
|
2209
|
+
Parameters
|
2210
|
+
----------
|
2211
|
+
dir : str, optional
|
2212
|
+
The initial directory that the dialog will open in.
|
2213
|
+
filter : str, optional
|
2214
|
+
A string that specifies the file types that can be selected.
|
2215
|
+
|
2216
|
+
Returns
|
2217
|
+
-------
|
2218
|
+
Optional[str]
|
2219
|
+
The path of the selected file or None if no file is selected.
|
2220
|
+
|
2221
|
+
Examples
|
2222
|
+
--------
|
2223
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2224
|
+
>>> file_path = app.open_file_dialog(dir="/home/user", filter="Text Files (*.txt)")
|
2225
|
+
"""
|
2226
|
+
return self.execute_command("open_file_dialog", {"dir": dir, "filter": filter})
|
2227
|
+
|
2228
|
+
def save_file_dialog(self, dir: Optional[str] = None, filter: Optional[str] = None) -> Optional[str]:
|
2229
|
+
"""
|
2230
|
+
Opens a file dialog to select a file to save.
|
2231
|
+
|
2232
|
+
Parameters
|
2233
|
+
----------
|
2234
|
+
dir : str, optional
|
2235
|
+
The initial directory that the dialog will open in.
|
2236
|
+
filter : str, optional
|
2237
|
+
A string that specifies the file types that can be saved.
|
2238
|
+
|
2239
|
+
Returns
|
2240
|
+
-------
|
2241
|
+
Optional[str]
|
2242
|
+
The path of the selected file or None if no file is selected.
|
2243
|
+
|
2244
|
+
Examples
|
2245
|
+
--------
|
2246
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2247
|
+
>>> file_path = app.save_file_dialog(dir="/home/user", filter="Text Files (*.txt)")
|
2248
|
+
"""
|
2249
|
+
return self.execute_command("save_file_dialog", {"dir": dir, "filter": filter})
|
2250
|
+
|
2251
|
+
def select_directory_dialog(self, dir: Optional[str] = None) -> Optional[str]:
|
2252
|
+
"""
|
2253
|
+
Opens a dialog to select a directory.
|
2254
|
+
|
2255
|
+
Parameters
|
2256
|
+
----------
|
2257
|
+
dir : str, optional
|
2258
|
+
The initial directory that the dialog will open in.
|
2259
|
+
|
2260
|
+
Returns
|
2261
|
+
-------
|
2262
|
+
Optional[str]
|
2263
|
+
The path of the selected directory or None if no directory is selected.
|
2264
|
+
|
2265
|
+
Examples
|
2266
|
+
--------
|
2267
|
+
>>> app = Pyloid(app_name="Pyloid-App")
|
2268
|
+
>>> directory_path = app.select_directory_dialog(dir="/home/user")
|
2269
|
+
"""
|
2270
|
+
return self.execute_command("select_directory_dialog", {"dir": dir})
|