Add file dialogs

This commit is contained in:
Semphris
2024-03-10 17:27:42 -04:00
committed by Sam Lantinga
parent 30e93b40c2
commit 70c2e15615
21 changed files with 2335 additions and 3 deletions

View File

@@ -0,0 +1,169 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* TODO: Macro? */
#import <Cocoa/Cocoa.h>
typedef enum
{
FDT_SAVE,
FDT_OPEN,
FDT_OPENFOLDER
} cocoa_FileDialogType;
void show_file_dialog(cocoa_FileDialogType type, SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
/* NSOpenPanel inherits from NSSavePanel */
NSSavePanel *dialog;
NSOpenPanel *dialog_as_open;
switch (type) {
case FDT_SAVE:
dialog = [NSSavePanel savePanel];
break;
case FDT_OPEN:
dialog_as_open = [NSOpenPanel openPanel];
[dialog_as_open setAllowsMultipleSelection:((allow_many == SDL_TRUE) ? YES : NO)];
dialog = dialog_as_open;
break;
case FDT_OPENFOLDER:
dialog_as_open = [NSOpenPanel openPanel];
[dialog_as_open setCanChooseFiles:NO];
[dialog_as_open setCanChooseDirectories:YES];
[dialog_as_open setAllowsMultipleSelection:((allow_many == SDL_TRUE) ? YES : NO)];
dialog = dialog_as_open;
break;
};
int n = -1;
while (filters[++n].name && filters[n].pattern);
NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:n ];
int has_all_files = 0;
for (int i = 0; i < n; i++) {
char *pattern = SDL_strdup(filters[i].pattern);
char *pattern_ptr = pattern;
if (!pattern_ptr) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
for (char *c = pattern; *c; c++) {
if (*c == ';') {
*c = '\0';
[types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
pattern_ptr = c + 1;
} else if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-' || (*c == '*' && (c[1] == '\0' || c[1] == ';')))) {
SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *c);
callback(userdata, NULL, -1);
SDL_free(pattern);
} else if (*c == '*') {
has_all_files = 1;
}
}
[types addObject: [NSString stringWithFormat: @"%s", pattern_ptr]];
SDL_free(pattern);
}
if (!has_all_files) {
if (@available(macOS 11.0, *)) {
[dialog setAllowedContentTypes:types];
} else {
[dialog setAllowedFileTypes:types];
}
}
/* Keep behavior consistent with other platforms */
[dialog setAllowsOtherFileTypes:YES];
if (default_location) {
[dialog setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:default_location]]];
}
NSWindow *w = NULL;
if (window) {
w = (__bridge NSWindow *)SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL);
}
if (w) {
// [dialog beginWithCompletionHandler:^(NSInteger result) {
[dialog beginSheetModalForWindow:w completionHandler:^(NSInteger result) {
// NSModalResponseOK for >= 10.13
if (result == NSFileHandlingPanelOKButton) {
if (dialog_as_open) {
NSArray* urls = [dialog_as_open URLs];
const char *files[[urls count] + 1];
for (int i = 0; i < [urls count]; i++) {
files[i] = [[[urls objectAtIndex:i] path] UTF8String];
}
files[[urls count]] = NULL;
callback(userdata, files, -1);
} else {
const char *files[2] = { [[[dialog URL] path] UTF8String], NULL };
callback(userdata, files, -1);
}
} else if (result == NSModalResponseCancel) {
const char *files[1] = { NULL };
callback(userdata, files, -1);
}
}];
} else {
// NSModalResponseOK for >= 10.10
if ([dialog runModal] == NSOKButton) {
if (dialog_as_open) {
NSArray* urls = [dialog_as_open URLs];
const char *files[[urls count] + 1];
for (int i = 0; i < [urls count]; i++) {
files[i] = [[[urls objectAtIndex:i] path] UTF8String];
}
files[[urls count]] = NULL;
callback(userdata, files, -1);
} else {
const char *files[2] = { [[[dialog URL] path] UTF8String], NULL };
callback(userdata, files, -1);
}
} else {
const char *files[1] = { NULL };
callback(userdata, files, -1);
}
}
}
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
show_file_dialog(FDT_OPEN, callback, userdata, window, filters, default_location, allow_many);
}
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
show_file_dialog(FDT_SAVE, callback, userdata, window, filters, default_location, 0);
}
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
show_file_dialog(FDT_OPENFOLDER, callback, userdata, window, NULL, default_location, allow_many);
}

View File

@@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* TODO: Macro? */
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}

View File

