@zappdev/cli 0.1.0
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.
- package/README.md +55 -0
- package/dist/zapp-cli.js +9471 -0
- package/native/src/app/app.zc +490 -0
- package/native/src/event/event.zc +24 -0
- package/native/src/event/events.zc +70 -0
- package/native/src/platform/darwin/backend.zc +923 -0
- package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
- package/native/src/platform/darwin/bootstrap.zc +9 -0
- package/native/src/platform/darwin/engine_jsc.zc +86 -0
- package/native/src/platform/darwin/engine_qjs.zc +92 -0
- package/native/src/platform/darwin/platform.zc +156 -0
- package/native/src/platform/darwin/webview.zc +550 -0
- package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
- package/native/src/platform/darwin/window.zc +1223 -0
- package/native/src/platform/darwin/worker/common.zc +223 -0
- package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
- package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
- package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
- package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
- package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
- package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
- package/native/src/platform/darwin/worker/dispatch.zc +55 -0
- package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
- package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
- package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
- package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
- package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
- package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
- package/native/src/platform/darwin/worker/jsc.zc +670 -0
- package/native/src/platform/darwin/worker/mod.zc +30 -0
- package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
- package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
- package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
- package/native/src/platform/darwin/worker/qjs.zc +1053 -0
- package/native/src/platform/darwin/worker/timers.zc +149 -0
- package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
- package/native/src/platform/platform.zc +64 -0
- package/native/src/platform/shared/log.zc +156 -0
- package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
- package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
- package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
- package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
- package/native/src/platform/shared/worker_registry.zc +206 -0
- package/native/src/platform/window.zc +446 -0
- package/native/src/platform/windows/backend.zc +452 -0
- package/native/src/platform/windows/backend_bootstrap.zc +9 -0
- package/native/src/platform/windows/bootstrap.zc +9 -0
- package/native/src/platform/windows/engine_qjs.zc +60 -0
- package/native/src/platform/windows/platform.zc +387 -0
- package/native/src/platform/windows/webview.zc +1175 -0
- package/native/src/platform/windows/webview_bootstrap.zc +9 -0
- package/native/src/platform/windows/window.zc +1271 -0
- package/native/src/platform/windows/worker/common.zc +409 -0
- package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
- package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
- package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
- package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
- package/native/src/platform/windows/worker/core/url_core.zc +216 -0
- package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
- package/native/src/platform/windows/worker/dispatch.zc +34 -0
- package/native/src/platform/windows/worker/mod.zc +46 -0
- package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
- package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
- package/native/src/platform/windows/worker/qjs.zc +1049 -0
- package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
- package/native/src/platform/worker.zc +8 -0
- package/native/src/service/service.zc +228 -0
- package/native/vendor/quickjs-ng/.gitattributes +4 -0
- package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
- package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
- package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
- package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
- package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
- package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
- package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
- package/native/vendor/quickjs-ng/.gitmodules +5 -0
- package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
- package/native/vendor/quickjs-ng/LICENSE +24 -0
- package/native/vendor/quickjs-ng/Makefile +149 -0
- package/native/vendor/quickjs-ng/amalgam.js +53 -0
- package/native/vendor/quickjs-ng/api-test.c +927 -0
- package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
- package/native/vendor/quickjs-ng/ctest.c +17 -0
- package/native/vendor/quickjs-ng/cutils.h +2013 -0
- package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
- package/native/vendor/quickjs-ng/dtoa.c +1619 -0
- package/native/vendor/quickjs-ng/dtoa.h +87 -0
- package/native/vendor/quickjs-ng/examples/fib.c +67 -0
- package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
- package/native/vendor/quickjs-ng/examples/hello.js +1 -0
- package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
- package/native/vendor/quickjs-ng/examples/meson.build +17 -0
- package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
- package/native/vendor/quickjs-ng/examples/point.c +154 -0
- package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
- package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
- package/native/vendor/quickjs-ng/fuzz.c +51 -0
- package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/native/vendor/quickjs-ng/gen/hello.c +53 -0
- package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
- package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
- package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
- package/native/vendor/quickjs-ng/libregexp.c +2687 -0
- package/native/vendor/quickjs-ng/libregexp.h +98 -0
- package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
- package/native/vendor/quickjs-ng/libunicode.c +1746 -0
- package/native/vendor/quickjs-ng/libunicode.h +126 -0
- package/native/vendor/quickjs-ng/list.h +107 -0
- package/native/vendor/quickjs-ng/lre-test.c +73 -0
- package/native/vendor/quickjs-ng/meson.build +684 -0
- package/native/vendor/quickjs-ng/meson_options.txt +6 -0
- package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
- package/native/vendor/quickjs-ng/qjs.c +748 -0
- package/native/vendor/quickjs-ng/qjsc.c +673 -0
- package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
- package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
- package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
- package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
- package/native/vendor/quickjs-ng/quickjs.c +60259 -0
- package/native/vendor/quickjs-ng/quickjs.h +1419 -0
- package/native/vendor/quickjs-ng/repl.js +1927 -0
- package/native/vendor/quickjs-ng/run-test262.c +2417 -0
- package/native/vendor/quickjs-ng/standalone.js +129 -0
- package/native/vendor/quickjs-ng/tests/assert.js +49 -0
- package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
- package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
- package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
- package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
- package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
- package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
- package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
- package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
- package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
- package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
- package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
- package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
- package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
- package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
- package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
- package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
- package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
- package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
- package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
- package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
- package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
- package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
- package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
- package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
- package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
- package/native/vendor/quickjs-ng/tests/empty.js +0 -0
- package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
- package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
- package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
- package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
- package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
- package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
- package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
- package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
- package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
- package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
- package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
- package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
- package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
- package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
- package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
- package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
- package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
- package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
- package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
- package/native/vendor/quickjs-ng/tests.conf +14 -0
- package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
- package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
- package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/native/vendor/quickjs-ng/update-version.sh +32 -0
- package/native/vendor/webview2/include/WebView2.h +60636 -0
- package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
- package/package.json +33 -0
- package/src/backend.ts +139 -0
- package/src/build-config.ts +87 -0
- package/src/build.ts +276 -0
- package/src/common.ts +195 -0
- package/src/config.ts +89 -0
- package/src/dev.ts +164 -0
- package/src/generate.ts +200 -0
- package/src/icons.ts +116 -0
- package/src/init.ts +190 -0
- package/src/package.ts +150 -0
- package/src/zapp-cli.ts +263 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
import "../window.zc";
|
|
2
|
+
import "./webview.zc";
|
|
3
|
+
|
|
4
|
+
raw {
|
|
5
|
+
#ifdef __APPLE__
|
|
6
|
+
#undef panic
|
|
7
|
+
#include <Cocoa/Cocoa.h>
|
|
8
|
+
#include <dispatch/dispatch.h>
|
|
9
|
+
#import <objc/runtime.h>
|
|
10
|
+
|
|
11
|
+
// Window event IDs (must match src/event/events.zc)
|
|
12
|
+
#define ZAPP_EVENT_WINDOW_READY 0
|
|
13
|
+
#define ZAPP_EVENT_WINDOW_FOCUS 1
|
|
14
|
+
#define ZAPP_EVENT_WINDOW_BLUR 2
|
|
15
|
+
#define ZAPP_EVENT_WINDOW_RESIZE 3
|
|
16
|
+
#define ZAPP_EVENT_WINDOW_MOVE 4
|
|
17
|
+
#define ZAPP_EVENT_WINDOW_CLOSE 5
|
|
18
|
+
#define ZAPP_EVENT_WINDOW_MINIMIZE 6
|
|
19
|
+
#define ZAPP_EVENT_WINDOW_MAXIMIZE 7
|
|
20
|
+
#define ZAPP_EVENT_WINDOW_RESTORE 8
|
|
21
|
+
#define ZAPP_EVENT_WINDOW_FULLSCREEN 9
|
|
22
|
+
#define ZAPP_EVENT_WINDOW_UNFULLSCREEN 10
|
|
23
|
+
|
|
24
|
+
#ifndef ZAPP_EVENT_RESULT_CANCEL
|
|
25
|
+
#define ZAPP_EVENT_RESULT_CANCEL 1
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
extern void zapp_darwin_reset_all_workers(void);
|
|
29
|
+
extern void zapp_darwin_reset_owner_workers(const char* owner_id);
|
|
30
|
+
extern void zapp_darwin_webview_create(void* window_ptr, bool inspectable);
|
|
31
|
+
extern void zapp_dispatch_window_event_to_bridge(const char* window_id, int event_id, int width, int height, int x, int y);
|
|
32
|
+
static int zapp_window_get_numeric_id(NSString* windowId);
|
|
33
|
+
extern int zapp_window_trigger_event_with_data(int id, int event_id, int w, int h, int x, int y);
|
|
34
|
+
|
|
35
|
+
// Per-window delegate that terminates attached workers when the window closes.
|
|
36
|
+
@interface ZappWindowDelegate : NSObject <NSWindowDelegate>
|
|
37
|
+
@property (nonatomic, copy) NSString* ownerId;
|
|
38
|
+
@property (nonatomic, copy) NSString* windowId;
|
|
39
|
+
@property (nonatomic, assign) BOOL bridgeReady;
|
|
40
|
+
@property (nonatomic, assign) BOOL pendingFocusEvent;
|
|
41
|
+
@property (nonatomic, assign) BOOL wasZoomed;
|
|
42
|
+
@property (nonatomic, assign) BOOL closeGuarded;
|
|
43
|
+
@property (nonatomic, assign) BOOL forceClose;
|
|
44
|
+
@end
|
|
45
|
+
|
|
46
|
+
static void zapp_window_unregister(NSWindow* window);
|
|
47
|
+
|
|
48
|
+
// Helper to fire both native callback and bridge event
|
|
49
|
+
static void zapp_fire_window_event(NSString* windowId, int event_id, int w, int h, int x, int y) {
|
|
50
|
+
if (windowId == nil) return;
|
|
51
|
+
int numId = zapp_window_get_numeric_id(windowId);
|
|
52
|
+
if (numId >= 0) zapp_window_trigger_event_with_data(numId, event_id, w, h, x, y);
|
|
53
|
+
zapp_dispatch_window_event_to_bridge([windowId UTF8String], event_id, w, h, x, y);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@implementation ZappWindowDelegate
|
|
57
|
+
- (instancetype)init {
|
|
58
|
+
self = [super init];
|
|
59
|
+
if (self) {
|
|
60
|
+
_bridgeReady = NO;
|
|
61
|
+
_pendingFocusEvent = NO;
|
|
62
|
+
_wasZoomed = NO;
|
|
63
|
+
_closeGuarded = NO;
|
|
64
|
+
_forceClose = NO;
|
|
65
|
+
}
|
|
66
|
+
return self;
|
|
67
|
+
}
|
|
68
|
+
- (BOOL)windowShouldClose:(NSWindow*)sender {
|
|
69
|
+
(void)sender;
|
|
70
|
+
if (self.forceClose) {
|
|
71
|
+
self.forceClose = NO;
|
|
72
|
+
return YES;
|
|
73
|
+
}
|
|
74
|
+
if (self.windowId) {
|
|
75
|
+
int numId = zapp_window_get_numeric_id(self.windowId);
|
|
76
|
+
if (numId >= 0) {
|
|
77
|
+
int result = zapp_window_trigger_event_with_data(numId, ZAPP_EVENT_WINDOW_CLOSE, 0, 0, 0, 0);
|
|
78
|
+
if (result == ZAPP_EVENT_RESULT_CANCEL) return NO;
|
|
79
|
+
}
|
|
80
|
+
if (self.closeGuarded) {
|
|
81
|
+
zapp_dispatch_window_event_to_bridge([self.windowId UTF8String], ZAPP_EVENT_WINDOW_CLOSE, 0, 0, 0, 0);
|
|
82
|
+
return NO;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return YES;
|
|
86
|
+
}
|
|
87
|
+
- (void)windowWillClose:(NSNotification*)notification {
|
|
88
|
+
NSWindow* window = (NSWindow*)[notification object];
|
|
89
|
+
if (window) {
|
|
90
|
+
zapp_window_unregister(window);
|
|
91
|
+
}
|
|
92
|
+
if (self.ownerId) {
|
|
93
|
+
zapp_darwin_reset_owner_workers([self.ownerId UTF8String]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
- (void)windowDidBecomeKey:(NSNotification*)notification {
|
|
97
|
+
(void)notification;
|
|
98
|
+
if (self.windowId) {
|
|
99
|
+
int numId = zapp_window_get_numeric_id(self.windowId);
|
|
100
|
+
if (numId >= 0) zapp_window_trigger_event_with_data(numId, ZAPP_EVENT_WINDOW_FOCUS, 0, 0, 0, 0);
|
|
101
|
+
if (self.bridgeReady) {
|
|
102
|
+
zapp_dispatch_window_event_to_bridge([self.windowId UTF8String], ZAPP_EVENT_WINDOW_FOCUS, 0, 0, 0, 0);
|
|
103
|
+
} else {
|
|
104
|
+
self.pendingFocusEvent = YES;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
- (void)windowDidResignKey:(NSNotification*)notification {
|
|
109
|
+
(void)notification;
|
|
110
|
+
if (self.windowId) {
|
|
111
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_BLUR, 0, 0, 0, 0);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
- (void)windowDidResize:(NSNotification*)notification {
|
|
115
|
+
NSWindow* window = (NSWindow*)[notification object];
|
|
116
|
+
if (self.windowId && window) {
|
|
117
|
+
NSRect frame = [window frame];
|
|
118
|
+
int w = (int)frame.size.width;
|
|
119
|
+
int h = (int)frame.size.height;
|
|
120
|
+
int x = (int)frame.origin.x;
|
|
121
|
+
int y = (int)frame.origin.y;
|
|
122
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_RESIZE, w, h, x, y);
|
|
123
|
+
// Detect maximize (zoom) transitions
|
|
124
|
+
BOOL isZoomed = [window isZoomed];
|
|
125
|
+
if (isZoomed && !self.wasZoomed) {
|
|
126
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_MAXIMIZE, w, h, x, y);
|
|
127
|
+
} else if (!isZoomed && self.wasZoomed) {
|
|
128
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_RESTORE, w, h, x, y);
|
|
129
|
+
}
|
|
130
|
+
self.wasZoomed = isZoomed;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
- (void)windowDidMove:(NSNotification*)notification {
|
|
134
|
+
NSWindow* window = (NSWindow*)[notification object];
|
|
135
|
+
if (self.windowId && window) {
|
|
136
|
+
NSRect frame = [window frame];
|
|
137
|
+
int w = (int)frame.size.width;
|
|
138
|
+
int h = (int)frame.size.height;
|
|
139
|
+
int x = (int)frame.origin.x;
|
|
140
|
+
int y = (int)frame.origin.y;
|
|
141
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_MOVE, w, h, x, y);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
- (void)windowDidMiniaturize:(NSNotification*)notification {
|
|
145
|
+
(void)notification;
|
|
146
|
+
if (self.windowId) {
|
|
147
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_MINIMIZE, 0, 0, 0, 0);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
- (void)windowDidDeminiaturize:(NSNotification*)notification {
|
|
151
|
+
(void)notification;
|
|
152
|
+
if (self.windowId) {
|
|
153
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_RESTORE, 0, 0, 0, 0);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
|
|
157
|
+
(void)notification;
|
|
158
|
+
if (self.windowId) {
|
|
159
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_FULLSCREEN, 0, 0, 0, 0);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
- (void)windowDidExitFullScreen:(NSNotification*)notification {
|
|
163
|
+
(void)notification;
|
|
164
|
+
if (self.windowId) {
|
|
165
|
+
zapp_fire_window_event(self.windowId, ZAPP_EVENT_WINDOW_UNFULLSCREEN, 0, 0, 0, 0);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
@end
|
|
169
|
+
|
|
170
|
+
// Called when webview signals readiness - replays any buffered events
|
|
171
|
+
void zapp_window_set_bridge_ready(const char* window_id) {
|
|
172
|
+
if (window_id == NULL) return;
|
|
173
|
+
NSString* wid = [NSString stringWithUTF8String:window_id];
|
|
174
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
175
|
+
ZappWindowDelegate* delegate = (ZappWindowDelegate*)[window delegate];
|
|
176
|
+
if ([delegate isKindOfClass:[ZappWindowDelegate class]] && [delegate.windowId isEqualToString:wid]) {
|
|
177
|
+
delegate.bridgeReady = YES;
|
|
178
|
+
if (delegate.pendingFocusEvent) {
|
|
179
|
+
delegate.pendingFocusEvent = NO;
|
|
180
|
+
zapp_dispatch_window_event_to_bridge([delegate.windowId UTF8String], ZAPP_EVENT_WINDOW_FOCUS, 0, 0, 0, 0);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Key for retaining the window delegate via associated object (NSWindow.delegate is weak).
|
|
188
|
+
static const char kZappWindowDelegateKey = 0;
|
|
189
|
+
#endif
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn darwin_window_create(options: WindowOptions*) -> void* {
|
|
193
|
+
let handle: void* = NULL;
|
|
194
|
+
raw {
|
|
195
|
+
#ifdef __APPLE__
|
|
196
|
+
@autoreleasepool {
|
|
197
|
+
__block void* out = NULL;
|
|
198
|
+
void (^work)(void) = ^{
|
|
199
|
+
// Build style mask from options
|
|
200
|
+
NSUInteger styleMask = 0;
|
|
201
|
+
if (!options->borderless) {
|
|
202
|
+
styleMask |= NSWindowStyleMaskTitled;
|
|
203
|
+
if (options->closable) styleMask |= NSWindowStyleMaskClosable;
|
|
204
|
+
if (options->minimizable) styleMask |= NSWindowStyleMaskMiniaturizable;
|
|
205
|
+
if (options->resizable) styleMask |= NSWindowStyleMaskResizable;
|
|
206
|
+
} else {
|
|
207
|
+
styleMask = NSWindowStyleMaskBorderless;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
NSRect frame = NSMakeRect(options->x, options->y, options->width, options->height);
|
|
211
|
+
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
|
|
212
|
+
styleMask:styleMask
|
|
213
|
+
backing:NSBackingStoreBuffered
|
|
214
|
+
defer:NO];
|
|
215
|
+
[window setReleasedWhenClosed:NO];
|
|
216
|
+
|
|
217
|
+
NSString* titleStr = [NSString stringWithUTF8String:(const char*)options->title];
|
|
218
|
+
[window setTitle:titleStr];
|
|
219
|
+
|
|
220
|
+
if (options->titleBarStyle == WINDOW_TITLEBAR_STYLE_HIDDEN || options->titleBarStyle == WINDOW_TITLEBAR_STYLE_HIDDEN_INSET) {
|
|
221
|
+
[window setStyleMask:([window styleMask] | NSWindowStyleMaskFullSizeContentView)];
|
|
222
|
+
[window setTitleVisibility:NSWindowTitleHidden];
|
|
223
|
+
[window setTitlebarAppearsTransparent:YES];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (options->titleBarStyle == WINDOW_TITLEBAR_STYLE_HIDDEN_INSET) {
|
|
227
|
+
#if defined(NSWindowToolbarStyleUnifiedCompact)
|
|
228
|
+
if ([window respondsToSelector:@selector(setToolbarStyle:)]) {
|
|
229
|
+
[window setToolbarStyle:NSWindowToolbarStyleUnifiedCompact];
|
|
230
|
+
}
|
|
231
|
+
#endif
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options->transparent) {
|
|
235
|
+
[window setOpaque:NO];
|
|
236
|
+
[window setBackgroundColor:[NSColor clearColor]];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options->alwaysOnTop) {
|
|
240
|
+
[window setLevel:NSFloatingWindowLevel];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
bool inspectable = options->webContentInspectable > 0;
|
|
244
|
+
zapp_darwin_webview_create((__bridge void*)window, inspectable);
|
|
245
|
+
|
|
246
|
+
NSString* windowId = [NSString stringWithFormat:@"win-%p", window];
|
|
247
|
+
NSString* ownerId = [NSString stringWithFormat:@"owner-%p", window];
|
|
248
|
+
ZappWindowDelegate* windowDelegate = [[ZappWindowDelegate alloc] init];
|
|
249
|
+
windowDelegate.windowId = windowId;
|
|
250
|
+
windowDelegate.ownerId = ownerId;
|
|
251
|
+
objc_setAssociatedObject(window, &kZappWindowDelegateKey, windowDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
252
|
+
[window setDelegate:windowDelegate];
|
|
253
|
+
|
|
254
|
+
if (options->fullscreen) {
|
|
255
|
+
[window toggleFullScreen:nil];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (options->visible && !options->hidden) {
|
|
259
|
+
[window makeKeyAndOrderFront:nil];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
out = (__bridge_retained void*)window;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
if ([NSThread isMainThread]) {
|
|
266
|
+
work();
|
|
267
|
+
} else {
|
|
268
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
handle = out;
|
|
272
|
+
}
|
|
273
|
+
#endif
|
|
274
|
+
}
|
|
275
|
+
return handle;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fn darwin_window_destroy(window: void*) -> void {
|
|
279
|
+
raw {
|
|
280
|
+
#ifdef __APPLE__
|
|
281
|
+
@autoreleasepool {
|
|
282
|
+
void (^work)(void) = ^{
|
|
283
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
284
|
+
if (nsWindow) {
|
|
285
|
+
if ([[NSApp windows] count] <= 1) {
|
|
286
|
+
zapp_darwin_reset_all_workers();
|
|
287
|
+
}
|
|
288
|
+
[nsWindow close];
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
if ([NSThread isMainThread]) {
|
|
292
|
+
work();
|
|
293
|
+
} else {
|
|
294
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
#endif
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fn darwin_window_show(window: void*) -> void {
|
|
302
|
+
raw {
|
|
303
|
+
#ifdef __APPLE__
|
|
304
|
+
@autoreleasepool {
|
|
305
|
+
void (^work)(void) = ^{
|
|
306
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
307
|
+
if (nsWindow) {
|
|
308
|
+
[nsWindow makeKeyAndOrderFront:nil];
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
if ([NSThread isMainThread]) { work(); }
|
|
312
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
313
|
+
}
|
|
314
|
+
#endif
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn darwin_window_hide(window: void*) -> void {
|
|
319
|
+
raw {
|
|
320
|
+
#ifdef __APPLE__
|
|
321
|
+
@autoreleasepool {
|
|
322
|
+
void (^work)(void) = ^{
|
|
323
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
324
|
+
if (nsWindow) {
|
|
325
|
+
[nsWindow orderOut:nil];
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
if ([NSThread isMainThread]) { work(); }
|
|
329
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
330
|
+
}
|
|
331
|
+
#endif
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fn darwin_window_minimize(window: void*) -> void {
|
|
336
|
+
raw {
|
|
337
|
+
#ifdef __APPLE__
|
|
338
|
+
@autoreleasepool {
|
|
339
|
+
void (^work)(void) = ^{
|
|
340
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
341
|
+
if (nsWindow) {
|
|
342
|
+
[nsWindow miniaturize:nil];
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
if ([NSThread isMainThread]) {
|
|
346
|
+
work();
|
|
347
|
+
} else {
|
|
348
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
#endif
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fn darwin_window_unminimize(window: void*) -> void {
|
|
356
|
+
raw {
|
|
357
|
+
#ifdef __APPLE__
|
|
358
|
+
@autoreleasepool {
|
|
359
|
+
void (^work)(void) = ^{
|
|
360
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
361
|
+
if (nsWindow) {
|
|
362
|
+
[nsWindow deminiaturize:nil];
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
if ([NSThread isMainThread]) {
|
|
366
|
+
work();
|
|
367
|
+
} else {
|
|
368
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
#endif
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
fn darwin_window_maximize(window: void*) -> void {
|
|
376
|
+
raw {
|
|
377
|
+
#ifdef __APPLE__
|
|
378
|
+
@autoreleasepool {
|
|
379
|
+
void (^work)(void) = ^{
|
|
380
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
381
|
+
if (nsWindow) {
|
|
382
|
+
[nsWindow zoom:nil];
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
if ([NSThread isMainThread]) {
|
|
386
|
+
work();
|
|
387
|
+
} else {
|
|
388
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
#endif
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn darwin_window_unmaximize(window: void*) -> void {
|
|
396
|
+
raw {
|
|
397
|
+
#ifdef __APPLE__
|
|
398
|
+
@autoreleasepool {
|
|
399
|
+
void (^work)(void) = ^{
|
|
400
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
401
|
+
if (nsWindow) {
|
|
402
|
+
if ([nsWindow isZoomed]) {
|
|
403
|
+
[nsWindow zoom:nil];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
if ([NSThread isMainThread]) {
|
|
408
|
+
work();
|
|
409
|
+
} else {
|
|
410
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
#endif
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
fn darwin_window_register_numeric_id(window: void*, numeric_id: int) -> void {
|
|
418
|
+
raw {
|
|
419
|
+
#ifdef __APPLE__
|
|
420
|
+
if (window == NULL) return;
|
|
421
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
422
|
+
NSString* windowId = [NSString stringWithFormat:@"win-%p", nsWindow];
|
|
423
|
+
zapp_window_register_numeric_id(windowId, numeric_id);
|
|
424
|
+
#endif
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
fn darwin_window_toggle_minimize(window: void*) -> void {
|
|
429
|
+
raw {
|
|
430
|
+
#ifdef __APPLE__
|
|
431
|
+
@autoreleasepool {
|
|
432
|
+
void (^work)(void) = ^{
|
|
433
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
434
|
+
if (nsWindow) {
|
|
435
|
+
if ([nsWindow isMiniaturized]) {
|
|
436
|
+
[nsWindow deminiaturize:nil];
|
|
437
|
+
} else {
|
|
438
|
+
[nsWindow miniaturize:nil];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
if ([NSThread isMainThread]) {
|
|
443
|
+
work();
|
|
444
|
+
} else {
|
|
445
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
#endif
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fn darwin_window_toggle_maximize(window: void*) -> void {
|
|
453
|
+
raw {
|
|
454
|
+
#ifdef __APPLE__
|
|
455
|
+
@autoreleasepool {
|
|
456
|
+
void (^work)(void) = ^{
|
|
457
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
458
|
+
if (nsWindow) {
|
|
459
|
+
[nsWindow zoom:nil];
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
if ([NSThread isMainThread]) {
|
|
463
|
+
work();
|
|
464
|
+
} else {
|
|
465
|
+
dispatch_sync(dispatch_get_main_queue(), work);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
#endif
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
fn darwin_window_set_title(window: void*, title: string) -> void {
|
|
473
|
+
raw {
|
|
474
|
+
#ifdef __APPLE__
|
|
475
|
+
@autoreleasepool {
|
|
476
|
+
void (^work)(void) = ^{
|
|
477
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
478
|
+
if (nsWindow && title) {
|
|
479
|
+
[nsWindow setTitle:[NSString stringWithUTF8String:(const char*)title]];
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
if ([NSThread isMainThread]) { work(); }
|
|
483
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
484
|
+
}
|
|
485
|
+
#endif
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
fn darwin_window_set_size(window: void*, width: int, height: int) -> void {
|
|
490
|
+
raw {
|
|
491
|
+
#ifdef __APPLE__
|
|
492
|
+
@autoreleasepool {
|
|
493
|
+
void (^work)(void) = ^{
|
|
494
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
495
|
+
if (nsWindow) {
|
|
496
|
+
NSRect frame = [nsWindow frame];
|
|
497
|
+
frame.size = NSMakeSize((CGFloat)width, (CGFloat)height);
|
|
498
|
+
[nsWindow setFrame:frame display:YES animate:YES];
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
if ([NSThread isMainThread]) { work(); }
|
|
502
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
503
|
+
}
|
|
504
|
+
#endif
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fn darwin_window_set_position(window: void*, x: int, y: int) -> void {
|
|
509
|
+
raw {
|
|
510
|
+
#ifdef __APPLE__
|
|
511
|
+
@autoreleasepool {
|
|
512
|
+
void (^work)(void) = ^{
|
|
513
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
514
|
+
if (nsWindow) {
|
|
515
|
+
[nsWindow setFrameOrigin:NSMakePoint((CGFloat)x, (CGFloat)y)];
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
if ([NSThread isMainThread]) { work(); }
|
|
519
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
520
|
+
}
|
|
521
|
+
#endif
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
fn darwin_window_set_fullscreen(window: void*, on: bool) -> void {
|
|
526
|
+
raw {
|
|
527
|
+
#ifdef __APPLE__
|
|
528
|
+
@autoreleasepool {
|
|
529
|
+
void (^work)(void) = ^{
|
|
530
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
531
|
+
if (nsWindow) {
|
|
532
|
+
BOOL isFS = ([nsWindow styleMask] & NSWindowStyleMaskFullScreen) != 0;
|
|
533
|
+
if ((on && !isFS) || (!on && isFS)) {
|
|
534
|
+
[nsWindow toggleFullScreen:nil];
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
if ([NSThread isMainThread]) { work(); }
|
|
539
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
540
|
+
}
|
|
541
|
+
#endif
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
fn darwin_window_set_always_on_top(window: void*, on: bool) -> void {
|
|
546
|
+
raw {
|
|
547
|
+
#ifdef __APPLE__
|
|
548
|
+
@autoreleasepool {
|
|
549
|
+
void (^work)(void) = ^{
|
|
550
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
551
|
+
if (nsWindow) {
|
|
552
|
+
[nsWindow setLevel:on ? NSFloatingWindowLevel : NSNormalWindowLevel];
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
if ([NSThread isMainThread]) { work(); }
|
|
556
|
+
else { dispatch_sync(dispatch_get_main_queue(), work); }
|
|
557
|
+
}
|
|
558
|
+
#endif
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
fn darwin_window_get_size(window: void*) -> WindowSize {
|
|
563
|
+
let s = WindowSize{ width: 0, height: 0 };
|
|
564
|
+
raw {
|
|
565
|
+
#ifdef __APPLE__
|
|
566
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
567
|
+
if (nsWindow) {
|
|
568
|
+
NSRect frame = [nsWindow frame];
|
|
569
|
+
s.width = (int)frame.size.width;
|
|
570
|
+
s.height = (int)frame.size.height;
|
|
571
|
+
}
|
|
572
|
+
#endif
|
|
573
|
+
}
|
|
574
|
+
return s;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
fn darwin_window_get_position(window: void*) -> WindowPosition {
|
|
578
|
+
let p = WindowPosition{ x: 0, y: 0 };
|
|
579
|
+
raw {
|
|
580
|
+
#ifdef __APPLE__
|
|
581
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
582
|
+
if (nsWindow) {
|
|
583
|
+
NSRect frame = [nsWindow frame];
|
|
584
|
+
p.x = (int)frame.origin.x;
|
|
585
|
+
p.y = (int)frame.origin.y;
|
|
586
|
+
}
|
|
587
|
+
#endif
|
|
588
|
+
}
|
|
589
|
+
return p;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
fn darwin_window_is_minimized(window: void*) -> bool {
|
|
593
|
+
raw {
|
|
594
|
+
#ifdef __APPLE__
|
|
595
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
596
|
+
if (nsWindow) return [nsWindow isMiniaturized] ? true : false;
|
|
597
|
+
#endif
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
fn darwin_window_is_maximized(window: void*) -> bool {
|
|
603
|
+
raw {
|
|
604
|
+
#ifdef __APPLE__
|
|
605
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
606
|
+
if (nsWindow) return [nsWindow isZoomed] ? true : false;
|
|
607
|
+
#endif
|
|
608
|
+
}
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
fn darwin_window_is_fullscreen(window: void*) -> bool {
|
|
613
|
+
raw {
|
|
614
|
+
#ifdef __APPLE__
|
|
615
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
616
|
+
if (nsWindow) return ([nsWindow styleMask] & NSWindowStyleMaskFullScreen) != 0;
|
|
617
|
+
#endif
|
|
618
|
+
}
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
fn darwin_window_force_close(window: void*) -> void {
|
|
623
|
+
raw {
|
|
624
|
+
#ifdef __APPLE__
|
|
625
|
+
NSWindow* nsWindow = (__bridge NSWindow*)window;
|
|
626
|
+
if (nsWindow) {
|
|
627
|
+
ZappWindowDelegate* del = (ZappWindowDelegate*)[nsWindow delegate];
|
|
628
|
+
if ([del isKindOfClass:[ZappWindowDelegate class]]) {
|
|
629
|
+
del.forceClose = YES;
|
|
630
|
+
}
|
|
631
|
+
[nsWindow close];
|
|
632
|
+
}
|
|
633
|
+
#endif
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
raw {
|
|
638
|
+
#ifdef __APPLE__
|
|
639
|
+
extern void* app_get_active(void);
|
|
640
|
+
extern void zapp_handle_message(void* app_ptr, char* msg_c);
|
|
641
|
+
extern void window_manager_trigger_on_ready(int window_id);
|
|
642
|
+
|
|
643
|
+
// Strong-reference registry so JS-created windows survive until explicitly closed.
|
|
644
|
+
static NSMutableDictionary<NSString*, NSWindow*>* zapp_window_registry = nil;
|
|
645
|
+
static NSMutableDictionary<NSString*, NSNumber*>* zapp_window_numeric_ids = nil;
|
|
646
|
+
|
|
647
|
+
static void zapp_window_registry_ensure(void) {
|
|
648
|
+
if (zapp_window_registry == nil) {
|
|
649
|
+
zapp_window_registry = [NSMutableDictionary dictionary];
|
|
650
|
+
}
|
|
651
|
+
if (zapp_window_numeric_ids == nil) {
|
|
652
|
+
zapp_window_numeric_ids = [NSMutableDictionary dictionary];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
static NSString* zapp_window_register(NSWindow* window) {
|
|
657
|
+
zapp_window_registry_ensure();
|
|
658
|
+
NSString* windowId = [NSString stringWithFormat:@"win-%p", window];
|
|
659
|
+
[zapp_window_registry setObject:window forKey:windowId];
|
|
660
|
+
return windowId;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
static void zapp_window_register_numeric_id(NSString* windowId, int numericId) {
|
|
664
|
+
zapp_window_registry_ensure();
|
|
665
|
+
[zapp_window_numeric_ids setObject:@(numericId) forKey:windowId];
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
static int zapp_window_get_numeric_id(NSString* windowId) {
|
|
669
|
+
zapp_window_registry_ensure();
|
|
670
|
+
NSNumber* num = [zapp_window_numeric_ids objectForKey:windowId];
|
|
671
|
+
return num ? [num intValue] : -1;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
void zapp_darwin_window_register_numeric_id(const char* window_id, int numeric_id) {
|
|
675
|
+
NSString* windowId = [NSString stringWithUTF8String:window_id];
|
|
676
|
+
zapp_window_register_numeric_id(windowId, numeric_id);
|
|
677
|
+
}
|
|
678
|
+
#endif
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
raw {
|
|
682
|
+
#ifdef __APPLE__
|
|
683
|
+
static void zapp_window_unregister(NSWindow* window) {
|
|
684
|
+
if (zapp_window_registry == nil || window == nil) return;
|
|
685
|
+
NSString* keyToRemove = nil;
|
|
686
|
+
for (NSString* key in zapp_window_registry) {
|
|
687
|
+
if (zapp_window_registry[key] == window) {
|
|
688
|
+
keyToRemove = key;
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (keyToRemove) [zapp_window_registry removeObjectForKey:keyToRemove];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
static NSWindow* zapp_window_lookup(NSString* windowId) {
|
|
696
|
+
zapp_window_registry_ensure();
|
|
697
|
+
if (windowId == nil || [windowId length] == 0) return nil;
|
|
698
|
+
|
|
699
|
+
NSWindow* win = [zapp_window_registry objectForKey:windowId];
|
|
700
|
+
if (win != nil) return win;
|
|
701
|
+
|
|
702
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
703
|
+
NSString* ownerId = [NSString stringWithFormat:@"owner-%p", window];
|
|
704
|
+
if ([ownerId isEqualToString:windowId]) return window;
|
|
705
|
+
}
|
|
706
|
+
return nil;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
extern void zapp_backend_dispatch_window_result(const char* payload_json);
|
|
710
|
+
extern void zapp_backend_dispatch_window_ready(const char* window_id);
|
|
711
|
+
|
|
712
|
+
static void zapp_dispatch_window_result_to_all(const char* payload_json) {
|
|
713
|
+
NSString* payload = [NSString stringWithUTF8String:payload_json];
|
|
714
|
+
payload = [payload stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
|
|
715
|
+
payload = [payload stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
|
716
|
+
NSString* js = [NSString stringWithFormat:
|
|
717
|
+
@"(function(){var b=globalThis[Symbol.for('zapp.bridge')];if(b&&typeof b.dispatchWindowResult==='function'){b.dispatchWindowResult('%@');}})();",
|
|
718
|
+
payload
|
|
719
|
+
];
|
|
720
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
721
|
+
NSView* content = [window contentView];
|
|
722
|
+
if ([content isKindOfClass:[WKWebView class]]) {
|
|
723
|
+
WKWebView* webview = (WKWebView*)content;
|
|
724
|
+
[webview evaluateJavaScript:js completionHandler:nil];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// Also dispatch to backend context
|
|
728
|
+
zapp_backend_dispatch_window_result(payload_json);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
static void zapp_dispatch_dialog_result(const char* payload_json) {
|
|
732
|
+
NSString* payload = [NSString stringWithUTF8String:payload_json];
|
|
733
|
+
payload = [payload stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
|
|
734
|
+
payload = [payload stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
|
735
|
+
NSString* js = [NSString stringWithFormat:
|
|
736
|
+
@"(function(){var b=globalThis[Symbol.for('zapp.bridge')];if(b&&typeof b.dispatchDialogResult==='function'){b.dispatchDialogResult('%@');}})();",
|
|
737
|
+
payload
|
|
738
|
+
];
|
|
739
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
740
|
+
NSView* content = [window contentView];
|
|
741
|
+
if ([content isKindOfClass:[WKWebView class]]) {
|
|
742
|
+
WKWebView* webview = (WKWebView*)content;
|
|
743
|
+
[webview evaluateJavaScript:js completionHandler:nil];
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Event names matching WindowEvent enum in TypeScript runtime
|
|
749
|
+
static const char* zapp_get_window_event_name(int event_id) {
|
|
750
|
+
switch (event_id) {
|
|
751
|
+
case ZAPP_EVENT_WINDOW_READY: return "ready";
|
|
752
|
+
case ZAPP_EVENT_WINDOW_FOCUS: return "focus";
|
|
753
|
+
case ZAPP_EVENT_WINDOW_BLUR: return "blur";
|
|
754
|
+
case ZAPP_EVENT_WINDOW_RESIZE: return "resize";
|
|
755
|
+
case ZAPP_EVENT_WINDOW_MOVE: return "move";
|
|
756
|
+
case ZAPP_EVENT_WINDOW_CLOSE: return "close";
|
|
757
|
+
case ZAPP_EVENT_WINDOW_MINIMIZE: return "minimize";
|
|
758
|
+
case ZAPP_EVENT_WINDOW_MAXIMIZE: return "maximize";
|
|
759
|
+
case ZAPP_EVENT_WINDOW_RESTORE: return "restore";
|
|
760
|
+
case ZAPP_EVENT_WINDOW_FULLSCREEN: return "fullscreen";
|
|
761
|
+
case ZAPP_EVENT_WINDOW_UNFULLSCREEN: return "unfullscreen";
|
|
762
|
+
default: return "unknown";
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
void zapp_dispatch_window_event_to_bridge(const char* window_id, int event_id, int width, int height, int x, int y) {
|
|
767
|
+
if (window_id == NULL) return;
|
|
768
|
+
const char* event_name = zapp_get_window_event_name(event_id);
|
|
769
|
+
NSString* escapedEvent = [NSString stringWithUTF8String:event_name];
|
|
770
|
+
escapedEvent = [escapedEvent stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
|
771
|
+
NSString* windowIdStr = [NSString stringWithUTF8String:window_id];
|
|
772
|
+
|
|
773
|
+
// Only include size/position payload for events where it's meaningful
|
|
774
|
+
BOOL hasPayload = (event_id == ZAPP_EVENT_WINDOW_RESIZE || event_id == ZAPP_EVENT_WINDOW_MOVE ||
|
|
775
|
+
event_id == ZAPP_EVENT_WINDOW_MAXIMIZE || event_id == ZAPP_EVENT_WINDOW_RESTORE);
|
|
776
|
+
NSString* js;
|
|
777
|
+
if (hasPayload) {
|
|
778
|
+
js = [NSString stringWithFormat:
|
|
779
|
+
@"(function(){var b=globalThis[Symbol.for('zapp.bridge')];if(b&&typeof b.dispatchWindowEvent==='function'){b.dispatchWindowEvent('%@','%@','{\"width\":%d,\"height\":%d,\"x\":%d,\"y\":%d}');}})();",
|
|
780
|
+
windowIdStr, escapedEvent, width, height, x, y
|
|
781
|
+
];
|
|
782
|
+
} else {
|
|
783
|
+
js = [NSString stringWithFormat:
|
|
784
|
+
@"(function(){var b=globalThis[Symbol.for('zapp.bridge')];if(b&&typeof b.dispatchWindowEvent==='function'){b.dispatchWindowEvent('%@','%@');}})();",
|
|
785
|
+
windowIdStr, escapedEvent
|
|
786
|
+
];
|
|
787
|
+
}
|
|
788
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
789
|
+
NSView* content = [window contentView];
|
|
790
|
+
if ([content isKindOfClass:[WKWebView class]]) {
|
|
791
|
+
WKWebView* webview = (WKWebView*)content;
|
|
792
|
+
[webview evaluateJavaScript:js completionHandler:nil];
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
void app_handle_window_message(App* app, char* action, char* payload_json) {
|
|
798
|
+
if (action == NULL || payload_json == NULL) return;
|
|
799
|
+
NSString* actionStr = [NSString stringWithUTF8String:action];
|
|
800
|
+
NSString* payload = [NSString stringWithUTF8String:payload_json];
|
|
801
|
+
NSData* payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
|
|
802
|
+
if (payloadData == nil) return;
|
|
803
|
+
NSDictionary* body = [NSJSONSerialization JSONObjectWithData:payloadData options:0 error:nil];
|
|
804
|
+
if (![body isKindOfClass:[NSDictionary class]]) return;
|
|
805
|
+
|
|
806
|
+
if ([actionStr isEqualToString:@"ready"]) {
|
|
807
|
+
NSString* windowId = body[@"windowId"];
|
|
808
|
+
if ([windowId isKindOfClass:[NSString class]]) {
|
|
809
|
+
zapp_backend_dispatch_window_ready([windowId UTF8String]);
|
|
810
|
+
zapp_window_set_bridge_ready([windowId UTF8String]);
|
|
811
|
+
// Trigger native on_ready callback
|
|
812
|
+
int numericId = zapp_window_get_numeric_id(windowId);
|
|
813
|
+
if (numericId >= 0) {
|
|
814
|
+
window_manager_trigger_on_ready(numericId);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if ([actionStr isEqualToString:@"create"]) {
|
|
821
|
+
NSString* requestId = body[@"requestId"];
|
|
822
|
+
NSDictionary* opts = body[@"options"];
|
|
823
|
+
if (![requestId isKindOfClass:[NSString class]] || [requestId length] == 0) return;
|
|
824
|
+
if (![opts isKindOfClass:[NSDictionary class]]) opts = @{};
|
|
825
|
+
|
|
826
|
+
void (^createWork)(void) = ^{
|
|
827
|
+
NSString* title = [opts[@"title"] isKindOfClass:[NSString class]] ? opts[@"title"] : @"Zapp Window";
|
|
828
|
+
CGFloat width = [opts[@"width"] respondsToSelector:@selector(doubleValue)] ? [opts[@"width"] doubleValue] : 800;
|
|
829
|
+
CGFloat height = [opts[@"height"] respondsToSelector:@selector(doubleValue)] ? [opts[@"height"] doubleValue] : 600;
|
|
830
|
+
CGFloat x = [opts[@"x"] respondsToSelector:@selector(doubleValue)] ? [opts[@"x"] doubleValue] : 0;
|
|
831
|
+
CGFloat y = [opts[@"y"] respondsToSelector:@selector(doubleValue)] ? [opts[@"y"] doubleValue] : 0;
|
|
832
|
+
|
|
833
|
+
NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
|
834
|
+
|
|
835
|
+
NSRect frame = NSMakeRect(x, y, width, height);
|
|
836
|
+
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
|
|
837
|
+
styleMask:styleMask
|
|
838
|
+
backing:NSBackingStoreBuffered
|
|
839
|
+
defer:NO];
|
|
840
|
+
[window setReleasedWhenClosed:NO];
|
|
841
|
+
[window setTitle:title];
|
|
842
|
+
|
|
843
|
+
NSString* titleBarStyle = [opts[@"titleBarStyle"] isKindOfClass:[NSString class]] ? opts[@"titleBarStyle"] : @"default";
|
|
844
|
+
if ([titleBarStyle isEqualToString:@"hidden"] || [titleBarStyle isEqualToString:@"hiddenInset"]) {
|
|
845
|
+
[window setStyleMask:([window styleMask] | NSWindowStyleMaskFullSizeContentView)];
|
|
846
|
+
[window setTitleVisibility:NSWindowTitleHidden];
|
|
847
|
+
[window setTitlebarAppearsTransparent:YES];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
extern bool app_get_bootstrap_web_content_inspectable(void);
|
|
851
|
+
zapp_darwin_webview_create((__bridge void*)window, app_get_bootstrap_web_content_inspectable());
|
|
852
|
+
|
|
853
|
+
NSString* windowId = zapp_window_register(window);
|
|
854
|
+
|
|
855
|
+
NSString* ownerId = [NSString stringWithFormat:@"owner-%p", window];
|
|
856
|
+
ZappWindowDelegate* windowDelegate = [[ZappWindowDelegate alloc] init];
|
|
857
|
+
windowDelegate.windowId = windowId;
|
|
858
|
+
windowDelegate.ownerId = ownerId;
|
|
859
|
+
objc_setAssociatedObject(window, &kZappWindowDelegateKey, windowDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
860
|
+
[window setDelegate:windowDelegate];
|
|
861
|
+
|
|
862
|
+
id visible = opts[@"visible"];
|
|
863
|
+
BOOL shouldShow = (visible == nil || ([visible respondsToSelector:@selector(boolValue)] && [visible boolValue]));
|
|
864
|
+
if (shouldShow) {
|
|
865
|
+
[window makeKeyAndOrderFront:nil];
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
NSString* responseJson = [NSString stringWithFormat:
|
|
869
|
+
@"{\"requestId\":\"%@\",\"id\":\"%@\",\"ok\":true}",
|
|
870
|
+
requestId, windowId
|
|
871
|
+
];
|
|
872
|
+
zapp_dispatch_window_result_to_all([responseJson UTF8String]);
|
|
873
|
+
|
|
874
|
+
// If a custom URL was specified, navigate to it (initial URL already loaded by webview create)
|
|
875
|
+
NSString* url = [opts[@"url"] isKindOfClass:[NSString class]] ? opts[@"url"] : nil;
|
|
876
|
+
if (url != nil && [url length] > 0) {
|
|
877
|
+
NSView* content = [window contentView];
|
|
878
|
+
if ([content isKindOfClass:[WKWebView class]]) {
|
|
879
|
+
WKWebView* webview = (WKWebView*)content;
|
|
880
|
+
NSURL* loadUrl;
|
|
881
|
+
if ([url hasPrefix:@"http://"] || [url hasPrefix:@"https://"]) {
|
|
882
|
+
loadUrl = [NSURL URLWithString:url];
|
|
883
|
+
} else {
|
|
884
|
+
// Relative URL: use the same scheme/base as the initial URL
|
|
885
|
+
NSString* baseUrl = [NSString stringWithUTF8String:zapp_build_initial_url()];
|
|
886
|
+
if ([baseUrl hasPrefix:@"http"]) {
|
|
887
|
+
// Dev mode: relative to dev server
|
|
888
|
+
NSURL* base = [NSURL URLWithString:baseUrl];
|
|
889
|
+
loadUrl = [NSURL URLWithString:url relativeToURL:base];
|
|
890
|
+
} else {
|
|
891
|
+
// Prod mode: use zapp:// scheme
|
|
892
|
+
if ([url hasPrefix:@"/"]) {
|
|
893
|
+
loadUrl = [NSURL URLWithString:[NSString stringWithFormat:@"zapp://app%@", url]];
|
|
894
|
+
} else {
|
|
895
|
+
loadUrl = [NSURL URLWithString:[NSString stringWithFormat:@"zapp://app/%@", url]];
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (loadUrl) {
|
|
900
|
+
[webview loadRequest:[NSURLRequest requestWithURL:loadUrl]];
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
if ([NSThread isMainThread]) { createWork(); }
|
|
907
|
+
else { dispatch_async(dispatch_get_main_queue(), createWork); }
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// All other actions operate on an existing window
|
|
912
|
+
NSString* windowId = body[@"windowId"];
|
|
913
|
+
if (![windowId isKindOfClass:[NSString class]] || [windowId length] == 0) return;
|
|
914
|
+
|
|
915
|
+
if ([actionStr isEqualToString:@"setCloseGuard"]) {
|
|
916
|
+
id guardVal = body[@"guard"];
|
|
917
|
+
BOOL guard = [guardVal respondsToSelector:@selector(boolValue)] && [guardVal boolValue];
|
|
918
|
+
void (^work)(void) = ^{
|
|
919
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
920
|
+
NSString* wid = [NSString stringWithFormat:@"win-%p", window];
|
|
921
|
+
if ([wid isEqualToString:windowId]) {
|
|
922
|
+
ZappWindowDelegate* del = (ZappWindowDelegate*)[window delegate];
|
|
923
|
+
if ([del isKindOfClass:[ZappWindowDelegate class]]) {
|
|
924
|
+
del.closeGuarded = guard;
|
|
925
|
+
}
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
if ([NSThread isMainThread]) { work(); }
|
|
931
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if ([actionStr isEqualToString:@"destroy"]) {
|
|
936
|
+
void (^work)(void) = ^{
|
|
937
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
938
|
+
NSString* wid = [NSString stringWithFormat:@"win-%p", window];
|
|
939
|
+
if ([wid isEqualToString:windowId]) {
|
|
940
|
+
ZappWindowDelegate* del = (ZappWindowDelegate*)[window delegate];
|
|
941
|
+
if ([del isKindOfClass:[ZappWindowDelegate class]]) {
|
|
942
|
+
del.forceClose = YES;
|
|
943
|
+
}
|
|
944
|
+
[window close];
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
if ([NSThread isMainThread]) { work(); }
|
|
950
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if ([actionStr isEqualToString:@"setDragRegion"]) {
|
|
955
|
+
id dragVal = body[@"drag"];
|
|
956
|
+
BOOL drag = [dragVal respondsToSelector:@selector(boolValue)] && [dragVal boolValue];
|
|
957
|
+
void (^work)(void) = ^{
|
|
958
|
+
// Find the window by matching its windowId (win-%p format)
|
|
959
|
+
for (NSWindow* window in [NSApp windows]) {
|
|
960
|
+
NSString* wid = [NSString stringWithFormat:@"win-%p", window];
|
|
961
|
+
if ([wid isEqualToString:windowId]) {
|
|
962
|
+
NSView* content = [window contentView];
|
|
963
|
+
if ([content isKindOfClass:[ZappWebView class]]) {
|
|
964
|
+
[(ZappWebView*)content setInDragRegion:drag];
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
if ([NSThread isMainThread]) { work(); }
|
|
971
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
void (^actionWork)(void) = ^{
|
|
976
|
+
NSWindow* nsWindow = zapp_window_lookup(windowId);
|
|
977
|
+
if (nsWindow == nil) return;
|
|
978
|
+
|
|
979
|
+
if ([actionStr isEqualToString:@"close"]) {
|
|
980
|
+
[nsWindow close];
|
|
981
|
+
} else if ([actionStr isEqualToString:@"show"]) {
|
|
982
|
+
[nsWindow makeKeyAndOrderFront:nil];
|
|
983
|
+
} else if ([actionStr isEqualToString:@"hide"]) {
|
|
984
|
+
[nsWindow orderOut:nil];
|
|
985
|
+
} else if ([actionStr isEqualToString:@"minimize"]) {
|
|
986
|
+
[nsWindow miniaturize:nil];
|
|
987
|
+
} else if ([actionStr isEqualToString:@"maximize"]) {
|
|
988
|
+
[nsWindow zoom:nil];
|
|
989
|
+
} else if ([actionStr isEqualToString:@"unminimize"]) {
|
|
990
|
+
[nsWindow deminiaturize:nil];
|
|
991
|
+
} else if ([actionStr isEqualToString:@"unmaximize"]) {
|
|
992
|
+
if ([nsWindow isZoomed]) [nsWindow zoom:nil];
|
|
993
|
+
} else if ([actionStr isEqualToString:@"toggle_minimize"]) {
|
|
994
|
+
if ([nsWindow isMiniaturized]) [nsWindow deminiaturize:nil];
|
|
995
|
+
else [nsWindow miniaturize:nil];
|
|
996
|
+
} else if ([actionStr isEqualToString:@"toggle_maximize"]) {
|
|
997
|
+
[nsWindow zoom:nil];
|
|
998
|
+
} else if ([actionStr isEqualToString:@"set_title"]) {
|
|
999
|
+
NSString* title = body[@"title"];
|
|
1000
|
+
if ([title isKindOfClass:[NSString class]]) [nsWindow setTitle:title];
|
|
1001
|
+
} else if ([actionStr isEqualToString:@"set_size"]) {
|
|
1002
|
+
NSNumber* w = body[@"width"];
|
|
1003
|
+
NSNumber* h = body[@"height"];
|
|
1004
|
+
if ([w respondsToSelector:@selector(doubleValue)] && [h respondsToSelector:@selector(doubleValue)]) {
|
|
1005
|
+
NSRect frame = [nsWindow frame];
|
|
1006
|
+
frame.size = NSMakeSize([w doubleValue], [h doubleValue]);
|
|
1007
|
+
[nsWindow setFrame:frame display:YES animate:YES];
|
|
1008
|
+
}
|
|
1009
|
+
} else if ([actionStr isEqualToString:@"set_position"]) {
|
|
1010
|
+
NSNumber* px = body[@"x"];
|
|
1011
|
+
NSNumber* py = body[@"y"];
|
|
1012
|
+
if ([px respondsToSelector:@selector(doubleValue)] && [py respondsToSelector:@selector(doubleValue)]) {
|
|
1013
|
+
[nsWindow setFrameOrigin:NSMakePoint([px doubleValue], [py doubleValue])];
|
|
1014
|
+
}
|
|
1015
|
+
} else if ([actionStr isEqualToString:@"set_fullscreen"]) {
|
|
1016
|
+
id onVal = body[@"on"];
|
|
1017
|
+
if ([onVal respondsToSelector:@selector(boolValue)]) {
|
|
1018
|
+
BOOL on = [onVal boolValue];
|
|
1019
|
+
BOOL isFS = ([nsWindow styleMask] & NSWindowStyleMaskFullScreen) != 0;
|
|
1020
|
+
if ((on && !isFS) || (!on && isFS)) {
|
|
1021
|
+
[nsWindow toggleFullScreen:nil];
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
} else if ([actionStr isEqualToString:@"set_always_on_top"]) {
|
|
1025
|
+
id onVal = body[@"on"];
|
|
1026
|
+
if ([onVal respondsToSelector:@selector(boolValue)]) {
|
|
1027
|
+
[nsWindow setLevel:[onVal boolValue] ? NSFloatingWindowLevel : NSNormalWindowLevel];
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
if ([NSThread isMainThread]) { actionWork(); }
|
|
1033
|
+
else { dispatch_async(dispatch_get_main_queue(), actionWork); }
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
void app_handle_app_action(App* app, char* action) {
|
|
1037
|
+
(void)app;
|
|
1038
|
+
if (action == NULL) return;
|
|
1039
|
+
NSString* actionStr = [NSString stringWithUTF8String:action];
|
|
1040
|
+
|
|
1041
|
+
void (^work)(void) = ^{
|
|
1042
|
+
if ([actionStr isEqualToString:@"quit"]) {
|
|
1043
|
+
[NSApp terminate:nil];
|
|
1044
|
+
} else if ([actionStr isEqualToString:@"hide"]) {
|
|
1045
|
+
[NSApp hide:nil];
|
|
1046
|
+
} else if ([actionStr isEqualToString:@"show"]) {
|
|
1047
|
+
[NSApp unhide:nil];
|
|
1048
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
if ([NSThread isMainThread]) { work(); }
|
|
1053
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// --- Open external URL ---
|
|
1057
|
+
void app_open_external(App* app, char* payload) {
|
|
1058
|
+
(void)app;
|
|
1059
|
+
if (payload == NULL) return;
|
|
1060
|
+
NSString* jsonStr = [NSString stringWithUTF8String:(const char*)payload];
|
|
1061
|
+
NSData* data = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
|
|
1062
|
+
if (data == nil) return;
|
|
1063
|
+
NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
1064
|
+
if (![dict isKindOfClass:[NSDictionary class]]) return;
|
|
1065
|
+
NSString* urlStr = dict[@"url"];
|
|
1066
|
+
if (![urlStr isKindOfClass:[NSString class]] || [urlStr length] == 0) return;
|
|
1067
|
+
NSURL* url = [NSURL URLWithString:urlStr];
|
|
1068
|
+
if (url) {
|
|
1069
|
+
[[NSWorkspace sharedWorkspace] openURL:url];
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// --- Dialog handling ---
|
|
1074
|
+
|
|
1075
|
+
void app_handle_dialog(App* app, char* action, char* payload_json) {
|
|
1076
|
+
(void)app;
|
|
1077
|
+
if (action == NULL || payload_json == NULL) return;
|
|
1078
|
+
NSString* actionStr = [NSString stringWithUTF8String:action];
|
|
1079
|
+
NSString* payload = [NSString stringWithUTF8String:payload_json];
|
|
1080
|
+
NSData* payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
|
|
1081
|
+
if (payloadData == nil) return;
|
|
1082
|
+
NSDictionary* body = [NSJSONSerialization JSONObjectWithData:payloadData options:0 error:nil];
|
|
1083
|
+
if (![body isKindOfClass:[NSDictionary class]]) return;
|
|
1084
|
+
|
|
1085
|
+
NSString* requestId = body[@"requestId"];
|
|
1086
|
+
if (![requestId isKindOfClass:[NSString class]] || [requestId length] == 0) return;
|
|
1087
|
+
|
|
1088
|
+
if ([actionStr isEqualToString:@"openFile"]) {
|
|
1089
|
+
void (^work)(void) = ^{
|
|
1090
|
+
NSOpenPanel* panel = [NSOpenPanel openPanel];
|
|
1091
|
+
NSString* title = body[@"title"];
|
|
1092
|
+
if ([title isKindOfClass:[NSString class]]) [panel setTitle:title];
|
|
1093
|
+
id multiVal = body[@"multiple"];
|
|
1094
|
+
if ([multiVal respondsToSelector:@selector(boolValue)]) {
|
|
1095
|
+
[panel setAllowsMultipleSelection:[multiVal boolValue]];
|
|
1096
|
+
}
|
|
1097
|
+
id dirVal = body[@"directory"];
|
|
1098
|
+
if ([dirVal respondsToSelector:@selector(boolValue)] && [dirVal boolValue]) {
|
|
1099
|
+
[panel setCanChooseDirectories:YES];
|
|
1100
|
+
[panel setCanChooseFiles:NO];
|
|
1101
|
+
} else {
|
|
1102
|
+
[panel setCanChooseDirectories:NO];
|
|
1103
|
+
[panel setCanChooseFiles:YES];
|
|
1104
|
+
}
|
|
1105
|
+
NSArray* filters = body[@"filters"];
|
|
1106
|
+
if ([filters isKindOfClass:[NSArray class]] && [filters count] > 0) {
|
|
1107
|
+
NSMutableArray<NSString*>* extensions = [NSMutableArray array];
|
|
1108
|
+
for (NSDictionary* filter in filters) {
|
|
1109
|
+
NSArray* exts = filter[@"extensions"];
|
|
1110
|
+
if ([exts isKindOfClass:[NSArray class]]) {
|
|
1111
|
+
for (NSString* ext in exts) {
|
|
1112
|
+
if ([ext isKindOfClass:[NSString class]]) {
|
|
1113
|
+
[extensions addObject:ext];
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if ([extensions count] > 0) {
|
|
1119
|
+
// Use allowedFileTypes (available on all macOS versions)
|
|
1120
|
+
[panel setAllowedFileTypes:extensions];
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
NSString* defaultPath = body[@"defaultPath"];
|
|
1124
|
+
if ([defaultPath isKindOfClass:[NSString class]] && [defaultPath length] > 0) {
|
|
1125
|
+
[panel setDirectoryURL:[NSURL fileURLWithPath:defaultPath]];
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
NSModalResponse result = [panel runModal];
|
|
1129
|
+
NSString* responseJson;
|
|
1130
|
+
if (result == NSModalResponseOK) {
|
|
1131
|
+
NSMutableArray<NSString*>* paths = [NSMutableArray array];
|
|
1132
|
+
for (NSURL* url in [panel URLs]) {
|
|
1133
|
+
[paths addObject:[url path]];
|
|
1134
|
+
}
|
|
1135
|
+
NSData* pathsData = [NSJSONSerialization dataWithJSONObject:paths options:0 error:nil];
|
|
1136
|
+
NSString* pathsStr = [[NSString alloc] initWithData:pathsData encoding:NSUTF8StringEncoding];
|
|
1137
|
+
responseJson = [NSString stringWithFormat:
|
|
1138
|
+
@"{\"requestId\":\"%@\",\"ok\":true,\"paths\":%@}",
|
|
1139
|
+
requestId, pathsStr];
|
|
1140
|
+
} else {
|
|
1141
|
+
responseJson = [NSString stringWithFormat:
|
|
1142
|
+
@"{\"requestId\":\"%@\",\"ok\":false,\"cancelled\":true}",
|
|
1143
|
+
requestId];
|
|
1144
|
+
}
|
|
1145
|
+
zapp_dispatch_dialog_result([responseJson UTF8String]);
|
|
1146
|
+
};
|
|
1147
|
+
if ([NSThread isMainThread]) { work(); }
|
|
1148
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if ([actionStr isEqualToString:@"saveFile"]) {
|
|
1153
|
+
void (^work)(void) = ^{
|
|
1154
|
+
NSSavePanel* panel = [NSSavePanel savePanel];
|
|
1155
|
+
NSString* title = body[@"title"];
|
|
1156
|
+
if ([title isKindOfClass:[NSString class]]) [panel setTitle:title];
|
|
1157
|
+
NSString* defaultName = body[@"defaultName"];
|
|
1158
|
+
if ([defaultName isKindOfClass:[NSString class]] && [defaultName length] > 0) {
|
|
1159
|
+
[panel setNameFieldStringValue:defaultName];
|
|
1160
|
+
}
|
|
1161
|
+
NSString* defaultPath = body[@"defaultPath"];
|
|
1162
|
+
if ([defaultPath isKindOfClass:[NSString class]] && [defaultPath length] > 0) {
|
|
1163
|
+
[panel setDirectoryURL:[NSURL fileURLWithPath:defaultPath]];
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
NSModalResponse result = [panel runModal];
|
|
1167
|
+
NSString* responseJson;
|
|
1168
|
+
if (result == NSModalResponseOK && [panel URL]) {
|
|
1169
|
+
NSString* path = [[panel URL] path];
|
|
1170
|
+
NSString* escaped = [path stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
|
|
1171
|
+
responseJson = [NSString stringWithFormat:
|
|
1172
|
+
@"{\"requestId\":\"%@\",\"ok\":true,\"path\":\"%@\"}",
|
|
1173
|
+
requestId, escaped];
|
|
1174
|
+
} else {
|
|
1175
|
+
responseJson = [NSString stringWithFormat:
|
|
1176
|
+
@"{\"requestId\":\"%@\",\"ok\":false,\"cancelled\":true}",
|
|
1177
|
+
requestId];
|
|
1178
|
+
}
|
|
1179
|
+
zapp_dispatch_dialog_result([responseJson UTF8String]);
|
|
1180
|
+
};
|
|
1181
|
+
if ([NSThread isMainThread]) { work(); }
|
|
1182
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if ([actionStr isEqualToString:@"message"]) {
|
|
1187
|
+
void (^work)(void) = ^{
|
|
1188
|
+
NSAlert* alert = [[NSAlert alloc] init];
|
|
1189
|
+
NSString* title = body[@"title"];
|
|
1190
|
+
NSString* message = body[@"message"];
|
|
1191
|
+
NSString* kind = body[@"kind"];
|
|
1192
|
+
NSArray* buttons = body[@"buttons"];
|
|
1193
|
+
if ([title isKindOfClass:[NSString class]]) [alert setMessageText:title];
|
|
1194
|
+
if ([message isKindOfClass:[NSString class]]) [alert setInformativeText:message];
|
|
1195
|
+
if ([kind isKindOfClass:[NSString class]]) {
|
|
1196
|
+
if ([kind isEqualToString:@"warning"]) [alert setAlertStyle:NSAlertStyleWarning];
|
|
1197
|
+
else if ([kind isEqualToString:@"critical"]) [alert setAlertStyle:NSAlertStyleCritical];
|
|
1198
|
+
else [alert setAlertStyle:NSAlertStyleInformational];
|
|
1199
|
+
}
|
|
1200
|
+
if ([buttons isKindOfClass:[NSArray class]]) {
|
|
1201
|
+
for (NSString* btn in buttons) {
|
|
1202
|
+
if ([btn isKindOfClass:[NSString class]]) {
|
|
1203
|
+
[alert addButtonWithTitle:btn];
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if ([[alert buttons] count] == 0) {
|
|
1208
|
+
[alert addButtonWithTitle:@"OK"];
|
|
1209
|
+
}
|
|
1210
|
+
NSModalResponse result = [alert runModal];
|
|
1211
|
+
int buttonIndex = (int)(result - NSAlertFirstButtonReturn);
|
|
1212
|
+
NSString* responseJson = [NSString stringWithFormat:
|
|
1213
|
+
@"{\"requestId\":\"%@\",\"ok\":true,\"button\":%d}",
|
|
1214
|
+
requestId, buttonIndex];
|
|
1215
|
+
zapp_dispatch_dialog_result([responseJson UTF8String]);
|
|
1216
|
+
};
|
|
1217
|
+
if ([NSThread isMainThread]) { work(); }
|
|
1218
|
+
else { dispatch_async(dispatch_get_main_queue(), work); }
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
#endif
|
|
1223
|
+
}
|