qyro-engine 1.0.0a1__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.
Files changed (49) hide show
  1. qyro/__init__.py +71 -0
  2. qyro/adapters/__init__.py +22 -0
  3. qyro/adapters/environment/__init__.py +3 -0
  4. qyro/adapters/environment/system_environment.py +57 -0
  5. qyro/adapters/frameworks/__init__.py +21 -0
  6. qyro/adapters/frameworks/base.py +20 -0
  7. qyro/adapters/frameworks/factory.py +62 -0
  8. qyro/adapters/frameworks/headless_adapter.py +57 -0
  9. qyro/adapters/frameworks/kivy_adapter.py +79 -0
  10. qyro/adapters/frameworks/pyqt5_adapter.py +23 -0
  11. qyro/adapters/frameworks/pyqt6_adapter.py +35 -0
  12. qyro/adapters/frameworks/pyside2_adapter.py +28 -0
  13. qyro/adapters/frameworks/pyside6_adapter.py +35 -0
  14. qyro/adapters/frameworks/qt_base.py +107 -0
  15. qyro/adapters/frameworks/tkinter_adapter.py +80 -0
  16. qyro/adapters/platform/__init__.py +3 -0
  17. qyro/adapters/platform/detector.py +73 -0
  18. qyro/adapters/resources/__init__.py +3 -0
  19. qyro/adapters/resources/filesystem_resources.py +120 -0
  20. qyro/adapters/settings/__init__.py +3 -0
  21. qyro/adapters/settings/json_settings.py +86 -0
  22. qyro/adapters/telemetry/__init__.py +4 -0
  23. qyro/adapters/telemetry/console_hook.py +38 -0
  24. qyro/adapters/telemetry/sentry_hook.py +51 -0
  25. qyro/application/__init__.py +28 -0
  26. qyro/application/ports/__init__.py +18 -0
  27. qyro/application/ports/environment.py +37 -0
  28. qyro/application/ports/resources.py +23 -0
  29. qyro/application/ports/settings.py +22 -0
  30. qyro/application/ports/telemetry.py +27 -0
  31. qyro/application/ports/ui_framework.py +49 -0
  32. qyro/application/use_cases/__init__.py +13 -0
  33. qyro/application/use_cases/initialize_engine.py +52 -0
  34. qyro/application/use_cases/load_settings.py +25 -0
  35. qyro/application/use_cases/resolve_resource.py +30 -0
  36. qyro/client/__init__.py +3 -0
  37. qyro/client/component.py +529 -0
  38. qyro/client/context.py +510 -0
  39. qyro/container.py +79 -0
  40. qyro/domain/__init__.py +33 -0
  41. qyro/domain/entities.py +83 -0
  42. qyro/domain/errors.py +49 -0
  43. qyro/ui/__init__.py +8 -0
  44. qyro/ui/component.py +8 -0
  45. qyro_engine-1.0.0a1.dist-info/LICENSE +21 -0
  46. qyro_engine-1.0.0a1.dist-info/METADATA +477 -0
  47. qyro_engine-1.0.0a1.dist-info/NOTICE +159 -0
  48. qyro_engine-1.0.0a1.dist-info/RECORD +49 -0
  49. qyro_engine-1.0.0a1.dist-info/WHEEL +4 -0
