Browse Source

dialog: Fix save file chooser with xdg portal

This correctly sets the xdg portal fields for targeting a specific
new filename or existing file.

"current_name" sets the dialogs placeholder name.
"current_file" targets an existing file.
"current_folder" for when the target is a folder.
Colin Kinloch 3 tháng trước cách đây
mục cha
commit
c79a18d0fa
2 tập tin đã thay đổi với 92 bổ sung9 xóa
  1. 54 8
      src/dialog/unix/SDL_portaldialog.c
  2. 38 1
      test/testdialog.c

+ 54 - 8
src/dialog/unix/SDL_portaldialog.c

@@ -26,6 +26,8 @@
 #ifdef SDL_USE_LIBDBUS
 
 #include <errno.h>
+#include <libgen.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -294,7 +296,12 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
     bool allow_many = SDL_GetBooleanProperty(props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
     const char *default_location = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, NULL);
     const char *accept = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, NULL);
+    char *location_name = NULL;
+    char *location_folder = NULL;
+    struct stat statbuf;
     bool open_folders = false;
+    bool save_file_existing = false;
+    bool save_file_new_named = false;
 
     switch (type) {
     case SDL_FILEDIALOG_OPENFILE:
@@ -305,6 +312,28 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
     case SDL_FILEDIALOG_SAVEFILE:
         method = "SaveFile";
         method_title = SDL_GetStringProperty(props, SDL_PROP_FILE_DIALOG_TITLE_STRING, "Save File");
+        if (default_location) {
+            if (stat(default_location, &statbuf) == 0) {
+                save_file_existing = S_ISREG(statbuf.st_mode);
+            } else if (errno == ENOENT) {
+                char *dirc = SDL_strdup(default_location);
+                if (dirc) {
+                    location_folder = SDL_strdup(dirname(dirc));
+                    SDL_free(dirc);
+                    if (location_folder) {
+                        save_file_new_named = (stat(location_folder, &statbuf) == 0) && S_ISDIR(statbuf.st_mode);
+                    }
+                }
+            }
+
+            if (save_file_existing || save_file_new_named) {
+                char *basec = SDL_strdup(default_location);
+                if (basec) {
+                    location_name = SDL_strdup(basename(basec));
+                    SDL_free(basec);
+                }
+            }
+        }
         break;
 
     case SDL_FILEDIALOG_OPENFOLDER:
@@ -317,7 +346,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
         /* This is already checked in ../SDL_dialog.c; this silences compiler warnings */
         SDL_SetError("Invalid file dialog type: %d", type);
         callback(userdata, NULL, -1);
-        return;
+        goto cleanup;
     }
 
     SDL_DBusContext *dbus = SDL_DBus_GetContext();
@@ -335,20 +364,20 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
     if (err_msg) {
         SDL_SetError("%s", err_msg);
         callback(userdata, NULL, -1);
-        return;
+        goto cleanup;
     }
 
     if (dbus == NULL) {
         SDL_SetError("Failed to connect to DBus");
         callback(userdata, NULL, -1);
-        return;
+        goto cleanup;
     }
 
     msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method);
     if (msg == NULL) {
         SDL_SetError("Failed to send message to portal");
         callback(userdata, NULL, -1);
-        return;
+        goto cleanup;
     }
 
     dbus->message_iter_init_append(msg, &params);
@@ -362,7 +391,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
             handle_str = SDL_malloc(len * sizeof(char));
             if (!handle_str) {
                 callback(userdata, NULL, -1);
-                return;
+                goto cleanup;
             }
 
             SDL_snprintf(handle_str, len, "%s%s", WAYLAND_HANDLE_PREFIX, parent_handle);
@@ -373,7 +402,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
                 handle_str = SDL_malloc(len * sizeof(char));
                 if (!handle_str) {
                     callback(userdata, NULL, -1);
-                    return;
+                    goto cleanup;
                 }
 
                 // The portal wants X11 window ID numbers in hex.
@@ -393,7 +422,7 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
     handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1));
     if (!handle_str) {
         callback(userdata, NULL, -1);
-        return;
+        goto cleanup;
     }
     SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id);
     DBus_AppendStringOption(dbus, &options, "handle_token", handle_str);