@@ -0,0 +1,233 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../../core/haiku/SDL_BeApp.h"
#include <string>
#include <vector>
#include <sys/stat.h>
#include <FilePanel.h>
#include <Entry.h>
#include <Looper.h>
#include <Messenger.h>
#include <Path.h>
#include <TypeConstants.h>
bool StringEndsWith(const std::string& str, const std::string& end)
{
return str.size() >= end.size() && !str.compare(str.size() - end.size(), end.size(), end);
}
std::vector<std::string> StringSplit(const std::string& str, const std::string& split)
{
std::vector<std::string> retval;
std::string s = str;
size_t pos = 0;
while ((pos = s.find(split)) != std::string::npos) {
retval.push_back(s.substr(0, pos));
s = s.substr(pos + split.size());
}
retval.push_back(s);
return retval;
}
class SDLBRefFilter : public BRefFilter
{
public:
SDLBRefFilter(const SDL_DialogFileFilter *filters) :
BRefFilter(),
m_filters(filters)
{
}
virtual bool Filter(const entry_ref *ref, BNode *node, struct stat_beos *stat, const char *mimeType) override
{
BEntry entry(ref);
BPath path;
entry.GetPath(&path);
std::string result = path.Path();
if (!m_filters)
return true;
struct stat info;
node->GetStat(&info);
if (S_ISDIR(info.st_mode))
return true;
const auto *filter = m_filters;
while (filter->name && filter->pattern) {
for (const auto& suffix : StringSplit(filter->pattern, ";")) {
if (StringEndsWith(result, std::string(".") + suffix)) {
return true;
}
}
filter++;
}
return false;
}
private:
const SDL_DialogFileFilter * const m_filters;
};
class CallbackLooper : public BLooper
{
public:
CallbackLooper(SDL_DialogFileCallback callback, void *userdata) :
m_callback(callback),
m_userdata(userdata),
m_files(),
m_messenger(),
m_panel(),
m_filter()
{
}
~CallbackLooper()
{
delete m_messenger;
delete m_panel;
delete m_filter;
}
void SetToBeFreed(BMessenger *messenger, BFilePanel *panel, SDLBRefFilter *filter)
{
m_messenger = messenger;
m_panel = panel;
m_filter = filter;
}
virtual void MessageReceived(BMessage *msg) override
{
entry_ref file;
BPath path;
BEntry entry;
std::string result;
const char *filename;
int32 nFiles = 0;
switch (msg->what)
{
case B_REFS_RECEIVED: // Open
msg->GetInfo("refs", NULL, &nFiles);
for (int i = 0; i < nFiles; i++) {
msg->FindRef("refs", i, &file);
entry.SetTo(&file);
entry.GetPath(&path);
result = path.Path();
m_files.push_back(result);
}
break;
case B_SAVE_REQUESTED: // Save
msg->FindRef("directory", &file);
entry.SetTo(&file);
entry.GetPath(&path);
result = path.Path();
result += "/";
msg->FindString("name", &filename);
result += filename;
m_files.push_back(result);
break;
case B_CANCEL: // Whenever the dialog is closed (Cancel but also after Open and Save)
{
nFiles = m_files.size();
const char* files[nFiles + 1];
for (int i = 0; i < nFiles; i++) {
files[i] = m_files[i].c_str();
}
files[nFiles] = NULL;
m_callback(m_userdata, files, -1);
Quit();
SDL_QuitBeApp();
delete this;
}
break;
default:
BHandler::MessageReceived(msg);
break;
}
}
private:
SDL_DialogFileCallback m_callback;
void *m_userdata;
std::vector<std::string> m_files;
// Only to free stuff later
BMessenger *m_messenger;
BFilePanel *m_panel;
SDLBRefFilter *m_filter;
};
void ShowDialog(bool save, SDL_DialogFileCallback callback, void *userdata, bool many, bool modal, const SDL_DialogFileFilter *filters, bool folder, const char *location)
{
if (SDL_InitBeApp()) {
char* err = SDL_strdup(SDL_GetError());
SDL_SetError("Couldn't init Be app: %s", err);
SDL_free(err);
callback(userdata, NULL, -1);
return;
}
// No unique_ptr's because they need to survive the end of the function
CallbackLooper *looper = new CallbackLooper(callback, userdata);
BMessenger *messenger = new BMessenger(NULL, looper);
SDLBRefFilter *filter = new SDLBRefFilter(filters);
BEntry entry;
entry_ref entryref;
if (location) {
entry.SetTo(location);
entry.GetRef(&entryref);
}
BFilePanel *panel = new BFilePanel(save ? B_SAVE_PANEL : B_OPEN_PANEL, messenger, location ? &entryref : NULL, folder ? B_DIRECTORY_NODE : B_FILE_NODE, many, NULL, filter, modal);
looper->SetToBeFreed(messenger, panel, filter);
looper->Run();
panel->Show();
}
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, const char *default_location, SDL_bool allow_many)
{
ShowDialog(false, callback, userdata, allow_many == SDL_TRUE, !!window, filters, false, default_location);
}
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const SDL_DialogFileFilter *filters, const char *default_location)
{
ShowDialog(true, callback, userdata, false, !!window, filters, false, default_location);
}
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void *userdata, SDL_Window *window, const char* default_location, SDL_bool allow_many)
{
// Use a dummy filter to avoid showing files in the dialog
SDL_DialogFileFilter filter[] = {{}};
ShowDialog(false, callback, userdata, allow_many == SDL_TRUE, !!window, filter, true, default_location);
}

View File

