@zogien/native-sdk 0.11.0-fork.4 → 0.11.0-fork.5
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/build/app.zig +50 -34
- package/package.json +9 -9
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/skill-data/core/references/bridge-security-native-capabilities.md +4 -0
- package/src/platform/linux/cef_host.cpp +22 -0
- package/src/platform/linux/gtk_host.c +317 -34
- package/src/platform/linux/gtk_host.h +37 -0
- package/src/platform/linux/root.zig +114 -20
- package/src/platform/macos/appkit_host.h +44 -0
- package/src/platform/macos/appkit_host.m +301 -67
- package/src/platform/macos/cef_host.mm +21 -0
- package/src/platform/macos/root.zig +117 -23
- package/src/platform/null_platform.zig +136 -0
- package/src/platform/root.zig +6 -0
- package/src/platform/types.zig +81 -0
- package/src/platform/windows/dialog_test.cpp +303 -0
- package/src/platform/windows/host_loop_test.cpp +169 -0
- package/src/platform/windows/root.zig +225 -39
- package/src/platform/windows/session_end_test.cpp +303 -0
- package/src/platform/windows/webview2_host.cpp +1064 -280
- package/src/root.zig +7 -0
- package/src/runtime/builtin_bridge.zig +108 -13
- package/src/runtime/core.zig +24 -3
- package/src/runtime/dialog_tests.zig +367 -0
- package/src/runtime/dialogs.zig +385 -0
- package/src/runtime/effects.zig +71 -2
- package/src/runtime/effects_host_tests.zig +69 -1
- package/src/runtime/flow.zig +14 -6
- package/src/runtime/flow_reference.zig +14 -6
- package/src/runtime/platform_bridge_tests.zig +1 -0
- package/src/runtime/root.zig +5 -0
- package/src/runtime/system_services.zig +64 -82
- package/src/runtime/ui_app.zig +67 -0
- package/tools/native-sdk/main.zig +1 -1
|
@@ -817,6 +817,26 @@ static int NativeSdkCredentialStatus(OSStatus status, int missingCode) {
|
|
|
817
817
|
@property(nonatomic, strong) NSString *openCommand;
|
|
818
818
|
@end
|
|
819
819
|
|
|
820
|
+
/* One asynchronous dialog (native_sdk_appkit_request_dialog): what is
|
|
821
|
+
* showing, where, and once it finishes, the result poll hands back. */
|
|
822
|
+
@interface NativeSdkDialog : NSObject
|
|
823
|
+
@property(nonatomic, assign) uint64_t dialogId;
|
|
824
|
+
@property(nonatomic, assign) uint64_t parentWindowId;
|
|
825
|
+
@property(nonatomic, strong) NSSavePanel *panel;
|
|
826
|
+
@property(nonatomic, strong) NSAlert *alert;
|
|
827
|
+
/* The sheet's window, when the dialog is attached to one. */
|
|
828
|
+
@property(nonatomic, weak) NSWindow *sheetParent;
|
|
829
|
+
/* A parentless alert runs modally from the next loop turn. */
|
|
830
|
+
@property(nonatomic, assign) BOOL modalRunning;
|
|
831
|
+
@property(nonatomic, assign) BOOL cancelRequested;
|
|
832
|
+
@property(nonatomic, assign) int outcome;
|
|
833
|
+
@property(nonatomic, assign) int button;
|
|
834
|
+
@property(nonatomic, strong) NSArray<NSString *> *paths;
|
|
835
|
+
@end
|
|
836
|
+
|
|
837
|
+
@implementation NativeSdkDialog
|
|
838
|
+
@end
|
|
839
|
+
|
|
820
840
|
@interface NativeSdkAppKitHost : NSObject <WKNavigationDelegate, NSMenuDelegate, NSUserNotificationCenterDelegate>
|
|
821
841
|
@property(nonatomic, strong) NSWindow *window;
|
|
822
842
|
@property(nonatomic, strong) WKWebView *webView;
|
|
@@ -881,6 +901,10 @@ static int NativeSdkCredentialStatus(OSStatus status, int missingCode) {
|
|
|
881
901
|
* block starts still gets its own turn. */
|
|
882
902
|
@property(atomic, assign) BOOL crossThreadFramePending;
|
|
883
903
|
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NSTimer *> *appTimers;
|
|
904
|
+
/* Asynchronous dialogs by id while presented, and finished ones in the
|
|
905
|
+
* order they finished, awaiting poll. */
|
|
906
|
+
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NativeSdkDialog *> *dialogs;
|
|
907
|
+
@property(nonatomic, strong) NSMutableArray<NativeSdkDialog *> *finishedDialogs;
|
|
884
908
|
/* Notification userInfo persists with the delivered notification, so it
|
|
885
909
|
* carries only a per-process token. Commands remain in this live host;
|
|
886
910
|
* stale notifications from an earlier execution cannot dispatch into a
|
|
@@ -1242,6 +1266,25 @@ static int NativeSdkCredentialStatus(OSStatus status, int missingCode) {
|
|
|
1242
1266
|
- (void)emitShortcutWithId:(NSString *)identifier key:(NSString *)key modifiers:(uint32_t)modifiers event:(NSEvent *)event;
|
|
1243
1267
|
@end
|
|
1244
1268
|
|
|
1269
|
+
static void NativeSdkCancelDialogsForWindow(NativeSdkAppKitHost *host, uint64_t windowId);
|
|
1270
|
+
static void NativeSdkCancelAllDialogs(NativeSdkAppKitHost *host);
|
|
1271
|
+
|
|
1272
|
+
/* [NSApp stop:] and stopModal take effect only after the loop handles its
|
|
1273
|
+
* next event; post an empty one so a stop requested from a timer or block
|
|
1274
|
+
* ends the loop now. */
|
|
1275
|
+
static void NativeSdkPostEmptyEvent(void) {
|
|
1276
|
+
NSEvent *event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
|
1277
|
+
location:NSZeroPoint
|
|
1278
|
+
modifierFlags:0
|
|
1279
|
+
timestamp:0
|
|
1280
|
+
windowNumber:0
|
|
1281
|
+
context:nil
|
|
1282
|
+
subtype:0
|
|
1283
|
+
data1:0
|
|
1284
|
+
data2:0];
|
|
1285
|
+
[NSApp postEvent:event atStart:NO];
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1245
1288
|
// Recursively re-emit the gpu-surface resize event for every metal
|
|
1246
1289
|
// surface under `view` (the tall-titlebar chrome re-query path).
|
|
1247
1290
|
static void NativeSdkEmitGpuSurfaceResizes(NSView *view) {
|
|
@@ -1423,6 +1466,9 @@ static NSPoint NativeSdkViewLocalYDownPoint(NSView *view, NSPoint point) {
|
|
|
1423
1466
|
|
|
1424
1467
|
- (void)windowWillClose:(NSNotification *)notification {
|
|
1425
1468
|
(void)notification;
|
|
1469
|
+
// End this window's sheets while it can still take them down; a
|
|
1470
|
+
// sheet on a closed window never calls its completion handler.
|
|
1471
|
+
NativeSdkCancelDialogsForWindow(self.host, self.windowId);
|
|
1426
1472
|
if (self.observesContentLayout) {
|
|
1427
1473
|
NSWindow *window = self.host.windows[@(self.windowId)];
|
|
1428
1474
|
[window removeObserver:self forKeyPath:@"contentLayoutRect"];
|
|
@@ -8182,6 +8228,8 @@ static float NativeSdkCaptureReadRemixedSample(const AudioBufferList *buffers, c
|
|
|
8182
8228
|
self.nativeViewExplicitTextKeys = [[NSMutableSet alloc] init];
|
|
8183
8229
|
self.bridgeEnabledChildWebViewKeys = [[NSMutableSet alloc] init];
|
|
8184
8230
|
self.appTimers = [[NSMutableDictionary alloc] init];
|
|
8231
|
+
self.dialogs = [[NSMutableDictionary alloc] init];
|
|
8232
|
+
self.finishedDialogs = [[NSMutableArray alloc] init];
|
|
8185
8233
|
self.notificationActionCommands = [[NSMutableDictionary alloc] init];
|
|
8186
8234
|
self.notificationActionTokensByIdentifier = [[NSMutableDictionary alloc] init];
|
|
8187
8235
|
self.statusItems = [[NSMutableDictionary alloc] init];
|
|
@@ -8497,6 +8545,9 @@ static float NativeSdkCaptureReadRemixedSample(const AudioBufferList *buffers, c
|
|
|
8497
8545
|
- (void)closeWindowWithId:(uint64_t)windowId {
|
|
8498
8546
|
NSWindow *window = self.windows[@(windowId)];
|
|
8499
8547
|
if (!window) return;
|
|
8548
|
+
// performClose: refuses a window with a sheet attached, so end this
|
|
8549
|
+
// window's dialogs first (Electron's NativeWindowMac::Close does too).
|
|
8550
|
+
NativeSdkCancelDialogsForWindow(self, windowId);
|
|
8500
8551
|
// A runtime-initiated close is the app DECIDING to close: it must
|
|
8501
8552
|
// not bounce off the window's own .hide close policy, so it skips
|
|
8502
8553
|
// performClose: (which runs windowShouldClose:) and closes
|
|
@@ -10766,17 +10817,9 @@ static NSString *NativeSdkSigningTeamIdentifier(NSString *bundlePath) {
|
|
|
10766
10817
|
}
|
|
10767
10818
|
[self stopAppearanceObservers];
|
|
10768
10819
|
[self stopApplicationActivationObservers];
|
|
10820
|
+
NativeSdkCancelAllDialogs(self);
|
|
10769
10821
|
[NSApp stop:nil];
|
|
10770
|
-
|
|
10771
|
-
location:NSZeroPoint
|
|
10772
|
-
modifierFlags:0
|
|
10773
|
-
timestamp:0
|
|
10774
|
-
windowNumber:0
|
|
10775
|
-
context:nil
|
|
10776
|
-
subtype:0
|
|
10777
|
-
data1:0
|
|
10778
|
-
data2:0];
|
|
10779
|
-
[NSApp postEvent:event atStart:NO];
|
|
10822
|
+
NativeSdkPostEmptyEvent();
|
|
10780
10823
|
}
|
|
10781
10824
|
|
|
10782
10825
|
/* Drain the quit verb a pre-run dispatch parked (see
|
|
@@ -14358,23 +14401,71 @@ static void NativeSdkConfigurePanelExtensions(NSSavePanel *panel, NSArray<NSStri
|
|
|
14358
14401
|
}
|
|
14359
14402
|
}
|
|
14360
14403
|
|
|
14404
|
+
static NSString *NativeSdkDialogString(const char *bytes, size_t len) {
|
|
14405
|
+
if (!bytes || len == 0) return nil;
|
|
14406
|
+
return [[NSString alloc] initWithBytes:bytes length:len encoding:NSUTF8StringEncoding];
|
|
14407
|
+
}
|
|
14408
|
+
|
|
14409
|
+
static NSOpenPanel *NativeSdkMakeOpenPanel(const native_sdk_appkit_open_dialog_opts_t *opts) {
|
|
14410
|
+
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
14411
|
+
NSString *title = NativeSdkDialogString(opts->title, opts->title_len);
|
|
14412
|
+
if (title) panel.title = title;
|
|
14413
|
+
NSString *path = NativeSdkDialogString(opts->default_path, opts->default_path_len);
|
|
14414
|
+
if (path) panel.directoryURL = [NSURL fileURLWithPath:path];
|
|
14415
|
+
panel.canChooseFiles = opts->allow_directories == 0;
|
|
14416
|
+
panel.canChooseDirectories = opts->allow_directories != 0;
|
|
14417
|
+
panel.allowsMultipleSelection = opts->allow_multiple != 0;
|
|
14418
|
+
NativeSdkConfigurePanelExtensions(panel, NativeSdkParseExtensions(opts->extensions, opts->extensions_len));
|
|
14419
|
+
return panel;
|
|
14420
|
+
}
|
|
14421
|
+
|
|
14422
|
+
static NSSavePanel *NativeSdkMakeSavePanel(const native_sdk_appkit_save_dialog_opts_t *opts) {
|
|
14423
|
+
NSSavePanel *panel = [NSSavePanel savePanel];
|
|
14424
|
+
NSString *title = NativeSdkDialogString(opts->title, opts->title_len);
|
|
14425
|
+
if (title) panel.title = title;
|
|
14426
|
+
NSString *path = NativeSdkDialogString(opts->default_path, opts->default_path_len);
|
|
14427
|
+
if (path) panel.directoryURL = [NSURL fileURLWithPath:path];
|
|
14428
|
+
NSString *name = NativeSdkDialogString(opts->default_name, opts->default_name_len);
|
|
14429
|
+
if (name) panel.nameFieldStringValue = name;
|
|
14430
|
+
NativeSdkConfigurePanelExtensions(panel, NativeSdkParseExtensions(opts->extensions, opts->extensions_len));
|
|
14431
|
+
return panel;
|
|
14432
|
+
}
|
|
14433
|
+
|
|
14434
|
+
static NSAlert *NativeSdkMakeAlert(const native_sdk_appkit_message_dialog_opts_t *opts) {
|
|
14435
|
+
NSAlert *alert = [[NSAlert alloc] init];
|
|
14436
|
+
switch (opts->style) {
|
|
14437
|
+
case 1: alert.alertStyle = NSAlertStyleWarning; break;
|
|
14438
|
+
case 2: alert.alertStyle = NSAlertStyleCritical; break;
|
|
14439
|
+
default: alert.alertStyle = NSAlertStyleInformational; break;
|
|
14440
|
+
}
|
|
14441
|
+
NSString *title = NativeSdkDialogString(opts->title, opts->title_len);
|
|
14442
|
+
NSString *message = NativeSdkDialogString(opts->message, opts->message_len);
|
|
14443
|
+
NSString *informative = NativeSdkDialogString(opts->informative_text, opts->informative_text_len);
|
|
14444
|
+
if (message.length > 0) {
|
|
14445
|
+
alert.messageText = message;
|
|
14446
|
+
} else if (title.length > 0) {
|
|
14447
|
+
alert.messageText = title;
|
|
14448
|
+
}
|
|
14449
|
+
if (informative.length > 0) {
|
|
14450
|
+
alert.informativeText = informative;
|
|
14451
|
+
}
|
|
14452
|
+
if (opts->message && opts->message_len > 0) {
|
|
14453
|
+
alert.window.title = title.length > 0 ? title : @"";
|
|
14454
|
+
}
|
|
14455
|
+
NSString *primary = NativeSdkDialogString(opts->primary_button, opts->primary_button_len);
|
|
14456
|
+
[alert addButtonWithTitle:primary ?: @"OK"];
|
|
14457
|
+
NSString *secondary = NativeSdkDialogString(opts->secondary_button, opts->secondary_button_len);
|
|
14458
|
+
if (secondary) [alert addButtonWithTitle:secondary];
|
|
14459
|
+
NSString *tertiary = NativeSdkDialogString(opts->tertiary_button, opts->tertiary_button_len);
|
|
14460
|
+
if (tertiary) [alert addButtonWithTitle:tertiary];
|
|
14461
|
+
return alert;
|
|
14462
|
+
}
|
|
14463
|
+
|
|
14361
14464
|
native_sdk_appkit_open_dialog_result_t native_sdk_appkit_show_open_dialog(native_sdk_appkit_host_t *host, const native_sdk_appkit_open_dialog_opts_t *opts, char *buffer, size_t buffer_len) {
|
|
14362
14465
|
(void)host;
|
|
14363
14466
|
native_sdk_appkit_open_dialog_result_t result = { .count = 0, .bytes_written = 0 };
|
|
14364
14467
|
@autoreleasepool {
|
|
14365
|
-
NSOpenPanel *panel =
|
|
14366
|
-
if (opts->title && opts->title_len > 0) {
|
|
14367
|
-
panel.title = [[NSString alloc] initWithBytes:opts->title length:opts->title_len encoding:NSUTF8StringEncoding];
|
|
14368
|
-
}
|
|
14369
|
-
if (opts->default_path && opts->default_path_len > 0) {
|
|
14370
|
-
NSString *path = [[NSString alloc] initWithBytes:opts->default_path length:opts->default_path_len encoding:NSUTF8StringEncoding];
|
|
14371
|
-
panel.directoryURL = [NSURL fileURLWithPath:path];
|
|
14372
|
-
}
|
|
14373
|
-
panel.canChooseFiles = opts->allow_directories == 0;
|
|
14374
|
-
panel.canChooseDirectories = opts->allow_directories != 0;
|
|
14375
|
-
panel.allowsMultipleSelection = opts->allow_multiple != 0;
|
|
14376
|
-
NativeSdkConfigurePanelExtensions(panel, NativeSdkParseExtensions(opts->extensions, opts->extensions_len));
|
|
14377
|
-
|
|
14468
|
+
NSOpenPanel *panel = NativeSdkMakeOpenPanel(opts);
|
|
14378
14469
|
if ([panel runModal] != NSModalResponseOK) return result;
|
|
14379
14470
|
|
|
14380
14471
|
size_t offset = 0;
|
|
@@ -14401,19 +14492,7 @@ native_sdk_appkit_open_dialog_result_t native_sdk_appkit_show_open_dialog(native
|
|
|
14401
14492
|
size_t native_sdk_appkit_show_save_dialog(native_sdk_appkit_host_t *host, const native_sdk_appkit_save_dialog_opts_t *opts, char *buffer, size_t buffer_len) {
|
|
14402
14493
|
(void)host;
|
|
14403
14494
|
@autoreleasepool {
|
|
14404
|
-
NSSavePanel *panel =
|
|
14405
|
-
if (opts->title && opts->title_len > 0) {
|
|
14406
|
-
panel.title = [[NSString alloc] initWithBytes:opts->title length:opts->title_len encoding:NSUTF8StringEncoding];
|
|
14407
|
-
}
|
|
14408
|
-
if (opts->default_path && opts->default_path_len > 0) {
|
|
14409
|
-
NSString *path = [[NSString alloc] initWithBytes:opts->default_path length:opts->default_path_len encoding:NSUTF8StringEncoding];
|
|
14410
|
-
panel.directoryURL = [NSURL fileURLWithPath:path];
|
|
14411
|
-
}
|
|
14412
|
-
if (opts->default_name && opts->default_name_len > 0) {
|
|
14413
|
-
panel.nameFieldStringValue = [[NSString alloc] initWithBytes:opts->default_name length:opts->default_name_len encoding:NSUTF8StringEncoding];
|
|
14414
|
-
}
|
|
14415
|
-
NativeSdkConfigurePanelExtensions(panel, NativeSdkParseExtensions(opts->extensions, opts->extensions_len));
|
|
14416
|
-
|
|
14495
|
+
NSSavePanel *panel = NativeSdkMakeSavePanel(opts);
|
|
14417
14496
|
if ([panel runModal] != NSModalResponseOK) return 0;
|
|
14418
14497
|
|
|
14419
14498
|
NSString *path = panel.URL.path;
|
|
@@ -14429,43 +14508,198 @@ size_t native_sdk_appkit_show_save_dialog(native_sdk_appkit_host_t *host, const
|
|
|
14429
14508
|
int native_sdk_appkit_show_message_dialog(native_sdk_appkit_host_t *host, const native_sdk_appkit_message_dialog_opts_t *opts) {
|
|
14430
14509
|
(void)host;
|
|
14431
14510
|
@autoreleasepool {
|
|
14432
|
-
NSAlert *alert =
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14511
|
+
NSAlert *alert = NativeSdkMakeAlert(opts);
|
|
14512
|
+
NSModalResponse response = [alert runModal];
|
|
14513
|
+
if (response == NSAlertFirstButtonReturn) return 0;
|
|
14514
|
+
if (response == NSAlertSecondButtonReturn) return 1;
|
|
14515
|
+
return 2;
|
|
14516
|
+
}
|
|
14517
|
+
}
|
|
14518
|
+
|
|
14519
|
+
/* Record a dialog's result and wake the loop; the runtime collects it with
|
|
14520
|
+
* poll. Never runs app code: AppKit calls completion handlers inside its
|
|
14521
|
+
* own transaction (electron#26884), so the result waits for the wake. */
|
|
14522
|
+
static void NativeSdkFinishDialog(NativeSdkAppKitHost *host, NativeSdkDialog *dialog, NSModalResponse response) {
|
|
14523
|
+
if (!host) return;
|
|
14524
|
+
NSNumber *key = @(dialog.dialogId);
|
|
14525
|
+
if (host.dialogs[key] != dialog) return;
|
|
14526
|
+
[host.dialogs removeObjectForKey:key];
|
|
14527
|
+
dialog.outcome = NATIVE_SDK_APPKIT_DIALOG_CANCELLED;
|
|
14528
|
+
if (dialog.alert) {
|
|
14529
|
+
if (response >= NSAlertFirstButtonReturn && response <= NSAlertThirdButtonReturn) {
|
|
14530
|
+
dialog.outcome = NATIVE_SDK_APPKIT_DIALOG_SELECTED;
|
|
14531
|
+
dialog.button = (int)(response - NSAlertFirstButtonReturn);
|
|
14532
|
+
}
|
|
14533
|
+
} else if (response == NSModalResponseOK) {
|
|
14534
|
+
NSMutableArray<NSString *> *paths = [NSMutableArray array];
|
|
14535
|
+
if ([dialog.panel isKindOfClass:[NSOpenPanel class]]) {
|
|
14536
|
+
for (NSURL *url in ((NSOpenPanel *)dialog.panel).URLs) {
|
|
14537
|
+
if (url.path) [paths addObject:url.path];
|
|
14538
|
+
}
|
|
14539
|
+
} else if (dialog.panel.URL.path) {
|
|
14540
|
+
[paths addObject:dialog.panel.URL.path];
|
|
14541
|
+
}
|
|
14542
|
+
dialog.paths = paths;
|
|
14543
|
+
if (paths.count > 0) dialog.outcome = NATIVE_SDK_APPKIT_DIALOG_SELECTED;
|
|
14544
|
+
}
|
|
14545
|
+
dialog.panel = nil;
|
|
14546
|
+
dialog.alert = nil;
|
|
14547
|
+
if (host.didShutdown) return;
|
|
14548
|
+
[host.finishedDialogs addObject:dialog];
|
|
14549
|
+
[host wakeFromAnyThread];
|
|
14550
|
+
}
|
|
14551
|
+
|
|
14552
|
+
static void NativeSdkPresentDialog(NativeSdkAppKitHost *host, NativeSdkDialog *dialog, NSWindow *parent) {
|
|
14553
|
+
__weak NativeSdkAppKitHost *weakHost = host;
|
|
14554
|
+
if (dialog.panel) {
|
|
14555
|
+
void (^done)(NSModalResponse) = ^(NSModalResponse response) {
|
|
14556
|
+
NativeSdkFinishDialog(weakHost, dialog, response);
|
|
14557
|
+
};
|
|
14558
|
+
if (parent) {
|
|
14559
|
+
dialog.sheetParent = parent;
|
|
14560
|
+
[dialog.panel beginSheetModalForWindow:parent completionHandler:done];
|
|
14454
14561
|
} else {
|
|
14455
|
-
[
|
|
14562
|
+
[dialog.panel beginWithCompletionHandler:done];
|
|
14456
14563
|
}
|
|
14457
|
-
|
|
14458
|
-
|
|
14564
|
+
return;
|
|
14565
|
+
}
|
|
14566
|
+
if (parent) {
|
|
14567
|
+
dialog.sheetParent = parent;
|
|
14568
|
+
[dialog.alert beginSheetModalForWindow:parent completionHandler:^(NSModalResponse response) {
|
|
14569
|
+
NativeSdkFinishDialog(weakHost, dialog, response);
|
|
14570
|
+
}];
|
|
14571
|
+
return;
|
|
14572
|
+
}
|
|
14573
|
+
// NSAlert has no modeless form. With no window to attach a sheet to,
|
|
14574
|
+
// run it modally from a fresh loop turn, outside any runtime
|
|
14575
|
+
// dispatch, so events keep reaching the app meanwhile (Electron runs
|
|
14576
|
+
// a parentless message box modally too). A run loop block, not
|
|
14577
|
+
// dispatch_async: a modal loop inside a main-queue block keeps the
|
|
14578
|
+
// serial main queue from draining, and wakes and quits ride it.
|
|
14579
|
+
CFRunLoopRef mainLoop = CFRunLoopGetMain();
|
|
14580
|
+
CFRunLoopPerformBlock(mainLoop, kCFRunLoopCommonModes, ^{
|
|
14581
|
+
NativeSdkAppKitHost *strongHost = weakHost;
|
|
14582
|
+
if (!strongHost || dialog.cancelRequested || strongHost.dialogs[@(dialog.dialogId)] != dialog) {
|
|
14583
|
+
NativeSdkFinishDialog(strongHost, dialog, NSModalResponseCancel);
|
|
14584
|
+
return;
|
|
14459
14585
|
}
|
|
14460
|
-
|
|
14461
|
-
|
|
14586
|
+
dialog.modalRunning = YES;
|
|
14587
|
+
const NSModalResponse response = [dialog.alert runModal];
|
|
14588
|
+
dialog.modalRunning = NO;
|
|
14589
|
+
NativeSdkFinishDialog(strongHost, dialog, response);
|
|
14590
|
+
// A stop during the alert ended only the modal loop; [NSApp stop:]
|
|
14591
|
+
// inside a modal session never reaches the main one.
|
|
14592
|
+
if (strongHost.didShutdown) {
|
|
14593
|
+
[NSApp stop:nil];
|
|
14594
|
+
NativeSdkPostEmptyEvent();
|
|
14462
14595
|
}
|
|
14596
|
+
});
|
|
14597
|
+
CFRunLoopWakeUp(mainLoop);
|
|
14598
|
+
}
|
|
14463
14599
|
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14600
|
+
static void NativeSdkCancelDialog(NativeSdkDialog *dialog) {
|
|
14601
|
+
NSWindow *sheet = dialog.panel ?: dialog.alert.window;
|
|
14602
|
+
if (dialog.sheetParent && sheet) {
|
|
14603
|
+
// Ending the sheet on its window detaches it at once, so a close
|
|
14604
|
+
// right after is not refused for a sheet still attached.
|
|
14605
|
+
[dialog.sheetParent endSheet:sheet returnCode:NSModalResponseCancel];
|
|
14606
|
+
return;
|
|
14607
|
+
}
|
|
14608
|
+
if (dialog.panel) {
|
|
14609
|
+
[dialog.panel cancel:nil];
|
|
14610
|
+
return;
|
|
14611
|
+
}
|
|
14612
|
+
if (!dialog.alert) return;
|
|
14613
|
+
if (dialog.modalRunning) {
|
|
14614
|
+
[NSApp stopModalWithCode:NSModalResponseCancel];
|
|
14615
|
+
NativeSdkPostEmptyEvent();
|
|
14616
|
+
} else {
|
|
14617
|
+
// Not started yet: the deferred presentation finishes it instead.
|
|
14618
|
+
dialog.cancelRequested = YES;
|
|
14619
|
+
}
|
|
14620
|
+
}
|
|
14621
|
+
|
|
14622
|
+
static void NativeSdkCancelDialogsForWindow(NativeSdkAppKitHost *host, uint64_t windowId) {
|
|
14623
|
+
for (NativeSdkDialog *dialog in [host.dialogs.allValues copy]) {
|
|
14624
|
+
if (dialog.parentWindowId == windowId) NativeSdkCancelDialog(dialog);
|
|
14625
|
+
}
|
|
14626
|
+
}
|
|
14627
|
+
|
|
14628
|
+
static void NativeSdkCancelAllDialogs(NativeSdkAppKitHost *host) {
|
|
14629
|
+
for (NativeSdkDialog *dialog in [host.dialogs.allValues copy]) NativeSdkCancelDialog(dialog);
|
|
14630
|
+
[host.finishedDialogs removeAllObjects];
|
|
14631
|
+
}
|
|
14632
|
+
|
|
14633
|
+
int native_sdk_appkit_request_dialog(native_sdk_appkit_host_t *host, const native_sdk_appkit_dialog_request_t *request) {
|
|
14634
|
+
NativeSdkAppKitHost *object = (__bridge NativeSdkAppKitHost *)host;
|
|
14635
|
+
if (!object || !request || object.didShutdown || request->id == 0) return 0;
|
|
14636
|
+
@autoreleasepool {
|
|
14637
|
+
NativeSdkDialog *dialog = [[NativeSdkDialog alloc] init];
|
|
14638
|
+
dialog.dialogId = request->id;
|
|
14639
|
+
dialog.parentWindowId = request->parent_window_id;
|
|
14640
|
+
switch (request->kind) {
|
|
14641
|
+
case NATIVE_SDK_APPKIT_DIALOG_OPEN:
|
|
14642
|
+
if (!request->open_opts) return 0;
|
|
14643
|
+
dialog.panel = NativeSdkMakeOpenPanel(request->open_opts);
|
|
14644
|
+
break;
|
|
14645
|
+
case NATIVE_SDK_APPKIT_DIALOG_SAVE:
|
|
14646
|
+
if (!request->save_opts) return 0;
|
|
14647
|
+
dialog.panel = NativeSdkMakeSavePanel(request->save_opts);
|
|
14648
|
+
break;
|
|
14649
|
+
case NATIVE_SDK_APPKIT_DIALOG_MESSAGE:
|
|
14650
|
+
if (!request->message_opts) return 0;
|
|
14651
|
+
dialog.alert = NativeSdkMakeAlert(request->message_opts);
|
|
14652
|
+
break;
|
|
14653
|
+
default:
|
|
14654
|
+
return 0;
|
|
14655
|
+
}
|
|
14656
|
+
// A parent that cannot host a sheet right now (hidden, minimized,
|
|
14657
|
+
// already showing one) gets a modeless panel instead, as Electron
|
|
14658
|
+
// does for invisible parents (electron#22671).
|
|
14659
|
+
NSWindow *parent = request->parent_window_id != 0 ? object.windows[@(request->parent_window_id)] : nil;
|
|
14660
|
+
if (parent && (!parent.isVisible || parent.isMiniaturized || parent.attachedSheet)) parent = nil;
|
|
14661
|
+
if (!parent) dialog.parentWindowId = 0;
|
|
14662
|
+
object.dialogs[@(dialog.dialogId)] = dialog;
|
|
14663
|
+
NativeSdkPresentDialog(object, dialog, parent);
|
|
14664
|
+
return 1;
|
|
14665
|
+
}
|
|
14666
|
+
}
|
|
14667
|
+
|
|
14668
|
+
void native_sdk_appkit_cancel_dialog(native_sdk_appkit_host_t *host, uint64_t id) {
|
|
14669
|
+
NativeSdkAppKitHost *object = (__bridge NativeSdkAppKitHost *)host;
|
|
14670
|
+
if (!object) return;
|
|
14671
|
+
NativeSdkDialog *dialog = object.dialogs[@(id)];
|
|
14672
|
+
if (dialog) NativeSdkCancelDialog(dialog);
|
|
14673
|
+
}
|
|
14674
|
+
|
|
14675
|
+
int native_sdk_appkit_poll_dialog_result(native_sdk_appkit_host_t *host, char *buffer, size_t buffer_len, native_sdk_appkit_dialog_result_t *result) {
|
|
14676
|
+
NativeSdkAppKitHost *object = (__bridge NativeSdkAppKitHost *)host;
|
|
14677
|
+
if (!object || !result || object.finishedDialogs.count == 0) return 0;
|
|
14678
|
+
NativeSdkDialog *dialog = object.finishedDialogs.firstObject;
|
|
14679
|
+
[object.finishedDialogs removeObjectAtIndex:0];
|
|
14680
|
+
*result = (native_sdk_appkit_dialog_result_t){ .id = dialog.dialogId, .outcome = dialog.outcome, .button = dialog.button };
|
|
14681
|
+
size_t offset = 0;
|
|
14682
|
+
size_t count = 0;
|
|
14683
|
+
for (NSString *path in dialog.paths) {
|
|
14684
|
+
NSData *data = [path dataUsingEncoding:NSUTF8StringEncoding];
|
|
14685
|
+
if (!data) continue;
|
|
14686
|
+
const size_t needed = data.length + (count > 0 ? 1 : 0);
|
|
14687
|
+
if (!buffer || needed > buffer_len - offset) {
|
|
14688
|
+
// More paths than the runtime can hold: report failure rather
|
|
14689
|
+
// than a silently shortened selection.
|
|
14690
|
+
result->outcome = NATIVE_SDK_APPKIT_DIALOG_FAILED;
|
|
14691
|
+
offset = 0;
|
|
14692
|
+
count = 0;
|
|
14693
|
+
break;
|
|
14694
|
+
}
|
|
14695
|
+
if (count > 0) buffer[offset++] = '\n';
|
|
14696
|
+
memcpy(buffer + offset, data.bytes, data.length);
|
|
14697
|
+
offset += data.length;
|
|
14698
|
+
count++;
|
|
14468
14699
|
}
|
|
14700
|
+
result->count = count;
|
|
14701
|
+
result->bytes_written = offset;
|
|
14702
|
+
return 1;
|
|
14469
14703
|
}
|
|
14470
14704
|
|
|
14471
14705
|
static NSImage *NativeSdkTrayImageWithOpacity(NSImage *source, double opacity) {
|
|
@@ -3712,6 +3712,27 @@ int native_sdk_appkit_show_message_dialog(native_sdk_appkit_host_t *host, const
|
|
|
3712
3712
|
}
|
|
3713
3713
|
}
|
|
3714
3714
|
|
|
3715
|
+
/* Asynchronous dialogs are not offered here: root.zig leaves them unbound
|
|
3716
|
+
* for the Chromium engine, so the runtime shows the blocking ones above. */
|
|
3717
|
+
int native_sdk_appkit_request_dialog(native_sdk_appkit_host_t *host, const native_sdk_appkit_dialog_request_t *request) {
|
|
3718
|
+
(void)host;
|
|
3719
|
+
(void)request;
|
|
3720
|
+
return 0;
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
void native_sdk_appkit_cancel_dialog(native_sdk_appkit_host_t *host, uint64_t id) {
|
|
3724
|
+
(void)host;
|
|
3725
|
+
(void)id;
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
int native_sdk_appkit_poll_dialog_result(native_sdk_appkit_host_t *host, char *buffer, size_t buffer_len, native_sdk_appkit_dialog_result_t *result) {
|
|
3729
|
+
(void)host;
|
|
3730
|
+
(void)buffer;
|
|
3731
|
+
(void)buffer_len;
|
|
3732
|
+
(void)result;
|
|
3733
|
+
return 0;
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3715
3736
|
static NSImage *NativeSdkCefTrayImageWithOpacity(NSImage *source, double opacity) {
|
|
3716
3737
|
if (!source) return nil;
|
|
3717
3738
|
const CGFloat fraction = MAX(0.0, MIN(1.0, opacity));
|