From e2af056d911aff675d4e930620bd53b69d3ab375 Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Sun, 25 May 2025 15:55:48 +0100 Subject: [PATCH] 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. --- src/dialog/unix/SDL_portaldialog.c | 43 +++++++++++++++++++++++++++++- test/testdialog.c | 25 ++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index efecd12419026..7ba566fa9ff26 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -25,9 +25,11 @@ #ifdef SDL_USE_LIBDBUS +#include #include #include #include +#include #include #define PORTAL_DESTINATION "org.freedesktop.portal.Desktop" @@ -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,22 @@ 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) || S_ISLNK(statbuf.st_mode); + } else if (errno == ENOENT) { + char *dirc = SDL_strdup(default_location); + location_folder = SDL_strdup(dirname(dirc)); + SDL_free(dirc); + 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); + location_name = SDL_strdup(basename(basec)); + SDL_free(basec); + } + } break; case SDL_FILEDIALOG_OPENFOLDER: @@ -410,8 +433,26 @@ 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) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Create save dialog for 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) { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Create save dialog for new file"); + DBus_AppendByteArray(dbus, &options, "current_folder", location_folder); + DBus_AppendStringOption(dbus, &options, "current_name", location_name); + } else { + SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Create dialog at folder"); + DBus_AppendByteArray(dbus, &options, "current_folder", default_location); + } + + SDL_free(location_name); + SDL_free(location_folder); } + if (accept) { DBus_AppendStringOption(dbus, &options, "accept_label", accept); } diff --git a/test/testdialog.c b/test/testdialog.c index dcff5bd9f00d5..e517c5c0a971f 100644 --- a/test/testdialog.c +++ b/test/testdialog.c @@ -14,6 +14,7 @@ #include #include #include +#include const SDL_DialogFileFilter filters[] = { { "All files", "*" }, @@ -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++; @@ -55,6 +65,7 @@ int main(int argc, char *argv[]) const SDL_FRect open_folder_rect = { 370, 50, 220, 140 }; int i; const char *initial_path = NULL; + char *saved_path = NULL; /* Initialize test framework */ state = SDLTest_CommonCreateState(argv, 0); @@ -116,7 +127,19 @@ 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); + const char *default_filename = "Untitled.index"; + const size_t save_path_total_length = SDL_strlen(initial_path) + SDL_strlen(default_filename) + 1; + char *save_path; + if (saved_path) { + save_path = SDL_strdup(saved_path); + } else { + save_path = (char *)SDL_malloc(save_path_total_length); + *save_path = '\0'; + SDL_strlcat(save_path, initial_path, save_path_total_length); + SDL_strlcat(save_path, default_filename, save_path_total_length); + } + SDL_ShowSaveFileDialog(callback, &saved_path, w, filters, SDL_arraysize(filters), save_path); + SDL_free(save_path); } } }