qyro/__init__.py ADDED
@@ -0,0 +1,71 @@
1
+ """
2
+ Qyro.
3
+ Clean Architecture runtime engine and lifecycle framework for Python desktop GUI apps.
4
+ Decoupled from CLI tools, independent and multi-toolkit ready.
5
+ """
6
+
7
+ from qyro.client.context import ApplicationContext
8
+ from qyro.client.component import Component
9
+ from qyro.container import EngineContainer
10
+ from qyro.adapters.platform.detector import PlatformDetector
11
+ from qyro.domain.entities import PlatformType, ExecutionMode, AppMetadata
12
+ from qyro.domain.errors import (
13
+ QyroEngineError,
14
+ ResourceNotFoundError,
15
+ SettingsNotFoundError,
16
+ FrameworkNotAvailableError,
17
+ )
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ # Module-level singleton container for quick procedural usage
22
+ _default_container: EngineContainer | None = None
23
+
24
+
25
+ def _get_default_container() -> EngineContainer:
26
+ global _default_container
27
+ app_container = ApplicationContext._global_container
28
+ if app_container is not None:
29
+ _default_container = app_container
30
+ return app_container
31
+
32
+ if _default_container is None:
33
+ _default_container = EngineContainer()
34
+ return _default_container
35
+
36
+
37
+ def get_resource(*segments: str, required: bool = True) -> str:
38
+ """Convenience helper to resolve resource path without manually creating ApplicationContext."""
39
+ container = _get_default_container()
40
+ return str(container.resolve_resource_use_case.execute(*segments, required=required))
41
+
42
+
43
+ def load_build_settings() -> dict:
44
+ """Convenience helper to load build settings dictionary."""
45
+ container = _get_default_container()
46
+ return container.load_settings_use_case.execute().raw_settings
47
+
48
+
49
+ def is_frozen() -> bool:
50
+ """Convenience helper to check if running in a frozen executable."""
51
+ container = _get_default_container()
52
+ return container.env_adapter.is_frozen()
53
+
54
+
55
+
56
+ __all__ = [
57
+ "ApplicationContext",
58
+ "Component",
59
+ "EngineContainer",
60
+ "PlatformDetector",
61
+ "PlatformType",
62
+ "ExecutionMode",
63
+ "AppMetadata",
64
+ "QyroEngineError",
65
+ "ResourceNotFoundError",
66
+ "SettingsNotFoundError",
67
+ "FrameworkNotAvailableError",
68
+ "get_resource",
69
+ "load_build_settings",
70
+ "is_frozen",
71
+ ]
@@ -0,0 +1,22 @@
1
+ """
2
+ Qyro - Adapters Layer.
3
+ Concrete implementations of application ports.
4
+ """
5
+
6
+ from .environment.system_environment import SystemEnvironmentAdapter
7
+ from .settings.json_settings import JsonSettingsAdapter
8
+ from .resources.filesystem_resources import FileSystemResourceAdapter
9
+ from .telemetry.console_hook import ConsoleExceptionHookAdapter
10
+ from .telemetry.sentry_hook import SentryExceptionHookAdapter
11
+ from .platform.detector import PlatformDetector
12
+ from .frameworks.factory import FrameworkFactory
13
+
14
+ __all__ = [
15
+ "SystemEnvironmentAdapter",
16
+ "JsonSettingsAdapter",
17
+ "FileSystemResourceAdapter",
18
+ "ConsoleExceptionHookAdapter",
19
+ "SentryExceptionHookAdapter",
20
+ "PlatformDetector",
21
+ "FrameworkFactory",
22
+ ]
@@ -0,0 +1,3 @@
1
+ from .system_environment import SystemEnvironmentAdapter
2
+
3
+ __all__ = ["SystemEnvironmentAdapter"]
@@ -0,0 +1,57 @@
1
+ """
2
+ System Environment Adapter.
3
+ Implements IEnvironmentPort for PyInstaller, cx_Freeze, and Source environments.
4
+ """
5
+
6
+ import sys
7
+ from pathlib import Path
8
+ from qyro.application.ports.environment import IEnvironmentPort
9
+ from qyro.domain.entities import ExecutionMode, PlatformType
10
+ from qyro.adapters.platform.detector import PlatformDetector
11
+
12
+
13
+ class SystemEnvironmentAdapter(IEnvironmentPort):
14
+ """Detects runtime execution specifics across operating systems."""
15
+
16
+ def __init__(self, custom_root: Path | None = None) -> None:
17
+ self._custom_root = custom_root
18
+
19
+ def is_frozen(self) -> bool:
20
+ return getattr(sys, "frozen", False)
21
+
22
+ def get_execution_mode(self) -> ExecutionMode:
23
+ if self.is_frozen():
24
+ if hasattr(sys, "_MEIPASS"):
25
+ return ExecutionMode.FROZEN_PYINSTALLER
26
+ return ExecutionMode.FROZEN_GENERIC
27
+ return ExecutionMode.SOURCE
28
+
29
+ def get_platform(self) -> PlatformType:
30
+ return PlatformDetector.get_platform()
31
+
32
+ def get_root_dir(self) -> Path:
33
+ if self._custom_root:
34
+ return self._custom_root
35
+
36
+ if self.is_frozen():
37
+ # Executable directory
38
+ return Path(sys.executable).resolve().parent
39
+
40
+ # Source mode: find project root containing pyproject.toml or src/
41
+ cwd = Path.cwd()
42
+ for parent in [cwd] + list(cwd.parents):
43
+ if (parent / "pyproject.toml").exists() or (parent / "src").exists():
44
+ return parent
45
+ return cwd
46
+
47
+ def get_bundle_dir(self) -> Path:
48
+ if self.is_frozen() and hasattr(sys, "_MEIPASS"):
49
+ return Path(getattr(sys, "_MEIPASS")).resolve()
50
+
51
+ # macOS bundle check
52
+ if self.is_frozen() and sys.platform == "darwin":
53
+ mac_resources = Path(sys.executable).resolve().parent.parent / "Resources"
54
+ if mac_resources.exists():
55
+ return mac_resources
56
+
57
+ return self.get_root_dir()
@@ -0,0 +1,21 @@
1
+ from .base import BaseUIFrameworkAdapter
2
+ from .qt_base import BaseQtAdapter
3
+ from .pyside6_adapter import PySide6Adapter
4
+ from .pyqt6_adapter import PyQt6Adapter
5
+ from .pyside2_adapter import PySide2Adapter
6
+ from .pyqt5_adapter import PyQt5Adapter
7
+ from .kivy_adapter import KivyAdapter
8
+ from .tkinter_adapter import TkinterAdapter
9
+ from .factory import FrameworkFactory
10
+
11
+ __all__ = [
12
+ "BaseUIFrameworkAdapter",
13
+ "BaseQtAdapter",
14
+ "PySide6Adapter",
15
+ "PyQt6Adapter",
16
+ "PySide2Adapter",
17
+ "PyQt5Adapter",
18
+ "KivyAdapter",
19
+ "TkinterAdapter",
20
+ "FrameworkFactory",
21
+ ]
@@ -0,0 +1,20 @@
1
+ """
2
+ Base UI Framework Adapter.
3
+ Common abstract implementation for GUI toolkits.
4
+ """
5
+
6
+ from abc import abstractmethod
7
+ from typing import Any, List, Optional
8
+ from qyro.application.ports.ui_framework import IUIFrameworkPort
9
+
10
+
11
+ class BaseUIFrameworkAdapter(IUIFrameworkPort):
12
+ """Base framework adapter storing app reference and lifecycle state."""
13
+
14
+ def __init__(self) -> None:
15
+ self._app: Any = None
16
+ self._is_running: bool = False
17
+
18
+ @property
19
+ def app_instance(self) -> Any:
20
+ return self._app
@@ -0,0 +1,62 @@
1
+ """
2
+ Framework Factory.
3
+ Detects available UI toolkits or instantiates the user's requested engine backend.
4
+ """
5
+
6
+ from typing import Optional
7
+ from qyro.application.ports.ui_framework import IUIFrameworkPort
8
+ from qyro.domain.errors import FrameworkNotAvailableError
9
+ from .pyside6_adapter import PySide6Adapter
10
+ from .pyqt6_adapter import PyQt6Adapter
11
+ from .pyside2_adapter import PySide2Adapter
12
+ from .pyqt5_adapter import PyQt5Adapter
13
+ from .kivy_adapter import KivyAdapter
14
+ from .tkinter_adapter import TkinterAdapter
15
+ from .headless_adapter import HeadlessAdapter
16
+
17
+
18
+ class FrameworkFactory:
19
+ """Instantiates framework adapters based on preference or automatic detection."""
20
+
21
+ _FRAMEWORK_REGISTRY = {
22
+ "pyside6": PySide6Adapter,
23
+ "pyqt6": PyQt6Adapter,
24
+ "pyside2": PySide2Adapter,
25
+ "pyqt5": PyQt5Adapter,
26
+ "kivy": KivyAdapter,
27
+ "tkinter": TkinterAdapter,
28
+ "headless": HeadlessAdapter,
29
+ }
30
+
31
+ @classmethod
32
+ def create(cls, name: Optional[str] = None) -> IUIFrameworkPort:
33
+ if name:
34
+ normalized = name.strip().lower()
35
+ adapter_cls = cls._FRAMEWORK_REGISTRY.get(normalized)
36
+ if not adapter_cls:
37
+ raise FrameworkNotAvailableError(
38
+ name,
39
+ f"Supported frameworks: {', '.join(cls._FRAMEWORK_REGISTRY.keys())}"
40
+ )
41
+ adapter = adapter_cls()
42
+ if not adapter.is_available():
43
+ raise FrameworkNotAvailableError(adapter.framework_name)
44
+ return adapter
45
+
46
+ # Auto-detect in order of preference
47
+ preference = [
48
+ PySide6Adapter,
49
+ PyQt6Adapter,
50
+ PySide2Adapter,
51
+ PyQt5Adapter,
52
+ KivyAdapter,
53
+ TkinterAdapter,
54
+ HeadlessAdapter,
55
+ ]
56
+ for candidate_cls in preference:
57
+ candidate = candidate_cls()
58
+ if candidate.is_available():
59
+ return candidate
60
+
61
+ return HeadlessAdapter()
62
+
@@ -0,0 +1,57 @@
1
+ """
2
+ Headless Framework Adapter for CI, tests, and headless environments.
3
+ """
4
+
5
+ from typing import Any, List, Optional
6
+ from qyro.application.ports.ui_framework import IUIFrameworkPort
7
+
8
+
9
+ class HeadlessApplication:
10
+ """Mock GUI application instance for headless environments."""
11
+
12
+ def __init__(self, argv: Optional[List[str]] = None) -> None:
13
+ self.argv = argv or []
14
+
15
+ def exec(self) -> int:
16
+ return 0
17
+
18
+ def exec_(self) -> int:
19
+ return 0
20
+
21
+
22
+ class HeadlessAdapter(IUIFrameworkPort):
23
+ """Fallback adapter when running in headless mode or without GUI libraries installed."""
24
+
25
+ def __init__(self) -> None:
26
+ super().__init__()
27
+ self.applied_app_icon: Optional[str] = None
28
+ self.applied_window_icons: dict = {}
29
+ self.applied_window_titles: dict = {}
30
+
31
+ @property
32
+ def framework_name(self) -> str:
33
+ return "Headless"
34
+
35
+ def is_available(self) -> bool:
36
+ return True
37
+
38
+ def create_application(self, argv: Optional[List[str]] = None) -> Any:
39
+ return HeadlessApplication(argv)
40
+
41
+ def run(self) -> int:
42
+ return 0
43
+
44
+ def exit(self, code: int = 0) -> None:
45
+ pass
46
+
47
+ def set_application_icon(self, icon_path: str) -> bool:
48
+ self.applied_app_icon = icon_path
49
+ return True
50
+
51
+ def set_window_icon(self, window: Any, icon_path: str) -> bool:
52
+ self.applied_window_icons[window] = icon_path
53
+ return True
54
+
55
+ def set_window_title(self, window: Any, title: str) -> bool:
56
+ self.applied_window_titles[window] = title
57
+ return True
@@ -0,0 +1,79 @@
1
+ """
2
+ Kivy and Tkinter Framework Adapters for Qyro.
3
+ Supports cross-platform desktop/mobile (Kivy) and lightweight builtin (Tkinter) runtimes.
4
+ """
5
+
6
+ import sys
7
+ from typing import Any, List, Optional
8
+ from .base import BaseUIFrameworkAdapter
9
+
10
+
11
+ class KivyAdapter(BaseUIFrameworkAdapter):
12
+ """Kivy toolkit adapter for multi-touch and mobile/desktop apps."""
13
+
14
+ @property
15
+ def framework_name(self) -> str:
16
+ return "Kivy"
17
+
18
+ def is_available(self) -> bool:
19
+ try:
20
+ import kivy # noqa: F401
21
+ return True
22
+ except ImportError:
23
+ return False
24
+
25
+ def create_application(self, argv: Optional[List[str]] = None) -> Any:
26
+ try:
27
+ from kivy.app import App
28
+ running = App.get_running_app()
29
+ if running:
30
+ self._app = running
31
+ return self._app
32
+ except Exception:
33
+ pass
34
+ return None
35
+
36
+ def run(self) -> int:
37
+ if not self._app:
38
+ try:
39
+ from kivy.app import App
40
+ self._app = App.get_running_app()
41
+ except Exception:
42
+ pass
43
+ if not self._app:
44
+ from kivy.app import App
45
+
46
+ class GenericKivyApp(App):
47
+ pass
48
+
49
+ self._app = GenericKivyApp()
50
+
51
+ self._is_running = True
52
+ self._app.run()
53
+ return 0
54
+
55
+ def exit(self, code: int = 0) -> None:
56
+ if self._app and hasattr(self._app, "stop"):
57
+ self._app.stop()
58
+ self._is_running = False
59
+
60
+ def set_application_icon(self, icon_path: str) -> bool:
61
+ if self._app and hasattr(self._app, "icon"):
62
+ self._app.icon = icon_path
63
+ return True
64
+ return False
65
+
66
+ def set_window_icon(self, window: Any, icon_path: str) -> bool:
67
+ if window and hasattr(window, "icon"):
68
+ window.icon = icon_path
69
+ return True
70
+ return self.set_application_icon(icon_path)
71
+
72
+ def set_window_title(self, window: Any, title: str) -> bool:
73
+ if window and hasattr(window, "title"):
74
+ window.title = title
75
+ return True
76
+ if self._app and hasattr(self._app, "title"):
77
+ self._app.title = title
78
+ return True
79
+ return False
@@ -0,0 +1,23 @@
1
+ from .qt_base import BaseQtAdapter
2
+
3
+
4
+ class PyQt5Adapter(BaseQtAdapter):
5
+ """PyQt5 toolkit adapter for Qt 5."""
6
+
7
+ def __init__(self) -> None:
8
+ qapp = None
9
+ if self.is_available():
10
+ from PyQt5.QtWidgets import QApplication
11
+ qapp = QApplication
12
+ super().__init__(qapp_class=qapp, is_qt6=False)
13
+
14
+ @property
15
+ def framework_name(self) -> str:
16
+ return "PyQt5"
17
+
18
+ def is_available(self) -> bool:
19
+ try:
20
+ import PyQt5.QtWidgets # noqa: F401
21
+ return True
22
+ except ImportError:
23
+ return False
@@ -0,0 +1,35 @@
1
+ """
2
+ PyQt6 Adapter.
3
+ Riverbank PyQt6 binding implementation for Qyro.
4
+ """
5
+
6
+ from typing import Any, List, Optional
7
+ from .qt_base import BaseQtAdapter
8
+
9
+
10
+ class PyQt6Adapter(BaseQtAdapter):
11
+ """PyQt6 toolkit adapter."""
12
+
13
+ def __init__(self) -> None:
14
+ qapp = None
15
+ if self.is_available():
16
+ from PyQt6.QtWidgets import QApplication
17
+ qapp = QApplication
18
+ super().__init__(qapp_class=qapp, is_qt6=True)
19
+
20
+ @property
21
+ def framework_name(self) -> str:
22
+ return "PyQt6"
23
+
24
+ def is_available(self) -> bool:
25
+ try:
26
+ import PyQt6.QtWidgets # noqa: F401
27
+ return True
28
+ except ImportError:
29
+ return False
30
+
31
+ def create_application(self, argv: Optional[List[str]] = None) -> Any:
32
+ if not self._qapp_class and self.is_available():
33
+ from PyQt6.QtWidgets import QApplication
34
+ self._qapp_class = QApplication
35
+ return super().create_application(argv)
@@ -0,0 +1,28 @@
1
+ """
2
+ PySide2 and PyQt5 Legacy Adapters for Qt 5.
3
+ """
4
+
5
+ from .qt_base import BaseQtAdapter
6
+
7
+
8
+ class PySide2Adapter(BaseQtAdapter):
9
+ """PySide2 toolkit adapter for Qt 5."""
10
+
11
+ def __init__(self) -> None:
12
+ qapp = None
13
+ if self.is_available():
14
+ from PySide2.QtWidgets import QApplication
15
+ qapp = QApplication
16
+ super().__init__(qapp_class=qapp, is_qt6=False)
17
+
18
+ @property
19
+ def framework_name(self) -> str:
20
+ return "PySide2"
21
+
22
+ def is_available(self) -> bool:
23
+ try:
24
+ import PySide2.QtWidgets # noqa: F401
25
+ return True
26
+ except ImportError:
27
+ return False
28
+
@@ -0,0 +1,35 @@
1
+ """
2
+ PySide6 Adapter.
3
+ Official Qt 6 binding implementation for Qyro.
4
+ """
5
+
6
+ from typing import Any, List, Optional
7
+ from .qt_base import BaseQtAdapter
8
+
9
+
10
+ class PySide6Adapter(BaseQtAdapter):
11
+ """PySide6 toolkit adapter."""
12
+
13
+ def __init__(self) -> None:
14
+ qapp = None
15
+ if self.is_available():
16
+ from PySide6.QtWidgets import QApplication
17
+ qapp = QApplication
18
+ super().__init__(qapp_class=qapp, is_qt6=True)
19
+
20
+ @property
21
+ def framework_name(self) -> str:
22
+ return "PySide6"
23
+
24
+ def is_available(self) -> bool:
25
+ try:
26
+ import PySide6.QtWidgets # noqa: F401
27
+ return True
28
+ except ImportError:
29
+ return False
30
+
31
+ def create_application(self, argv: Optional[List[str]] = None) -> Any:
32
+ if not self._qapp_class and self.is_available():
33
+ from PySide6.QtWidgets import QApplication
34
+ self._qapp_class = QApplication
35
+ return super().create_application(argv)
@@ -0,0 +1,107 @@
1
+ """
2
+ Base Qt Adapter for PySide and PyQt bindings.
3
+ Encapsulates common QApplication creation, high-DPI attributes, and event loop execution.
4
+ """
5
+
6
+ import sys
7
+ from typing import Any, List, Optional
8
+ from .base import BaseUIFrameworkAdapter
9
+
10
+
11
+ class BaseQtAdapter(BaseUIFrameworkAdapter):
12
+ """Shared implementation details across PyQt and PySide versions."""
13
+
14
+ def __init__(self, qapp_class: Any, is_qt6: bool = True) -> None:
15
+ super().__init__()
16
+ self._qapp_class = qapp_class
17
+ self._is_qt6 = is_qt6
18
+
19
+ def create_application(self, argv: Optional[List[str]] = None) -> Any:
20
+ if self._qapp_class.instance():
21
+ self._app = self._qapp_class.instance()
22
+ return self._app
23
+
24
+ args = argv if argv is not None else sys.argv
25
+ self._app = self._qapp_class(args)
26
+ return self._app
27
+
28
+ def run(self) -> int:
29
+ if not self._app:
30
+ self.create_application()
31
+
32
+ self._is_running = True
33
+ # Qt 6 uses app.exec(), Qt 5 used app.exec_()
34
+ if hasattr(self._app, "exec"):
35
+ return self._app.exec()
36
+ return self._app.exec_()
37
+
38
+ def exit(self, code: int = 0) -> None:
39
+ if self._app:
40
+ self._app.exit(code)
41
+ self._is_running = False
42
+
43
+ @property
44
+ def binding_package(self) -> str:
45
+ """Determines the exact Qt package name associated with this adapter instance."""
46
+ if self._qapp_class and hasattr(self._qapp_class, "__module__"):
47
+ return self._qapp_class.__module__.split(".")[0]
48
+ return self.framework_name
49
+
50
+ def _create_qicon(self, icon_path: str, target: Optional[Any] = None) -> Optional[Any]:
51
+ """
52
+ Dynamically imports QIcon strictly from the adapter's matching Qt binding.
53
+ Avoids cross-importing other installed Qt bindings (e.g. PySide6 when running PyQt5),
54
+ which causes macOS duplicate symbol warnings, QPixmap crashes, and SIGABRT.
55
+ """
56
+ pkg = None
57
+ if target is not None:
58
+ for cls in getattr(type(target), "__mro__", []):
59
+ mod = getattr(cls, "__module__", "")
60
+ if mod:
61
+ root = mod.split(".")[0]
62
+ if root in ("PyQt5", "PySide6", "PyQt6", "PySide2"):
63
+ pkg = root
64
+ break
65
+
66
+ if not pkg:
67
+ pkg = self.binding_package
68
+
69
+ if pkg:
70
+ try:
71
+ mod = __import__(f"{pkg}.QtGui", fromlist=["QIcon"])
72
+ QIcon = getattr(mod, "QIcon", None)
73
+ if QIcon:
74
+ return QIcon(icon_path)
75
+ except Exception:
76
+ pass
77
+ return None
78
+
79
+ def set_application_icon(self, icon_path: str) -> bool:
80
+ if not self._app:
81
+ return False
82
+ qicon = self._create_qicon(icon_path, target=self._app)
83
+ if qicon and hasattr(self._app, "setWindowIcon"):
84
+ self._app.setWindowIcon(qicon)
85
+ return True
86
+ return False
87
+
88
+ def set_window_icon(self, window: Any, icon_path: str) -> bool:
89
+ if not window:
90
+ return False
91
+ qicon = self._create_qicon(icon_path, target=window)
92
+ if qicon and hasattr(window, "setWindowIcon"):
93
+ try:
94
+ window.setWindowIcon(qicon)
95
+ return True
96
+ except (RuntimeError, Exception):
97
+ return False
98
+ return False
99
+
100
+ def set_window_title(self, window: Any, title: str) -> bool:
101
+ if window and hasattr(window, "setWindowTitle"):
102
+ try:
103
+ window.setWindowTitle(title)
104
+ return True
105
+ except (RuntimeError, Exception):
106
+ return False
107
+ return False