Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/platform/linuxbsd/freedesktop_portal_desktop.cpp
10277 views
1
/**************************************************************************/
2
/* freedesktop_portal_desktop.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "freedesktop_portal_desktop.h"
32
33
#ifdef DBUS_ENABLED
34
35
#include "core/crypto/crypto_core.h"
36
#include "core/error/error_macros.h"
37
#include "core/os/os.h"
38
#include "core/string/ustring.h"
39
#include "core/variant/variant.h"
40
41
#ifdef SOWRAP_ENABLED
42
#include "dbus-so_wrap.h"
43
#else
44
#include <dbus/dbus.h>
45
#endif
46
47
#include <unistd.h>
48
49
#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop"
50
#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop"
51
52
#define BUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties"
53
#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings"
54
#define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser"
55
#define BUS_INTERFACE_SCREENSHOT "org.freedesktop.portal.Screenshot"
56
57
bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, ReadVariantType p_type, void *r_value) {
58
DBusMessageIter iter[3];
59
60
dbus_message_iter_init(p_reply_message, &iter[0]);
61
if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) {
62
return false;
63
}
64
65
dbus_message_iter_recurse(&iter[0], &iter[1]);
66
if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) {
67
return false;
68
}
69
70
dbus_message_iter_recurse(&iter[1], &iter[2]);
71
if (p_type == VAR_TYPE_COLOR) {
72
if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_STRUCT) {
73
return false;
74
}
75
DBusMessageIter struct_iter;
76
dbus_message_iter_recurse(&iter[2], &struct_iter);
77
int idx = 0;
78
while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) {
79
double value = 0.0;
80
dbus_message_iter_get_basic(&struct_iter, &value);
81
if (value < 0.0 || value > 1.0) {
82
return false;
83
}
84
if (idx == 0) {
85
static_cast<Color *>(r_value)->r = value;
86
} else if (idx == 1) {
87
static_cast<Color *>(r_value)->g = value;
88
} else if (idx == 2) {
89
static_cast<Color *>(r_value)->b = value;
90
}
91
idx++;
92
if (!dbus_message_iter_next(&struct_iter)) {
93
break;
94
}
95
}
96
if (idx != 3) {
97
return false;
98
}
99
} else if (p_type == VAR_TYPE_UINT32) {
100
if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_UINT32) {
101
return false;
102
}
103
dbus_message_iter_get_basic(&iter[2], r_value);
104
} else if (p_type == VAR_TYPE_BOOL) {
105
if (dbus_message_iter_get_arg_type(&iter[2]) != DBUS_TYPE_BOOLEAN) {
106
return false;
107
}
108
dbus_message_iter_get_basic(&iter[2], r_value);
109
}
110
return true;
111
}
112
113
bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char *p_key, ReadVariantType p_type, void *r_value) {
114
if (unsupported) {
115
return false;
116
}
117
118
DBusError error;
119
dbus_error_init(&error);
120
121
DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
122
if (dbus_error_is_set(&error)) {
123
if (OS::get_singleton()->is_stdout_verbose()) {
124
ERR_PRINT(vformat("Error opening D-Bus connection: %s", error.message));
125
}
126
dbus_error_free(&error);
127
unsupported = true;
128
return false;
129
}
130
131
DBusMessage *message = dbus_message_new_method_call(
132
BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SETTINGS,
133
"Read");
134
dbus_message_append_args(
135
message,
136
DBUS_TYPE_STRING, &p_namespace,
137
DBUS_TYPE_STRING, &p_key,
138
DBUS_TYPE_INVALID);
139
140
DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error);
141
dbus_message_unref(message);
142
if (dbus_error_is_set(&error)) {
143
if (OS::get_singleton()->is_stdout_verbose()) {
144
ERR_PRINT(vformat("Error on D-Bus communication: %s", error.message));
145
}
146
dbus_error_free(&error);
147
dbus_connection_unref(bus);
148
return false;
149
}
150
151
bool success = try_parse_variant(reply, p_type, r_value);
152
153
dbus_message_unref(reply);
154
dbus_connection_unref(bus);
155
156
return success;
157
}
158
159
uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() {
160
if (unsupported) {
161
return 0;
162
}
163
164
uint32_t value = 0;
165
if (read_setting("org.freedesktop.appearance", "color-scheme", VAR_TYPE_UINT32, &value)) {
166
return value;
167
} else {
168
return 0;
169
}
170
}
171
172
Color FreeDesktopPortalDesktop::get_appearance_accent_color() {
173
if (unsupported) {
174
return Color(0, 0, 0, 0);
175
}
176
177
Color value;
178
if (read_setting("org.freedesktop.appearance", "accent-color", VAR_TYPE_COLOR, &value)) {
179
return value;
180
} else {
181
return Color(0, 0, 0, 0);
182
}
183
}
184
185
uint32_t FreeDesktopPortalDesktop::get_high_contrast() {
186
if (unsupported) {
187
return -1;
188
}
189
190
dbus_bool_t value = false;
191
if (read_setting("org.gnome.desktop.a11y.interface", "high-contrast", VAR_TYPE_BOOL, &value)) {
192
return value;
193
}
194
return -1;
195
}
196
197
static const char *cs_empty = "";
198
199
void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const String &p_string) {
200
CharString cs = p_string.utf8();
201
const char *cs_ptr = cs.ptr();
202
if (cs_ptr) {
203
dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_ptr);
204
} else {
205
dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_empty);
206
}
207
}
208
209
void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options, HashMap<String, String> &r_ids) {
210
DBusMessageIter dict_iter;
211
DBusMessageIter var_iter;
212
DBusMessageIter arr_iter;
213
const char *choices_key = "choices";
214
215
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
216
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key);
217
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter);
218
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter);
219
220
r_ids.clear();
221
for (int i = 0; i < p_options.size(); i++) {
222
const Dictionary &item = p_options[i];
223
if (!item.has("name") || !item.has("values") || !item.has("default")) {
224
continue;
225
}
226
const String &name = item["name"];
227
const Vector<String> &options = item["values"];
228
int default_idx = item["default"];
229
230
DBusMessageIter struct_iter;
231
DBusMessageIter array_iter;
232
DBusMessageIter array_struct_iter;
233
dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
234
append_dbus_string(&struct_iter, "option_" + itos(i)); // ID.
235
append_dbus_string(&struct_iter, name); // User visible name.
236
r_ids["option_" + itos(i)] = name;
237
238
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter);
239
for (int j = 0; j < options.size(); j++) {
240
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
241
append_dbus_string(&array_struct_iter, itos(j));
242
append_dbus_string(&array_struct_iter, options[j]);
243
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
244
}
245
dbus_message_iter_close_container(&struct_iter, &array_iter);
246
if (options.is_empty()) {
247
append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection.
248
} else {
249
append_dbus_string(&struct_iter, itos(default_idx)); // Default selection.
250
}
251
252
dbus_message_iter_close_container(&arr_iter, &struct_iter);
253
}
254
dbus_message_iter_close_container(&var_iter, &arr_iter);
255
dbus_message_iter_close_container(&dict_iter, &var_iter);
256
dbus_message_iter_close_container(p_iter, &dict_iter);
257
}
258
259
void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts, const Vector<String> &p_filter_mimes) {
260
DBusMessageIter dict_iter;
261
DBusMessageIter var_iter;
262
DBusMessageIter arr_iter;
263
const char *filters_key = "filters";
264
265
ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size());
266
ERR_FAIL_COND(p_filter_names.size() != p_filter_mimes.size());
267
268
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
269
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);
270
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);
271
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);
272
for (int i = 0; i < p_filter_names.size(); i++) {
273
DBusMessageIter struct_iter;
274
DBusMessageIter array_iter;
275
DBusMessageIter array_struct_iter;
276
dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
277
append_dbus_string(&struct_iter, p_filter_names[i]);
278
279
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
280
const String &flt_orig = p_filter_exts[i];
281
String flt;
282
for (int j = 0; j < flt_orig.length(); j++) {
283
if (is_unicode_letter(flt_orig[j])) {
284
flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j]));
285
} else {
286
flt += flt_orig[j];
287
}
288
}
289
int filter_slice_count = flt.get_slice_count(",");
290
for (int j = 0; j < filter_slice_count; j++) {
291
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
292
String str = (flt.get_slicec(',', j).strip_edges());
293
{
294
const unsigned flt_type = 0;
295
dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &flt_type);
296
}
297
append_dbus_string(&array_struct_iter, str);
298
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
299
}
300
const String &mime = p_filter_mimes[i];
301
filter_slice_count = mime.get_slice_count(",");
302
for (int j = 0; j < filter_slice_count; j++) {
303
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
304
String str = mime.get_slicec(',', j).strip_edges();
305
{
306
const unsigned flt_type = 1;
307
dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &flt_type);
308
}
309
append_dbus_string(&array_struct_iter, str);
310
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
311
}
312
dbus_message_iter_close_container(&struct_iter, &array_iter);
313
dbus_message_iter_close_container(&arr_iter, &struct_iter);
314
}
315
dbus_message_iter_close_container(&var_iter, &arr_iter);
316
dbus_message_iter_close_container(&dict_iter, &var_iter);
317
dbus_message_iter_close_container(p_iter, &dict_iter);
318
}
319
320
void FreeDesktopPortalDesktop::append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array) {
321
DBusMessageIter dict_iter;
322
DBusMessageIter var_iter;
323
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
324
append_dbus_string(&dict_iter, p_key);
325
326
if (p_as_byte_array) {
327
DBusMessageIter arr_iter;
328
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "ay", &var_iter);
329
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "y", &arr_iter);
330
CharString cs = p_value.utf8();
331
const char *cs_ptr = cs.get_data();
332
do {
333
dbus_message_iter_append_basic(&arr_iter, DBUS_TYPE_BYTE, cs_ptr);
334
} while (*cs_ptr++);
335
dbus_message_iter_close_container(&var_iter, &arr_iter);
336
} else {
337
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "s", &var_iter);
338
append_dbus_string(&var_iter, p_value);
339
}
340
341
dbus_message_iter_close_container(&dict_iter, &var_iter);
342
dbus_message_iter_close_container(p_iter, &dict_iter);
343
}
344
345
void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value) {
346
DBusMessageIter dict_iter;
347
DBusMessageIter var_iter;
348
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
349
append_dbus_string(&dict_iter, p_key);
350
351
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "b", &var_iter);
352
{
353
int val = p_value;
354
dbus_message_iter_append_basic(&var_iter, DBUS_TYPE_BOOLEAN, &val);
355
}
356
357
dbus_message_iter_close_container(&dict_iter, &var_iter);
358
dbus_message_iter_close_container(p_iter, &dict_iter);
359
}
360
361
bool FreeDesktopPortalDesktop::color_picker_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Color &r_color) {
362
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
363
364
dbus_uint32_t resp_code;
365
dbus_message_iter_get_basic(p_iter, &resp_code);
366
if (resp_code != 0) {
367
r_cancel = true;
368
} else {
369
r_cancel = false;
370
ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false);
371
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false);
372
373
DBusMessageIter dict_iter;
374
dbus_message_iter_recurse(p_iter, &dict_iter);
375
while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {
376
DBusMessageIter iter;
377
dbus_message_iter_recurse(&dict_iter, &iter);
378
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {
379
const char *key;
380
dbus_message_iter_get_basic(&iter, &key);
381
dbus_message_iter_next(&iter);
382
383
DBusMessageIter var_iter;
384
dbus_message_iter_recurse(&iter, &var_iter);
385
if (strcmp(key, "color") == 0) { // (ddd)
386
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {
387
DBusMessageIter struct_iter;
388
dbus_message_iter_recurse(&var_iter, &struct_iter);
389
int idx = 0;
390
while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_DOUBLE) {
391
double value = 0.0;
392
dbus_message_iter_get_basic(&struct_iter, &value);
393
if (idx == 0) {
394
r_color.r = value;
395
} else if (idx == 1) {
396
r_color.g = value;
397
} else if (idx == 2) {
398
r_color.b = value;
399
}
400
idx++;
401
if (!dbus_message_iter_next(&struct_iter)) {
402
break;
403
}
404
}
405
}
406
}
407
}
408
if (!dbus_message_iter_next(&dict_iter)) {
409
break;
410
}
411
}
412
}
413
return true;
414
}
415
416
bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, const HashMap<String, String> &p_ids, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) {
417
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
418
419
dbus_uint32_t resp_code;
420
dbus_message_iter_get_basic(p_iter, &resp_code);
421
if (resp_code != 0) {
422
r_cancel = true;
423
} else {
424
r_cancel = false;
425
ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false);
426
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false);
427
428
DBusMessageIter dict_iter;
429
dbus_message_iter_recurse(p_iter, &dict_iter);
430
while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {
431
DBusMessageIter iter;
432
dbus_message_iter_recurse(&dict_iter, &iter);
433
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) {
434
const char *key;
435
dbus_message_iter_get_basic(&iter, &key);
436
dbus_message_iter_next(&iter);
437
438
DBusMessageIter var_iter;
439
dbus_message_iter_recurse(&iter, &var_iter);
440
if (strcmp(key, "current_filter") == 0) { // (sa(us))
441
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {
442
DBusMessageIter struct_iter;
443
dbus_message_iter_recurse(&var_iter, &struct_iter);
444
while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {
445
const char *value;
446
dbus_message_iter_get_basic(&struct_iter, &value);
447
String name = String::utf8(value);
448
449
r_index = p_names.find(name);
450
if (!dbus_message_iter_next(&struct_iter)) {
451
break;
452
}
453
}
454
}
455
} else if (strcmp(key, "choices") == 0) { // a(ss) {
456
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
457
DBusMessageIter struct_iter;
458
dbus_message_iter_recurse(&var_iter, &struct_iter);
459
while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) {
460
DBusMessageIter opt_iter;
461
dbus_message_iter_recurse(&struct_iter, &opt_iter);
462
const char *opt_key = nullptr;
463
dbus_message_iter_get_basic(&opt_iter, &opt_key);
464
String opt_skey = String::utf8(opt_key);
465
dbus_message_iter_next(&opt_iter);
466
const char *opt_val = nullptr;
467
dbus_message_iter_get_basic(&opt_iter, &opt_val);
468
String opt_sval = String::utf8(opt_val);
469
470
if (p_ids.has(opt_skey)) {
471
opt_skey = p_ids[opt_skey];
472
if (opt_sval == "true") {
473
r_options[opt_skey] = true;
474
} else if (opt_sval == "false") {
475
r_options[opt_skey] = false;
476
} else {
477
r_options[opt_skey] = opt_sval.to_int();
478
}
479
}
480
481
if (!dbus_message_iter_next(&struct_iter)) {
482
break;
483
}
484
}
485
}
486
} else if (strcmp(key, "uris") == 0) { // as
487
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
488
DBusMessageIter uri_iter;
489
dbus_message_iter_recurse(&var_iter, &uri_iter);
490
while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) {
491
const char *value;
492
dbus_message_iter_get_basic(&uri_iter, &value);
493
r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_file_decode());
494
if (!dbus_message_iter_next(&uri_iter)) {
495
break;
496
}
497
}
498
}
499
}
500
}
501
if (!dbus_message_iter_next(&dict_iter)) {
502
break;
503
}
504
}
505
}
506
return true;
507
}
508
509
bool FreeDesktopPortalDesktop::color_picker(const String &p_xid, const Callable &p_callback) {
510
if (unsupported) {
511
return false;
512
}
513
514
DBusError err;
515
dbus_error_init(&err);
516
517
// Open connection and add signal handler.
518
ColorPickerData cd;
519
cd.callback = p_callback;
520
521
CryptoCore::RandomGenerator rng;
522
ERR_FAIL_COND_V_MSG(rng.init(), false, "Failed to initialize random number generator.");
523
uint8_t uuid[64];
524
Error rng_err = rng.get_random_bytes(uuid, 64);
525
ERR_FAIL_COND_V_MSG(rng_err, false, "Failed to generate unique token.");
526
527
String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection));
528
String token = String::hex_encode_buffer(uuid, 64);
529
String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace_char('.', '_').remove_char(':'), token);
530
531
cd.path = path;
532
cd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);
533
dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err);
534
if (dbus_error_is_set(&err)) {
535
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
536
dbus_error_free(&err);
537
return false;
538
}
539
540
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SCREENSHOT, "PickColor");
541
{
542
DBusMessageIter iter;
543
dbus_message_iter_init_append(message, &iter);
544
545
append_dbus_string(&iter, p_xid);
546
547
DBusMessageIter arr_iter;
548
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter);
549
append_dbus_dict_string(&arr_iter, "handle_token", token);
550
dbus_message_iter_close_container(&iter, &arr_iter);
551
}
552
DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err);
553
dbus_message_unref(message);
554
555
if (!reply || dbus_error_is_set(&err)) {
556
ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));
557
dbus_error_free(&err);
558
dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err);
559
return false;
560
}
561
562
// Update signal path.
563
{
564
DBusMessageIter iter;
565
if (dbus_message_iter_init(reply, &iter)) {
566
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {
567
const char *new_path = nullptr;
568
dbus_message_iter_get_basic(&iter, &new_path);
569
if (String::utf8(new_path) != path) {
570
dbus_bus_remove_match(monitor_connection, cd.filter.utf8().get_data(), &err);
571
if (dbus_error_is_set(&err)) {
572
ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));
573
dbus_error_free(&err);
574
return false;
575
}
576
cd.filter = String::utf8(new_path);
577
dbus_bus_add_match(monitor_connection, cd.filter.utf8().get_data(), &err);
578
if (dbus_error_is_set(&err)) {
579
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
580
dbus_error_free(&err);
581
return false;
582
}
583
}
584
}
585
}
586
}
587
dbus_message_unref(reply);
588
589
MutexLock lock(color_picker_mutex);
590
color_pickers.push_back(cd);
591
592
return true;
593
}
594
595
bool FreeDesktopPortalDesktop::_is_interface_supported(const char *p_iface) {
596
bool supported = false;
597
DBusError err;
598
dbus_error_init(&err);
599
DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &err);
600
if (dbus_error_is_set(&err)) {
601
dbus_error_free(&err);
602
} else {
603
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_PROPERTIES, "Get");
604
if (message) {
605
const char *name_space = p_iface;
606
const char *key = "version";
607
dbus_message_append_args(
608
message,
609
DBUS_TYPE_STRING, &name_space,
610
DBUS_TYPE_STRING, &key,
611
DBUS_TYPE_INVALID);
612
DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 250, &err);
613
if (dbus_error_is_set(&err)) {
614
dbus_error_free(&err);
615
} else if (reply) {
616
DBusMessageIter iter;
617
if (dbus_message_iter_init(reply, &iter)) {
618
DBusMessageIter iter_ver;
619
dbus_message_iter_recurse(&iter, &iter_ver);
620
dbus_uint32_t ver_code;
621
dbus_message_iter_get_basic(&iter_ver, &ver_code);
622
print_verbose(vformat("PortalDesktop: %s version %d detected.", p_iface, ver_code));
623
supported = true;
624
}
625
dbus_message_unref(reply);
626
}
627
dbus_message_unref(message);
628
}
629
dbus_connection_unref(bus);
630
}
631
return supported;
632
}
633
634
bool FreeDesktopPortalDesktop::is_file_chooser_supported() {
635
static int supported = -1;
636
if (supported == -1) {
637
supported = _is_interface_supported(BUS_INTERFACE_FILE_CHOOSER);
638
}
639
return supported;
640
}
641
642
bool FreeDesktopPortalDesktop::is_settings_supported() {
643
static int supported = -1;
644
if (supported == -1) {
645
supported = _is_interface_supported(BUS_INTERFACE_SETTINGS);
646
}
647
return supported;
648
}
649
650
bool FreeDesktopPortalDesktop::is_screenshot_supported() {
651
static int supported = -1;
652
if (supported == -1) {
653
supported = _is_interface_supported(BUS_INTERFACE_SCREENSHOT);
654
}
655
return supported;
656
}
657
658
Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
659
if (unsupported) {
660
return FAILED;
661
}
662
663
ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);
664
ERR_FAIL_NULL_V(monitor_connection, FAILED);
665
666
Vector<String> filter_names;
667
Vector<String> filter_exts;
668
Vector<String> filter_mimes;
669
for (int i = 0; i < p_filters.size(); i++) {
670
Vector<String> tokens = p_filters[i].split(";");
671
if (tokens.size() >= 1) {
672
String flt = tokens[0].strip_edges();
673
String mime = (tokens.size() >= 3) ? tokens[2].strip_edges() : String();
674
if (!flt.is_empty() || !mime.is_empty()) {
675
if (tokens.size() >= 2) {
676
if (flt == "*.*") {
677
filter_exts.push_back("*");
678
} else {
679
filter_exts.push_back(flt);
680
}
681
filter_mimes.push_back(mime);
682
filter_names.push_back(tokens[1]);
683
} else {
684
if (flt == "*.*") {
685
filter_exts.push_back("*");
686
filter_names.push_back(RTR("All Files") + " (*.*)");
687
} else {
688
filter_exts.push_back(flt);
689
filter_names.push_back(flt);
690
}
691
filter_mimes.push_back(mime);
692
}
693
}
694
}
695
}
696
if (filter_names.is_empty()) {
697
filter_exts.push_back("*");
698
filter_mimes.push_back("");
699
filter_names.push_back(RTR("All Files") + " (*.*)");
700
}
701
702
DBusError err;
703
dbus_error_init(&err);
704
705
// Open connection and add signal handler.
706
FileDialogData fd;
707
fd.callback = p_callback;
708
fd.prev_focus = p_window_id;
709
fd.filter_names = filter_names;
710
fd.opt_in_cb = p_options_in_cb;
711
712
CryptoCore::RandomGenerator rng;
713
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
714
uint8_t uuid[64];
715
Error rng_err = rng.get_random_bytes(uuid, 64);
716
ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token.");
717
718
String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection));
719
String token = String::hex_encode_buffer(uuid, 64);
720
String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace_char('.', '_').remove_char(':'), token);
721
722
fd.path = path;
723
fd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name);
724
dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);
725
if (dbus_error_is_set(&err)) {
726
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
727
dbus_error_free(&err);
728
return FAILED;
729
}
730
731
// Generate FileChooser message.
732
const char *method = nullptr;
733
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
734
method = "SaveFile";
735
} else {
736
method = "OpenFile";
737
}
738
739
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);
740
{
741
DBusMessageIter iter;
742
dbus_message_iter_init_append(message, &iter);
743
744
append_dbus_string(&iter, p_xid);
745
append_dbus_string(&iter, p_title);
746
747
DBusMessageIter arr_iter;
748
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter);
749
750
append_dbus_dict_string(&arr_iter, "handle_token", token);
751
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
752
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
753
append_dbus_dict_filters(&arr_iter, filter_names, filter_exts, filter_mimes);
754
755
append_dbus_dict_options(&arr_iter, p_options, fd.option_ids);
756
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
757
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
758
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
759
}
760
761
dbus_message_iter_close_container(&iter, &arr_iter);
762
}
763
764
DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err);
765
dbus_message_unref(message);
766
767
if (!reply || dbus_error_is_set(&err)) {
768
ERR_PRINT(vformat("Failed to send DBus message: %s", err.message));
769
dbus_error_free(&err);
770
dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
771
return FAILED;
772
}
773
774
// Update signal path.
775
{
776
DBusMessageIter iter;
777
if (dbus_message_iter_init(reply, &iter)) {
778
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {
779
const char *new_path = nullptr;
780
dbus_message_iter_get_basic(&iter, &new_path);
781
if (String::utf8(new_path) != path) {
782
dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
783
if (dbus_error_is_set(&err)) {
784
ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message));
785
dbus_error_free(&err);
786
return FAILED;
787
}
788
fd.filter = String::utf8(new_path);
789
dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err);
790
if (dbus_error_is_set(&err)) {
791
ERR_PRINT(vformat("Failed to add DBus match: %s", err.message));
792
dbus_error_free(&err);
793
return FAILED;
794
}
795
}
796
}
797
}
798
}
799
dbus_message_unref(reply);
800
801
MutexLock lock(file_dialog_mutex);
802
file_dialogs.push_back(fd);
803
804
return OK;
805
}
806
807
void FreeDesktopPortalDesktop::process_callbacks() {
808
{
809
MutexLock lock(file_dialog_mutex);
810
while (!pending_file_cbs.is_empty()) {
811
FileDialogCallback cb = pending_file_cbs.front()->get();
812
pending_file_cbs.pop_front();
813
814
if (cb.opt_in_cb) {
815
Variant ret;
816
Callable::CallError ce;
817
const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options };
818
819
cb.callback.callp(args, 4, ret, ce);
820
if (ce.error != Callable::CallError::CALL_OK) {
821
ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce)));
822
}
823
} else {
824
Variant ret;
825
Callable::CallError ce;
826
const Variant *args[3] = { &cb.status, &cb.files, &cb.index };
827
828
cb.callback.callp(args, 3, ret, ce);
829
if (ce.error != Callable::CallError::CALL_OK) {
830
ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce)));
831
}
832
}
833
}
834
}
835
{
836
MutexLock lock(color_picker_mutex);
837
while (!pending_color_cbs.is_empty()) {
838
ColorPickerCallback cb = pending_color_cbs.front()->get();
839
pending_color_cbs.pop_front();
840
841
Variant ret;
842
Callable::CallError ce;
843
const Variant *args[2] = { &cb.status, &cb.color };
844
845
cb.callback.callp(args, 2, ret, ce);
846
if (ce.error != Callable::CallError::CALL_OK) {
847
ERR_PRINT(vformat("Failed to execute color picker callback: %s.", Variant::get_callable_error_text(cb.callback, args, 2, ce)));
848
}
849
}
850
}
851
}
852
853
void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) {
854
FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud;
855
856
while (!portal->monitor_thread_abort.is_set()) {
857
if (portal->monitor_connection) {
858
while (true) {
859
DBusMessage *msg = dbus_connection_pop_message(portal->monitor_connection);
860
if (!msg) {
861
break;
862
} else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Settings", "SettingChanged")) {
863
DBusMessageIter iter;
864
if (dbus_message_iter_init(msg, &iter)) {
865
const char *value;
866
dbus_message_iter_get_basic(&iter, &value);
867
String name_space = String::utf8(value);
868
dbus_message_iter_next(&iter);
869
dbus_message_iter_get_basic(&iter, &value);
870
String key = String::utf8(value);
871
872
if (name_space == "org.freedesktop.appearance" && (key == "color-scheme" || key == "accent-color")) {
873
callable_mp(portal, &FreeDesktopPortalDesktop::_system_theme_changed_callback).call_deferred();
874
}
875
}
876
} else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
877
String path = String::utf8(dbus_message_get_path(msg));
878
{
879
MutexLock lock(portal->file_dialog_mutex);
880
for (int i = 0; i < portal->file_dialogs.size(); i++) {
881
FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i];
882
if (fd.path == path) {
883
DBusMessageIter iter;
884
if (dbus_message_iter_init(msg, &iter)) {
885
bool cancel = false;
886
Vector<String> uris;
887
Dictionary options;
888
int index = 0;
889
file_chooser_parse_response(&iter, fd.filter_names, fd.option_ids, cancel, uris, index, options);
890
891
if (fd.callback.is_valid()) {
892
FileDialogCallback cb;
893
cb.callback = fd.callback;
894
cb.status = !cancel;
895
cb.files = uris;
896
cb.index = index;
897
cb.options = options;
898
cb.opt_in_cb = fd.opt_in_cb;
899
portal->pending_file_cbs.push_back(cb);
900
}
901
if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
902
callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
903
}
904
}
905
906
DBusError err;
907
dbus_error_init(&err);
908
dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err);
909
dbus_error_free(&err);
910
911
portal->file_dialogs.remove_at(i);
912
break;
913
}
914
}
915
}
916
{
917
MutexLock lock(portal->color_picker_mutex);
918
for (int i = 0; i < portal->color_pickers.size(); i++) {
919
FreeDesktopPortalDesktop::ColorPickerData &cd = portal->color_pickers.write[i];
920
if (cd.path == path) {
921
DBusMessageIter iter;
922
if (dbus_message_iter_init(msg, &iter)) {
923
bool cancel = false;
924
Color c;
925
color_picker_parse_response(&iter, cancel, c);
926
927
if (cd.callback.is_valid()) {
928
ColorPickerCallback cb;
929
cb.callback = cd.callback;
930
cb.color = c;
931
cb.status = !cancel;
932
portal->pending_color_cbs.push_back(cb);
933
}
934
}
935
936
DBusError err;
937
dbus_error_init(&err);
938
dbus_bus_remove_match(portal->monitor_connection, cd.filter.utf8().get_data(), &err);
939
dbus_error_free(&err);
940
941
portal->color_pickers.remove_at(i);
942
break;
943
}
944
}
945
}
946
}
947
dbus_message_unref(msg);
948
}
949
dbus_connection_read_write(portal->monitor_connection, 0);
950
}
951
952
OS::get_singleton()->delay_usec(50'000);
953
}
954
}
955
956
void FreeDesktopPortalDesktop::_system_theme_changed_callback() {
957
if (system_theme_changed.is_valid()) {
958
Variant ret;
959
Callable::CallError ce;
960
system_theme_changed.callp(nullptr, 0, ret, ce);
961
if (ce.error != Callable::CallError::CALL_OK) {
962
ERR_PRINT(vformat("Failed to execute system theme changed callback: %s.", Variant::get_callable_error_text(system_theme_changed, nullptr, 0, ce)));
963
}
964
}
965
}
966
967
FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() {
968
DBusError err;
969
dbus_error_init(&err);
970
monitor_connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
971
if (dbus_error_is_set(&err)) {
972
dbus_error_free(&err);
973
} else {
974
theme_path = "type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged'";
975
dbus_bus_add_match(monitor_connection, theme_path.utf8().get_data(), &err);
976
if (dbus_error_is_set(&err)) {
977
dbus_error_free(&err);
978
dbus_connection_unref(monitor_connection);
979
monitor_connection = nullptr;
980
}
981
dbus_connection_read_write(monitor_connection, 0);
982
}
983
984
if (!unsupported) {
985
monitor_thread_abort.clear();
986
monitor_thread.start(FreeDesktopPortalDesktop::_thread_monitor, this);
987
}
988
}
989
990
FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() {
991
monitor_thread_abort.set();
992
if (monitor_thread.is_started()) {
993
monitor_thread.wait_to_finish();
994
}
995
996
if (monitor_connection) {
997
DBusError err;
998
for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) {
999
dbus_error_init(&err);
1000
dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err);
1001
dbus_error_free(&err);
1002
}
1003
dbus_error_init(&err);
1004
dbus_bus_remove_match(monitor_connection, theme_path.utf8().get_data(), &err);
1005
dbus_error_free(&err);
1006
dbus_connection_unref(monitor_connection);
1007
}
1008
}
1009
1010
#endif // DBUS_ENABLED
1011
1012