jadeui 0.1.4__py3-none-win_arm64.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,158 @@
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 . import utils
60
+ from .app import JadeUIApp
61
+ from .core.types import RGBA, WebViewSettings, WebViewWindowOptions
62
+ from .downloader import (
63
+ VERSION as DLL_VERSION,
64
+ )
65
+ from .downloader import (
66
+ download_dll,
67
+ ensure_dll,
68
+ find_dll,
69
+ get_architecture,
70
+ )
71
+ from .events import EventEmitter, Events
72
+ from .exceptions import (
73
+ DLLLoadError,
74
+ InitializationError,
75
+ IPCError,
76
+ JadeUIError,
77
+ ServerError,
78
+ WindowCreationError,
79
+ )
80
+ from .ipc import IPCManager
81
+ from .router import Router
82
+ from .server import LocalServer
83
+ from .window import Backdrop, Theme, Window
84
+
85
+ __version__ = "0.1.4"
86
+ __author__ = "JadeView Team"
87
+ __license__ = "MIT"
88
+
89
+ __all__ = [
90
+ # Main classes
91
+ "JadeUIApp",
92
+ "Window",
93
+ "IPCManager",
94
+ "LocalServer",
95
+ "Router",
96
+ # Constants
97
+ "Theme",
98
+ "Backdrop",
99
+ "Events",
100
+ # Types
101
+ "RGBA",
102
+ "WebViewWindowOptions",
103
+ "WebViewSettings",
104
+ # Events
105
+ "EventEmitter",
106
+ # Exceptions
107
+ "JadeUIError",
108
+ "DLLLoadError",
109
+ "WindowCreationError",
110
+ "IPCError",
111
+ "ServerError",
112
+ "InitializationError",
113
+ # DLL Downloader
114
+ "download_dll",
115
+ "ensure_dll",
116
+ "find_dll",
117
+ "get_architecture",
118
+ "DLL_VERSION",
119
+ # Utilities
120
+ "utils",
121
+ ]
122
+
123
+
124
+ def create_app(
125
+ enable_dev_tools: bool = False,
126
+ user_data_dir: str = None,
127
+ log_file: str = None,
128
+ ) -> JadeUIApp:
129
+ """Create and initialize a JadeUI application
130
+
131
+ Convenience function for quick app creation.
132
+
133
+ Args:
134
+ enable_dev_tools: Whether to enable developer tools
135
+ user_data_dir: Directory for user data storage
136
+ log_file: Path to log file
137
+
138
+ Returns:
139
+ Initialized JadeUIApp instance
140
+
141
+ Example:
142
+ from jadeui import create_app, Window
143
+
144
+ app = create_app()
145
+
146
+ @app.on_ready
147
+ def setup():
148
+ Window(title="My App", url="https://example.com").show()
149
+
150
+ app.run()
151
+ """
152
+ app = JadeUIApp()
153
+ app.initialize(
154
+ enable_dev_tools=enable_dev_tools,
155
+ user_data_dir=user_data_dir,
156
+ log_file=log_file,
157
+ )
158
+ return app
jadeui/app.py ADDED
@@ -0,0 +1,447 @@
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 Any, Callable, Optional
13
+
14
+ from .core import DLLManager, LifecycleManager
15
+ from .core.types import (
16
+ AppReadyCallback,
17
+ WindowAllClosedCallback,
18
+ )
19
+ from .events import EventEmitter, GlobalEventManager
20
+ from .exceptions import InitializationError
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
+ def _get_app_name(self) -> str:
79
+ """获取应用名称用于数据目录隔离
80
+
81
+ 使用 JadeUI_ 前缀 + 随机名称,便于清理旧缓存
82
+
83
+ Returns:
84
+ 应用名称字符串
85
+ """
86
+ import uuid
87
+
88
+ return f"JadeUI_{uuid.uuid4().hex[:8]}"
89
+
90
+ def _cleanup_old_data_dirs(self) -> None:
91
+ """清理旧的 JadeUI 数据目录"""
92
+ import os
93
+ import shutil
94
+
95
+ temp_dir = os.environ.get("TEMP", os.environ.get("TMP", ""))
96
+ if not temp_dir:
97
+ return
98
+
99
+ jadeui_base = os.path.join(temp_dir, "JadeUI")
100
+ if not os.path.exists(jadeui_base):
101
+ return
102
+
103
+ try:
104
+ # 删除所有 JadeUI_ 开头的目录
105
+ for name in os.listdir(jadeui_base):
106
+ if name.startswith("JadeUI_"):
107
+ dir_path = os.path.join(jadeui_base, name)
108
+ if os.path.isdir(dir_path):
109
+ try:
110
+ shutil.rmtree(dir_path)
111
+ logger.debug(f"Cleaned up old data dir: {dir_path}")
112
+ except Exception as e:
113
+ logger.debug(f"Failed to clean {dir_path}: {e}")
114
+ except Exception as e:
115
+ logger.debug(f"Error during cleanup: {e}")
116
+
117
+ @classmethod
118
+ def get_instance(cls) -> Optional["JadeUIApp"]:
119
+ """Get the singleton instance of the app
120
+
121
+ Returns:
122
+ The app instance or None if not created
123
+ """
124
+ return cls._instance
125
+
126
+ def initialize(
127
+ self,
128
+ enable_dev_tools: bool = False,
129
+ log_file: Optional[str] = None,
130
+ data_directory: Optional[str] = None,
131
+ ) -> "JadeUIApp":
132
+ """Initialize the JadeUI application
133
+
134
+ Args:
135
+ enable_dev_tools: Whether to enable developer tools (F12)
136
+ log_file: Path to log file (None disables file logging)
137
+ data_directory: WebView data directory
138
+ (None uses default: %LOCALAPPDATA%/JadeUI/<random>)
139
+
140
+ Returns:
141
+ Self for chaining
142
+
143
+ Raises:
144
+ InitializationError: If initialization fails
145
+ """
146
+ if self._initialized:
147
+ return self
148
+
149
+ try:
150
+ # Store configuration
151
+ self._dev_tools_enabled = enable_dev_tools
152
+ self._log_file = log_file
153
+
154
+ # 清理旧的数据目录
155
+ self._cleanup_old_data_dirs()
156
+
157
+ # 设置默认数据目录: %TEMP%/JadeUI/<app_name>
158
+ if data_directory is None:
159
+ import os
160
+
161
+ app_name = self._get_app_name()
162
+
163
+ temp_dir = os.environ.get("TEMP", os.environ.get("TMP", ""))
164
+ if temp_dir:
165
+ data_directory = os.path.join(temp_dir, "JadeUI", app_name)
166
+
167
+ self._data_directory = data_directory
168
+
169
+ # Initialize lifecycle manager
170
+ self.lifecycle_manager.initialize()
171
+ self.lifecycle_manager.add_cleanup_callback(self._cleanup)
172
+
173
+ # Load DLL
174
+ self.dll_manager.load()
175
+
176
+ # Initialize JadeView DLL
177
+ # API: JadeView_init(enable_devtools, log_path, data_directory)
178
+ result = self.dll_manager.JadeView_init(
179
+ 1 if enable_dev_tools else 0,
180
+ log_file.encode("utf-8") if log_file else None,
181
+ data_directory.encode("utf-8") if data_directory else None,
182
+ )
183
+
184
+ if result == 0:
185
+ raise InitializationError("JadeView DLL initialization failed")
186
+
187
+ # Create global event manager
188
+ self._global_events = GlobalEventManager(self.dll_manager)
189
+
190
+ # Register global event handlers
191
+ self._register_global_events()
192
+
193
+ self._initialized = True
194
+ logger.info("JadeUI application initialized successfully")
195
+ return self
196
+
197
+ except Exception as e:
198
+ raise InitializationError(f"Failed to initialize JadeUI: {e}")
199
+
200
+ def get_webview_version(self) -> Optional[str]:
201
+ """Get the WebView engine version
202
+
203
+ Returns:
204
+ Version string (e.g., "120.0.2210.144") or None if not available
205
+ """
206
+ if not self._initialized:
207
+ return None
208
+
209
+ if not self.dll_manager.has_function("get_webview_version"):
210
+ logger.debug("get_webview_version not available in DLL")
211
+ return None
212
+
213
+ try:
214
+ buffer = ctypes.create_string_buffer(256)
215
+ result = self.dll_manager.get_webview_version(buffer, 256)
216
+ if result == 1:
217
+ return buffer.value.decode("utf-8")
218
+ return None
219
+ except Exception as e:
220
+ logger.debug(f"Failed to get WebView version: {e}")
221
+ return None
222
+
223
+ def _register_global_events(self) -> None:
224
+ """Register global event handlers with the DLL"""
225
+
226
+ # Create app-ready callback
227
+ @AppReadyCallback
228
+ def app_ready_callback(success: int, reason: ctypes.c_char_p) -> int:
229
+ reason_str = reason.decode("utf-8") if reason else "unknown"
230
+ return self._on_app_ready(success, reason_str)
231
+
232
+ # Create window-all-closed callback
233
+ @WindowAllClosedCallback
234
+ def window_all_closed_callback() -> int:
235
+ return self._on_window_all_closed()
236
+
237
+ # Store references to prevent garbage collection
238
+ self._callbacks.append(app_ready_callback)
239
+ self._callbacks.append(window_all_closed_callback)
240
+
241
+ # Register with DLL
242
+ self.dll_manager.jade_on(
243
+ b"app-ready",
244
+ ctypes.cast(app_ready_callback, ctypes.c_void_p),
245
+ )
246
+ self.dll_manager.jade_on(
247
+ b"window-all-closed",
248
+ ctypes.cast(window_all_closed_callback, ctypes.c_void_p),
249
+ )
250
+
251
+ logger.info("Global event handlers registered")
252
+
253
+ def _on_app_ready(self, success: int, reason: str) -> int:
254
+ """Handle app-ready event"""
255
+ try:
256
+ if success == 1 and reason == "success":
257
+ logger.info("JadeUI app ready")
258
+ self.emit("ready")
259
+ else:
260
+ logger.error(f"JadeUI app failed to initialize: {reason}")
261
+ self.emit(
262
+ "error",
263
+ InitializationError(f"App initialization failed: {reason}"),
264
+ )
265
+ except Exception as e:
266
+ logger.error(f"Error in app-ready handler: {e}")
267
+ return 0
268
+
269
+ def _on_window_all_closed(self) -> int:
270
+ """Handle window-all-closed event"""
271
+ logger.info("All windows closed")
272
+ self.emit("window-all-closed")
273
+ # Default behavior: cleanup when all windows are closed
274
+ self._cleanup()
275
+ return 0
276
+
277
+ def run(self) -> None:
278
+ """Start the application message loop
279
+
280
+ This method blocks until all windows are closed.
281
+ Call this after setting up event handlers and windows.
282
+
283
+ Supports Ctrl+C to terminate the application.
284
+ """
285
+ if not self._initialized:
286
+ self.initialize()
287
+
288
+ # 设置信号处理器
289
+ self._setup_signal_handlers()
290
+
291
+ logger.info("Starting JadeUI message loop")
292
+ try:
293
+ self.dll_manager.run_message_loop()
294
+ except KeyboardInterrupt:
295
+ logger.info("Application interrupted by user (Ctrl+C)")
296
+ finally:
297
+ self._cleanup()
298
+
299
+ def _setup_signal_handlers(self) -> None:
300
+ """设置信号处理器以支持 Ctrl+C"""
301
+ self._shutting_down = False
302
+
303
+ def signal_handler(signum, frame):
304
+ if self._shutting_down:
305
+ return
306
+ self._shutting_down = True
307
+ logger.info(f"Received signal {signum}, shutting down...")
308
+ self._force_quit()
309
+
310
+ # 注册 SIGINT (Ctrl+C) 和 SIGTERM
311
+ signal.signal(signal.SIGINT, signal_handler)
312
+ signal.signal(signal.SIGTERM, signal_handler)
313
+
314
+ # Windows: 设置控制台控制处理器
315
+ if sys.platform == "win32":
316
+ self._setup_windows_console_handler()
317
+
318
+ def _setup_windows_console_handler(self) -> None:
319
+ """设置 Windows 控制台控制处理器"""
320
+ try:
321
+ # 使用 ctypes 直接调用 Windows API
322
+ kernel32 = ctypes.windll.kernel32
323
+
324
+ # BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
325
+ HANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_ulong)
326
+
327
+ CTRL_C_EVENT = 0
328
+ CTRL_BREAK_EVENT = 1
329
+ CTRL_CLOSE_EVENT = 2
330
+
331
+ @HANDLER_ROUTINE
332
+ def console_handler(ctrl_type):
333
+ if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT):
334
+ if not self._shutting_down:
335
+ self._shutting_down = True
336
+ logger.info("Console control event received, shutting down...")
337
+ # 在新线程中执行清理,避免阻塞
338
+ threading.Thread(target=self._force_quit, daemon=True).start()
339
+ return True
340
+ return False
341
+
342
+ # 保存引用防止垃圾回收
343
+ self._console_handler = console_handler
344
+ kernel32.SetConsoleCtrlHandler(console_handler, True)
345
+ logger.debug("Windows console handler registered")
346
+ except Exception as e:
347
+ logger.warning(f"Could not set Windows console handler: {e}")
348
+
349
+ def _force_quit(self) -> None:
350
+ """强制退出应用"""
351
+ try:
352
+ # 清理窗口
353
+ self.dll_manager.cleanup_all_windows()
354
+
355
+ # Windows: 发送退出消息到消息循环
356
+ if sys.platform == "win32":
357
+ try:
358
+ user32 = ctypes.windll.user32
359
+ # PostQuitMessage 会导致 GetMessage 返回 0,从而退出消息循环
360
+ user32.PostQuitMessage(0)
361
+ except Exception as e:
362
+ logger.warning(f"PostQuitMessage failed: {e}")
363
+
364
+ except Exception as e:
365
+ logger.error(f"Error during force quit: {e}")
366
+ finally:
367
+ # 确保进程退出
368
+ import os
369
+
370
+ os._exit(0)
371
+
372
+ def quit(self) -> None:
373
+ """Quit the application
374
+
375
+ Cleans up all resources and exits the message loop.
376
+ """
377
+ logger.info("Quitting JadeUI application")
378
+ self.emit("before-quit")
379
+ self._cleanup()
380
+
381
+ def _cleanup(self) -> None:
382
+ """Clean up application resources"""
383
+ if not self._initialized:
384
+ return
385
+
386
+ try:
387
+ self.dll_manager.cleanup_all_windows()
388
+ logger.info("Application resources cleaned up")
389
+ except Exception as e:
390
+ logger.error(f"Error during cleanup: {e}")
391
+
392
+ def is_ready(self) -> bool:
393
+ """Check if the application is initialized and ready
394
+
395
+ Returns:
396
+ True if app is ready
397
+ """
398
+ return self._initialized
399
+
400
+ @property
401
+ def dev_tools_enabled(self) -> bool:
402
+ """Check if developer tools are enabled"""
403
+ return self._dev_tools_enabled
404
+
405
+ def on_ready(self, callback: Callable[[], Any]) -> Callable[[], Any]:
406
+ """Decorator for ready event
407
+
408
+ Args:
409
+ callback: Function to call when app is ready
410
+
411
+ Returns:
412
+ The callback function
413
+
414
+ Example:
415
+ @app.on_ready
416
+ def setup():
417
+ window = Window("My App")
418
+ window.show()
419
+ """
420
+ self.on("ready", callback)
421
+ return callback
422
+
423
+ def on_window_all_closed(self, callback: Callable[[], Any]) -> Callable[[], Any]:
424
+ """Decorator for window-all-closed event
425
+
426
+ Args:
427
+ callback: Function to call when all windows are closed
428
+
429
+ Returns:
430
+ The callback function
431
+ """
432
+ self.on("window-all-closed", callback)
433
+ return callback
434
+
435
+ def __enter__(self) -> "JadeUIApp":
436
+ """Context manager entry"""
437
+ if not self._initialized:
438
+ self.initialize()
439
+ return self
440
+
441
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
442
+ """Context manager exit"""
443
+ self._cleanup()
444
+
445
+ def __repr__(self) -> str:
446
+ status = "ready" if self._initialized else "not initialized"
447
+ 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 .lifecycle import LifecycleManager
9
+ from .types import (
10
+ RGBA,
11
+ AppReadyCallback,
12
+ FileDropCallback,
13
+ IpcCallback,
14
+ PageLoadCallback,
15
+ WebViewSettings,
16
+ WebViewWindowOptions,
17
+ WindowAllClosedCallback,
18
+ WindowEventCallback,
19
+ )
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
+ ]