@@ -0,0 +1,408 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "./SDL_dialog.h"
#include "../../core/linux/SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop"
#define PORTAL_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_INTERFACE "org.freedesktop.portal.FileChooser"
#define SIGNAL_SENDER "org.freedesktop.portal.Desktop"
#define SIGNAL_INTERFACE "org.freedesktop.portal.Request"
#define SIGNAL_NAME "Response"
#define SIGNAL_FILTER "type='signal', sender='"SIGNAL_SENDER"', interface='"SIGNAL_INTERFACE"', member='"SIGNAL_NAME"', path='"
#define HANDLE_LEN 10
typedef struct {
SDL_DialogFileCallback callback;
void *userdata;
const char *path;
} SignalCallback;
static void DBus_AppendStringOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value)
{
DBusMessageIter options_pair, options_value;
dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "s", &options_value);
dbus->message_iter_append_basic(&options_value, DBUS_TYPE_STRING, &value);
dbus->message_iter_close_container(&options_pair, &options_value);
dbus->message_iter_close_container(options, &options_pair);
}
static void DBus_AppendBoolOption(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, int value)
{
DBusMessageIter options_pair, options_value;
dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "b", &options_value);
dbus->message_iter_append_basic(&options_value, DBUS_TYPE_BOOLEAN, &value);
dbus->message_iter_close_container(&options_pair, &options_value);
dbus->message_iter_close_container(options, &options_pair);
}
static void DBus_AppendFilter(SDL_DBusContext *dbus, DBusMessageIter *parent, const SDL_DialogFileFilter *filter)
{
DBusMessageIter filter_entry, filter_array, filter_array_entry;
char *state = NULL, *patterns, *pattern, *glob_pattern;
int zero = 0;
dbus->message_iter_open_container(parent, DBUS_TYPE_STRUCT, NULL, &filter_entry);
dbus->message_iter_append_basic(&filter_entry, DBUS_TYPE_STRING, &filter->name);
dbus->message_iter_open_container(&filter_entry, DBUS_TYPE_ARRAY, "(us)", &filter_array);
patterns = SDL_strdup(filter->pattern);
if (!patterns) {
SDL_OutOfMemory();
goto cleanup;
}
pattern = SDL_strtok_r(patterns, ";", &state);
while (pattern) {
size_t max_len = SDL_strlen(pattern) + 3;
dbus->message_iter_open_container(&filter_array, DBUS_TYPE_STRUCT, NULL, &filter_array_entry);
dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_UINT32, &zero);
glob_pattern = SDL_calloc(sizeof(char), max_len);
if (!glob_pattern) {
SDL_OutOfMemory();
goto cleanup;
}
glob_pattern[0] = '*';
/* Special case: The '*' filter doesn't need to be rewritten */
if (pattern[0] != '*' || pattern[1]) {
glob_pattern[1] = '.';
SDL_strlcat(glob_pattern + 2, pattern, max_len);
}
dbus->message_iter_append_basic(&filter_array_entry, DBUS_TYPE_STRING, &glob_pattern);
SDL_free(glob_pattern);
dbus->message_iter_close_container(&filter_array, &filter_array_entry);
pattern = SDL_strtok_r(NULL, ";", &state);
}
cleanup:
SDL_free(patterns);
dbus->message_iter_close_container(&filter_entry, &filter_array);
dbus->message_iter_close_container(parent, &filter_entry);
}
static void DBus_AppendFilters(SDL_DBusContext *dbus, DBusMessageIter *options, const SDL_DialogFileFilter *filters)
{
DBusMessageIter options_pair, options_value, options_value_array;
static const char *filters_name = "filters";
dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &filters_name);
dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "a(sa(us))", &options_value);
dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "(sa(us))", &options_value_array);
for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; ++filter) {
DBus_AppendFilter(dbus, &options_value_array, filter);
}
dbus->message_iter_close_container(&options_value, &options_value_array);
dbus->message_iter_close_container(&options_pair, &options_value);
dbus->message_iter_close_container(options, &options_pair);
}
static void DBus_AppendByteArray(SDL_DBusContext *dbus, DBusMessageIter *options, const char *key, const char *value)
{
DBusMessageIter options_pair, options_value, options_array;
dbus->message_iter_open_container(options, DBUS_TYPE_DICT_ENTRY, NULL, &options_pair);
dbus->message_iter_append_basic(&options_pair, DBUS_TYPE_STRING, &key);
dbus->message_iter_open_container(&options_pair, DBUS_TYPE_VARIANT, "ay", &options_value);
dbus->message_iter_open_container(&options_value, DBUS_TYPE_ARRAY, "y", &options_array);
do {
dbus->message_iter_append_basic(&options_array, DBUS_TYPE_BYTE, value);
} while (*value++);
dbus->message_iter_close_container(&options_value, &options_array);
dbus->message_iter_close_container(&options_pair, &options_value);
dbus->message_iter_close_container(options, &options_pair);
}
static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) {
SDL_DBusContext *dbus = SDL_DBus_GetContext();
SignalCallback *signal_data = (SignalCallback *)data;
if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME)
&& dbus->message_has_path(msg, signal_data->path)) {
DBusMessageIter signal_iter, result_array, array_entry, value_entry, uri_entry;
uint32_t result;
size_t length = 2, current = 0;
const char **path;
dbus->message_iter_init(msg, &signal_iter);
/* Check if the parameters are what we expect */
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_UINT32)
goto not_our_signal;
dbus->message_iter_get_basic(&signal_iter, &result);
if (result == 1) {
/* cancelled */
const char *result_data[] = { NULL };
signal_data->callback(signal_data->userdata, result_data, -1); /* TODO: Set this to the last selected filter */
goto handled;
}
else if (result) {
/* some error occurred */
signal_data->callback(signal_data->userdata, NULL, -1);
goto handled;
}
if (!dbus->message_iter_next(&signal_iter))
goto not_our_signal;
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_ARRAY)
goto not_our_signal;
dbus->message_iter_recurse(&signal_iter, &result_array);
while (dbus->message_iter_get_arg_type(&result_array) == DBUS_TYPE_DICT_ENTRY)
{
const char *method;
dbus->message_iter_recurse(&result_array, &array_entry);
if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_STRING)
goto not_our_signal;
dbus->message_iter_get_basic(&array_entry, &method);
if (!SDL_strcmp(method, "uris")) {
/* we only care about the selected file paths */
break;
}
if (!dbus->message_iter_next(&result_array))
goto not_our_signal;
}
if (!dbus->message_iter_next(&array_entry))
goto not_our_signal;
if (dbus->message_iter_get_arg_type(&array_entry) != DBUS_TYPE_VARIANT)
goto not_our_signal;
dbus->message_iter_recurse(&array_entry, &value_entry);
if (dbus->message_iter_get_arg_type(&value_entry) != DBUS_TYPE_ARRAY)
goto not_our_signal;
dbus->message_iter_recurse(&value_entry, &uri_entry);
path = SDL_malloc(sizeof(const char *) * length);
if (!path) {
SDL_OutOfMemory();
signal_data->callback(signal_data->userdata, NULL, -1);
goto cleanup;
}
while (dbus->message_iter_get_arg_type(&uri_entry) == DBUS_TYPE_STRING)
{
if (current >= length - 1) {
++length;
path = SDL_realloc(path, sizeof(const char *) * length);
if (!path) {
SDL_OutOfMemory();
signal_data->callback(signal_data->userdata, NULL, -1);
goto cleanup;
}
}
dbus->message_iter_get_basic(&uri_entry, path + current);
dbus->message_iter_next(&uri_entry);
++current;
}
path[length - 1] = NULL;
signal_data->callback(signal_data->userdata, path, -1); /* TODO: Fetch the index of the filter that was used */
cleanup:
dbus->connection_remove_filter(conn, &DBus_MessageFilter, signal_data);
SDL_free(path);
SDL_free((void *)signal_data->path);
SDL_free(signal_data);
handled:
return DBUS_HANDLER_RESULT_HANDLED;
}
not_our_signal:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void DBus_OpenDialog(const char *method, const char *method_title, SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many, int open_folders)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
DBusMessage *msg;
DBusMessageIter params, options;
const char *signal_id;
char *handle_str, *filter;
int filter_len;
static const char *parent_window = ""; /* TODO: Consider using X11's PID or the Wayland handle */
static uint32_t handle_id = 0;
if (dbus == NULL) {
SDL_SetError("%s", "Failed to connect to DBus!");
return;
}
msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, method);
if (msg == NULL) {
SDL_SetError("%s", "Failed to send message to portal!");
return;
}
dbus->message_iter_init_append(msg, &params);
dbus->message_iter_append_basic(&params, DBUS_TYPE_STRING, &parent_window);
dbus->message_iter_append_basic(&params, DBUS_TYPE_STRING, &method_title);
dbus->message_iter_open_container(&params, DBUS_TYPE_ARRAY, "{sv}", &options);
handle_str = SDL_malloc(sizeof(char) * (HANDLE_LEN + 1));
if (!handle_str) {
SDL_OutOfMemory();
return;
}
SDL_snprintf(handle_str, HANDLE_LEN, "%u", ++handle_id);
DBus_AppendStringOption(dbus, &options, "handle_token", handle_str);
SDL_free(handle_str);
DBus_AppendBoolOption(dbus, &options, "modal", !!window);
if (allow_many == SDL_TRUE) {
DBus_AppendBoolOption(dbus, &options, "multiple", 1);
}
if (open_folders) {
DBus_AppendBoolOption(dbus, &options, "directory", 1);
}
if (filters) {
DBus_AppendFilters(dbus, &options, filters);
}
if (default_location) {
DBus_AppendByteArray(dbus, &options, "current_folder", default_location);
}
dbus->message_iter_close_container(&params, &options);
DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, DBUS_TIMEOUT_INFINITE, NULL);
if (reply) {
DBusMessageIter reply_iter;
dbus->message_iter_init(reply, &reply_iter);
if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_OBJECT_PATH)
{
SDL_SetError("%s", "Invalid response received by DBus!");
goto incorrect_type;
}
dbus->message_iter_get_basic(&reply_iter, &signal_id);
}
dbus->message_unref(msg);
filter_len = SDL_strlen(SIGNAL_FILTER) + SDL_strlen(signal_id) + 2;
filter = SDL_malloc(sizeof(char) * filter_len);
if (!filter) {
SDL_OutOfMemory();
goto incorrect_type;
}
SDL_snprintf(filter, filter_len, SIGNAL_FILTER"%s'", signal_id);
dbus->bus_add_match(dbus->session_conn, filter, NULL);
SDL_free(filter);
SignalCallback *data = SDL_malloc(sizeof(SignalCallback));
if (!data) {
SDL_OutOfMemory();
goto incorrect_type;
}
data->callback = callback;
data->userdata = userdata;
data->path = SDL_strdup(signal_id);
if (!data->path) {
SDL_OutOfMemory();
SDL_free(data);
goto incorrect_type;
}
/* TODO: This should be registered before opening the portal, or the filter will not catch
the message if it is sent before we register the filter.
*/
dbus->connection_add_filter(dbus->session_conn,
&DBus_MessageFilter, data, NULL);
dbus->connection_flush(dbus->session_conn);
incorrect_type:
dbus->message_unref(reply);
}
void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
DBus_OpenDialog("OpenFile", "Open File", callback, userdata, window, filters, default_location, allow_many, 0);
}
void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
DBus_OpenDialog("SaveFile", "Save File", callback, userdata, window, filters, default_location, 0, 0);
}
void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
DBus_OpenDialog("OpenFile", "Open Folder", callback, userdata, window, NULL, default_location, allow_many, 1);
}
int SDL_Portal_detect(void)
{
/* TODO */
return 0;
}
#else
/* Dummy implementation to avoid compilation problems */
void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}
void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}
void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
SDL_Unsupported();
callback(userdata, NULL, -1);
}
int SDL_Portal_detect(void)
{
return 0;
}
#endif /* SDL_USE_LIBDBUS */