@@ -410,7 +439,20 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
         DBus_AppendFilters(dbus, &options, filters, nfilters);
     }
     if (default_location) {
-        DBus_AppendByteArray(dbus, &options, "current_folder", default_location);
+        if (save_file_existing && location_name) {
+            /* Open a save dialog at an existing file */
+            DBus_AppendByteArray(dbus, &options, "current_file", default_location);
+            /* Setting "current_name" should not be necessary however the kde-desktop-portal sets the filename without an extension.
+             * An alternative would be to match the extension to a filter and set "current_filter".
+             */
+            DBus_AppendStringOption(dbus, &options, "current_name", location_name);
+        } else if (save_file_new_named && location_folder && location_name) {
+            /* Open a save dialog at a location with a suggested name */
+            DBus_AppendByteArray(dbus, &options, "current_folder", location_folder);
+            DBus_AppendStringOption(dbus, &options, "current_name", location_name);
+        } else {
+            DBus_AppendByteArray(dbus, &options, "current_folder", default_location);
+        }
     }
     if (accept) {
         DBus_AppendStringOption(dbus, &options, "accept_label", accept);
@@ -469,6 +511,10 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog
 
 incorrect_type:
     dbus->message_unref(reply);
+
+cleanup:
+    SDL_free(location_name);
+    SDL_free(location_folder);
 }
 
 bool SDL_Portal_detect(void)

+ 38 - 1
test/testdialog.c

@@ -12,6 +12,7 @@
 /* Sample program:  Create open and save dialogs. */
 
 #include <SDL3/SDL.h>
+#include <SDL3/SDL_iostream.h>
 #include <SDL3/SDL_main.h>
 #include <SDL3/SDL_test.h>
 
@@ -23,6 +24,8 @@ const SDL_DialogFileFilter filters[] = {
 };
 
 static void SDLCALL callback(void *userdata, const char * const *files, int filter) {
+    char **saved_path = userdata;
+
     if (files) {
         const char* filter_name = "(filter fetching unsupported)";
 
@@ -36,6 +39,13 @@ static void SDLCALL callback(void *userdata, const char * const *files, int filt
 
         SDL_Log("Filter used: '%s'", filter_name);
 
+        if (*files && saved_path) {
+            *saved_path = SDL_strdup(*files);
+            /* Create the file */
+            SDL_IOStream *stream = SDL_IOFromFile(*saved_path, "w");
+            SDL_CloseIO(stream);
+        }
+
         while (*files) {
             SDL_Log("'%s'", *files);
             files++;
@@ -45,6 +55,23 @@ static void SDLCALL callback(void *userdata, const char * const *files, int filt
     }
 }
 
+char *concat_strings(const char *a, const char *b)
+{
+    char *out = NULL;
+
+    if (a != NULL && b != NULL) {
+        const size_t out_size = SDL_strlen(a) + SDL_strlen(b) + 1;
+        out = (char *)SDL_malloc(out_size);
+        if (out) {
+            *out = '\0';
+            SDL_strlcat(out, a, out_size);
+            SDL_strlcat(out, b, out_size);
+        }
+    }
+
+    return out;
+}
+
 int main(int argc, char *argv[])
 {
     SDL_Window *w;
@@ -54,7 +81,9 @@ int main(int argc, char *argv[])
     const SDL_FRect save_file_rect = { 50, 290, 220, 140 };
     const SDL_FRect open_folder_rect = { 370, 50, 220, 140 };
     int i;
+    const char *default_filename = "Untitled.index";
     const char *initial_path = NULL;
+    char *last_saved_path = NULL;
 
     /* Initialize test framework */
     state = SDLTest_CommonCreateState(argv, 0);
@@ -116,7 +145,14 @@ int main(int argc, char *argv[])
                 } else if (SDL_PointInRectFloat(&p, &open_folder_rect)) {
                     SDL_ShowOpenFolderDialog(callback, NULL, w, initial_path, 1);
                 } else if (SDL_PointInRectFloat(&p, &save_file_rect)) {
-                    SDL_ShowSaveFileDialog(callback, NULL, w, filters, SDL_arraysize(filters), initial_path);
+                    char *save_path = NULL;
+                    if (last_saved_path) {
+                        save_path = SDL_strdup(last_saved_path);
+                    } else {
+                        save_path = concat_strings(initial_path, default_filename);
+                    }
+                    SDL_ShowSaveFileDialog(callback, &last_saved_path, w, filters, SDL_arraysize(filters), save_path ? save_path : default_filename);
+                    SDL_free(save_path);
                 }
             }
         }
@@ -145,6 +181,7 @@ int main(int argc, char *argv[])
         SDL_RenderPresent(r);
     }
 
+    SDL_free(last_saved_path);
     SDLTest_CleanupTextDrawing();
     SDL_DestroyRenderer(r);
     SDL_DestroyWindow(w);