jadeui 0.1.0__py3-none-win_amd64.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.
jadeui/__init__.py ADDED
@@ -0,0 +1,156 @@
1
+ """
2
+ JadeUI Python SDK - Desktop Application Framework
3
+
4
+ A Python SDK for creating desktop applications using JadeView's WebView technology.
5
+ Provides a clean, object-oriented API for window management, IPC communication,
6
+ and web frontend integration.
7
+
8
+ Example:
9
+ from jadeui import JadeUIApp, Window
10
+
11
+ app = JadeUIApp()
12
+
13
+ @app.on_ready
14
+ def on_ready():
15
+ window = Window(
16
+ title="My App",
17
+ width=1024,
18
+ height=768,
19
+ url="https://example.com"
20
+ )
21
+ window.show()
22
+
23
+ app.run()
24
+
25
+ Example with local server:
26
+ from jadeui import JadeUIApp, Window, LocalServer
27
+
28
+ app = JadeUIApp()
29
+ server = LocalServer()
30
+
31
+ @app.on_ready
32
+ def on_ready():
33
+ url = server.start("./web", "myapp")
34
+ window = Window(title="My App", url=f"{url}/index.html")
35
+ window.show()
36
+
37
+ app.run()
38
+
39
+ Example with IPC:
40
+ from jadeui import JadeUIApp, Window, IPCManager
41
+
42
+ app = JadeUIApp()
43
+ ipc = IPCManager()
44
+
45
+ @ipc.on("message")
46
+ def handle_message(window_id, message):
47
+ print(f"Received: {message}")
48
+ ipc.send(window_id, "message", f"Echo: {message}")
49
+ return 1
50
+
51
+ @app.on_ready
52
+ def on_ready():
53
+ window = Window(title="IPC Demo", url="...")
54
+ window.show()
55
+
56
+ app.run()
57
+ """
58
+
59
+ from .app import JadeUIApp
60
+ from .window import Window, Theme, Backdrop
61
+ from .ipc import IPCManager
62
+ from .server import LocalServer
63
+ from .events import EventEmitter, Events
64
+ from .router import Router
65
+ from .core.types import RGBA, WebViewWindowOptions, WebViewSettings
66
+ from .exceptions import (
67
+ JadeUIError,
68
+ DLLLoadError,
69
+ WindowCreationError,
70
+ IPCError,
71
+ ServerError,
72
+ InitializationError,
73
+ )
74
+ from .downloader import (
75
+ download_dll,
76
+ ensure_dll,
77
+ find_dll,
78
+ get_architecture,
79
+ VERSION as DLL_VERSION,
80
+ )
81
+ from . import utils
82
+
83
+ __version__ = "0.1.0"
84
+ __author__ = "JadeView Team"
85
+ __license__ = "MIT"
86
+
87
+ __all__ = [
88
+ # Main classes
89
+ "JadeUIApp",
90
+ "Window",
91
+ "IPCManager",
92
+ "LocalServer",
93
+ "Router",
94
+ # Constants
95
+ "Theme",
96
+ "Backdrop",
97
+ "Events",
98
+ # Types
99
+ "RGBA",
100
+ "WebViewWindowOptions",
101
+ "WebViewSettings",
102
+ # Events
103
+ "EventEmitter",
104
+ # Exceptions
105
+ "JadeUIError",
106
+ "DLLLoadError",
107
+ "WindowCreationError",
108
+ "IPCError",
109
+ "ServerError",
110
+ "InitializationError",
111
+ # DLL Downloader
112
+ "download_dll",
113
+ "ensure_dll",
114
+ "find_dll",
115
+ "get_architecture",
116
+ "DLL_VERSION",
117
+ # Utilities
118
+ "utils",
119
+ ]
120
+
121
+
122
+ def create_app(
123
+ enable_dev_tools: bool = False,
124
+ user_data_dir: str = None,
125
+ log_file: str = None,
126
+ ) -> JadeUIApp:
127
+ """Create and initialize a JadeUI application
128
+
129
+ Convenience function for quick app creation.
130
+
131
+ Args:
132
+ enable_dev_tools: Whether to enable developer tools
133
+ user_data_dir: Directory for user data storage
134
+ log_file: Path to log file
135
+
136
+ Returns:
137
+ Initialized JadeUIApp instance
138
+
139
+ Example:
140
+ from jadeui import create_app, Window
141
+
142
+ app = create_app()
143
+
144
+ @app.on_ready
145
+ def setup():
146
+ Window(title="My App", url="https://example.com").show()
147
+
148
+ app.run()
149
+ """
150
+ app = JadeUIApp()
151
+ app.initialize(
152
+ enable_dev_tools=enable_dev_tools,
153
+ user_data_dir=user_data_dir,
154
+ log_file=log_file,
155
+ )
156
+ return app
jadeui/app.py ADDED
@@ -0,0 +1,370 @@
1
+ """
2
+ JadeUI Application
3
+
4
+ Main application class for JadeUI desktop applications.
5
+ """
6
+
7
+ import ctypes
8
+ import logging
9
+ import signal
10
+ import sys
11
+ import threading
12
+ from typing import Optional, Callable, Any
13
+
14
+ from .core import DLLManager, LifecycleManager
15
+ from .events import EventEmitter, GlobalEventManager, Events
16
+ from .exceptions import InitializationError
17
+ from .core.types import (
18
+ AppReadyCallback,
19
+ WindowAllClosedCallback,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class JadeUIApp(EventEmitter):
26
+ """Main JadeUI application class
27
+
28
+ This is the central class for JadeUI applications. It manages the application
29
+ lifecycle, DLL loading, and global event handling.
30
+
31
+ Example:
32
+ app = JadeUIApp()
33
+
34
+ @app.on('ready')
35
+ def on_ready():
36
+ # Create windows here
37
+ window = Window("My App")
38
+ window.show()
39
+
40
+ app.run()
41
+
42
+ Events:
43
+ - 'ready': Fired when app is initialized and ready
44
+ - 'error': Fired when an error occurs
45
+ - 'window-all-closed': Fired when all windows are closed
46
+ - 'before-quit': Fired before app quits
47
+
48
+ Attributes:
49
+ dll_manager: The DLL manager instance
50
+ lifecycle_manager: The lifecycle manager instance
51
+ """
52
+
53
+ _instance: Optional["JadeUIApp"] = None
54
+
55
+ def __new__(cls) -> "JadeUIApp":
56
+ if cls._instance is None:
57
+ cls._instance = super().__new__(cls)
58
+ return cls._instance
59
+
60
+ def __init__(self):
61
+ if hasattr(self, "_initialized") and self._initialized:
62
+ return
63
+
64
+ super().__init__()
65
+ self._initialized = False
66
+ self.dll_manager = DLLManager()
67
+ self.lifecycle_manager = LifecycleManager()
68
+ self._dev_tools_enabled = False
69
+ self._log_file: Optional[str] = None
70
+ self._data_directory: Optional[str] = None
71
+
72
+ # Callback references to prevent garbage collection
73
+ self._callbacks: list = []
74
+
75
+ # Global event manager
76
+ self._global_events: Optional[GlobalEventManager] = None
77
+
78
+ @classmethod
79
+ def get_instance(cls) -> Optional["JadeUIApp"]:
80
+ """Get the singleton instance of the app
81
+
82
+ Returns:
83
+ The app instance or None if not created
84
+ """
85
+ return cls._instance
86
+
87
+ def initialize(
88
+ self,
89
+ enable_dev_tools: bool = False,
90
+ log_file: Optional[str] = None,
91
+ data_directory: Optional[str] = None,
92
+ ) -> "JadeUIApp":
93
+ """Initialize the JadeUI application
94
+
95
+ Args:
96
+ enable_dev_tools: Whether to enable developer tools (F12)
97
+ log_file: Path to log file (None disables file logging)
98
+ data_directory: WebView data directory (None uses default, current directory)
99
+
100
+ Returns:
101
+ Self for chaining
102
+
103
+ Raises:
104
+ InitializationError: If initialization fails
105
+ """
106
+ if self._initialized:
107
+ return self
108
+
109
+ try:
110
+ # Store configuration
111
+ self._dev_tools_enabled = enable_dev_tools
112
+ self._log_file = log_file
113
+ self._data_directory = data_directory
114
+
115
+ # Initialize lifecycle manager
116
+ self.lifecycle_manager.initialize()
117
+ self.lifecycle_manager.add_cleanup_callback(self._cleanup)
118
+
119
+ # Load DLL
120
+ self.dll_manager.load()
121
+
122
+ # Initialize JadeView DLL
123
+ # API: JadeView_init(enable_devtools, log_path, data_directory)
124
+ result = self.dll_manager.JadeView_init(
125
+ 1 if enable_dev_tools else 0,
126
+ log_file.encode("utf-8") if log_file else None,
127
+ data_directory.encode("utf-8") if data_directory else None,
128
+ )
129
+
130
+ if result == 0:
131
+ raise InitializationError("JadeView DLL initialization failed")
132
+
133
+ # Create global event manager
134
+ self._global_events = GlobalEventManager(self.dll_manager)
135
+
136
+ # Register global event handlers
137
+ self._register_global_events()
138
+
139
+ self._initialized = True
140
+ logger.info("JadeUI application initialized successfully")
141
+ return self
142
+
143
+ except Exception as e:
144
+ raise InitializationError(f"Failed to initialize JadeUI: {e}")
145
+
146
+ def _register_global_events(self) -> None:
147
+ """Register global event handlers with the DLL"""
148
+ # Create app-ready callback
149
+ @AppReadyCallback
150
+ def app_ready_callback(success: int, reason: ctypes.c_char_p) -> int:
151
+ reason_str = reason.decode("utf-8") if reason else "unknown"
152
+ return self._on_app_ready(success, reason_str)
153
+
154
+ # Create window-all-closed callback
155
+ @WindowAllClosedCallback
156
+ def window_all_closed_callback() -> int:
157
+ return self._on_window_all_closed()
158
+
159
+ # Store references to prevent garbage collection
160
+ self._callbacks.append(app_ready_callback)
161
+ self._callbacks.append(window_all_closed_callback)
162
+
163
+ # Register with DLL
164
+ self.dll_manager.jade_on(
165
+ b"app-ready",
166
+ ctypes.cast(app_ready_callback, ctypes.c_void_p),
167
+ )
168
+ self.dll_manager.jade_on(
169
+ b"window-all-closed",
170
+ ctypes.cast(window_all_closed_callback, ctypes.c_void_p),
171
+ )
172
+
173
+ logger.info("Global event handlers registered")
174
+
175
+ def _on_app_ready(self, success: int, reason: str) -> int:
176
+ """Handle app-ready event"""
177
+ try:
178
+ if success == 1 and reason == "success":
179
+ logger.info("JadeUI app ready")
180
+ self.emit("ready")
181
+ else:
182
+ logger.error(f"JadeUI app failed to initialize: {reason}")
183
+ self.emit(
184
+ "error",
185
+ InitializationError(f"App initialization failed: {reason}"),
186
+ )
187
+ except Exception as e:
188
+ logger.error(f"Error in app-ready handler: {e}")
189
+ return 0
190
+
191
+ def _on_window_all_closed(self) -> int:
192
+ """Handle window-all-closed event"""
193
+ logger.info("All windows closed")
194
+ self.emit("window-all-closed")
195
+ # Default behavior: cleanup when all windows are closed
196
+ self._cleanup()
197
+ return 0
198
+
199
+ def run(self) -> None:
200
+ """Start the application message loop
201
+
202
+ This method blocks until all windows are closed.
203
+ Call this after setting up event handlers and windows.
204
+
205
+ Supports Ctrl+C to terminate the application.
206
+ """
207
+ if not self._initialized:
208
+ self.initialize()
209
+
210
+ # 设置信号处理器
211
+ self._setup_signal_handlers()
212
+
213
+ logger.info("Starting JadeUI message loop")
214
+ try:
215
+ self.dll_manager.run_message_loop()
216
+ except KeyboardInterrupt:
217
+ logger.info("Application interrupted by user (Ctrl+C)")
218
+ finally:
219
+ self._cleanup()
220
+
221
+ def _setup_signal_handlers(self) -> None:
222
+ """设置信号处理器以支持 Ctrl+C"""
223
+ self._shutting_down = False
224
+
225
+ def signal_handler(signum, frame):
226
+ if self._shutting_down:
227
+ return
228
+ self._shutting_down = True
229
+ logger.info(f"Received signal {signum}, shutting down...")
230
+ self._force_quit()
231
+
232
+ # 注册 SIGINT (Ctrl+C) 和 SIGTERM
233
+ signal.signal(signal.SIGINT, signal_handler)
234
+ signal.signal(signal.SIGTERM, signal_handler)
235
+
236
+ # Windows: 设置控制台控制处理器
237
+ if sys.platform == "win32":
238
+ self._setup_windows_console_handler()
239
+
240
+ def _setup_windows_console_handler(self) -> None:
241
+ """设置 Windows 控制台控制处理器"""
242
+ try:
243
+ # 使用 ctypes 直接调用 Windows API
244
+ kernel32 = ctypes.windll.kernel32
245
+
246
+ # BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
247
+ HANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_ulong)
248
+
249
+ CTRL_C_EVENT = 0
250
+ CTRL_BREAK_EVENT = 1
251
+ CTRL_CLOSE_EVENT = 2
252
+
253
+ @HANDLER_ROUTINE
254
+ def console_handler(ctrl_type):
255
+ if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT):
256
+ if not self._shutting_down:
257
+ self._shutting_down = True
258
+ logger.info("Console control event received, shutting down...")
259
+ # 在新线程中执行清理,避免阻塞
260
+ threading.Thread(target=self._force_quit, daemon=True).start()
261
+ return True
262
+ return False
263
+
264
+ # 保存引用防止垃圾回收
265
+ self._console_handler = console_handler
266
+ kernel32.SetConsoleCtrlHandler(console_handler, True)
267
+ logger.debug("Windows console handler registered")
268
+ except Exception as e:
269
+ logger.warning(f"Could not set Windows console handler: {e}")
270
+
271
+ def _force_quit(self) -> None:
272
+ """强制退出应用"""
273
+ try:
274
+ # 清理窗口
275
+ self.dll_manager.cleanup_all_windows()
276
+
277
+ # Windows: 发送退出消息到消息循环
278
+ if sys.platform == "win32":
279
+ try:
280
+ user32 = ctypes.windll.user32
281
+ # PostQuitMessage 会导致 GetMessage 返回 0,从而退出消息循环
282
+ user32.PostQuitMessage(0)
283
+ except Exception as e:
284
+ logger.warning(f"PostQuitMessage failed: {e}")
285
+
286
+ except Exception as e:
287
+ logger.error(f"Error during force quit: {e}")
288
+ finally:
289
+ # 确保进程退出
290
+ import os
291
+ os._exit(0)
292
+
293
+ def quit(self) -> None:
294
+ """Quit the application
295
+
296
+ Cleans up all resources and exits the message loop.
297
+ """
298
+ logger.info("Quitting JadeUI application")
299
+ self.emit("before-quit")
300
+ self._cleanup()
301
+
302
+ def _cleanup(self) -> None:
303
+ """Clean up application resources"""
304
+ if not self._initialized:
305
+ return
306
+
307
+ try:
308
+ self.dll_manager.cleanup_all_windows()
309
+ logger.info("Application resources cleaned up")
310
+ except Exception as e:
311
+ logger.error(f"Error during cleanup: {e}")
312
+
313
+ def is_ready(self) -> bool:
314
+ """Check if the application is initialized and ready
315
+
316
+ Returns:
317
+ True if app is ready
318
+ """
319
+ return self._initialized
320
+
321
+ @property
322
+ def dev_tools_enabled(self) -> bool:
323
+ """Check if developer tools are enabled"""
324
+ return self._dev_tools_enabled
325
+
326
+ def on_ready(self, callback: Callable[[], Any]) -> Callable[[], Any]:
327
+ """Decorator for ready event
328
+
329
+ Args:
330
+ callback: Function to call when app is ready
331
+
332
+ Returns:
333
+ The callback function
334
+
335
+ Example:
336
+ @app.on_ready
337
+ def setup():
338
+ window = Window("My App")
339
+ window.show()
340
+ """
341
+ self.on("ready", callback)
342
+ return callback
343
+
344
+ def on_window_all_closed(
345
+ self, callback: Callable[[], Any]
346
+ ) -> Callable[[], Any]:
347
+ """Decorator for window-all-closed event
348
+
349
+ Args:
350
+ callback: Function to call when all windows are closed
351
+
352
+ Returns:
353
+ The callback function
354
+ """
355
+ self.on("window-all-closed", callback)
356
+ return callback
357
+
358
+ def __enter__(self) -> "JadeUIApp":
359
+ """Context manager entry"""
360
+ if not self._initialized:
361
+ self.initialize()
362
+ return self
363
+
364
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
365
+ """Context manager exit"""
366
+ self._cleanup()
367
+
368
+ def __repr__(self) -> str:
369
+ status = "ready" if self._initialized else "not initialized"
370
+ return f"JadeUIApp(status={status}, dev_tools={self._dev_tools_enabled})"
@@ -0,0 +1,33 @@
1
+ """
2
+ JadeUI Core Module
3
+
4
+ Low-level interfaces to the JadeView DLL and type definitions.
5
+ """
6
+
7
+ from .dll import DLLManager
8
+ from .types import (
9
+ RGBA,
10
+ WebViewWindowOptions,
11
+ WebViewSettings,
12
+ WindowEventCallback,
13
+ PageLoadCallback,
14
+ FileDropCallback,
15
+ AppReadyCallback,
16
+ IpcCallback,
17
+ WindowAllClosedCallback,
18
+ )
19
+ from .lifecycle import LifecycleManager
20
+
21
+ __all__ = [
22
+ "DLLManager",
23
+ "RGBA",
24
+ "WebViewWindowOptions",
25
+ "WebViewSettings",
26
+ "LifecycleManager",
27
+ "WindowEventCallback",
28
+ "PageLoadCallback",
29
+ "FileDropCallback",
30
+ "AppReadyCallback",
31
+ "IpcCallback",
32
+ "WindowAllClosedCallback",
33
+ ]