diff --git a/src/dialog/unix/SDL_portaldialog.c b/src/dialog/unix/SDL_portaldialog.c index dadfc9f5562e6..a7f96a3381994 100644 --- a/src/dialog/unix/SDL_portaldialog.c +++ b/src/dialog/unix/SDL_portaldialog.c @@ -26,6 +26,8 @@ #ifdef SDL_USE_LIBDBUS #include +#include +#include #include #include #include @@ -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,10 +346,11 @@ 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(); + DBusError error; DBusMessage *msg; DBusMessageIter params, options; const char *signal_id = NULL; @@ -332,23 +362,25 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog const char *err_msg = validate_filters(filters, nfilters); + dbus->error_init(&error); + 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, ¶ms); @@ -362,7 +394,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 +405,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 +425,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,14 +442,34 @@ 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); } dbus->message_iter_close_container(¶ms, &options); - DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL); + DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, &error); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to open dialog via DBus, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + if (reply) { DBusMessageIter reply_iter; dbus->message_iter_init(reply, &reply_iter); @@ -443,9 +495,16 @@ void SDL_Portal_ShowFileDialogWithProperties(SDL_FileDialogType type, SDL_Dialog } SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id); - dbus->bus_add_match(dbus->session_conn, filter, NULL); + dbus->bus_add_match(dbus->session_conn, filter, &error); SDL_free(filter); + if (dbus->error_is_set(&error)) { + SDL_SetError("Failed to set up DBus listener for dialog, %s: %s", error.name, error.message); + dbus->error_free(&error); + callback(userdata, NULL, -1); + goto cleanup; + } + SignalCallback *data = SDL_malloc(sizeof(SignalCallback)); if (!data) { callback(userdata, NULL, -1); @@ -469,6 +528,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) diff --git a/test/testdialog.c b/test/testdialog.c index dcff5bd9f00d5..37d005f4c5249 100644 --- a/test/testdialog.c +++ b/test/testdialog.c @@ -12,6 +12,7 @@ /* Sample program: Create open and save dialogs. */ #include +#include #include #include @@ -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);