View File

@@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
void SDL_Portal_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many);
void SDL_Portal_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location);
void SDL_Portal_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many);
/** @returns non-zero if available, zero if unavailable */
int SDL_Portal_detect(void);

View File

@@ -0,0 +1,85 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "./SDL_portaldialog.h"
#include "./SDL_zenitydialog.h"
static void (*detected_open)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many) = NULL;
static void (*detected_save)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location) = NULL;
static void (*detected_folder)(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many) = NULL;
/* Returns non-zero on success, 0 on failure */
static int detect_available_methods(void)
{
if (SDL_Portal_detect()) {
detected_open = SDL_Portal_ShowOpenFileDialog;
detected_save = SDL_Portal_ShowSaveFileDialog;
detected_folder = SDL_Portal_ShowOpenFolderDialog;
return 1;
}
if (SDL_Zenity_detect()) {
detected_open = SDL_Zenity_ShowOpenFileDialog;
detected_save = SDL_Zenity_ShowSaveFileDialog;
detected_folder = SDL_Zenity_ShowOpenFolderDialog;
return 2;
}
SDL_SetError("No supported method for file dialogs");
return 0;
}
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_open && !detect_available_methods()) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;
}
detected_open(callback, userdata, window, filters, default_location, allow_many);
}
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_save && !detect_available_methods()) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;
}
detected_save(callback, userdata, window, filters, default_location);
}
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
/* Call detect_available_methods() again each time in case the situation changed */
if (!detected_folder && !detect_available_methods()) {
/* SetError() done by detect_available_methods() */
callback(userdata, NULL, -1);
return;
}
detected_folder(callback, userdata, window, default_location, allow_many);
}

View File

