@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.
Files changed (35) hide show
  1. package/build/app.zig +50 -34
  2. package/package.json +9 -9
  3. package/packages/core/package-lock.json +2 -2
  4. package/packages/core/package.json +1 -1
  5. package/skill-data/core/references/bridge-security-native-capabilities.md +4 -0
  6. package/src/platform/linux/cef_host.cpp +22 -0
  7. package/src/platform/linux/gtk_host.c +317 -34
  8. package/src/platform/linux/gtk_host.h +37 -0
  9. package/src/platform/linux/root.zig +114 -20
  10. package/src/platform/macos/appkit_host.h +44 -0
  11. package/src/platform/macos/appkit_host.m +301 -67
  12. package/src/platform/macos/cef_host.mm +21 -0
  13. package/src/platform/macos/root.zig +117 -23
  14. package/src/platform/null_platform.zig +136 -0
  15. package/src/platform/root.zig +6 -0
  16. package/src/platform/types.zig +81 -0
  17. package/src/platform/windows/dialog_test.cpp +303 -0
  18. package/src/platform/windows/host_loop_test.cpp +169 -0
  19. package/src/platform/windows/root.zig +225 -39
  20. package/src/platform/windows/session_end_test.cpp +303 -0
  21. package/src/platform/windows/webview2_host.cpp +1064 -280
  22. package/src/root.zig +7 -0
  23. package/src/runtime/builtin_bridge.zig +108 -13
  24. package/src/runtime/core.zig +24 -3
  25. package/src/runtime/dialog_tests.zig +367 -0
  26. package/src/runtime/dialogs.zig +385 -0
  27. package/src/runtime/effects.zig +71 -2
  28. package/src/runtime/effects_host_tests.zig +69 -1
  29. package/src/runtime/flow.zig +14 -6
  30. package/src/runtime/flow_reference.zig +14 -6
  31. package/src/runtime/platform_bridge_tests.zig +1 -0
  32. package/src/runtime/root.zig +5 -0
  33. package/src/runtime/system_services.zig +64 -82
  34. package/src/runtime/ui_app.zig +67 -0
  35. 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
- NSEvent *event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
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 = [NSOpenPanel openPanel];
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 = [NSSavePanel savePanel];
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 = [[NSAlert alloc] init];
14433
- switch (opts->style) {
14434
- case 1: alert.alertStyle = NSAlertStyleWarning; break;
14435
- case 2: alert.alertStyle = NSAlertStyleCritical; break;
14436
- default: alert.alertStyle = NSAlertStyleInformational; break;
14437
- }
14438
- NSString *title = opts->title && opts->title_len > 0 ? [[NSString alloc] initWithBytes:opts->title length:opts->title_len encoding:NSUTF8StringEncoding] : nil;
14439
- NSString *message = opts->message && opts->message_len > 0 ? [[NSString alloc] initWithBytes:opts->message length:opts->message_len encoding:NSUTF8StringEncoding] : nil;
14440
- NSString *informative = opts->informative_text && opts->informative_text_len > 0 ? [[NSString alloc] initWithBytes:opts->informative_text length:opts->informative_text_len encoding:NSUTF8StringEncoding] : nil;
14441
- if (message.length > 0) {
14442
- alert.messageText = message;
14443
- } else if (title.length > 0) {
14444
- alert.messageText = title;
14445
- }
14446
- if (informative.length > 0) {
14447
- alert.informativeText = informative;
14448
- }
14449
- if (opts->message && opts->message_len > 0) {
14450
- alert.window.title = title.length > 0 ? title : @"";
14451
- }
14452
- if (opts->primary_button && opts->primary_button_len > 0) {
14453
- [alert addButtonWithTitle:[[NSString alloc] initWithBytes:opts->primary_button length:opts->primary_button_len encoding:NSUTF8StringEncoding]];
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
- [alert addButtonWithTitle:@"OK"];
14562
+ [dialog.panel beginWithCompletionHandler:done];
14456
14563
  }
14457
- if (opts->secondary_button && opts->secondary_button_len > 0) {
14458
- [alert addButtonWithTitle:[[NSString alloc] initWithBytes:opts->secondary_button length:opts->secondary_button_len encoding:NSUTF8StringEncoding]];
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
- if (opts->tertiary_button && opts->tertiary_button_len > 0) {
14461
- [alert addButtonWithTitle:[[NSString alloc] initWithBytes:opts->tertiary_button length:opts->tertiary_button_len encoding:NSUTF8StringEncoding]];
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
- NSModalResponse response = [alert runModal];
14465
- if (response == NSAlertFirstButtonReturn) return 0;
14466
- if (response == NSAlertSecondButtonReturn) return 1;
14467
- return 2;
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));