@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
package/build/app.zig
CHANGED
|
@@ -2827,8 +2827,7 @@ fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Res
|
|
|
2827
2827
|
// The vendored WebView2 SDK header (third_party/webview2)
|
|
2828
2828
|
// turns on the host's embedded-WebView layer; the host
|
|
2829
2829
|
// fails the compile by design if it cannot be found.
|
|
2830
|
-
app_mod
|
|
2831
|
-
if (windowsSdkWinrtIncludeDir(b, target)) |winrt| app_mod.addSystemIncludePath(.{ .cwd_relative = winrt });
|
|
2830
|
+
addWebView2HostIncludes(b, app_mod, dep.path("third_party/webview2/include"), target);
|
|
2832
2831
|
app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/webview2_host.cpp"), .flags = &.{"-std=c++17"} });
|
|
2833
2832
|
app_mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/gpu_surface_renderer.cpp"), .flags = &.{"-std=c++17"} });
|
|
2834
2833
|
// WebView2Loader.dll rides next to the installed app
|
|
@@ -2871,36 +2870,7 @@ fn linkPlatform(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Res
|
|
|
2871
2870
|
app_mod.addObjectFile(b.path(b.fmt("{s}/libcef_dll_wrapper/libcef_dll_wrapper.lib", .{cef_dir})));
|
|
2872
2871
|
},
|
|
2873
2872
|
}
|
|
2874
|
-
app_mod
|
|
2875
|
-
// The MSVC target compiles the hosts against the toolchain's own
|
|
2876
|
-
// STL, which pulls in msvcprt/vcruntime through its default-lib
|
|
2877
|
-
// directives; Zig's libc++/libc++abi cannot be built against the
|
|
2878
|
-
// MSVC CRT headers (cxxabi.h collides with vcruntime_typeinfo.h).
|
|
2879
|
-
if (target.result.abi != .msvc) app_mod.linkSystemLibrary("c++", .{});
|
|
2880
|
-
app_mod.linkSystemLibrary("user32", .{});
|
|
2881
|
-
app_mod.linkSystemLibrary("gdi32", .{});
|
|
2882
|
-
// Retained gpu_surface packets are composited into a hardware
|
|
2883
|
-
// Direct2D target; DirectWrite draws the engine-measured text
|
|
2884
|
-
// runs (including registered in-memory fonts) on that target.
|
|
2885
|
-
app_mod.linkSystemLibrary("d2d1", .{});
|
|
2886
|
-
app_mod.linkSystemLibrary("dwrite", .{});
|
|
2887
|
-
app_mod.linkSystemLibrary("imm32", .{});
|
|
2888
|
-
app_mod.linkSystemLibrary("comctl32", .{});
|
|
2889
|
-
app_mod.linkSystemLibrary("ole32", .{});
|
|
2890
|
-
app_mod.linkSystemLibrary("oleacc", .{});
|
|
2891
|
-
app_mod.linkSystemLibrary("shell32", .{});
|
|
2892
|
-
// TypeScript cores link ScriptC's host runtime, whose network-interface
|
|
2893
|
-
// helpers use GetAdaptersAddresses and Winsock address conversion; the
|
|
2894
|
-
// in-process service archive shares the same runtime and additionally
|
|
2895
|
-
// reaches advapi32 (crypto-backed randomness).
|
|
2896
|
-
app_mod.linkSystemLibrary("iphlpapi", .{});
|
|
2897
|
-
app_mod.linkSystemLibrary("ws2_32", .{});
|
|
2898
|
-
app_mod.linkSystemLibrary("advapi32", .{});
|
|
2899
|
-
// The audio backend: Media Foundation (session + source resolver
|
|
2900
|
-
// + streaming audio renderer) and WinHTTP (the cache fill).
|
|
2901
|
-
app_mod.linkSystemLibrary("mf", .{});
|
|
2902
|
-
app_mod.linkSystemLibrary("mfplat", .{});
|
|
2903
|
-
app_mod.linkSystemLibrary("winhttp", .{});
|
|
2873
|
+
linkWindowsHostLibraries(app_mod, target);
|
|
2904
2874
|
if (web_engine == .chromium) app_mod.linkSystemLibrary("libcef", .{});
|
|
2905
2875
|
}
|
|
2906
2876
|
}
|
|
@@ -2928,6 +2898,53 @@ fn addPlatformLinkSearchPaths(b: *std.Build, platform: PlatformOption, web_engin
|
|
|
2928
2898
|
|
|
2929
2899
|
/// The vendored WebView2Loader.dll for the target architecture, relative
|
|
2930
2900
|
/// to the framework root.
|
|
2901
|
+
/// The system libraries the Windows host links, for an app executable
|
|
2902
|
+
/// and for the host's own test harness alike.
|
|
2903
|
+
pub fn linkWindowsHostLibraries(module: *std.Build.Module, target: std.Build.ResolvedTarget) void {
|
|
2904
|
+
module.linkSystemLibrary("c", .{});
|
|
2905
|
+
// The MSVC target compiles the hosts against the toolchain's own
|
|
2906
|
+
// STL, which pulls in msvcprt/vcruntime through its default-lib
|
|
2907
|
+
// directives; Zig's libc++/libc++abi cannot be built against the
|
|
2908
|
+
// MSVC CRT headers (cxxabi.h collides with vcruntime_typeinfo.h).
|
|
2909
|
+
if (target.result.abi != .msvc) module.linkSystemLibrary("c++", .{});
|
|
2910
|
+
module.linkSystemLibrary("user32", .{});
|
|
2911
|
+
module.linkSystemLibrary("gdi32", .{});
|
|
2912
|
+
// Retained gpu_surface packets are composited into a hardware
|
|
2913
|
+
// Direct2D target; DirectWrite draws the engine-measured text
|
|
2914
|
+
// runs (including registered in-memory fonts) on that target.
|
|
2915
|
+
module.linkSystemLibrary("d2d1", .{});
|
|
2916
|
+
module.linkSystemLibrary("dwrite", .{});
|
|
2917
|
+
module.linkSystemLibrary("imm32", .{});
|
|
2918
|
+
module.linkSystemLibrary("comctl32", .{});
|
|
2919
|
+
module.linkSystemLibrary("ole32", .{});
|
|
2920
|
+
module.linkSystemLibrary("oleacc", .{});
|
|
2921
|
+
module.linkSystemLibrary("shell32", .{});
|
|
2922
|
+
// TypeScript cores link ScriptC's host runtime, whose network-interface
|
|
2923
|
+
// helpers use GetAdaptersAddresses and Winsock address conversion; the
|
|
2924
|
+
// in-process service archive shares the same runtime and additionally
|
|
2925
|
+
// reaches advapi32 (crypto-backed randomness).
|
|
2926
|
+
module.linkSystemLibrary("iphlpapi", .{});
|
|
2927
|
+
module.linkSystemLibrary("ws2_32", .{});
|
|
2928
|
+
module.linkSystemLibrary("advapi32", .{});
|
|
2929
|
+
// The audio backend: Media Foundation (session + source resolver
|
|
2930
|
+
// + streaming audio renderer) and WinHTTP (the cache fill).
|
|
2931
|
+
module.linkSystemLibrary("mf", .{});
|
|
2932
|
+
module.linkSystemLibrary("mfplat", .{});
|
|
2933
|
+
module.linkSystemLibrary("winhttp", .{});
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
/// The WebView2 host's include paths: the vendored WebView2 SDK header
|
|
2937
|
+
/// directory, plus WRL for MSVC targets (`windowsSdkWinrtIncludeDir`).
|
|
2938
|
+
pub fn addWebView2HostIncludes(b: *std.Build, module: *std.Build.Module, sdk_include: std.Build.LazyPath, target: std.Build.ResolvedTarget) void {
|
|
2939
|
+
module.addIncludePath(sdk_include);
|
|
2940
|
+
if (windowsSdkWinrtIncludeDir(b, target)) |winrt| module.addSystemIncludePath(.{ .cwd_relative = winrt });
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
/// The vendored WebView2Loader.dll's directory, relative to the SDK root.
|
|
2944
|
+
pub fn webView2LoaderDir(target: std.Build.ResolvedTarget) []const u8 {
|
|
2945
|
+
return std.fs.path.dirname(webView2LoaderSubPath(target)).?;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2931
2948
|
fn webView2LoaderSubPath(target: std.Build.ResolvedTarget) []const u8 {
|
|
2932
2949
|
return if (target.result.cpu.arch == .aarch64)
|
|
2933
2950
|
"third_party/webview2/arm64/WebView2Loader.dll"
|
|
@@ -2957,8 +2974,7 @@ fn addWebView2RuntimeRunFiles(dep: *std.Build.Dependency, target: std.Build.Reso
|
|
|
2957
2974
|
if (web_engine != .system) return;
|
|
2958
2975
|
if (!web_layer) return;
|
|
2959
2976
|
if (target.result.os.tag != .windows) return;
|
|
2960
|
-
|
|
2961
|
-
run.addPathDir(dep.builder.pathFromRoot(loader_dir));
|
|
2977
|
+
run.addPathDir(dep.builder.pathFromRoot(webView2LoaderDir(target)));
|
|
2962
2978
|
}
|
|
2963
2979
|
|
|
2964
2980
|
fn addCefRuntimeRunFiles(b: *std.Build, target: std.Build.ResolvedTarget, run: *std.Build.Step.Run, exe: *std.Build.Step.Compile, web_engine: WebEngineOption, cef_dir: []const u8) void {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zogien/native-sdk",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.5",
|
|
4
4
|
"description": "Community-maintained fork of Native SDK: TypeScript, Native markup, and native desktop tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"tar": "7.5.22"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@zogien/native-sdk-darwin-arm64": "0.11.0-fork.
|
|
45
|
-
"@zogien/native-sdk-darwin-x64": "0.11.0-fork.
|
|
46
|
-
"@zogien/native-sdk-linux-arm64-gnu": "0.11.0-fork.
|
|
47
|
-
"@zogien/native-sdk-linux-arm64-musl": "0.11.0-fork.
|
|
48
|
-
"@zogien/native-sdk-linux-x64-gnu": "0.11.0-fork.
|
|
49
|
-
"@zogien/native-sdk-linux-x64-musl": "0.11.0-fork.
|
|
50
|
-
"@zogien/native-sdk-win32-arm64": "0.11.0-fork.
|
|
51
|
-
"@zogien/native-sdk-win32-x64": "0.11.0-fork.
|
|
44
|
+
"@zogien/native-sdk-darwin-arm64": "0.11.0-fork.5",
|
|
45
|
+
"@zogien/native-sdk-darwin-x64": "0.11.0-fork.5",
|
|
46
|
+
"@zogien/native-sdk-linux-arm64-gnu": "0.11.0-fork.5",
|
|
47
|
+
"@zogien/native-sdk-linux-arm64-musl": "0.11.0-fork.5",
|
|
48
|
+
"@zogien/native-sdk-linux-x64-gnu": "0.11.0-fork.5",
|
|
49
|
+
"@zogien/native-sdk-linux-x64-musl": "0.11.0-fork.5",
|
|
50
|
+
"@zogien/native-sdk-win32-arm64": "0.11.0-fork.5",
|
|
51
|
+
"@zogien/native-sdk-win32-x64": "0.11.0-fork.5"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-sdk/core",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@native-sdk/core",
|
|
9
|
-
"version": "0.11.0-fork.
|
|
9
|
+
"version": "0.11.0-fork.5",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@scriptc/types": "0.3.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-sdk/core",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.5",
|
|
4
4
|
"description": "The TypeScript authoring tier: the app-core subset, its checker and contract frontend, the exact-pinned core compiler dependency, and the SDK module cores import",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -206,6 +206,10 @@ const result = await window.zero.invoke("native-sdk.dialog.showMessage", {
|
|
|
206
206
|
});
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
+
Each promise resolves when its dialog closes; the app keeps running meanwhile. `openFile` gives an array of paths and `saveFile` a path, or `null` when cancelled; `showMessage` gives `"primary"`, `"secondary"`, `"tertiary"`, or `"dismissed"` when closed without a button.
|
|
210
|
+
|
|
211
|
+
From Zig, use `runtime.requestOpenDialog`, `requestSaveDialog` or `requestMessageDialog` with a `DialogCompletion`: the answer arrives in `on_result` on a later dispatch. The older `runtime.showOpenDialog` family blocks the event dispatch until the dialog closes; do not use it in new code.
|
|
212
|
+
|
|
209
213
|
Use native dialogs for trusted app UI. Do not expose arbitrary filesystem access to remote or untrusted origins.
|
|
210
214
|
|
|
211
215
|
## Error handling
|
|
@@ -610,4 +610,26 @@ int native_sdk_gtk_show_message_dialog(Host *host, const void *opts) {
|
|
|
610
610
|
return 0;
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
+
/* Asynchronous dialogs are not offered here: root.zig leaves them unbound
|
|
614
|
+
* for the Chromium engine, so the runtime falls back to the blocking
|
|
615
|
+
* calls above. */
|
|
616
|
+
int native_sdk_gtk_request_dialog(Host *host, const void *request) {
|
|
617
|
+
(void)host;
|
|
618
|
+
(void)request;
|
|
619
|
+
return 0;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
void native_sdk_gtk_cancel_dialog(Host *host, uint64_t id) {
|
|
623
|
+
(void)host;
|
|
624
|
+
(void)id;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
int native_sdk_gtk_poll_dialog_result(Host *host, char *buffer, size_t buffer_len, void *result) {
|
|
628
|
+
(void)host;
|
|
629
|
+
(void)buffer;
|
|
630
|
+
(void)buffer_len;
|
|
631
|
+
(void)result;
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
613
635
|
}
|
|
@@ -396,6 +396,26 @@ typedef struct native_sdk_gtk_notification_action {
|
|
|
396
396
|
|
|
397
397
|
static const char native_sdk_notification_action_name[] = "native-sdk-notification-action";
|
|
398
398
|
|
|
399
|
+
/* One asynchronous dialog (native_sdk_gtk_request_dialog), from request
|
|
400
|
+
* until poll takes its result. GTK holds it as the finish callback's
|
|
401
|
+
* data; once native_sdk_gtk_destroy detaches it (host NULL) the callback
|
|
402
|
+
* only frees it. */
|
|
403
|
+
typedef struct native_sdk_gtk_dialog {
|
|
404
|
+
struct native_sdk_gtk_dialog *next;
|
|
405
|
+
native_sdk_gtk_host_t *host;
|
|
406
|
+
uint64_t id;
|
|
407
|
+
uint64_t parent_window_id;
|
|
408
|
+
int kind;
|
|
409
|
+
int folders;
|
|
410
|
+
GObject *dialog;
|
|
411
|
+
GCancellable *cancellable;
|
|
412
|
+
int finished;
|
|
413
|
+
uint64_t finish_serial;
|
|
414
|
+
int outcome;
|
|
415
|
+
int button;
|
|
416
|
+
GPtrArray *paths;
|
|
417
|
+
} native_sdk_gtk_dialog_t;
|
|
418
|
+
|
|
399
419
|
struct native_sdk_gtk_host {
|
|
400
420
|
GtkApplication *app;
|
|
401
421
|
/* Display providers are global and the display retains them until
|
|
@@ -476,6 +496,14 @@ struct native_sdk_gtk_host {
|
|
|
476
496
|
GHashTable *notification_actions;
|
|
477
497
|
GHashTable *notification_action_tokens_by_id;
|
|
478
498
|
native_sdk_gtk_audio_t audio;
|
|
499
|
+
/* Asynchronous dialogs not yet polled, newest first; finished ones
|
|
500
|
+
* are handed back in finish order. */
|
|
501
|
+
native_sdk_gtk_dialog_t *dialogs;
|
|
502
|
+
uint64_t dialog_finish_serial;
|
|
503
|
+
/* The queued wake that hands finished dialogs to the runtime, tracked
|
|
504
|
+
* like stop_idle_source: finishes coalesce into it, and destroy
|
|
505
|
+
* removes it so it never fires into a freed host. */
|
|
506
|
+
guint dialog_wake_source;
|
|
479
507
|
};
|
|
480
508
|
|
|
481
509
|
static void native_sdk_emit(native_sdk_gtk_host_t *host, native_sdk_gtk_event_t event);
|
|
@@ -483,6 +511,8 @@ static void native_sdk_context_menu_free(native_sdk_gtk_context_menu_t *menu);
|
|
|
483
511
|
static gboolean native_sdk_nudge_chrome_requery(gpointer data);
|
|
484
512
|
static gboolean native_sdk_on_file_drop(GtkDropTarget *target, const GValue *value, double x, double y, gpointer data);
|
|
485
513
|
static GtkWindow *native_sdk_parent_window(native_sdk_gtk_host_t *host);
|
|
514
|
+
static void native_sdk_cancel_dialogs_for_window(native_sdk_gtk_host_t *host, uint64_t window_id);
|
|
515
|
+
static void native_sdk_detach_dialogs(native_sdk_gtk_host_t *host);
|
|
486
516
|
static const char *native_sdk_shortcut_key_for_keyval(guint keyval, char *buffer, size_t buffer_len, int *uses_implicit_shift);
|
|
487
517
|
static void native_sdk_audio_release(native_sdk_gtk_host_t *host, int cancel_download);
|
|
488
518
|
|
|
@@ -2781,6 +2811,7 @@ static gboolean on_close_request(GtkWindow *window, gpointer data) {
|
|
|
2781
2811
|
(void)window;
|
|
2782
2812
|
native_sdk_gtk_window_t *win = data;
|
|
2783
2813
|
native_sdk_gtk_host_t *host = win->host;
|
|
2814
|
+
native_sdk_cancel_dialogs_for_window(host, win->id);
|
|
2784
2815
|
int closed_index = -1;
|
|
2785
2816
|
for (int i = 0; i < host->window_count; i++) {
|
|
2786
2817
|
if (&host->windows[i] == win) {
|
|
@@ -3479,6 +3510,8 @@ void native_sdk_gtk_destroy(native_sdk_gtk_host_t *host) {
|
|
|
3479
3510
|
/* Retire the audio pipeline (and cancel its cache download) before
|
|
3480
3511
|
* anything else goes away; everything runs on this thread. */
|
|
3481
3512
|
native_sdk_audio_release(host, 1);
|
|
3513
|
+
native_sdk_detach_dialogs(host);
|
|
3514
|
+
if (host->dialog_wake_source) g_source_remove(host->dialog_wake_source);
|
|
3482
3515
|
if (host->frame_timer) g_source_remove(host->frame_timer);
|
|
3483
3516
|
/* A stop turn still queued at teardown (the loop quit before the
|
|
3484
3517
|
* idle ran) must not fire into freed memory. */
|
|
@@ -6642,6 +6675,53 @@ static char *native_sdk_bytes_to_string(const char *bytes, size_t len) {
|
|
|
6642
6675
|
return bytes && len > 0 ? native_sdk_strndup(bytes, len) : NULL;
|
|
6643
6676
|
}
|
|
6644
6677
|
|
|
6678
|
+
/* The dialog builders hand back plain GObjects, keeping the GTK 4.10
|
|
6679
|
+
* dialog types out of file-scope signatures: against an older GTK this
|
|
6680
|
+
* file then fails only with the GTK-age diagnostics the GLib 2.72 receipt
|
|
6681
|
+
* allows (.github/scripts/glib272_error_allowlist.py). */
|
|
6682
|
+
static GObject *native_sdk_new_open_dialog(const native_sdk_gtk_open_dialog_opts_t *opts) {
|
|
6683
|
+
GtkFileDialog *dialog = gtk_file_dialog_new();
|
|
6684
|
+
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6685
|
+
if (title) gtk_file_dialog_set_title(dialog, title);
|
|
6686
|
+
free(title);
|
|
6687
|
+
return G_OBJECT(dialog);
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
static GObject *native_sdk_new_save_dialog(const native_sdk_gtk_save_dialog_opts_t *opts) {
|
|
6691
|
+
GtkFileDialog *dialog = gtk_file_dialog_new();
|
|
6692
|
+
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6693
|
+
char *default_name = native_sdk_bytes_to_string(opts->default_name, opts->default_name_len);
|
|
6694
|
+
if (title) gtk_file_dialog_set_title(dialog, title);
|
|
6695
|
+
if (default_name) gtk_file_dialog_set_initial_name(dialog, default_name);
|
|
6696
|
+
free(title);
|
|
6697
|
+
free(default_name);
|
|
6698
|
+
return G_OBJECT(dialog);
|
|
6699
|
+
}
|
|
6700
|
+
|
|
6701
|
+
static GObject *native_sdk_new_alert_dialog(const native_sdk_gtk_message_dialog_opts_t *opts) {
|
|
6702
|
+
/* The constructor requires a non-NULL printf-style format string. */
|
|
6703
|
+
GtkAlertDialog *dialog = gtk_alert_dialog_new("");
|
|
6704
|
+
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6705
|
+
char *message = native_sdk_bytes_to_string(opts->message, opts->message_len);
|
|
6706
|
+
char *informative = native_sdk_bytes_to_string(opts->informative_text, opts->informative_text_len);
|
|
6707
|
+
char *primary = native_sdk_bytes_to_string(opts->primary_button, opts->primary_button_len);
|
|
6708
|
+
char *secondary = native_sdk_bytes_to_string(opts->secondary_button, opts->secondary_button_len);
|
|
6709
|
+
char *tertiary = native_sdk_bytes_to_string(opts->tertiary_button, opts->tertiary_button_len);
|
|
6710
|
+
gtk_alert_dialog_set_message(dialog, title ? title : (message ? message : ""));
|
|
6711
|
+
if (informative || (title && message)) gtk_alert_dialog_set_detail(dialog, informative ? informative : message);
|
|
6712
|
+
const char *buttons[4] = { primary ? primary : "OK", NULL, NULL, NULL };
|
|
6713
|
+
if (secondary) buttons[1] = secondary;
|
|
6714
|
+
if (tertiary) buttons[2] = tertiary;
|
|
6715
|
+
gtk_alert_dialog_set_buttons(dialog, buttons);
|
|
6716
|
+
free(title);
|
|
6717
|
+
free(message);
|
|
6718
|
+
free(informative);
|
|
6719
|
+
free(primary);
|
|
6720
|
+
free(secondary);
|
|
6721
|
+
free(tertiary);
|
|
6722
|
+
return G_OBJECT(dialog);
|
|
6723
|
+
}
|
|
6724
|
+
|
|
6645
6725
|
static void native_sdk_open_dialog_done(GObject *source, GAsyncResult *result, gpointer data) {
|
|
6646
6726
|
native_sdk_file_dialog_state_t *state = data;
|
|
6647
6727
|
GError *error = NULL;
|
|
@@ -6668,13 +6748,10 @@ static void native_sdk_save_dialog_done(GObject *source, GAsyncResult *result, g
|
|
|
6668
6748
|
|
|
6669
6749
|
native_sdk_gtk_open_dialog_result_t native_sdk_gtk_show_open_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_open_dialog_opts_t *opts, char *buffer, size_t buffer_len) {
|
|
6670
6750
|
native_sdk_gtk_open_dialog_result_t result = {0};
|
|
6671
|
-
GtkFileDialog *dialog =
|
|
6672
|
-
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6673
|
-
if (title) gtk_file_dialog_set_title(dialog, title);
|
|
6751
|
+
GtkFileDialog *dialog = GTK_FILE_DIALOG(native_sdk_new_open_dialog(opts));
|
|
6674
6752
|
GtkWindow *parent = native_sdk_parent_window(host);
|
|
6675
6753
|
native_sdk_file_dialog_state_t state = { .loop = g_main_loop_new(NULL, FALSE) };
|
|
6676
6754
|
if (!state.loop) {
|
|
6677
|
-
if (title) free(title);
|
|
6678
6755
|
g_object_unref(dialog);
|
|
6679
6756
|
return result;
|
|
6680
6757
|
}
|
|
@@ -6711,22 +6788,15 @@ native_sdk_gtk_open_dialog_result_t native_sdk_gtk_show_open_dialog(native_sdk_g
|
|
|
6711
6788
|
result.bytes_written = overflow ? native_sdk_overflow_size(buffer_len) : offset;
|
|
6712
6789
|
g_object_unref(state.files);
|
|
6713
6790
|
}
|
|
6714
|
-
if (title) free(title);
|
|
6715
6791
|
g_object_unref(dialog);
|
|
6716
6792
|
return result;
|
|
6717
6793
|
}
|
|
6718
6794
|
|
|
6719
6795
|
size_t native_sdk_gtk_show_save_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_save_dialog_opts_t *opts, char *buffer, size_t buffer_len) {
|
|
6720
|
-
GtkFileDialog *dialog =
|
|
6721
|
-
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6722
|
-
char *default_name = native_sdk_bytes_to_string(opts->default_name, opts->default_name_len);
|
|
6723
|
-
if (title) gtk_file_dialog_set_title(dialog, title);
|
|
6724
|
-
if (default_name) gtk_file_dialog_set_initial_name(dialog, default_name);
|
|
6796
|
+
GtkFileDialog *dialog = GTK_FILE_DIALOG(native_sdk_new_save_dialog(opts));
|
|
6725
6797
|
GtkWindow *parent = native_sdk_parent_window(host);
|
|
6726
6798
|
native_sdk_file_dialog_state_t state = { .loop = g_main_loop_new(NULL, FALSE) };
|
|
6727
6799
|
if (!state.loop) {
|
|
6728
|
-
if (title) free(title);
|
|
6729
|
-
if (default_name) free(default_name);
|
|
6730
6800
|
g_object_unref(dialog);
|
|
6731
6801
|
return 0;
|
|
6732
6802
|
}
|
|
@@ -6748,8 +6818,6 @@ size_t native_sdk_gtk_show_save_dialog(native_sdk_gtk_host_t *host, const native
|
|
|
6748
6818
|
}
|
|
6749
6819
|
g_object_unref(state.file);
|
|
6750
6820
|
}
|
|
6751
|
-
if (title) free(title);
|
|
6752
|
-
if (default_name) free(default_name);
|
|
6753
6821
|
g_object_unref(dialog);
|
|
6754
6822
|
return written;
|
|
6755
6823
|
}
|
|
@@ -6771,34 +6839,249 @@ static void native_sdk_alert_done(GObject *source, GAsyncResult *result, gpointe
|
|
|
6771
6839
|
}
|
|
6772
6840
|
|
|
6773
6841
|
int native_sdk_gtk_show_message_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_message_dialog_opts_t *opts) {
|
|
6774
|
-
|
|
6775
|
-
GtkAlertDialog *dialog = gtk_alert_dialog_new("");
|
|
6776
|
-
char *title = native_sdk_bytes_to_string(opts->title, opts->title_len);
|
|
6777
|
-
char *message = native_sdk_bytes_to_string(opts->message, opts->message_len);
|
|
6778
|
-
char *informative = native_sdk_bytes_to_string(opts->informative_text, opts->informative_text_len);
|
|
6779
|
-
char *primary = native_sdk_bytes_to_string(opts->primary_button, opts->primary_button_len);
|
|
6780
|
-
char *secondary = native_sdk_bytes_to_string(opts->secondary_button, opts->secondary_button_len);
|
|
6781
|
-
char *tertiary = native_sdk_bytes_to_string(opts->tertiary_button, opts->tertiary_button_len);
|
|
6782
|
-
gtk_alert_dialog_set_message(dialog, title ? title : (message ? message : ""));
|
|
6783
|
-
if (informative || (title && message)) gtk_alert_dialog_set_detail(dialog, informative ? informative : message);
|
|
6784
|
-
const char *buttons[4] = { primary ? primary : "OK", NULL, NULL, NULL };
|
|
6785
|
-
if (secondary) buttons[1] = secondary;
|
|
6786
|
-
if (tertiary) buttons[2] = tertiary;
|
|
6787
|
-
gtk_alert_dialog_set_buttons(dialog, buttons);
|
|
6842
|
+
GtkAlertDialog *dialog = GTK_ALERT_DIALOG(native_sdk_new_alert_dialog(opts));
|
|
6788
6843
|
native_sdk_alert_state_t state = { .loop = g_main_loop_new(NULL, FALSE), .response = 0 };
|
|
6789
6844
|
if (state.loop) {
|
|
6790
6845
|
gtk_alert_dialog_choose(dialog, native_sdk_parent_window(host), NULL, native_sdk_alert_done, &state);
|
|
6791
6846
|
g_main_loop_run(state.loop);
|
|
6792
6847
|
g_main_loop_unref(state.loop);
|
|
6793
6848
|
}
|
|
6794
|
-
if (title) free(title);
|
|
6795
|
-
if (message) free(message);
|
|
6796
|
-
if (informative) free(informative);
|
|
6797
|
-
if (primary) free(primary);
|
|
6798
|
-
if (secondary) free(secondary);
|
|
6799
|
-
if (tertiary) free(tertiary);
|
|
6800
6849
|
g_object_unref(dialog);
|
|
6801
6850
|
if (state.response <= 0) return 0;
|
|
6802
6851
|
if (state.response == 1) return 1;
|
|
6803
6852
|
return 2;
|
|
6804
6853
|
}
|
|
6854
|
+
|
|
6855
|
+
/* GTK's dialogs are asynchronous already; the blocking calls above only
|
|
6856
|
+
* wrap them in a nested loop. The asynchronous ones drop that loop: the
|
|
6857
|
+
* finish callback records the result and schedules a wake, and the
|
|
6858
|
+
* runtime collects it with poll. The callback never emits, because GTask
|
|
6859
|
+
* may run it inside the dispatch that cancelled the dialog. */
|
|
6860
|
+
|
|
6861
|
+
static void native_sdk_dialog_free(native_sdk_gtk_dialog_t *entry) {
|
|
6862
|
+
if (entry->dialog) g_object_unref(entry->dialog);
|
|
6863
|
+
if (entry->cancellable) g_object_unref(entry->cancellable);
|
|
6864
|
+
if (entry->paths) g_ptr_array_unref(entry->paths);
|
|
6865
|
+
free(entry);
|
|
6866
|
+
}
|
|
6867
|
+
|
|
6868
|
+
static void native_sdk_dialog_unlink(native_sdk_gtk_host_t *host, native_sdk_gtk_dialog_t *entry) {
|
|
6869
|
+
for (native_sdk_gtk_dialog_t **link = &host->dialogs; *link; link = &(*link)->next) {
|
|
6870
|
+
if (*link == entry) {
|
|
6871
|
+
*link = entry->next;
|
|
6872
|
+
entry->next = NULL;
|
|
6873
|
+
return;
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
|
|
6878
|
+
static native_sdk_gtk_dialog_t *native_sdk_dialog_find(native_sdk_gtk_host_t *host, uint64_t id) {
|
|
6879
|
+
for (native_sdk_gtk_dialog_t *entry = host->dialogs; entry; entry = entry->next) {
|
|
6880
|
+
if (entry->id == id) return entry;
|
|
6881
|
+
}
|
|
6882
|
+
return NULL;
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
/* A dialog the app or a window close cancelled reports cancelled, like
|
|
6886
|
+
* one the user dismissed; anything else that went wrong is a failure. */
|
|
6887
|
+
static int native_sdk_dialog_error_outcome(const GError *error) {
|
|
6888
|
+
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return NATIVE_SDK_GTK_DIALOG_CANCELLED;
|
|
6889
|
+
#if GTK_CHECK_VERSION(4, 10, 0)
|
|
6890
|
+
if (g_error_matches(error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED) ||
|
|
6891
|
+
g_error_matches(error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED)) {
|
|
6892
|
+
return NATIVE_SDK_GTK_DIALOG_CANCELLED;
|
|
6893
|
+
}
|
|
6894
|
+
#endif
|
|
6895
|
+
return NATIVE_SDK_GTK_DIALOG_FAILED;
|
|
6896
|
+
}
|
|
6897
|
+
|
|
6898
|
+
static gboolean native_sdk_dialog_wake_idle(gpointer data) {
|
|
6899
|
+
native_sdk_gtk_host_t *host = data;
|
|
6900
|
+
host->dialog_wake_source = 0;
|
|
6901
|
+
if (!host->did_shutdown) native_sdk_emit(host, (native_sdk_gtk_event_t){ .kind = NATIVE_SDK_GTK_EVENT_WAKE });
|
|
6902
|
+
return G_SOURCE_REMOVE;
|
|
6903
|
+
}
|
|
6904
|
+
|
|
6905
|
+
static void native_sdk_dialog_finish(native_sdk_gtk_dialog_t *entry, GError *error) {
|
|
6906
|
+
if (error) {
|
|
6907
|
+
entry->outcome = native_sdk_dialog_error_outcome(error);
|
|
6908
|
+
g_error_free(error);
|
|
6909
|
+
}
|
|
6910
|
+
native_sdk_gtk_host_t *host = entry->host;
|
|
6911
|
+
if (!host) {
|
|
6912
|
+
/* The host went first (native_sdk_gtk_destroy detached this). */
|
|
6913
|
+
native_sdk_dialog_free(entry);
|
|
6914
|
+
return;
|
|
6915
|
+
}
|
|
6916
|
+
entry->finished = 1;
|
|
6917
|
+
entry->finish_serial = ++host->dialog_finish_serial;
|
|
6918
|
+
if (!host->dialog_wake_source) host->dialog_wake_source = g_idle_add(native_sdk_dialog_wake_idle, host);
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
static void native_sdk_dialog_add_file(native_sdk_gtk_dialog_t *entry, GFile *file) {
|
|
6922
|
+
char *path = file ? g_file_get_path(file) : NULL;
|
|
6923
|
+
if (path) g_ptr_array_add(entry->paths, path);
|
|
6924
|
+
}
|
|
6925
|
+
|
|
6926
|
+
static void native_sdk_dialog_files_done(GObject *source, GAsyncResult *result, gpointer data) {
|
|
6927
|
+
native_sdk_gtk_dialog_t *entry = data;
|
|
6928
|
+
GError *error = NULL;
|
|
6929
|
+
GListModel *files = NULL;
|
|
6930
|
+
if (entry->folders) {
|
|
6931
|
+
files = gtk_file_dialog_select_multiple_folders_finish(GTK_FILE_DIALOG(source), result, &error);
|
|
6932
|
+
} else {
|
|
6933
|
+
files = gtk_file_dialog_open_multiple_finish(GTK_FILE_DIALOG(source), result, &error);
|
|
6934
|
+
}
|
|
6935
|
+
if (files) {
|
|
6936
|
+
guint count = g_list_model_get_n_items(files);
|
|
6937
|
+
for (guint i = 0; i < count; i++) {
|
|
6938
|
+
GFile *file = G_FILE(g_list_model_get_item(files, i));
|
|
6939
|
+
native_sdk_dialog_add_file(entry, file);
|
|
6940
|
+
g_object_unref(file);
|
|
6941
|
+
}
|
|
6942
|
+
g_object_unref(files);
|
|
6943
|
+
}
|
|
6944
|
+
if (!error) entry->outcome = entry->paths->len > 0 ? NATIVE_SDK_GTK_DIALOG_SELECTED : NATIVE_SDK_GTK_DIALOG_CANCELLED;
|
|
6945
|
+
native_sdk_dialog_finish(entry, error);
|
|
6946
|
+
}
|
|
6947
|
+
|
|
6948
|
+
static void native_sdk_dialog_file_done(GObject *source, GAsyncResult *result, gpointer data) {
|
|
6949
|
+
native_sdk_gtk_dialog_t *entry = data;
|
|
6950
|
+
GError *error = NULL;
|
|
6951
|
+
/* Assigned rather than initialized, like `files` above: against an
|
|
6952
|
+
* older GTK that keeps the diagnostic to the receipt's
|
|
6953
|
+
* assignment-from-int shape (see the dialog builders). */
|
|
6954
|
+
GFile *file = NULL;
|
|
6955
|
+
file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(source), result, &error);
|
|
6956
|
+
if (file) {
|
|
6957
|
+
native_sdk_dialog_add_file(entry, file);
|
|
6958
|
+
g_object_unref(file);
|
|
6959
|
+
}
|
|
6960
|
+
if (!error) entry->outcome = entry->paths->len > 0 ? NATIVE_SDK_GTK_DIALOG_SELECTED : NATIVE_SDK_GTK_DIALOG_CANCELLED;
|
|
6961
|
+
native_sdk_dialog_finish(entry, error);
|
|
6962
|
+
}
|
|
6963
|
+
|
|
6964
|
+
static void native_sdk_dialog_alert_done(GObject *source, GAsyncResult *result, gpointer data) {
|
|
6965
|
+
native_sdk_gtk_dialog_t *entry = data;
|
|
6966
|
+
GError *error = NULL;
|
|
6967
|
+
const int response = gtk_alert_dialog_choose_finish(GTK_ALERT_DIALOG(source), result, &error);
|
|
6968
|
+
if (!error) {
|
|
6969
|
+
entry->outcome = NATIVE_SDK_GTK_DIALOG_SELECTED;
|
|
6970
|
+
entry->button = response > 2 ? 2 : (response < 0 ? 0 : response);
|
|
6971
|
+
}
|
|
6972
|
+
native_sdk_dialog_finish(entry, error);
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6975
|
+
/* A closing window takes its dialogs down with it. */
|
|
6976
|
+
static void native_sdk_cancel_dialogs_for_window(native_sdk_gtk_host_t *host, uint64_t window_id) {
|
|
6977
|
+
for (native_sdk_gtk_dialog_t *entry = host->dialogs; entry; entry = entry->next) {
|
|
6978
|
+
if (!entry->finished && entry->parent_window_id == window_id) g_cancellable_cancel(entry->cancellable);
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
|
|
6982
|
+
/* Host teardown: finished results are dropped, and open dialogs are
|
|
6983
|
+
* cancelled and left to free themselves when GTK reports them. */
|
|
6984
|
+
static void native_sdk_detach_dialogs(native_sdk_gtk_host_t *host) {
|
|
6985
|
+
while (host->dialogs) {
|
|
6986
|
+
native_sdk_gtk_dialog_t *entry = host->dialogs;
|
|
6987
|
+
host->dialogs = entry->next;
|
|
6988
|
+
entry->next = NULL;
|
|
6989
|
+
if (entry->finished) {
|
|
6990
|
+
native_sdk_dialog_free(entry);
|
|
6991
|
+
continue;
|
|
6992
|
+
}
|
|
6993
|
+
entry->host = NULL;
|
|
6994
|
+
/* Last touch: the callback may run, and free entry, right here. */
|
|
6995
|
+
g_cancellable_cancel(entry->cancellable);
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
|
|
6999
|
+
int native_sdk_gtk_request_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_dialog_request_t *request) {
|
|
7000
|
+
if (!host || !request || request->id == 0 || host->did_shutdown) return 0;
|
|
7001
|
+
if (native_sdk_dialog_find(host, request->id)) return 0;
|
|
7002
|
+
if ((request->kind == NATIVE_SDK_GTK_DIALOG_OPEN && !request->open_opts) ||
|
|
7003
|
+
(request->kind == NATIVE_SDK_GTK_DIALOG_SAVE && !request->save_opts) ||
|
|
7004
|
+
(request->kind == NATIVE_SDK_GTK_DIALOG_MESSAGE && !request->message_opts) ||
|
|
7005
|
+
request->kind < NATIVE_SDK_GTK_DIALOG_OPEN || request->kind > NATIVE_SDK_GTK_DIALOG_MESSAGE) {
|
|
7006
|
+
return 0;
|
|
7007
|
+
}
|
|
7008
|
+
native_sdk_gtk_dialog_t *entry = calloc(1, sizeof(*entry));
|
|
7009
|
+
if (!entry) return 0;
|
|
7010
|
+
entry->host = host;
|
|
7011
|
+
entry->id = request->id;
|
|
7012
|
+
entry->kind = request->kind;
|
|
7013
|
+
entry->outcome = NATIVE_SDK_GTK_DIALOG_CANCELLED;
|
|
7014
|
+
entry->cancellable = g_cancellable_new();
|
|
7015
|
+
entry->paths = g_ptr_array_new_with_free_func(g_free);
|
|
7016
|
+
native_sdk_gtk_window_t *win = request->parent_window_id != 0 ? native_sdk_find_window(host, request->parent_window_id) : NULL;
|
|
7017
|
+
GtkWindow *parent = win ? win->gtk_window : NULL;
|
|
7018
|
+
if (parent) entry->parent_window_id = request->parent_window_id;
|
|
7019
|
+
entry->next = host->dialogs;
|
|
7020
|
+
host->dialogs = entry;
|
|
7021
|
+
switch (request->kind) {
|
|
7022
|
+
case NATIVE_SDK_GTK_DIALOG_OPEN: {
|
|
7023
|
+
GtkFileDialog *dialog = GTK_FILE_DIALOG(native_sdk_new_open_dialog(request->open_opts));
|
|
7024
|
+
entry->dialog = G_OBJECT(dialog);
|
|
7025
|
+
entry->folders = request->open_opts->allow_directories != 0;
|
|
7026
|
+
if (entry->folders) {
|
|
7027
|
+
gtk_file_dialog_select_multiple_folders(dialog, parent, entry->cancellable, native_sdk_dialog_files_done, entry);
|
|
7028
|
+
} else {
|
|
7029
|
+
gtk_file_dialog_open_multiple(dialog, parent, entry->cancellable, native_sdk_dialog_files_done, entry);
|
|
7030
|
+
}
|
|
7031
|
+
break;
|
|
7032
|
+
}
|
|
7033
|
+
case NATIVE_SDK_GTK_DIALOG_SAVE: {
|
|
7034
|
+
GtkFileDialog *dialog = GTK_FILE_DIALOG(native_sdk_new_save_dialog(request->save_opts));
|
|
7035
|
+
entry->dialog = G_OBJECT(dialog);
|
|
7036
|
+
gtk_file_dialog_save(dialog, parent, entry->cancellable, native_sdk_dialog_file_done, entry);
|
|
7037
|
+
break;
|
|
7038
|
+
}
|
|
7039
|
+
default: {
|
|
7040
|
+
GtkAlertDialog *dialog = GTK_ALERT_DIALOG(native_sdk_new_alert_dialog(request->message_opts));
|
|
7041
|
+
entry->dialog = G_OBJECT(dialog);
|
|
7042
|
+
gtk_alert_dialog_choose(dialog, parent, entry->cancellable, native_sdk_dialog_alert_done, entry);
|
|
7043
|
+
break;
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
return 1;
|
|
7047
|
+
}
|
|
7048
|
+
|
|
7049
|
+
void native_sdk_gtk_cancel_dialog(native_sdk_gtk_host_t *host, uint64_t id) {
|
|
7050
|
+
if (!host) return;
|
|
7051
|
+
native_sdk_gtk_dialog_t *entry = native_sdk_dialog_find(host, id);
|
|
7052
|
+
if (entry && !entry->finished) g_cancellable_cancel(entry->cancellable);
|
|
7053
|
+
}
|
|
7054
|
+
|
|
7055
|
+
int native_sdk_gtk_poll_dialog_result(native_sdk_gtk_host_t *host, char *buffer, size_t buffer_len, native_sdk_gtk_dialog_result_t *result) {
|
|
7056
|
+
if (!host || !result) return 0;
|
|
7057
|
+
native_sdk_gtk_dialog_t *oldest = NULL;
|
|
7058
|
+
for (native_sdk_gtk_dialog_t *entry = host->dialogs; entry; entry = entry->next) {
|
|
7059
|
+
if (entry->finished && (!oldest || entry->finish_serial < oldest->finish_serial)) oldest = entry;
|
|
7060
|
+
}
|
|
7061
|
+
if (!oldest) return 0;
|
|
7062
|
+
native_sdk_dialog_unlink(host, oldest);
|
|
7063
|
+
*result = (native_sdk_gtk_dialog_result_t){ .id = oldest->id, .outcome = oldest->outcome, .button = oldest->button };
|
|
7064
|
+
size_t offset = 0;
|
|
7065
|
+
size_t count = 0;
|
|
7066
|
+
for (guint i = 0; i < oldest->paths->len; i++) {
|
|
7067
|
+
const char *path = g_ptr_array_index(oldest->paths, i);
|
|
7068
|
+
const size_t len = strlen(path);
|
|
7069
|
+
const size_t needed = len + (count > 0 ? 1 : 0);
|
|
7070
|
+
if (!buffer || needed > buffer_len - offset) {
|
|
7071
|
+
/* More paths than the runtime holds: a failure, not a
|
|
7072
|
+
* silently shortened selection. */
|
|
7073
|
+
result->outcome = NATIVE_SDK_GTK_DIALOG_FAILED;
|
|
7074
|
+
offset = 0;
|
|
7075
|
+
count = 0;
|
|
7076
|
+
break;
|
|
7077
|
+
}
|
|
7078
|
+
if (count > 0) buffer[offset++] = '\n';
|
|
7079
|
+
memcpy(buffer + offset, path, len);
|
|
7080
|
+
offset += len;
|
|
7081
|
+
count++;
|
|
7082
|
+
}
|
|
7083
|
+
result->count = count;
|
|
7084
|
+
result->bytes_written = offset;
|
|
7085
|
+
native_sdk_dialog_free(oldest);
|
|
7086
|
+
return 1;
|
|
7087
|
+
}
|