@@ -0,0 +1,488 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "./SDL_dialog.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
typedef enum
{
ZENITY_MULTIPLE = 0x1,
ZENITY_DIRECTORY = 0x2,
ZENITY_SAVE = 0x4
} zenityFlags;
typedef struct
{
SDL_DialogFileCallback callback;
void* userdata;
const char* filename;
const SDL_DialogFileFilter *filters;
Uint32 flags;
} zenityArgs;
#define CLEAR_AND_RETURN() \
{ \
while (--nextarg >= 0) { \
SDL_free(argv[nextarg]); \
} \
SDL_free(argv); \
return NULL; \
}
#define CHECK_OOM() \
{ \
if (!argv[nextarg - 1]) { \
SDL_OutOfMemory(); \
CLEAR_AND_RETURN() \
} \
\
if (nextarg > argc) { \
SDL_SetError("Zenity dialog problem: argc (%d) < nextarg (%d)", \
argc, nextarg); \
CLEAR_AND_RETURN() \
} \
}
/* Exec call format:
*
* /usr/bin/env zenity --file-selection --separator=\n [--multiple]
* [--directory] [--save] [--filename FILENAME]
* [--file-filter=Filter Name | *.filt *.fn ...]...
*/
static char** generate_args(const zenityArgs* info)
{
int argc = 4;
int nextarg = 0;
char **argv = NULL;
/* ARGC PASS */
if (info->flags & ZENITY_MULTIPLE) {
argc++;
}
if (info->flags & ZENITY_DIRECTORY) {
argc++;
}
if (info->flags & ZENITY_SAVE) {
argc++;
}
if (info->filename) {
argc += 2;
}
if (info->filters) {
const SDL_DialogFileFilter *filter_ptr = info->filters;
while (filter_ptr->name && filter_ptr->pattern) {
argc++;
filter_ptr++;
}
}
argv = SDL_malloc(sizeof(char *) * argc + 1);
if (!argv) {
SDL_OutOfMemory();
return NULL;
}
argv[nextarg++] = SDL_strdup("/usr/bin/env");
CHECK_OOM()
argv[nextarg++] = SDL_strdup("zenity");
CHECK_OOM()
argv[nextarg++] = SDL_strdup("--file-selection");
CHECK_OOM()
argv[nextarg++] = SDL_strdup("--separator=\n");
CHECK_OOM()
/* ARGV PASS */
if (info->flags & ZENITY_MULTIPLE) {
argv[nextarg++] = SDL_strdup("--multiple");
CHECK_OOM()
}
if (info->flags & ZENITY_DIRECTORY) {
argv[nextarg++] = SDL_strdup("--directory");
CHECK_OOM()
}
if (info->flags & ZENITY_SAVE) {
argv[nextarg++] = SDL_strdup("--save");
CHECK_OOM()
}
if (info->filename) {
argv[nextarg++] = SDL_strdup("--filename");
CHECK_OOM()
argv[nextarg++] = SDL_strdup(info->filename);
CHECK_OOM()
}
if (info->filters) {
const SDL_DialogFileFilter *filter_ptr = info->filters;
while (filter_ptr->name && filter_ptr->pattern) {
/* *Normally*, no filter arg should exceed 4096 bytes. */
char buffer[4096];
SDL_snprintf(buffer, 4096, "--file-filter=%s | *.", filter_ptr->name);
size_t i_buf = SDL_strlen(buffer);
/* "|" is a special character for Zenity */
for (char *c = buffer; *c; c++) {
if (*c == '|') {
*c = ' ';
}
}
for (size_t i_pat = 0; i_buf < 4095 && filter_ptr->pattern[i_pat]; i_pat++) {
const char *c = filter_ptr->pattern + i_pat;
if (*c == ';') {
/* Disallow empty patterns (might bug Zenity) */
int at_end = (c[1] == '\0');
int at_mid = (c[1] == ';');
int at_beg = (i_pat == 0);
if (at_end || at_mid || at_beg) {
const char *pos_str = "";
if (at_end) {
pos_str = "end";
} else if (at_mid) {
pos_str = "middle";
} else if (at_beg) {
pos_str = "beginning";
}
SDL_SetError("Empty pattern file extension (at %s of list)", pos_str);
CLEAR_AND_RETURN()
}
if (i_buf + 3 >= 4095) {
i_buf += 3;
break;
}
buffer[i_buf++] = ' ';
buffer[i_buf++] = '*';
buffer[i_buf++] = '.';
} else if (*c == '*' && (c[1] == '\0' || c[1] == ';') && (i_pat == 0 || *(c - 1) == ';')) {
buffer[i_buf++] = '*';
} else if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_' || *c == '-')) {
SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *c);
CLEAR_AND_RETURN()
} else {
buffer[i_buf++] = *c;
}
}
if (i_buf >= 4095) {
SDL_SetError("Filter '%s' wouldn't fit in a 4096 byte buffer; please report your use case if you need filters that long", filter_ptr->name);
CLEAR_AND_RETURN()
}
buffer[i_buf] = '\0';
argv[nextarg++] = SDL_strdup(buffer);
CHECK_OOM()
filter_ptr++;
}
}
argv[nextarg++] = NULL;
return argv;
}
void free_args(char **argv)
{
char **ptr = argv;
while (*ptr) {
SDL_free(*ptr);
ptr++;
}
SDL_free(argv);
}
/* TODO: Zenity survives termination of the parent */
static void run_zenity(zenityArgs* arg_struct)
{
SDL_DialogFileCallback callback = arg_struct->callback;
void* userdata = arg_struct->userdata;
int out[2];
pid_t process;
int status = -1;
if (pipe(out) < 0) {
SDL_SetError("Could not create pipe: %s", strerror(errno));
callback(userdata, NULL, -1);
return;
}
/* Args are only needed in the forked process, but generating them early
allows catching the error messages in the main process */
char **args = generate_args(arg_struct);
if (!args) {
/* SDL_SetError will have been called already */
callback(userdata, NULL, -1);
return;
}
process = fork();
if (process < 0) {
SDL_SetError("Could not fork process: %s", strerror(errno));
close(out[0]);
close(out[1]);
free_args(args);
callback(userdata, NULL, -1);
return;
} else if (process == 0){
dup2(out[1], STDOUT_FILENO);
close(STDERR_FILENO); /* Hide errors from Zenity to stderr */
close(out[0]);
close(out[1]);
/* Recent versions of Zenity have different exit codes, but picks up
different codes from the environment */
SDL_setenv("ZENITY_OK", "0", 1);
SDL_setenv("ZENITY_CANCEL", "1", 1);
SDL_setenv("ZENITY_ESC", "1", 1);
SDL_setenv("ZENITY_EXTRA", "2", 1);
SDL_setenv("ZENITY_ERROR", "2", 1);
SDL_setenv("ZENITY_TIMEOUT", "2", 1);
execv(args[0], args);
exit(errno + 128);
} else {
char readbuffer[2048];
size_t bytes_read = 0, bytes_last_read;
char *container = NULL;
close(out[1]);
free_args(args);
while ((bytes_last_read = read(out[0], readbuffer, sizeof(readbuffer)))) {
char *new_container = SDL_realloc(container, bytes_read + bytes_last_read);
if (!new_container) {
SDL_OutOfMemory();
SDL_free(container);
close(out[0]);
callback(userdata, NULL, -1);
return;
}
container = new_container;
SDL_memcpy(container + bytes_read, readbuffer, bytes_last_read);
bytes_read += bytes_last_read;
}
close(out[0]);
if (waitpid(process, &status, 0) == -1) {
SDL_SetError("waitpid failed");
SDL_free(container);
callback(userdata, NULL, -1);
return;
}
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
}
size_t narray = 1;
char **array = (char **) SDL_malloc((narray + 1) * sizeof(char *));
if (!array) {
SDL_OutOfMemory();
SDL_free(container);
callback(userdata, NULL, -1);
return;
}
array[0] = container;
array[1] = NULL;
for (int i = 0; i < bytes_read; i++) {
if (container[i] == '\n') {
container[i] = '\0';
/* Reading from a process often leaves a trailing \n, so ignore the last one */
if (i < bytes_read - 1) {
array[narray] = container + i + 1;
narray++;
char **new_array = (char **) SDL_realloc(array, (narray + 1) * sizeof(char *));
if (!new_array) {
SDL_OutOfMemory();
SDL_free(container);
SDL_free(array);
callback(userdata, NULL, -1);
return;
}
array = new_array;
array[narray] = NULL;
}
}
}
/* 0 = the user chose one or more files, 1 = the user canceled the dialog */
if (status == 0 || status == 1) {
callback(userdata, (const char * const*) array, -1);
} else {
SDL_SetError("Could not run zenity: exit code %d (may be zenity or execv+128)", status);
callback(userdata, NULL, -1);
}
SDL_free(array);
SDL_free(container);
}
}
static int run_zenity_thread(void* ptr)
{
run_zenity(ptr);
SDL_free(ptr);
return 0;
}
void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
zenityArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(*args));
if (!args) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->callback = callback;
args->userdata = userdata;
args->filename = default_location;
args->filters = filters;
args->flags = (allow_many == SDL_TRUE) ? ZENITY_MULTIPLE : 0;
thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFileDialog", (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}
void SDL_Zenity_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
zenityArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(zenityArgs));
if (args == NULL) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->callback = callback;
args->userdata = userdata;
args->filename = default_location;
args->filters = filters;
args->flags = ZENITY_SAVE;
thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowSaveFileDialog", (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}
void SDL_Zenity_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
zenityArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(zenityArgs));
if (args == NULL) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->callback = callback;
args->userdata = userdata;
args->filename = default_location;
args->filters = NULL;
args->flags = ((allow_many == SDL_TRUE) ? ZENITY_MULTIPLE : 0) | ZENITY_DIRECTORY;
thread = SDL_CreateThread(run_zenity_thread, "SDL_ShowOpenFolderDialog", (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}
int SDL_Zenity_detect(void)
{
pid_t process;
int status = -1;
process = fork();
if (process < 0) {
SDL_SetError("Could not fork process: %s", strerror(errno));
return 0;
} else if (process == 0){
/* Disable output */
close(STDERR_FILENO);
close(STDOUT_FILENO);
execl("/usr/bin/env", "/usr/bin/env", "zenity", "--version", NULL);
exit(errno + 128);
} else {
if (waitpid(process, &status, 0) == -1) {
SDL_SetError("waitpid failed");
return 0;
}
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
}
return !status;
}
}

