diff --git a/data/meson.build b/data/meson.build index af8d30b0b..e3bc6a538 100644 --- a/data/meson.build +++ b/data/meson.build @@ -42,6 +42,10 @@ portal_sources = files( 'org.freedesktop.portal.Wallpaper.xml', ) +portal_experimental_sources = files( + 'org.freedesktop.portal.experimental.Credential.xml', +) + portal_host_sources = files( 'org.freedesktop.host.portal.Registry.xml', ) @@ -73,10 +77,23 @@ portal_impl_sources = files( 'org.freedesktop.impl.portal.Wallpaper.xml', ) +portal_impl_experimental_sources = files( + 'org.freedesktop.impl.portal.experimental.Credential.xml', +) +portal_handler_experimental_sources = files( + 'org.freedesktop.handler.portal.experimental.Credential.xml', +) + background_monitor_sources = files( 'org.freedesktop.background.Monitor.xml', ) -install_data([portal_sources, portal_host_sources, portal_impl_sources], +install_data([ + portal_sources, + portal_experimental_sources, + portal_host_sources, + portal_impl_sources, + portal_impl_experimental_sources, + ], install_dir: datadir / 'dbus-1' / 'interfaces', ) diff --git a/data/org.freedesktop.handler.portal.experimental.Credential.xml b/data/org.freedesktop.handler.portal.experimental.Credential.xml new file mode 100644 index 000000000..be6acca30 --- /dev/null +++ b/data/org.freedesktop.handler.portal.experimental.Credential.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.impl.portal.experimental.Credential.xml b/data/org.freedesktop.impl.portal.experimental.Credential.xml new file mode 100644 index 000000000..89a834e24 --- /dev/null +++ b/data/org.freedesktop.impl.portal.experimental.Credential.xml @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.portal.experimental.Credential.xml b/data/org.freedesktop.portal.experimental.Credential.xml new file mode 100644 index 000000000..96f67440b --- /dev/null +++ b/data/org.freedesktop.portal.experimental.Credential.xml @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop-portal/credential.c b/desktop-portal/credential.c new file mode 100644 index 000000000..89bad0cca --- /dev/null +++ b/desktop-portal/credential.c @@ -0,0 +1,383 @@ +/* + * Copyright © 2025 Isaiah Inuwa + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Isaiah Inuwa + */ + +#include "credential.h" + +#include + +#include + +#include "dex-aio.h" +#include "gio/gio.h" +#include "glib.h" +#include "glibconfig.h" +#include "xdp-app-info.h" +#include "xdp-context.h" +#include "xdp-experimental-dbus.h" +#include "xdp-experimental-handler-dbus.h" +#include "xdp-portal-config.h" +#include "xdp-request-dex.h" +#include "xdp-utils.h" + +static gboolean handle_create_credential (XdpDbusExperimentalCredential *object, + GDBusMethodInvocation *invocation, + const gchar *arg_parent_window, + const gchar *arg_origin, + const gchar *arg_type, + GVariant *arg_options); + +static gboolean handle_get_credential (XdpDbusExperimentalCredential *object, + GDBusMethodInvocation *invocation, + const gchar *arg_parent_window, + const gchar *arg_origin, + GVariant *arg_options); +struct _XdpCredential +{ + XdpDbusExperimentalCredentialSkeleton parent_instance; + + XdpContext *context; + XdpDbusExperimentalHandlerCredential *handler; +}; + +#define XDP_TYPE_CREDENTIAL (xdp_dbus_experimental_credential_get_type ()) +G_DECLARE_FINAL_TYPE (XdpCredential, xdp_credential, XDP, CREDENTIAL, XdpDbusExperimentalCredentialSkeleton) + +static void +xdp_credential_iface_init (XdpDbusExperimentalCredentialIface *iface); + +G_DEFINE_FINAL_TYPE_WITH_CODE ( + XdpCredential, + xdp_credential, + XDP_DBUS_EXPERIMENTAL_TYPE_CREDENTIAL_SKELETON, + G_IMPLEMENT_INTERFACE (XDP_DBUS_EXPERIMENTAL_TYPE_CREDENTIAL, xdp_credential_iface_init) + ); + +static void xdp_credential_iface_init (XdpDbusExperimentalCredentialIface *iface) +{ + iface->handle_create_credential = handle_create_credential; + iface->handle_get_credential = handle_get_credential; +} + +static void xdp_credential_dispose (GObject *object) +{ + XdpCredential *credential = XDP_CREDENTIAL (object); + + g_clear_object (&credential->handler); + + G_OBJECT_CLASS (xdp_credential_parent_class)->dispose (object); +} + +static void xdp_credential_init (XdpCredential *credential) {} + +static void xdp_credential_class_init (XdpCredentialClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xdp_credential_dispose; +} + +static XdpCredential * +xdp_credential_new(XdpContext *context, XdpDbusExperimentalHandlerCredential *handler) +{ + XdpCredential *credential; + + credential = g_object_new(xdp_credential_get_type(), NULL); + credential->context = context; + credential->handler = g_object_ref(handler); + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (credential->handler), G_MAXINT); + + xdp_dbus_experimental_credential_set_conditional_create ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + xdp_dbus_experimental_credential_set_conditional_get ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + xdp_dbus_experimental_credential_set_hybrid_transport ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), TRUE); + xdp_dbus_experimental_credential_set_passkey_platform_authenticator ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), TRUE); + xdp_dbus_experimental_credential_set_user_verifying_platform_authenticator ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + xdp_dbus_experimental_credential_set_related_origins ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), TRUE); + xdp_dbus_experimental_credential_set_signal_all_accepted_credentials ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + xdp_dbus_experimental_credential_set_signal_current_user_details ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + xdp_dbus_experimental_credential_set_signal_unknown_credential ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), FALSE); + + xdp_dbus_experimental_credential_set_version ( + XDP_DBUS_EXPERIMENTAL_CREDENTIAL (credential), 1); + + return credential; +} + +const gchar *CREDENTIALSD_HANDLER_DBUS_NAME = + "xyz.iinuwa.credentialsd.Credentials"; + +static XdpOptionKey create_credential_options[] = { + {"handle_token", G_VARIANT_TYPE_STRING, NULL}, + {"origin", G_VARIANT_TYPE_STRING, NULL}, + {"top_origin", G_VARIANT_TYPE_STRING, NULL}, + {"type", G_VARIANT_TYPE_STRING, NULL}, + {"public_key", G_VARIANT_TYPE_STRING, NULL}, +}; + +/** + * create_credential_validate_options: + * @arg_options: (transfer none): options passed to the frontend. + * @error: (transfer none): pointer to an error pointer that will be populated on error. + * Returns: (transfer full): Filtered list of options to pass to the handler. + */ +static GVariant * +create_credential_validate_options (GVariant *arg_options, + GError **error) +{ + g_auto (GVariantBuilder) options = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + gboolean validated = xdp_filter_options (arg_options, + &options, + create_credential_options, + G_N_ELEMENTS (create_credential_options), + NULL, + error); + if (!validated) + return NULL; + else + return g_variant_ref_sink (g_variant_builder_end (&options)); +} + +static gboolean handle_create_credential (XdpDbusExperimentalCredential *object, + GDBusMethodInvocation *invocation, + const gchar *arg_parent_window, + const gchar *arg_origin, + const gchar *arg_type, + GVariant *arg_options) +{ + XdpCredential *credential = XDP_CREDENTIAL (object); + g_autoptr (XdpRequestDex) request = NULL; + g_autoptr (GVariant) options = NULL; + g_autoptr (GError) error = NULL; + + XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation); + const gchar *app_id = xdp_app_info_get_id (app_info); + + options = create_credential_validate_options (arg_options, &error); + if (!options) + { + g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + request = dex_await_object (xdp_request_dex_new ( + credential->context, + app_info, + G_DBUS_INTERFACE_SKELETON (object), + G_DBUS_PROXY (credential->handler), + options + ), + &error); + if (!request) + { + g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_dbus_experimental_credential_complete_create_credential ( + object, + invocation, + xdp_request_dex_get_object_path (request)); + + { + g_autoptr (XdpDbusExperimentalHandlerCredentialCreateCredentialResult) result = NULL; + result = dex_await_boxed (xdp_dbus_experimental_handler_credential_call_create_credential_future ( + credential->handler, + arg_parent_window, + arg_origin, + arg_type, + options, + app_id + ), + &error); + + if (result) { + xdp_request_dex_emit_response (request, + result->response, + result->results); + } else { + g_dbus_error_strip_remote_error (error); + g_warning ("Handler call failed: %s (%d)", error->message, error->code); + xdp_request_dex_emit_response (request, + XDG_DESKTOP_PORTAL_RESPONSE_OTHER, + NULL); + } + } + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static XdpOptionKey get_credential_options[] = { + {"handle_token", G_VARIANT_TYPE_STRING, NULL}, + {"origin", G_VARIANT_TYPE_STRING, NULL}, + {"top_origin", G_VARIANT_TYPE_STRING, NULL}, + {"public_key", G_VARIANT_TYPE_STRING, NULL}, +}; + +/** + * get_credential_validate_options: + * @arg_options: (transfer none): options passed to the frontend. + * @error: (transfer none): pointer to an error pointer that will be populated on error. + * Returns: (transfer full): Filtered list of options to pass to the handler. + */ +static GVariant * +get_credential_validate_options (GVariant *arg_options, + GError **error) +{ + g_auto (GVariantBuilder) options = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + gboolean validated = xdp_filter_options (arg_options, + &options, + get_credential_options, + G_N_ELEMENTS (get_credential_options), + NULL, + error); + if (!validated) + return NULL; + else + return g_variant_ref_sink (g_variant_builder_end (&options)); +} + +static gboolean handle_get_credential (XdpDbusExperimentalCredential *object, + GDBusMethodInvocation *invocation, + const gchar *arg_parent_window, + const gchar *arg_origin, + GVariant *arg_options) +{ + XdpCredential *credential = XDP_CREDENTIAL (object); + g_autoptr (XdpRequestDex) request = NULL; + g_autoptr (GVariant) options = NULL; + g_autoptr (GError) error = NULL; + + XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation); + const gchar *app_id = xdp_app_info_get_id (app_info); + + options = get_credential_validate_options (arg_options, &error); + if (!options) + { + g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + request = dex_await_object (xdp_request_dex_new ( + credential->context, + app_info, + G_DBUS_INTERFACE_SKELETON (object), + G_DBUS_PROXY (credential->handler), + options + ), + &error); + if (!request) + { + g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + xdp_dbus_experimental_credential_complete_get_credential ( + object, + invocation, + xdp_request_dex_get_object_path (request)); + + { + g_autoptr (XdpDbusExperimentalHandlerCredentialGetCredentialResult) result = NULL; + result = dex_await_boxed (xdp_dbus_experimental_handler_credential_call_get_credential_future ( + credential->handler, + arg_parent_window, + arg_origin, + options, + app_id + ), + &error); + + if (result) { + xdp_request_dex_emit_response (request, + result->response, + result->results); + } else { + g_dbus_error_strip_remote_error (error); + g_warning ("Handler call failed: %s (%d)", error->message, error->code); + xdp_request_dex_emit_response (request, + XDG_DESKTOP_PORTAL_RESPONSE_OTHER, + NULL); + } + } + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +DexFuture * +init_credential (gpointer user_data) +{ + g_info ("Initializing Credential Portal"); + + XdpContext *context = XDP_CONTEXT (user_data); + g_autoptr (XdpCredential) credential = NULL; + g_autoptr (XdpDbusExperimentalHandlerCredential) handler = NULL; + g_autoptr (GError) error = NULL; + + GDBusConnection *connection = xdp_context_get_connection (context); + { + XdpPortalConfig *config = xdp_context_get_config (context); + XdpImplConfig *impl_config; + + impl_config = + xdp_portal_config_find (config, CREDENTIAL_EXPERIMENTAL_DBUS_IMPL_IFACE); + + if (impl_config == NULL) { + g_debug ("impl_config is NULL"); + return dex_future_new_true (); + } + + g_debug ("found impl"); + } + + g_debug ("creating handler proxy..."); + + handler = dex_await_object (xdp_dbus_experimental_handler_credential_proxy_new_future ( + connection, + G_DBUS_PROXY_FLAGS_NONE, + CREDENTIALSD_HANDLER_DBUS_NAME, + DESKTOP_DBUS_PATH), + &error); + + if (!handler) + { + g_warning ("Failed to create credential proxy: %s", error->message); + return dex_future_new_false (); + } + g_debug ("created handler proxy."); + + credential = xdp_credential_new (context, g_steal_pointer (&handler)); + + xdp_context_take_and_export_portal (context, + G_DBUS_INTERFACE_SKELETON (g_steal_pointer (&credential)), + XDP_CONTEXT_EXPORT_FLAGS_RUN_IN_FIBER); + + return dex_future_new_true (); +} diff --git a/desktop-portal/credential.h b/desktop-portal/credential.h new file mode 100644 index 000000000..8beb09a93 --- /dev/null +++ b/desktop-portal/credential.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2025 Isaiah Inuwa + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Isaiah Inuwa + */ + + +#pragma once + +#include + +DexFuture * +init_credential (gpointer user_data); + diff --git a/desktop-portal/meson.build b/desktop-portal/meson.build index 120001338..42c0b0dbf 100644 --- a/desktop-portal/meson.build +++ b/desktop-portal/meson.build @@ -13,6 +13,14 @@ portal_built_sources = gnome.gdbus_codegen( docbook: 'portal', extra_args: gdbus_codegen_extra_args, ) +portal_experimental_built_sources = gnome.gdbus_codegen( + 'xdp-experimental-dbus', + sources: portal_experimental_sources, + interface_prefix: 'org.freedesktop.portal.experimental', + namespace: 'XdpDbusExperimental', + docbook: 'portal', + extra_args: gdbus_codegen_extra_args, +) host_built_sources = gnome.gdbus_codegen( 'xdp-host-dbus', sources: portal_host_sources, @@ -29,6 +37,22 @@ impl_built_sources = gnome.gdbus_codegen( docbook: 'portal', extra_args: gdbus_codegen_extra_args, ) +impl_experimental_built_sources = gnome.gdbus_codegen( + 'xdp-impl-experimental-dbus', + sources: portal_impl_experimental_sources, + interface_prefix: 'org.freedesktop.impl.portal.experimental', + namespace: 'XdpDbusExperimentalImpl', + docbook: 'portal', + extra_args: gdbus_codegen_extra_args, +) +handler_experimental_built_sources = gnome.gdbus_codegen( + 'xdp-experimental-handler-dbus', + sources: portal_handler_experimental_sources, + interface_prefix: 'org.freedesktop.handler.portal.experimental', + namespace: 'XdpDbusExperimentalHandler', + docbook: 'portal', + extra_args: gdbus_codegen_extra_args, +) background_monitor_built_sources = gnome.gdbus_codegen( 'xdp-background-dbus', sources: background_monitor_sources, @@ -50,7 +74,7 @@ else endif xdp_method_info_built_sources = configure_file( - command: [find_program('generate-method-info.py'), portal_sources], + command: [find_program('generate-method-info.py'), portal_sources, portal_experimental_sources], capture: true, output: 'xdp-method-info-generated.c', ) @@ -62,6 +86,7 @@ xdg_desktop_portal_sources = files( 'background.c', 'camera.c', 'clipboard.c', + 'credential.c', 'dynamic-launcher.c', 'email.c', 'file-chooser.c', @@ -105,8 +130,10 @@ xdg_desktop_portal_sources += [ xdp_utils_sources, xdp_method_info_sources, portal_built_sources, + portal_experimental_built_sources, host_built_sources, impl_built_sources, + handler_experimental_built_sources, background_monitor_built_sources, geoclue_built_sources, ] diff --git a/desktop-portal/xdp-context.c b/desktop-portal/xdp-context.c index 50b305de4..6f1665bfe 100644 --- a/desktop-portal/xdp-context.c +++ b/desktop-portal/xdp-context.c @@ -10,6 +10,7 @@ #include "background.h" #include "camera.h" #include "clipboard.h" +#include "credential.h" #include "dynamic-launcher.h" #include "email.h" #include "file-chooser.h" @@ -484,6 +485,23 @@ xdp_context_register (XdpContext *context, #endif init_registry (context); + // Experimental feature flags + char *experimental_flag_env = + getenv ("XDG_DESKTOP_PORTAL_ENABLE_EXPERIMENTAL"); + if (experimental_flag_env != NULL) + { + gchar **flags = g_strsplit (experimental_flag_env, ",", -1); + for (gint i = 0; flags[i] != NULL; i++) + { + gchar *flag = flags[i]; + if (g_strcmp0 (flag, "credential") == 0) + { + g_debug ("Enabling experimental portal credential"); + init_portal_in_fiber(context, init_credential); + } + } + } + return TRUE; } diff --git a/doc/api-reference.rst b/doc/api-reference.rst index 0b192515f..bdb295763 100644 --- a/doc/api-reference.rst +++ b/doc/api-reference.rst @@ -65,3 +65,4 @@ replacement will be documented in :doc:`org.freedesktop.host.portal.Registry doc-org.freedesktop.portal.Trash.rst doc-org.freedesktop.portal.Usb.rst doc-org.freedesktop.portal.Wallpaper.rst + doc-org.freedesktop.portal.experimental.Credential.rst diff --git a/doc/fix-rst-dbus.py b/doc/fix-rst-dbus.py index 731384679..ed39bee95 100755 --- a/doc/fix-rst-dbus.py +++ b/doc/fix-rst-dbus.py @@ -87,7 +87,15 @@ def split_sections(lines, output_prefix): def adjust_title(lines): title = lines[3].strip() - if title.startswith("org.freedesktop.portal."): + if title.startswith("org.freedesktop.portal.experimental."): + adjusted_title = ( + title.replace("org.freedesktop.portal.experimental.", "") + " 🧪" + ) + elif title.startswith("org.freedesktop.impl.portal.experimental."): + adjusted_title = ( + title.replace("org.freedesktop.impl.portal.experimental.", "") + " 🧪" + ) + elif title.startswith("org.freedesktop.portal."): adjusted_title = title.replace("org.freedesktop.portal.", "") elif title.startswith("org.freedesktop.impl.portal"): adjusted_title = title.replace("org.freedesktop.impl.portal.", "") diff --git a/doc/impl-dbus-interfaces.rst b/doc/impl-dbus-interfaces.rst index c78c645be..0133487d4 100644 --- a/doc/impl-dbus-interfaces.rst +++ b/doc/impl-dbus-interfaces.rst @@ -36,3 +36,4 @@ accessible to sandboxed applications. doc-org.freedesktop.impl.portal.Settings.rst doc-org.freedesktop.impl.portal.Usb.rst doc-org.freedesktop.impl.portal.Wallpaper.rst + doc-org.freedesktop.impl.portal.experimental.Credential.rst diff --git a/doc/meson.build b/doc/meson.build index 4a94d984a..5a46b9c85 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -40,13 +40,13 @@ endif if build_documentation # Gather the XML files under data all_interfaces_xml = [] - foreach i: portal_sources + foreach i: portal_sources + portal_experimental_sources all_interfaces_xml += i endforeach foreach i: portal_host_sources all_interfaces_xml += i endforeach - foreach i: portal_impl_sources + foreach i: portal_impl_sources + portal_impl_experimental_sources all_interfaces_xml += i endforeach foreach i: background_monitor_sources diff --git a/shared/xdp-types.h b/shared/xdp-types.h index 8a5709709..b0dab411c 100644 --- a/shared/xdp-types.h +++ b/shared/xdp-types.h @@ -30,6 +30,9 @@ typedef struct _XdpDbusImplAccess XdpDbusImplAccess; #define DESKTOP_DBUS_IFACE "org.freedesktop.portal" #define DESKTOP_DBUS_IMPL_IFACE "org.freedesktop.impl.portal" #define DESKTOP_DBUS_PATH "/org/freedesktop/portal/desktop" +#define DESKTOP_EXPERIMENTAL_DBUS_IFACE DESKTOP_DBUS_IFACE ".experimental" +#define DESKTOP_EXPERIMENTAL_DBUS_IMPL_IFACE \ + DESKTOP_DBUS_IMPL_IFACE ".experimental" #define ACCESS_DBUS_IMPL_IFACE DESKTOP_DBUS_IMPL_IFACE ".Access" @@ -52,6 +55,11 @@ typedef struct _XdpDbusImplAccess XdpDbusImplAccess; #define CLIPBOARD_DBUS_IFACE DESKTOP_DBUS_IFACE ".Clipboard" #define CLIPBOARD_DBUS_IMPL_IFACE DESKTOP_DBUS_IMPL_IFACE ".Clipboard" +#define CREDENTIAL_EXPERIMENTAL_DBUS_IFACE \ + DESKTOP_EXPERIMENTAL_DBUS_IFACE ".Credential" +#define CREDENTIAL_EXPERIMENTAL_DBUS_IMPL_IFACE \ + DESKTOP_EXPERIMENTAL_DBUS_IMPL_IFACE ".Credential" + #define DYNAMIC_LAUNCHER_DBUS_IFACE DESKTOP_DBUS_IFACE ".DynamicLauncher" #define DYNAMIC_LAUNCHER_DBUS_IMPL_IFACE DESKTOP_DBUS_IMPL_IFACE ".DynamicLauncher"