Skip to content

dialog: Fix save file chooser with xdg portal #13007

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 73 additions & 10 deletions src/dialog/unix/SDL_portaldialog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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;
Expand All @@ -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, &params);
Expand All @@ -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);
Expand All @@ -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.
Expand All @@ -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);
Expand All @@ -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(&params, &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);
Expand All @@ -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);
Expand All @@ -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)
Expand Down
39 changes: 38 additions & 1 deletion test/testdialog.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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)";

Expand All @@ -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++;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
Loading