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