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);
39 void append_path_variant(DBusMessageIter& arr,
const char* key,
const std::string& path)
41 DBusMessageIter entry, variant, bytes;
42 dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY,
nullptr, &entry);
43 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
static_cast<const void*
>(&key));
44 dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"ay", &variant);
45 dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
"y", &bytes);
46 const char* data = path.c_str();
47 dbus_message_iter_append_fixed_array(&bytes, DBUS_TYPE_BYTE, &data,
static_cast<int>(path.size() + 1));
48 dbus_message_iter_close_container(&variant, &bytes);
49 dbus_message_iter_close_container(&entry, &variant);
50 dbus_message_iter_close_container(&arr, &entry);
59 void append_filters(DBusMessageIter& opts,
const std::vector<SystemFileFilter>& filters)
61 if (filters.empty()) {
65 DBusMessageIter entry, variant, outer, filter_struct, pattern_arr, pattern_struct;
66 const char* key =
"filters";
68 dbus_message_iter_open_container(&opts, DBUS_TYPE_DICT_ENTRY,
nullptr, &entry);
69 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
static_cast<const void*
>(&key));
70 dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"a(sa(us))", &variant);
71 dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
"(sa(us))", &outer);
73 for (
const auto& f : filters) {
74 dbus_message_iter_open_container(&outer, DBUS_TYPE_STRUCT,
nullptr, &filter_struct);
75 const char*
label = f.name.c_str();
76 dbus_message_iter_append_basic(&filter_struct, DBUS_TYPE_STRING,
static_cast<const void*
>(&
label));
77 dbus_message_iter_open_container(&filter_struct, DBUS_TYPE_ARRAY,
"(us)", &pattern_arr);
79 for (
const auto& ext : f.extensions) {
80 std::string glob = (ext ==
"*") ?
"*" :
"*." + ext;
81 const char* glob_cstr = glob.c_str();
82 dbus_uint32_t
type = 0;
83 dbus_message_iter_open_container(&pattern_arr, DBUS_TYPE_STRUCT,
nullptr, &pattern_struct);
84 dbus_message_iter_append_basic(&pattern_struct, DBUS_TYPE_UINT32, &
type);
85 dbus_message_iter_append_basic(&pattern_struct, DBUS_TYPE_STRING,
static_cast<const void*
>(&glob_cstr));
86 dbus_message_iter_close_container(&pattern_arr, &pattern_struct);
89 dbus_message_iter_close_container(&filter_struct, &pattern_arr);
90 dbus_message_iter_close_container(&outer, &filter_struct);
93 dbus_message_iter_close_container(&variant, &outer);
94 dbus_message_iter_close_container(&entry, &variant);
95 dbus_message_iter_close_container(&opts, &entry);
103 std::string percent_decode(std::string_view sv)
106 out.reserve(sv.size());
107 for (
size_t i = 0; i < sv.size(); ++i) {
108 if (sv[i] ==
'%' && i + 2 < sv.size()) {
109 auto hex = [](
char c) ->
int {
110 if (c >=
'0' && c <=
'9')
112 if (c >=
'A' && c <=
'F')
114 if (c >=
'a' && c <=
'f')
118 int hi = hex(sv[i + 1]);
119 int lo = hex(sv[i + 2]);
120 if (hi >= 0 && lo >= 0) {
121 out +=
static_cast<char>((hi << 4) | lo);
141 const bool connected = dbus_connection_read_write(conn, 100);
142 DBusMessage* msg = dbus_connection_pop_message(conn);
145 dbus_connection_close(conn);
146 dbus_connection_unref(conn);
153 const bool is_signal = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL;
154 const bool on_request = (dbus_message_get_path(msg) == request_path);
155 const bool is_response = dbus_message_has_member(msg, k_signal_response);
157 if (is_signal && on_request && is_response) {
158 dbus_uint32_t response_code = 0;
159 DBusMessageIter iter;
160 dbus_message_iter_init(msg, &iter);
161 dbus_message_iter_get_basic(&iter, &response_code);
163 if (response_code != 0) {
164 dbus_message_unref(msg);
165 dbus_connection_close(conn);
166 dbus_connection_unref(conn);
174 dbus_message_iter_next(&iter);
175 DBusMessageIter results;
176 dbus_message_iter_recurse(&iter, &results);
178 std::string chosen_path;
180 while (dbus_message_iter_get_arg_type(&results) == DBUS_TYPE_DICT_ENTRY) {
182 dbus_message_iter_recurse(&results, &kv);
184 const char* key =
nullptr;
185 dbus_message_iter_get_basic(&kv,
static_cast<void*
>(&key));
187 if (key && std::string_view(key) ==
"uris") {
188 dbus_message_iter_next(&kv);
189 DBusMessageIter variant, arr;
190 dbus_message_iter_recurse(&kv, &variant);
191 dbus_message_iter_recurse(&variant, &arr);
193 if (dbus_message_iter_get_arg_type(&arr) == DBUS_TYPE_STRING) {
194 const char* uri =
nullptr;
195 dbus_message_iter_get_basic(&arr,
static_cast<void*
>(&uri));
197 std::string_view sv(uri);
198 if (sv.starts_with(
"file://")) {
201 chosen_path = percent_decode(sv);
206 dbus_message_iter_next(&results);
209 dbus_message_unref(msg);
210 dbus_connection_close(conn);
211 dbus_connection_unref(conn);
213 if (chosen_path.empty()) {
216 callback(std::filesystem::path(chosen_path));
221 dbus_message_unref(msg);
231DBusBackend::~DBusBackend()
236bool DBusBackend::initialize()
243 dbus_error_init(&err);
245 m_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
247 if (!m_conn || dbus_error_is_set(&err)) {
249 "DBusBackend: session bus connection failed: {}",
250 dbus_error_is_set(&err) ? err.message :
"unknown");
251 dbus_error_free(&err);
255 dbus_connection_set_exit_on_disconnect(m_conn, FALSE);
257 m_initialized =
true;
259 "DBusBackend initialized");
263void DBusBackend::shutdown()
265 if (!m_initialized) {
269 dbus_connection_close(m_conn);
270 dbus_connection_unref(m_conn);
272 m_initialized =
false;
275 "DBusBackend shutdown");
282void DBusBackend::open_file(
284 std::vector<SystemFileFilter> filters,
285 std::filesystem::path start_dir)
287 invoke_portal(
"OpenFile", std::move(
callback), filters, start_dir, {});
290void DBusBackend::save_file(
292 std::string suggested_name,
293 std::vector<SystemFileFilter> filters,
294 std::filesystem::path start_dir)
296 invoke_portal(
"SaveFile", std::move(
callback), filters, start_dir, suggested_name);
299void DBusBackend::invoke_portal(
302 const std::vector<SystemFileFilter>& filters,
303 const std::filesystem::path& start_dir,
304 const std::string& suggested)
const
306 if (!m_initialized) {
312 dbus_error_init(&err);
315 DBusConnection* call_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
316 if (!call_conn || dbus_error_is_set(&err)) {
318 "DBusBackend: failed to open per-call connection: {}",
319 dbus_error_is_set(&err) ? err.message :
"unknown");
320 dbus_error_free(&err);
325 dbus_connection_set_exit_on_disconnect(call_conn, FALSE);
327 const std::string response_match = std::string(
"type='signal',interface='") + k_request_iface
328 +
"',member='" + k_signal_response +
"'";
330 dbus_bus_add_match(call_conn, response_match.c_str(), &err);
331 if (dbus_error_is_set(&err)) {
333 "DBusBackend: add_match failed: {}", err.message);
334 dbus_error_free(&err);
335 dbus_connection_close(call_conn);
336 dbus_connection_unref(call_conn);
341 DBusMessage* call_msg = dbus_message_new_method_call(
342 k_portal_service, k_portal_path, k_portal_iface, method);
345 dbus_connection_close(call_conn);
346 dbus_connection_unref(call_conn);
352 const char* title = (std::string_view(method) ==
"SaveFile") ?
"Save File" :
"Open File";
354 DBusMessageIter args, opts;
355 dbus_message_iter_init_append(call_msg, &args);
356 dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
static_cast<const void*
>(&
parent));
357 dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
static_cast<const void*
>(&title));
358 dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
"{sv}", &opts);
360 if (!start_dir.empty()) {
361 append_path_variant(opts,
"current_folder", start_dir.string());
363 if (!suggested.empty()) {
364 append_string_variant(opts,
"current_name", suggested.c_str());
367 append_filters(opts, filters);
369 dbus_message_iter_close_container(&args, &opts);
371 DBusMessage* reply = dbus_connection_send_with_reply_and_block(call_conn, call_msg, 5000, &err);
372 dbus_message_unref(call_msg);
374 if (!reply || dbus_error_is_set(&err)) {
376 "DBusBackend: portal method call failed: {}",
377 dbus_error_is_set(&err) ? err.message :
"no reply");
378 dbus_error_free(&err);
379 dbus_connection_close(call_conn);
380 dbus_connection_unref(call_conn);
385 const char* request_path_cstr =
nullptr;
386 DBusMessageIter reply_iter;
387 dbus_message_iter_init(reply, &reply_iter);
388 dbus_message_iter_get_basic(&reply_iter,
static_cast<void*
>(&request_path_cstr));
389 std::string request_path = request_path_cstr ? request_path_cstr :
"";
390 dbus_message_unref(reply);
392 if (request_path.empty()) {
393 dbus_connection_close(call_conn);
394 dbus_connection_unref(call_conn);
399 std::thread(dispatch_loop, call_conn, std::move(request_path), std::move(
callback)).detach();
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
std::vector< uint32_t > parent
std::vector< float > * out
vk::PhysicalDeviceType type
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.