From a762cc0013dd1e54dfd2dee5804eaddc1bd842d6 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Mon, 9 Oct 2023 23:14:55 +0200 Subject: [PATCH] Update nfd to 1.1.0. --- nfd/nfd.h | 114 ++++++++++++++++------------ nfd/nfd_portal.cpp | 184 ++++++++++++++++++++++++++++++++------------- 2 files changed, 199 insertions(+), 99 deletions(-) diff --git a/nfd/nfd.h b/nfd/nfd.h index eb9ba6d8..495e8831 100644 --- a/nfd/nfd.h +++ b/nfd/nfd.h @@ -10,6 +10,23 @@ #ifndef _NFD_H #define _NFD_H +#if defined(_WIN32) +#if defined(NFD_EXPORT) +#define NFD_API __declspec(dllexport) +#elif defined(NFD_SHARED) +#define NFD_API __declspec(dllimport) +#endif +#else +#if defined(NFD_EXPORT) || defined(NFD_SHARED) +#if defined(__GNUC__) || defined(__clang__) +#define NFD_API __attribute__((visibility("default"))) +#endif +#endif +#endif +#ifndef NFD_API +#define NFD_API +#endif + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -64,60 +81,60 @@ typedef struct { /* free a file path that was returned by the dialogs */ /* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */ -void NFD_FreePathN(nfdnchar_t* filePath); +NFD_API void NFD_FreePathN(nfdnchar_t* filePath); /* initialize NFD - call this for every thread that might use NFD, before calling any other NFD * functions on that thread */ -nfdresult_t NFD_Init(void); +NFD_API nfdresult_t NFD_Init(void); /* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */ -void NFD_Quit(void); +NFD_API void NFD_Quit(void); /* single file open dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns * NFD_OKAY */ /* If filterCount is zero, filterList is ignored (you can use NULL) */ /* If defaultPath is NULL, the operating system will decide */ -nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath, - const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount, - const nfdnchar_t* defaultPath); +NFD_API nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath, + const nfdnfilteritem_t* filterList, + nfdfiltersize_t filterCount, + const nfdnchar_t* defaultPath); /* multiple file open dialog */ /* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function * returns NFD_OKAY */ /* If filterCount is zero, filterList is ignored (you can use NULL) */ /* If defaultPath is NULL, the operating system will decide */ -nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, - const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount, - const nfdnchar_t* defaultPath); +NFD_API nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, + const nfdnfilteritem_t* filterList, + nfdfiltersize_t filterCount, + const nfdnchar_t* defaultPath); /* save dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns * NFD_OKAY */ /* If filterCount is zero, filterList is ignored (you can use NULL) */ /* If defaultPath is NULL, the operating system will decide */ -nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, - const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount, - const nfdnchar_t* defaultPath, - const nfdnchar_t* defaultName); +NFD_API nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, + const nfdnfilteritem_t* filterList, + nfdfiltersize_t filterCount, + const nfdnchar_t* defaultPath, + const nfdnchar_t* defaultName); /* select folder dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns * NFD_OKAY */ /* If defaultPath is NULL, the operating system will decide */ -nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath); +NFD_API nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath); /* Get last error -- set when nfdresult_t returns NFD_ERROR */ /* Returns the last error that was set, or NULL if there is no error. */ /* The memory is owned by NFD and should not be freed by user code. */ /* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any * conversion. */ -const char* NFD_GetError(void); +NFD_API const char* NFD_GetError(void); /* clear the error */ -void NFD_ClearError(void); +NFD_API void NFD_ClearError(void); /* path set operations */ #ifdef _WIN32 @@ -131,36 +148,37 @@ typedef unsigned int nfdpathsetsize_t; /* Gets the number of entries stored in pathSet */ /* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we * might not actually have this number of usable paths */ -nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count); +NFD_API nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count); /* Gets the UTF-8 path at offset index */ /* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function * returns NFD_OKAY */ -nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, - nfdpathsetsize_t index, - nfdnchar_t** outPath); +NFD_API nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, + nfdpathsetsize_t index, + nfdnchar_t** outPath); /* Free the path gotten by NFD_PathSet_GetPathN */ #ifdef _WIN32 #define NFD_PathSet_FreePathN NFD_FreePathN #elif __APPLE__ #define NFD_PathSet_FreePathN NFD_FreePathN #else -void NFD_PathSet_FreePathN(const nfdnchar_t* filePath); +NFD_API void NFD_PathSet_FreePathN(const nfdnchar_t* filePath); #endif // _WIN32, __APPLE__ /* Gets an enumerator of the path set. */ /* It is the caller's responsibility to free `enumerator` via NFD_PathSet_FreeEnum() if this * function returns NFD_OKAY, and it should be freed before freeing the pathset. */ -nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t* outEnumerator); +NFD_API nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, + nfdpathsetenum_t* outEnumerator); /* Frees an enumerator of the path set. */ -void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator); +NFD_API void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator); /* Gets the next item from the path set enumerator. * If there are no more items, then *outPaths will be set to NULL. */ /* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePath() if this * function returns NFD_OKAY and `*outPath` is not null */ -nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath); +NFD_API nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath); /* Free the pathSet */ -void NFD_PathSet_Free(const nfdpathset_t* pathSet); +NFD_API void NFD_PathSet_Free(const nfdpathset_t* pathSet); #ifdef _WIN32 @@ -177,50 +195,50 @@ typedef struct { /* UTF-8 compatibility functions */ /* free a file path that was returned */ -void NFD_FreePathU8(nfdu8char_t* outPath); +NFD_API void NFD_FreePathU8(nfdu8char_t* outPath); /* single file open dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns * NFD_OKAY */ -nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath, - const nfdu8filteritem_t* filterList, - nfdfiltersize_t count, - const nfdu8char_t* defaultPath); - -/* multiple file open dialog */ -/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function - * returns NFD_OKAY */ -nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths, +NFD_API nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath, const nfdu8filteritem_t* filterList, nfdfiltersize_t count, const nfdu8char_t* defaultPath); +/* multiple file open dialog */ +/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function + * returns NFD_OKAY */ +NFD_API nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths, + const nfdu8filteritem_t* filterList, + nfdfiltersize_t count, + const nfdu8char_t* defaultPath); + /* save dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns * NFD_OKAY */ -nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, - const nfdu8filteritem_t* filterList, - nfdfiltersize_t count, - const nfdu8char_t* defaultPath, - const nfdu8char_t* defaultName); +NFD_API nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, + const nfdu8filteritem_t* filterList, + nfdfiltersize_t count, + const nfdu8char_t* defaultPath, + const nfdu8char_t* defaultName); /* select folder dialog */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns * NFD_OKAY */ -nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath); +NFD_API nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath); /* Get the UTF-8 path at offset index */ /* It is the caller's responsibility to free `outPath` via NFD_FreePathU8() if this function returns * NFD_OKAY */ -nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet, - nfdpathsetsize_t index, - nfdu8char_t** outPath); +NFD_API nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet, + nfdpathsetsize_t index, + nfdu8char_t** outPath); /* Gets the next item from the path set enumerator. * If there are no more items, then *outPaths will be set to NULL. */ /* It is the caller's responsibility to free `*outPath` via NFD_PathSet_FreePathU8() if this * function returns NFD_OKAY and `*outPath` is not null */ -nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath); +NFD_API nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath); #define NFD_PathSet_FreePathU8 NFD_FreePathU8 diff --git a/nfd/nfd_portal.cpp b/nfd/nfd_portal.cpp index f37e0916..23e1f1f4 100644 --- a/nfd/nfd_portal.cpp +++ b/nfd/nfd_portal.cpp @@ -77,7 +77,10 @@ struct DBusMessage_Guard { DBusConnection* dbus_conn; /* current D-Bus error */ DBusError dbus_err; -/* current error (may be a pointer to the D-Bus error message above, or a pointer to some string +/* current non D-Bus error */ +constexpr size_t OWNED_ERR_LEN = 1024; +char owned_err[OWNED_ERR_LEN]{}; +/* current error (may be a pointer to dbus_err.message, owned_err, or a pointer to some string * literal) */ const char* err_ptr = nullptr; /* the unique name of our connection, used for the Request handle; owned by D-Bus so we don't free @@ -88,6 +91,14 @@ void NFDi_SetError(const char* msg) { err_ptr = msg; } +void NFDi_SetFormattedError(const char* format, ...) { + va_list args; + va_start(args, format); + vsnprintf(owned_err, OWNED_ERR_LEN, format, args); + va_end(args); + err_ptr = owned_err; +} + template T* copy(const T* begin, const T* end, T* out) { for (; begin != end; ++begin) { @@ -119,6 +130,10 @@ constexpr const char* STR_CURRENT_FOLDER = "current_folder"; constexpr const char* STR_CURRENT_FILE = "current_file"; constexpr const char* STR_ALL_FILES = "All files"; constexpr const char* STR_ASTERISK = "*"; +constexpr const char* DBUS_DESTINATION = "org.freedesktop.portal.Desktop"; +constexpr const char* DBUS_PATH = "/org/freedesktop/portal/desktop"; +constexpr const char* DBUS_FILECHOOSER_IFACE = "org.freedesktop.portal.FileChooser"; +constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request"; template void AppendOpenFileQueryTitle(DBusMessageIter&); @@ -469,7 +484,7 @@ void AppendSaveFileQueryDictEntryCurrentName(DBusMessageIter& sub_iter, const ch dbus_message_iter_close_container(&sub_iter, &sub_sub_iter); } -void AppendSaveFileQueryDictEntryCurrentFolder(DBusMessageIter& sub_iter, const char* path) { +void AppendOpenFileQueryDictEntryCurrentFolder(DBusMessageIter& sub_iter, const char* path) { if (!path) return; DBusMessageIter sub_sub_iter; DBusMessageIter variant_iter; @@ -536,7 +551,8 @@ template void AppendOpenFileQueryParams(DBusMessage* query, const char* handle_token, const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount) { + nfdfiltersize_t filterCount, + const nfdnchar_t* defaultPath) { DBusMessageIter iter; dbus_message_iter_init_append(query, &iter); @@ -550,6 +566,7 @@ void AppendOpenFileQueryParams(DBusMessage* query, AppendOpenFileQueryDictEntryMultiple(sub_iter); AppendOpenFileQueryDictEntryDirectory(sub_iter); AppendOpenFileQueryDictEntryFilters(sub_iter, filterList, filterCount); + AppendOpenFileQueryDictEntryCurrentFolder(sub_iter, defaultPath); dbus_message_iter_close_container(&iter, &sub_iter); } @@ -572,7 +589,7 @@ void AppendSaveFileQueryParams(DBusMessage* query, AppendOpenFileQueryDictEntryHandleToken(sub_iter, handle_token); AppendSaveFileQueryDictEntryFilters(sub_iter, filterList, filterCount, defaultName); AppendSaveFileQueryDictEntryCurrentName(sub_iter, defaultName); - AppendSaveFileQueryDictEntryCurrentFolder(sub_iter, defaultPath); + AppendOpenFileQueryDictEntryCurrentFolder(sub_iter, defaultPath); AppendSaveFileQueryDictEntryCurrentFile(sub_iter, defaultPath, defaultName); dbus_message_iter_close_container(&iter, &sub_iter); } @@ -656,7 +673,9 @@ nfdresult_t ReadResponseResults(DBusMessage* msg, DBusMessageIter& resultsIter) return NFD_CANCEL; } else { // Some error occurred - NFDi_SetError("D-Bus file dialog interaction was ended abruptly."); + NFDi_SetFormattedError( + "D-Bus file dialog interaction was ended abruptly with response code %u.", + resp_code); return NFD_ERROR; } } @@ -770,47 +789,37 @@ nfdresult_t ReadResponseUrisSingleAndCurrentExtension(DBusMessage* msg, [&tmp_extn](DBusMessageIter& current_filter_iter) { // current_filter is best_effort, so if we fail, we still return NFD_OKAY. if (dbus_message_iter_get_arg_type(¤t_filter_iter) != DBUS_TYPE_STRUCT) { - // NFDi_SetError("D-Bus response signal current_filter iter is not a struct."); return NFD_OKAY; } DBusMessageIter current_filter_struct_iter; dbus_message_iter_recurse(¤t_filter_iter, ¤t_filter_struct_iter); if (!dbus_message_iter_next(¤t_filter_struct_iter)) { - // NFDi_SetError("D-Bus response signal current_filter struct iter ended - // prematurely."); return NFD_OKAY; } if (dbus_message_iter_get_arg_type(¤t_filter_struct_iter) != DBUS_TYPE_ARRAY) { - // NFDi_SetError("D-Bus response signal URI sub iter is not a string."); return NFD_OKAY; } DBusMessageIter current_filter_array_iter; dbus_message_iter_recurse(¤t_filter_struct_iter, ¤t_filter_array_iter); if (dbus_message_iter_get_arg_type(¤t_filter_array_iter) != DBUS_TYPE_STRUCT) { - // NFDi_SetError("D-Bus response signal current_filter iter is not a struct."); return NFD_OKAY; } DBusMessageIter current_filter_extn_iter; dbus_message_iter_recurse(¤t_filter_array_iter, ¤t_filter_extn_iter); if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_UINT32) { - // NFDi_SetError("D-Bus response signal URI sub iter is not a string."); return NFD_OKAY; } dbus_uint32_t type; dbus_message_iter_get_basic(¤t_filter_extn_iter, &type); if (type != 0) { - // NFDi_SetError("Wrong filter type."); return NFD_OKAY; } if (!dbus_message_iter_next(¤t_filter_extn_iter)) { - // NFDi_SetError("D-Bus response signal current_filter struct iter ended - // prematurely."); return NFD_OKAY; } if (dbus_message_iter_get_arg_type(¤t_filter_extn_iter) != DBUS_TYPE_STRING) { - // NFDi_SetError("D-Bus response signal URI sub iter is not a string."); return NFD_OKAY; } dbus_message_iter_get_basic(¤t_filter_extn_iter, &tmp_extn); @@ -1013,22 +1022,25 @@ constexpr size_t FILE_URI_PREFIX_LEN = sizeof(FILE_URI_PREFIX) - 1; // buffer, and make outPath point to it, and returns NFD_OKAY. Otherwise, does not modify outPath // and returns NFD_ERROR (with the correct error set) nfdresult_t AllocAndCopyFilePath(const char* fileUri, char*& outPath) { + const char* file_uri_iter = fileUri; const char* prefix_begin = FILE_URI_PREFIX; const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN; - for (; prefix_begin != prefix_end; ++prefix_begin, ++fileUri) { - if (*prefix_begin != *fileUri) { - NFDi_SetError("D-Bus freedesktop portal returned a URI that is not a file URI."); + for (; prefix_begin != prefix_end; ++prefix_begin, ++file_uri_iter) { + if (*prefix_begin != *file_uri_iter) { + NFDi_SetFormattedError( + "D-Bus freedesktop portal returned \"%s\", which is not a file URI.", fileUri); return NFD_ERROR; } } size_t decoded_len; const char* file_uri_end; - if (!TryUriDecodeLen(fileUri, decoded_len, file_uri_end)) { - NFDi_SetError("D-Bus freedesktop portal returned a malformed URI."); + if (!TryUriDecodeLen(file_uri_iter, decoded_len, file_uri_end)) { + NFDi_SetFormattedError("D-Bus freedesktop portal returned a malformed URI \"%s\".", + fileUri); return NFD_ERROR; } char* const path_without_prefix = NFDi_Malloc(decoded_len + 1); - char* const out_end = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix); + char* const out_end = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix); *out_end = '\0'; outPath = path_without_prefix; return NFD_OKAY; @@ -1055,19 +1067,22 @@ bool TryGetValidExtension(const char* extn, // expected to be either in the form "*.abc" or "*", but this function will check for it, and ignore // the extension if it is not in the correct form. nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, char*& outPath) { + const char* file_uri_iter = fileUri; const char* prefix_begin = FILE_URI_PREFIX; const char* const prefix_end = FILE_URI_PREFIX + FILE_URI_PREFIX_LEN; - for (; prefix_begin != prefix_end; ++prefix_begin, ++fileUri) { - if (*prefix_begin != *fileUri) { - NFDi_SetError("D-Bus freedesktop portal returned a URI that is not a file URI."); + for (; prefix_begin != prefix_end; ++prefix_begin, ++file_uri_iter) { + if (*prefix_begin != *file_uri_iter) { + NFDi_SetFormattedError( + "D-Bus freedesktop portal returned \"%s\", which is not a file URI.", fileUri); return NFD_ERROR; } } size_t decoded_len; const char* file_uri_end; - if (!TryUriDecodeLen(fileUri, decoded_len, file_uri_end)) { - NFDi_SetError("D-Bus freedesktop portal returned a malformed URI."); + if (!TryUriDecodeLen(file_uri_iter, decoded_len, file_uri_end)) { + NFDi_SetFormattedError("D-Bus freedesktop portal returned a malformed URI \"%s\".", + fileUri); return NFD_ERROR; } @@ -1084,14 +1099,14 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, if (*file_it == '.' || !TryGetValidExtension(extn, trimmed_extn, trimmed_extn_end)) { // has file extension already or no valid extension in `extn` char* const path_without_prefix = NFDi_Malloc(decoded_len + 1); - char* const out_end = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix); + char* const out_end = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix); *out_end = '\0'; outPath = path_without_prefix; } else { // no file extension and we have a valid extension char* const path_without_prefix = NFDi_Malloc(decoded_len + (trimmed_extn_end - trimmed_extn)); - char* const out_mid = UriDecodeUnchecked(fileUri, file_uri_end, path_without_prefix); + char* const out_mid = UriDecodeUnchecked(file_uri_iter, file_uri_end, path_without_prefix); char* const out_end = copy(trimmed_extn, trimmed_extn_end, out_mid); *out_end = '\0'; outPath = path_without_prefix; @@ -1107,7 +1122,8 @@ nfdresult_t AllocAndCopyFilePathWithExtn(const char* fileUri, const char* extn, template nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, const nfdnfilteritem_t* filterList, - nfdfiltersize_t filterCount) { + nfdfiltersize_t filterCount, + const nfdnchar_t* defaultPath) { const char* handle_token_ptr; char* handle_obj_path = MakeUniqueObjectPath(&handle_token_ptr); Free_Guard handle_obj_path_guard(handle_obj_path); @@ -1124,13 +1140,11 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, // TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on // Wayland? - DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.FileChooser", - "OpenFile"); + DBusMessage* query = dbus_message_new_method_call( + DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "OpenFile"); DBusMessage_Guard query_guard(query); AppendOpenFileQueryParams( - query, handle_token_ptr, filterList, filterCount); + query, handle_token_ptr, filterList, filterCount, defaultPath); DBusMessage* reply = dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err); @@ -1169,7 +1183,7 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg, DBusMessage* msg = dbus_connection_pop_message(dbus_conn); if (!msg) break; - if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + if (dbus_message_is_signal(msg, DBUS_REQUEST_IFACE, "Response")) { // this is the response we're looking for outMsg = msg; return NFD_OKAY; @@ -1208,10 +1222,8 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg, // TODO: use XOpenDisplay()/XGetInputFocus() to find xid of window... but what should one do on // Wayland? - DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", - "org.freedesktop.portal.FileChooser", - "SaveFile"); + DBusMessage* query = dbus_message_new_method_call( + DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "SaveFile"); DBusMessage_Guard query_guard(query); AppendSaveFileQueryParams( query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName); @@ -1253,7 +1265,7 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg, DBusMessage* msg = dbus_connection_pop_message(dbus_conn); if (!msg) break; - if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + if (dbus_message_is_signal(msg, DBUS_REQUEST_IFACE, "Response")) { // this is the response we're looking for outMsg = msg; return NFD_OKAY; @@ -1267,6 +1279,57 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg, return NFD_ERROR; } +nfdresult_t NFD_DBus_GetVersion(dbus_uint32_t& outVersion) { + DBusError err; // need a separate error object because we don't want to mess with the old one + // if it's stil set + dbus_error_init(&err); + + DBusMessage* query = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.DBus.Properties", + "Get"); + DBusMessage_Guard query_guard(query); + { + DBusMessageIter iter; + dbus_message_iter_init_append(query, &iter); + + constexpr const char* STR_INTERFACE = "org.freedesktop.portal.FileChooser"; + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_INTERFACE); + constexpr const char* STR_VERSION = "version"; + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_VERSION); + } + + DBusMessage* reply = + dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err); + if (!reply) { + dbus_error_free(&dbus_err); + dbus_move_error(&err, &dbus_err); + NFDi_SetError(dbus_err.message); + return NFD_ERROR; + } + DBusMessage_Guard reply_guard(reply); + { + DBusMessageIter iter; + if (!dbus_message_iter_init(reply, &iter)) { + NFDi_SetError("D-Bus reply for version query is missing an argument."); + return NFD_ERROR; + } + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + NFDi_SetError("D-Bus reply for version query is not a variant."); + return NFD_ERROR; + } + DBusMessageIter variant_iter; + dbus_message_iter_recurse(&iter, &variant_iter); + if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_UINT32) { + NFDi_SetError("D-Bus reply for version query is not a uint32."); + return NFD_ERROR; + } + dbus_message_iter_get_basic(&variant_iter, &outVersion); + } + + return NFD_OKAY; +} + } // namespace /* public */ @@ -1281,7 +1344,7 @@ void NFD_ClearError(void) { } nfdresult_t NFD_Init(void) { - // Initialize dbus_error + // Initialize dbus_err dbus_error_init(&dbus_err); // Get DBus connection dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err); @@ -1312,11 +1375,10 @@ nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { - (void)defaultPath; // Default path not supported for portal backend - DBusMessage* msg; { - const nfdresult_t res = NFD_DBus_OpenFile(msg, filterList, filterCount); + const nfdresult_t res = + NFD_DBus_OpenFile(msg, filterList, filterCount, defaultPath); if (res != NFD_OKAY) { return res; } @@ -1338,11 +1400,10 @@ nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { - (void)defaultPath; // Default path not supported for portal backend - DBusMessage* msg; { - const nfdresult_t res = NFD_DBus_OpenFile(msg, filterList, filterCount); + const nfdresult_t res = + NFD_DBus_OpenFile(msg, filterList, filterCount, defaultPath); if (res != NFD_OKAY) { return res; } @@ -1401,9 +1462,25 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { (void)defaultPath; // Default path not supported for portal backend + { + dbus_uint32_t version; + const nfdresult_t res = NFD_DBus_GetVersion(version); + if (res != NFD_OKAY) { + return res; + } + if (version < 3) { + NFDi_SetFormattedError( + "The xdg-desktop-portal installed on this system does not support a folder picker; " + "at least version 3 of the org.freedesktop.portal.FileChooser interface is " + "required but the installed interface version is %u.", + version); + return NFD_ERROR; + } + } + DBusMessage* msg; { - const nfdresult_t res = NFD_DBus_OpenFile(msg, nullptr, 0); + const nfdresult_t res = NFD_DBus_OpenFile(msg, nullptr, 0, defaultPath); if (res != NFD_OKAY) { return res; } @@ -1435,10 +1512,15 @@ nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, DBusMessage* msg = const_cast(static_cast(pathSet)); DBusMessageIter uri_iter; ReadResponseUrisUnchecked(msg, uri_iter); - while (index > 0) { - --index; + nfdpathsetsize_t rem_index = index; + while (rem_index > 0) { + --rem_index; if (!dbus_message_iter_next(&uri_iter)) { - NFDi_SetError("Index out of bounds."); + NFDi_SetFormattedError( + "Index out of bounds; you asked for index %u but there are only %u file paths " + "available.", + index, + index - rem_index); return NFD_ERROR; } }