@zogien/native-sdk 0.11.0-fork.3 → 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/README.md +1 -1
- package/build/app.zig +53 -34
- package/build/cli.zig +22 -0
- package/package.json +10 -10
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/schemas/app.schema.json +9 -1
- package/skill-data/core/references/bridge-security-native-capabilities.md +4 -0
- package/skill-data/ts-core/SKILL.md +2 -0
- package/src/app_runner/app_assets.zig +85 -1
- package/src/app_runner/root.zig +63 -3
- package/src/app_runner/ts_core_main.zig +8 -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 +53 -0
- package/src/platform/macos/appkit_host.m +667 -67
- package/src/platform/macos/cef_host.mm +21 -0
- package/src/platform/macos/root.zig +146 -29
- package/src/platform/null_platform.zig +136 -0
- package/src/platform/root.zig +7 -0
- package/src/platform/types.zig +90 -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 +262 -39
- package/src/platform/windows/session_end_test.cpp +303 -0
- package/src/platform/windows/webview2_host.cpp +1435 -306
- package/src/primitives/app_manifest/root.zig +1 -0
- package/src/primitives/app_manifest/tests.zig +35 -0
- package/src/primitives/app_manifest/types.zig +12 -0
- package/src/primitives/app_manifest/validation.zig +44 -6
- package/src/root.zig +8 -0
- package/src/runtime/builtin_bridge.zig +108 -13
- package/src/runtime/channel_sources.zig +49 -1
- 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 +83 -3
- package/src/runtime/effects_host_tests.zig +69 -1
- package/src/runtime/flow.zig +20 -6
- package/src/runtime/flow_reference.zig +20 -6
- package/src/runtime/platform_bridge_tests.zig +1 -0
- package/src/runtime/root.zig +9 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/runtime/system_services.zig +64 -82
- package/src/runtime/ui_app.zig +67 -0
- package/src/tooling/manifest.zig +111 -3
- package/src/tooling/package.zig +98 -8
- package/src/tooling/raw_manifest.zig +7 -0
- package/src/tooling/root.zig +2 -0
- package/src/tooling/templates.zig +44 -2
- package/src/tooling/update.zig +228 -10
- package/src/tooling/windows_installer.zig +245 -0
- package/src/tooling/zip_writer.zig +211 -0
- package/src/updater/engine.zig +936 -0
- package/src/updater/feed.zig +261 -12
- package/src/updater/macos.zig +187 -0
- package/src/updater/root.zig +41 -0
- package/src/updater/windows.zig +470 -0
- package/src/windows_install/config.zig +247 -0
- package/src/windows_install/main.zig +690 -0
- package/src/windows_install/payload.zig +146 -0
- package/src/windows_install/switch.zig +184 -0
- package/src/windows_install/tests.zig +11 -0
- package/src/windows_install/win32.zig +248 -0
- package/tools/native-sdk/main.zig +10 -9
|
@@ -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
|
+
}
|
|
@@ -156,6 +156,38 @@ typedef struct {
|
|
|
156
156
|
size_t tertiary_button_len;
|
|
157
157
|
} native_sdk_gtk_message_dialog_opts_t;
|
|
158
158
|
|
|
159
|
+
enum {
|
|
160
|
+
NATIVE_SDK_GTK_DIALOG_OPEN = 0,
|
|
161
|
+
NATIVE_SDK_GTK_DIALOG_SAVE = 1,
|
|
162
|
+
NATIVE_SDK_GTK_DIALOG_MESSAGE = 2,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
enum {
|
|
166
|
+
NATIVE_SDK_GTK_DIALOG_SELECTED = 0,
|
|
167
|
+
NATIVE_SDK_GTK_DIALOG_CANCELLED = 1,
|
|
168
|
+
NATIVE_SDK_GTK_DIALOG_FAILED = 2,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/* An asynchronous dialog request (native_sdk_gtk_request_dialog): kind
|
|
172
|
+
* picks which of the option pointers is set. */
|
|
173
|
+
typedef struct {
|
|
174
|
+
uint64_t id;
|
|
175
|
+
uint64_t parent_window_id;
|
|
176
|
+
int kind;
|
|
177
|
+
const native_sdk_gtk_open_dialog_opts_t *open_opts;
|
|
178
|
+
const native_sdk_gtk_save_dialog_opts_t *save_opts;
|
|
179
|
+
const native_sdk_gtk_message_dialog_opts_t *message_opts;
|
|
180
|
+
} native_sdk_gtk_dialog_request_t;
|
|
181
|
+
|
|
182
|
+
/* A finished asynchronous dialog; paths are newline-joined. */
|
|
183
|
+
typedef struct {
|
|
184
|
+
uint64_t id;
|
|
185
|
+
int outcome;
|
|
186
|
+
int button;
|
|
187
|
+
size_t count;
|
|
188
|
+
size_t bytes_written;
|
|
189
|
+
} native_sdk_gtk_dialog_result_t;
|
|
190
|
+
|
|
159
191
|
/* resizable/titlebar_style/min_width/min_height mirror the AppKit host's
|
|
160
192
|
* window options: titlebar_style 0 = standard decorations, 1 =
|
|
161
193
|
* hidden_inset, 2 = hidden_inset_tall (the hidden styles create the
|
|
@@ -287,6 +319,11 @@ int native_sdk_gtk_clipboard_write_data(native_sdk_gtk_host_t *host, const char
|
|
|
287
319
|
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);
|
|
288
320
|
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);
|
|
289
321
|
int native_sdk_gtk_show_message_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_message_dialog_opts_t *opts);
|
|
322
|
+
/* Show a dialog without a nested loop: it returns at once, and the
|
|
323
|
+
* result waits for poll after a wake. Returns 0 when nothing was shown. */
|
|
324
|
+
int native_sdk_gtk_request_dialog(native_sdk_gtk_host_t *host, const native_sdk_gtk_dialog_request_t *request);
|
|
325
|
+
void native_sdk_gtk_cancel_dialog(native_sdk_gtk_host_t *host, uint64_t id);
|
|
326
|
+
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);
|
|
290
327
|
|
|
291
328
|
#ifdef __cplusplus
|
|
292
329
|
}
|
|
@@ -234,9 +234,29 @@ const GtkMessageDialogOpts = extern struct {
|
|
|
234
234
|
tertiary_button_len: usize,
|
|
235
235
|
};
|
|
236
236
|
|
|
237
|
+
const GtkDialogRequest = extern struct {
|
|
238
|
+
id: u64,
|
|
239
|
+
parent_window_id: u64,
|
|
240
|
+
kind: c_int,
|
|
241
|
+
open_opts: ?*const GtkOpenDialogOpts,
|
|
242
|
+
save_opts: ?*const GtkSaveDialogOpts,
|
|
243
|
+
message_opts: ?*const GtkMessageDialogOpts,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const GtkDialogResult = extern struct {
|
|
247
|
+
id: u64,
|
|
248
|
+
outcome: c_int,
|
|
249
|
+
button: c_int,
|
|
250
|
+
count: usize,
|
|
251
|
+
bytes_written: usize,
|
|
252
|
+
};
|
|
253
|
+
|
|
237
254
|
extern fn native_sdk_gtk_show_open_dialog(host: *GtkHost, opts: *const GtkOpenDialogOpts, buffer: [*]u8, buffer_len: usize) GtkOpenDialogResult;
|
|
238
255
|
extern fn native_sdk_gtk_show_save_dialog(host: *GtkHost, opts: *const GtkSaveDialogOpts, buffer: [*]u8, buffer_len: usize) usize;
|
|
239
256
|
extern fn native_sdk_gtk_show_message_dialog(host: *GtkHost, opts: *const GtkMessageDialogOpts) c_int;
|
|
257
|
+
extern fn native_sdk_gtk_request_dialog(host: *GtkHost, request: *const GtkDialogRequest) c_int;
|
|
258
|
+
extern fn native_sdk_gtk_cancel_dialog(host: *GtkHost, id: u64) void;
|
|
259
|
+
extern fn native_sdk_gtk_poll_dialog_result(host: *GtkHost, buffer: [*]u8, buffer_len: usize, result: *GtkDialogResult) c_int;
|
|
240
260
|
|
|
241
261
|
/// The startup-window twin of the runtime's create-time `.hide` gate:
|
|
242
262
|
/// GTK reports `window_hide_on_close` false (no status item exists to
|
|
@@ -390,6 +410,9 @@ pub const LinuxPlatform = struct {
|
|
|
390
410
|
.show_open_dialog_fn = showOpenDialog,
|
|
391
411
|
.show_save_dialog_fn = showSaveDialog,
|
|
392
412
|
.show_message_dialog_fn = showMessageDialog,
|
|
413
|
+
.request_dialog_fn = if (self.web_engine == .system) requestDialog else null,
|
|
414
|
+
.cancel_dialog_fn = if (self.web_engine == .system) cancelDialog else null,
|
|
415
|
+
.poll_dialog_result_fn = if (self.web_engine == .system) pollDialogResult else null,
|
|
393
416
|
.open_external_url_fn = openExternalUrl,
|
|
394
417
|
.reveal_path_fn = revealPath,
|
|
395
418
|
.show_notification_fn = showNotification,
|
|
@@ -1271,11 +1294,9 @@ fn closeWebView(context: ?*anyopaque, window_id: platform_mod.WindowId, label: [
|
|
|
1271
1294
|
if (native_sdk_gtk_close_webview(self.host, window_id, label.ptr, label.len) == 0) return error.WebViewNotFound;
|
|
1272
1295
|
}
|
|
1273
1296
|
|
|
1274
|
-
fn
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
const ext_str = flattenFilters(options.filters, &ext_buf);
|
|
1278
|
-
const opts = GtkOpenDialogOpts{
|
|
1297
|
+
fn gtkOpenDialogOpts(options: platform_mod.OpenDialogOptions, ext_buf: []u8) GtkOpenDialogOpts {
|
|
1298
|
+
const ext_str = flattenFilters(options.filters, ext_buf);
|
|
1299
|
+
return .{
|
|
1279
1300
|
.title = options.title.ptr,
|
|
1280
1301
|
.title_len = options.title.len,
|
|
1281
1302
|
.default_path = options.default_path.ptr,
|
|
@@ -1285,16 +1306,11 @@ fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions,
|
|
|
1285
1306
|
.allow_directories = if (options.allow_directories) 1 else 0,
|
|
1286
1307
|
.allow_multiple = if (options.allow_multiple) 1 else 0,
|
|
1287
1308
|
};
|
|
1288
|
-
const result = native_sdk_gtk_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1289
|
-
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
|
|
1290
|
-
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
|
|
1291
1309
|
}
|
|
1292
1310
|
|
|
1293
|
-
fn
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
const ext_str = flattenFilters(options.filters, &ext_buf);
|
|
1297
|
-
const opts = GtkSaveDialogOpts{
|
|
1311
|
+
fn gtkSaveDialogOpts(options: platform_mod.SaveDialogOptions, ext_buf: []u8) GtkSaveDialogOpts {
|
|
1312
|
+
const ext_str = flattenFilters(options.filters, ext_buf);
|
|
1313
|
+
return .{
|
|
1298
1314
|
.title = options.title.ptr,
|
|
1299
1315
|
.title_len = options.title.len,
|
|
1300
1316
|
.default_path = options.default_path.ptr,
|
|
@@ -1304,15 +1320,10 @@ fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions,
|
|
|
1304
1320
|
.extensions = ext_str.ptr,
|
|
1305
1321
|
.extensions_len = ext_str.len,
|
|
1306
1322
|
};
|
|
1307
|
-
const written = native_sdk_gtk_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1308
|
-
if (written > buffer.len) return error.NoSpaceLeft;
|
|
1309
|
-
if (written == 0) return null;
|
|
1310
|
-
return buffer[0..written];
|
|
1311
1323
|
}
|
|
1312
1324
|
|
|
1313
|
-
fn
|
|
1314
|
-
|
|
1315
|
-
const opts = GtkMessageDialogOpts{
|
|
1325
|
+
fn gtkMessageDialogOpts(options: platform_mod.MessageDialogOptions) GtkMessageDialogOpts {
|
|
1326
|
+
return .{
|
|
1316
1327
|
.style = @intFromEnum(options.style),
|
|
1317
1328
|
.title = options.title.ptr,
|
|
1318
1329
|
.title_len = options.title.len,
|
|
@@ -1327,9 +1338,92 @@ fn showMessageDialog(context: ?*anyopaque, options: platform_mod.MessageDialogOp
|
|
|
1327
1338
|
.tertiary_button = options.tertiary_button.ptr,
|
|
1328
1339
|
.tertiary_button_len = options.tertiary_button.len,
|
|
1329
1340
|
};
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
fn showOpenDialog(context: ?*anyopaque, options: platform_mod.OpenDialogOptions, buffer: []u8) anyerror!platform_mod.OpenDialogResult {
|
|
1344
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1345
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1346
|
+
const opts = gtkOpenDialogOpts(options, &ext_buf);
|
|
1347
|
+
const result = native_sdk_gtk_show_open_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1348
|
+
if (result.bytes_written > buffer.len) return error.NoSpaceLeft;
|
|
1349
|
+
return .{ .count = result.count, .paths = buffer[0..result.bytes_written] };
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
fn showSaveDialog(context: ?*anyopaque, options: platform_mod.SaveDialogOptions, buffer: []u8) anyerror!?[]const u8 {
|
|
1353
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1354
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1355
|
+
const opts = gtkSaveDialogOpts(options, &ext_buf);
|
|
1356
|
+
const written = native_sdk_gtk_show_save_dialog(self.host, &opts, buffer.ptr, buffer.len);
|
|
1357
|
+
if (written > buffer.len) return error.NoSpaceLeft;
|
|
1358
|
+
if (written == 0) return null;
|
|
1359
|
+
return buffer[0..written];
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
fn showMessageDialog(context: ?*anyopaque, options: platform_mod.MessageDialogOptions) anyerror!platform_mod.MessageDialogResult {
|
|
1363
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1364
|
+
const opts = gtkMessageDialogOpts(options);
|
|
1330
1365
|
return @enumFromInt(native_sdk_gtk_show_message_dialog(self.host, &opts));
|
|
1331
1366
|
}
|
|
1332
1367
|
|
|
1368
|
+
// The host copies every option before returning, so they may live on this
|
|
1369
|
+
// stack; the result comes back through pollDialogResult on a later wake.
|
|
1370
|
+
fn requestDialog(context: ?*anyopaque, request: platform_mod.DialogRequest) anyerror!void {
|
|
1371
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1372
|
+
var ext_buf: [1024]u8 = undefined;
|
|
1373
|
+
var open_opts: GtkOpenDialogOpts = undefined;
|
|
1374
|
+
var save_opts: GtkSaveDialogOpts = undefined;
|
|
1375
|
+
var message_opts: GtkMessageDialogOpts = undefined;
|
|
1376
|
+
var native_request = GtkDialogRequest{
|
|
1377
|
+
.id = request.id,
|
|
1378
|
+
.parent_window_id = request.parent_window_id orelse 0,
|
|
1379
|
+
.kind = @intFromEnum(std.meta.activeTag(request.options)),
|
|
1380
|
+
.open_opts = null,
|
|
1381
|
+
.save_opts = null,
|
|
1382
|
+
.message_opts = null,
|
|
1383
|
+
};
|
|
1384
|
+
switch (request.options) {
|
|
1385
|
+
.open => |options| {
|
|
1386
|
+
open_opts = gtkOpenDialogOpts(options, &ext_buf);
|
|
1387
|
+
native_request.open_opts = &open_opts;
|
|
1388
|
+
},
|
|
1389
|
+
.save => |options| {
|
|
1390
|
+
save_opts = gtkSaveDialogOpts(options, &ext_buf);
|
|
1391
|
+
native_request.save_opts = &save_opts;
|
|
1392
|
+
},
|
|
1393
|
+
.message => |options| {
|
|
1394
|
+
message_opts = gtkMessageDialogOpts(options);
|
|
1395
|
+
native_request.message_opts = &message_opts;
|
|
1396
|
+
},
|
|
1397
|
+
}
|
|
1398
|
+
if (native_sdk_gtk_request_dialog(self.host, &native_request) == 0) return error.DialogUnavailable;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
fn cancelDialog(context: ?*anyopaque, id: platform_mod.DialogRequestId) void {
|
|
1402
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1403
|
+
native_sdk_gtk_cancel_dialog(self.host, id);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
fn pollDialogResult(context: ?*anyopaque, buffer: []u8) ?platform_mod.DialogResult {
|
|
1407
|
+
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1408
|
+
var result: GtkDialogResult = undefined;
|
|
1409
|
+
if (native_sdk_gtk_poll_dialog_result(self.host, buffer.ptr, buffer.len, &result) == 0) return null;
|
|
1410
|
+
return .{
|
|
1411
|
+
.id = result.id,
|
|
1412
|
+
.outcome = switch (result.outcome) {
|
|
1413
|
+
0 => .selected,
|
|
1414
|
+
1 => .cancelled,
|
|
1415
|
+
else => .failed,
|
|
1416
|
+
},
|
|
1417
|
+
.paths = buffer[0..@min(result.bytes_written, buffer.len)],
|
|
1418
|
+
.count = result.count,
|
|
1419
|
+
.button = switch (result.button) {
|
|
1420
|
+
1 => .secondary,
|
|
1421
|
+
2 => .tertiary,
|
|
1422
|
+
else => .primary,
|
|
1423
|
+
},
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1333
1427
|
fn openExternalUrl(context: ?*anyopaque, url: []const u8) anyerror!void {
|
|
1334
1428
|
const self: *LinuxPlatform = @ptrCast(@alignCast(context.?));
|
|
1335
1429
|
if (self.web_engine != .system) return error.UnsupportedService;
|