View File

@@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
void SDL_Zenity_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many);
void SDL_Zenity_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location);
void SDL_Zenity_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many);
/** @returns non-zero if available, zero if unavailable */
int SDL_Zenity_detect(void);

View File

@@ -0,0 +1,460 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* TODO: Macro? */
/* TODO: Better includes? */
#include <windows.h>
#include <shlobj.h>
#include "../../core/windows/SDL_windows.h"
#include "../../thread/SDL_systhread.h"
typedef struct
{
int is_save;
const SDL_DialogFileFilter *filters;
const char* default_file;
const char* default_folder;
SDL_Window* parent;
DWORD flags;
SDL_DialogFileCallback callback;
void* userdata;
} winArgs;
typedef struct
{
SDL_Window* parent;
SDL_DialogFileCallback callback;
void* userdata;
} winFArgs;
/** Converts dialog.nFilterIndex to SDL-compatible value */
int getFilterIndex(int as_reported_by_windows, const SDL_DialogFileFilter *filters)
{
int filter_index = as_reported_by_windows - 1;
if (filter_index < 0) {
filter_index = 0;
for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; filter++) {
filter_index++;
}
}
return filter_index;
}
/* TODO: The new version of file dialogs */
void windows_ShowFileDialog(void *ptr)
{
winArgs *args = (winArgs *) ptr;
int is_save = args->is_save;
const SDL_DialogFileFilter *filters = args->filters;
const char* default_file = args->default_file;
const char* default_folder = args->default_folder;
SDL_Window* parent = args->parent;
DWORD flags = args->flags;
SDL_DialogFileCallback callback = args->callback;
void* userdata = args->userdata;
/* GetOpenFileName and GetSaveFileName have the same signature
(yes, LPOPENFILENAMEW even for the save dialog) */
typedef BOOL (WINAPI *pfnGetAnyFileNameW)(LPOPENFILENAMEW);
typedef DWORD (WINAPI *pfnCommDlgExtendedError)(void);
HMODULE lib = LoadLibraryW(L"Comdlg32.dll");
pfnGetAnyFileNameW pGetAnyFileName = NULL;
pfnCommDlgExtendedError pCommDlgExtendedError = NULL;
if (lib) {
pGetAnyFileName = (pfnGetAnyFileNameW) GetProcAddress(lib, is_save ? "GetSaveFileNameW" : "GetOpenFileNameW");
pCommDlgExtendedError = (pfnCommDlgExtendedError) GetProcAddress(lib, "CommDlgExtendedError");
} else {
SDL_SetError("Couldn't load Comdlg32.dll");
callback(userdata, NULL, -1);
return;
}
if (!pGetAnyFileName) {
SDL_SetError("Couldn't load GetOpenFileName/GetSaveFileName from library");
callback(userdata, NULL, -1);
return;
}
if (!pCommDlgExtendedError) {
SDL_SetError("Couldn't load CommDlgExtendedError from library");
callback(userdata, NULL, -1);
return;
}
HWND window = NULL;
if (parent) {
window = (HWND) SDL_GetProperty(SDL_GetWindowProperties(parent), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
}
wchar_t filebuffer[MAX_PATH] = L"";
wchar_t initfolder[MAX_PATH] = L"";
/* Necessary for the return code below */
SDL_memset(filebuffer, 0, MAX_PATH * sizeof(wchar_t));
if (default_file) {
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_file, -1, filebuffer, MAX_PATH);
}
if (default_folder) {
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, default_folder, -1, filebuffer, MAX_PATH);
}
size_t len = 0;
for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; filter++) {
const char *pattern_ptr = filter->pattern;
len += SDL_strlen(filter->name) + SDL_strlen(filter->pattern) + 4;
while (*pattern_ptr) {
if (*pattern_ptr == ';') {
len += 2;
}
pattern_ptr++;
}
}
wchar_t *filterlist = SDL_malloc((len + 1) * sizeof(wchar_t));
if (!filterlist) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
wchar_t *filter_ptr = filterlist;
for (const SDL_DialogFileFilter *filter = filters; filter && filter->name && filter->pattern; filter++) {
size_t l = SDL_strlen(filter->name);
const char *pattern_ptr = filter->pattern;
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filter->name, -1, filter_ptr, MAX_PATH);
filter_ptr += l + 1;
*filter_ptr++ = L'*';
*filter_ptr++ = L'.';
while (*pattern_ptr) {
if (*pattern_ptr == ';') {
*filter_ptr++ = L';';
*filter_ptr++ = L'*';
*filter_ptr++ = L'.';
} else if (*pattern_ptr == '*' && (pattern_ptr[1] == '\0' || pattern_ptr[1] == ';')) {
*filter_ptr++ = L'*';
} else if (!((*pattern_ptr >= 'a' && *pattern_ptr <= 'z') || (*pattern_ptr >= 'A' && *pattern_ptr <= 'Z') || (*pattern_ptr >= '0' && *pattern_ptr <= '9') || *pattern_ptr == '.' || *pattern_ptr == '_' || *pattern_ptr == '-')) {
SDL_SetError("Illegal character in pattern name: %c (Only alphanumeric characters, periods, underscores and hyphens allowed)", *pattern_ptr);
callback(userdata, NULL, -1);
} else {
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, pattern_ptr, 1, filter_ptr, 1);
filter_ptr++;
}
pattern_ptr++;
}
*filter_ptr++ = '\0';
}
*filter_ptr = '\0';
OPENFILENAMEW dialog;
dialog.lStructSize = sizeof(OPENFILENAME);
dialog.hwndOwner = window;
dialog.hInstance = 0;
dialog.lpstrFilter = filterlist;
dialog.lpstrCustomFilter = NULL;
dialog.nMaxCustFilter = 0;
dialog.nFilterIndex = 0;
dialog.lpstrFile = filebuffer;
dialog.nMaxFile = MAX_PATH;
dialog.lpstrFileTitle = *filebuffer ? filebuffer : NULL;
dialog.nMaxFileTitle = MAX_PATH;
dialog.lpstrInitialDir = *initfolder ? initfolder : NULL;
dialog.lpstrTitle = NULL;
dialog.Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY;
dialog.nFileOffset = 0;
dialog.nFileExtension = 0;
dialog.lpstrDefExt = NULL;
dialog.lCustData = 0;
dialog.lpfnHook = NULL;
dialog.lpTemplateName = NULL;
/* Skipped many mac-exclusive and reserved members */
dialog.FlagsEx = 0;
BOOL result = pGetAnyFileName(&dialog);
SDL_free(filterlist);
if (result) {
if (!(flags & OFN_ALLOWMULTISELECT)) {
/* File is a C string stored in dialog.lpstrFile */
char *chosen_file = WIN_StringToUTF8W(dialog.lpstrFile);
const char* opts[2] = { chosen_file, NULL };
callback(userdata, opts, getFilterIndex(dialog.nFilterIndex, filters));
SDL_free(chosen_file);
} else {
/* File is either a C string if the user chose a single file, else
it's a series of strings formatted like:
"C:\\path\\to\\folder\0filename1.ext\0filename2.ext\0\0"
The code below will only stop on a double NULL in all cases, so
it is important that the rest of the buffer has been zeroed. */
char chosen_folder[MAX_PATH];
char chosen_file[MAX_PATH];
wchar_t *file_ptr = dialog.lpstrFile;
size_t nfiles = 0;
size_t chosen_folder_size;
char **chosen_files_list = (char **) SDL_malloc(sizeof(char *) * (nfiles + 1));
if (!chosen_files_list) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
chosen_files_list[nfiles] = NULL;
if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_folder, MAX_PATH, NULL, NULL) >= MAX_PATH) {
SDL_SetError("Path too long or invalid character in path");
SDL_free(chosen_files_list);
callback(userdata, NULL, -1);
return;
}
chosen_folder_size = SDL_strlen(chosen_folder);
SDL_strlcpy(chosen_file, chosen_folder, MAX_PATH);
chosen_file[chosen_folder_size] = '\\';
file_ptr += SDL_strlen(chosen_folder) + 1;
while (*file_ptr) {
nfiles++;
char **new_cfl = (char **) SDL_realloc(chosen_files_list, sizeof(char*) * (nfiles + 1));
if (!new_cfl) {
SDL_OutOfMemory();
for (size_t i = 0; i < nfiles - 1; i++) {
SDL_free(chosen_files_list[i]);
}
SDL_free(chosen_files_list);
callback(userdata, NULL, -1);
return;
}
chosen_files_list = new_cfl;
chosen_files_list[nfiles] = NULL;
int diff = ((int) chosen_folder_size) + 1;
if (WideCharToMultiByte(CP_UTF8, 0, file_ptr, -1, chosen_file + diff, MAX_PATH - diff, NULL, NULL) >= MAX_PATH - diff) {
SDL_SetError("Path too long or invalid character in path");
for (size_t i = 0; i < nfiles - 1; i++) {
SDL_free(chosen_files_list[i]);
}
SDL_free(chosen_files_list);
callback(userdata, NULL, -1);
return;
}
file_ptr += SDL_strlen(chosen_file) + 1 - diff;
chosen_files_list[nfiles - 1] = SDL_strdup(chosen_file);
if (!chosen_files_list[nfiles - 1]) {
SDL_OutOfMemory();
for (size_t i = 0; i < nfiles - 1; i++) {
SDL_free(chosen_files_list[i]);
}
SDL_free(chosen_files_list);
callback(userdata, NULL, -1);
return;
}
}
callback(userdata, (const char * const*) chosen_files_list, getFilterIndex(dialog.nFilterIndex, filters));
for (size_t i = 0; i < nfiles; i++) {
SDL_free(chosen_files_list[i]);
}
SDL_free(chosen_files_list);
}
} else {
DWORD error = pCommDlgExtendedError();
/* Error code 0 means the user clicked the cancel button. */
if (error == 0) {
/* Unlike SDL's handling of errors, Windows does reset the error
code to 0 after calling GetOpenFileName if another Windows
function before set a different error code, so it's safe to
check for success. */
const char* opts[1] = { NULL };
callback(userdata, opts, getFilterIndex(dialog.nFilterIndex, filters));
} else {
SDL_SetError("Windows error, CommDlgExtendedError: %ld", pCommDlgExtendedError());
callback(userdata, NULL, -1);
}
}
}
int windows_file_dialog_thread(void* ptr)
{
windows_ShowFileDialog(ptr);
SDL_free(ptr);
return 0;
}
void windows_ShowFolderDialog(void* ptr)
{
winFArgs *args = (winFArgs *) ptr;
SDL_Window *window = args->parent;
SDL_DialogFileCallback callback = args->callback;
void *userdata = args->userdata;
HWND parent = NULL;
if (window) {
parent = (HWND) SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
}
wchar_t buffer[MAX_PATH];
BROWSEINFOW dialog;
dialog.hwndOwner = parent;
dialog.pidlRoot = NULL;
/* Windows docs say this is `LPTSTR` - apparently it's actually `LPWSTR`*/
dialog.pszDisplayName = buffer;
dialog.lpszTitle = NULL;
dialog.ulFlags = BIF_USENEWUI;
dialog.lpfn = NULL;
dialog.lParam = 0;
dialog.iImage = 0;
if (SHBrowseForFolderW(&dialog)) {
char *chosen_file = WIN_StringToUTF8W(buffer);
const char *files[2] = { chosen_file, NULL };
callback(userdata, (const char * const*) files, -1);
SDL_free(chosen_file);
} else {
const char *files[1] = { NULL };
callback(userdata, (const char * const*) files, -1);
}
}
int windows_folder_dialog_thread(void* ptr)
{
windows_ShowFolderDialog(ptr);
SDL_free(ptr);
return 0;
}
void SDL_ShowOpenFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location, SDL_bool allow_many)
{
winArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(winArgs));
if (args == NULL) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->is_save = 0;
args->filters = filters;
args->default_file = default_location;
args->default_folder = NULL;
args->parent = window;
args->flags = (allow_many == SDL_TRUE) ? OFN_ALLOWMULTISELECT : 0;
args->callback = callback;
args->userdata = userdata;
thread = SDL_CreateThreadInternal(windows_file_dialog_thread, "SDL_ShowOpenFileDialog", 0, (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}
void SDL_ShowSaveFileDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const SDL_DialogFileFilter *filters, const char* default_location)
{
winArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(winArgs));
if (args == NULL) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->is_save = 1;
args->filters = filters;
args->default_file = default_location;
args->default_folder = NULL;
args->parent = window;
args->flags = 0;
args->callback = callback;
args->userdata = userdata;
thread = SDL_CreateThreadInternal(windows_file_dialog_thread, "SDL_ShowSaveFileDialog", 0, (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}
void SDL_ShowOpenFolderDialog(SDL_DialogFileCallback callback, void* userdata, SDL_Window* window, const char* default_location, SDL_bool allow_many)
{
winFArgs *args;
SDL_Thread *thread;
args = SDL_malloc(sizeof(winFArgs));
if (args == NULL) {
SDL_OutOfMemory();
callback(userdata, NULL, -1);
return;
}
args->parent = window;
args->callback = callback;
args->userdata = userdata;
thread = SDL_CreateThreadInternal(windows_folder_dialog_thread, "SDL_ShowOpenFolderDialog", 0, (void *) args);
if (thread == NULL) {
callback(userdata, NULL, -1);
return;
}
SDL_DetachThread(thread);
}