1#ifdef MAYAFLUX_PLATFORM_LINUX
7#if __has_include(<dbus/dbus.h>)
9#elif __has_include(<dbus-1.0/dbus/dbus.h>)
10#include <dbus-1.0/dbus/dbus.h>
12#error "dbus/dbus.h not found"
19 constexpr const char* k_portal_service =
"org.freedesktop.portal.Desktop";
20 constexpr const char* k_portal_path =
"/org/freedesktop/portal/desktop";
21 constexpr const char* k_portal_iface =
"org.freedesktop.portal.FileChooser";
22 constexpr const char* k_request_iface =
"org.freedesktop.portal.Request";
23 constexpr const char* k_signal_response =
"Response";
25 void append_string_variant(DBusMessageIter& arr,
const char* key,
const char* value)
27 DBusMessageIter entry, variant;
28 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
nullptr, &entry);
29 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
static_cast<const void*
>(&key));
30 dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"s", &variant);
31 dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING,
static_cast<const void*
>(&value));
32 dbus_message_iter_close_container(&entry, &variant);
33 dbus_message_iter_close_container(&arr, &entry);
42 void append_filters(DBusMessageIter& opts,
const std::vector<SystemFileFilter>& filters)
44 if (filters.empty()) {
48 DBusMessageIter entry, variant, outer, filter_struct, pattern_arr, pattern_struct;
49 const char* key =
"filters";
51 dbus_message_iter_open_container(&opts, DBUS_TYPE_DICT_ENTRY,
nullptr, &entry);
52 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
static_cast<const void*
>(&key));
53 dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"a(sa(us))", &variant);
54 dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
"(sa(us))", &outer);
56 for (
const auto& f : filters) {
57 dbus_message_iter_open_container(&outer, DBUS_TYPE_STRUCT,
nullptr, &filter_struct);
58 const char* label = f.name.c_str();
59 dbus_message_iter_append_basic(&filter_struct, DBUS_TYPE_STRING,
static_cast<const void*
>(&label));
60 dbus_message_iter_open_container(&filter_struct, DBUS_TYPE_ARRAY,
"(us)", &pattern_arr);
62 for (
const auto& ext : f.extensions) {
63 std::string glob = (ext ==
"*") ?
"*" :
"*." + ext;
64 const char* glob_cstr = glob.c_str();
65 dbus_uint32_t type = 0;
66 dbus_message_iter_open_container(&pattern_arr, DBUS_TYPE_STRUCT,
nullptr, &pattern_struct);
67 dbus_message_iter_append_basic(&pattern_struct, DBUS_TYPE_UINT32, &type);
68 dbus_message_iter_append_basic(&pattern_struct, DBUS_TYPE_STRING,
static_cast<const void*
>(&glob_cstr));
69 dbus_message_iter_close_container(&pattern_arr, &pattern_struct);
72 dbus_message_iter_close_container(&filter_struct, &pattern_arr);
73 dbus_message_iter_close_container(&outer, &filter_struct);
76 dbus_message_iter_close_container(&variant, &outer);
77 dbus_message_iter_close_container(&entry, &variant);
78 dbus_message_iter_close_container(&opts, &entry);
86 std::string percent_decode(std::string_view sv)
89 out.reserve(sv.size());
90 for (
size_t i = 0; i < sv.size(); ++i) {
91 if (sv[i] ==
'%' && i + 2 < sv.size()) {
92 auto hex = [](
char c) ->
int {
93 if (c >=
'0' && c <=
'9')
95 if (c >=
'A' && c <=
'F')
97 if (c >=
'a' && c <=
'f')
101 int hi = hex(sv[i + 1]);
102 int lo = hex(sv[i + 2]);
103 if (hi >= 0 && lo >= 0) {
104 out +=
static_cast<char>((hi << 4) | lo);
121 void dispatch_loop(DBusConnection* conn, std::string request_path,
FileDialogCallback callback)
124 dbus_connection_read_write(conn, 100);
125 DBusMessage* msg = dbus_connection_pop_message(conn);
130 const bool is_signal = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL;
131 const bool on_request = (dbus_message_get_path(msg) == request_path);
132 const bool is_response = dbus_message_has_member(msg, k_signal_response);
134 if (is_signal && on_request && is_response) {
135 dbus_uint32_t response_code = 0;
136 DBusMessageIter iter;
137 dbus_message_iter_init(msg, &iter);
138 dbus_message_iter_get_basic(&iter, &response_code);
140 if (response_code != 0) {
141 dbus_message_unref(msg);
142 dbus_connection_close(conn);
143 dbus_connection_unref(conn);
144 callback(std::unexpected(
151 dbus_message_iter_next(&iter);
152 DBusMessageIter results;
153 dbus_message_iter_recurse(&iter, &results);
155 std::string chosen_path;
157 while (dbus_message_iter_get_arg_type(&results) == DBUS_TYPE_DICT_ENTRY) {
159 dbus_message_iter_recurse(&results, &kv);
161 const char* key =
nullptr;
162 dbus_message_iter_get_basic(&kv,
static_cast<void*
>(&key));
164 if (key && std::string_view(key) ==
"uris") {
165 dbus_message_iter_next(&kv);
166 DBusMessageIter variant, arr;
167 dbus_message_iter_recurse(&kv, &variant);
168 dbus_message_iter_recurse(&variant, &arr);
170 if (dbus_message_iter_get_arg_type(&arr) == DBUS_TYPE_STRING) {
171 const char* uri =
nullptr;
172 dbus_message_iter_get_basic(&arr,
static_cast<void*
>(&uri));
174 std::string_view sv(uri);
175 if (sv.starts_with(
"file://")) {
178 chosen_path = percent_decode(sv);
183 dbus_message_iter_next(&results);
186 dbus_message_unref(msg);
187 dbus_connection_close(conn);
188 dbus_connection_unref(conn);
190 if (chosen_path.empty()) {
193 callback(std::filesystem::path(chosen_path));
198 dbus_message_unref(msg);
208DBusBackend::~DBusBackend()
213bool DBusBackend::initialize()
220 dbus_error_init(&err);
222 m_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
224 if (!m_conn || dbus_error_is_set(&err)) {
226 "DBusBackend: session bus connection failed: {}",
227 dbus_error_is_set(&err) ? err.message :
"unknown");
228 dbus_error_free(&err);
232 dbus_connection_set_exit_on_disconnect(m_conn, FALSE);
234 m_initialized =
true;
236 "DBusBackend initialized");
240void DBusBackend::shutdown()
242 if (!m_initialized) {
246 dbus_connection_close(m_conn);
247 dbus_connection_unref(m_conn);
249 m_initialized =
false;
252 "DBusBackend shutdown");
259void DBusBackend::open_file(
261 std::vector<SystemFileFilter> filters,
262 std::filesystem::path start_dir)
264 invoke_portal(
"OpenFile", std::move(callback), filters, start_dir, {});
267void DBusBackend::save_file(
269 std::string suggested_name,
270 std::vector<SystemFileFilter> filters,
271 std::filesystem::path start_dir)
273 invoke_portal(
"SaveFile", std::move(callback), filters, start_dir, suggested_name);
276void DBusBackend::invoke_portal(
279 const std::vector<SystemFileFilter>& filters,
280 const std::filesystem::path& start_dir,
281 const std::string& suggested)
const
283 if (!m_initialized) {
289 dbus_error_init(&err);
292 DBusConnection* call_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
293 if (!call_conn || dbus_error_is_set(&err)) {
295 "DBusBackend: failed to open per-call connection: {}",
296 dbus_error_is_set(&err) ? err.message :
"unknown");
297 dbus_error_free(&err);
302 dbus_connection_set_exit_on_disconnect(call_conn, FALSE);
304 DBusMessage* call_msg = dbus_message_new_method_call(
305 k_portal_service, k_portal_path, k_portal_iface, method);
308 dbus_connection_close(call_conn);
309 dbus_connection_unref(call_conn);
314 const char* parent =
"";
315 const char* title = (std::string_view(method) ==
"SaveFile") ?
"Save File" :
"Open File";
317 DBusMessageIter args, opts;
318 dbus_message_iter_init_append(call_msg, &args);
319 dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
static_cast<const void*
>(&parent));
320 dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
static_cast<const void*
>(&title));
321 dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
"{sv}", &opts);
323 if (!start_dir.empty()) {
324 std::string uri =
"file://" + start_dir.string();
325 append_string_variant(opts,
"current_folder", uri.c_str());
327 if (!suggested.empty()) {
328 append_string_variant(opts,
"current_name", suggested.c_str());
331 append_filters(opts, filters);
333 dbus_message_iter_close_container(&args, &opts);
335 DBusMessage* reply = dbus_connection_send_with_reply_and_block(call_conn, call_msg, 5000, &err);
336 dbus_message_unref(call_msg);
338 if (!reply || dbus_error_is_set(&err)) {
340 "DBusBackend: portal method call failed: {}",
341 dbus_error_is_set(&err) ? err.message :
"no reply");
342 dbus_error_free(&err);
343 dbus_connection_close(call_conn);
344 dbus_connection_unref(call_conn);
349 const char* request_path_cstr =
nullptr;
350 DBusMessageIter reply_iter;
351 dbus_message_iter_init(reply, &reply_iter);
352 dbus_message_iter_get_basic(&reply_iter,
static_cast<void*
>(&request_path_cstr));
353 std::string request_path = request_path_cstr ? request_path_cstr :
"";
354 dbus_message_unref(reply);
356 if (request_path.empty()) {
357 dbus_connection_close(call_conn);
358 dbus_connection_unref(call_conn);
363 std::string match =
"type='signal',path='" + request_path
364 +
"',interface='" + k_request_iface
365 +
"',member='" + k_signal_response +
"'";
367 dbus_bus_add_match(call_conn, match.c_str(), &err);
368 dbus_connection_flush(call_conn);
370 if (dbus_error_is_set(&err)) {
372 "DBusBackend: add_match failed: {}", err.message);
373 dbus_error_free(&err);
374 dbus_connection_close(call_conn);
375 dbus_connection_unref(call_conn);
380 std::thread(dispatch_loop, call_conn, std::move(request_path), std::move(callback)).detach();
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
SystemDialogError
Failure modes for OS dialog operations.
@ BackendError
Platform backend failed to open or communicate.
@ Cancelled
User dismissed the dialog without completing it.
std::function< void(FileDialogResult)> FileDialogCallback
Callback type for all file dialog operations.
@ API
API calls from external code.
@ Core
Core engine, backend, subsystems.