diff --git a/Makefile.am.inc b/Makefile.am.inc index 7be4c4f89..32b3d3656 100644 --- a/Makefile.am.inc +++ b/Makefile.am.inc @@ -9,6 +9,7 @@ PORTAL_IFACE_FILES =\ $(top_srcdir)/data/org.freedesktop.portal.FileChooser.xml \ $(top_srcdir)/data/org.freedesktop.portal.FileTransfer.xml \ $(top_srcdir)/data/org.freedesktop.portal.GameMode.xml \ + $(top_srcdir)/data/org.freedesktop.portal.GlobalShortcuts.xml \ $(top_srcdir)/data/org.freedesktop.portal.Inhibit.xml \ $(top_srcdir)/data/org.freedesktop.portal.Location.xml \ $(top_srcdir)/data/org.freedesktop.portal.MemoryMonitor.xml \ @@ -38,6 +39,7 @@ PORTAL_IMPL_IFACE_FILES =\ $(top_srcdir)/data/org.freedesktop.impl.portal.DynamicLauncher.xml \ $(top_srcdir)/data/org.freedesktop.impl.portal.Email.xml \ $(top_srcdir)/data/org.freedesktop.impl.portal.FileChooser.xml \ + $(top_srcdir)/data/org.freedesktop.impl.portal.GlobalShortcuts.xml \ $(top_srcdir)/data/org.freedesktop.impl.portal.Inhibit.xml \ $(top_srcdir)/data/org.freedesktop.impl.portal.Lockdown.xml \ $(top_srcdir)/data/org.freedesktop.impl.portal.Notification.xml \ diff --git a/data/meson.build b/data/meson.build index 489479f18..22355c2d9 100644 --- a/data/meson.build +++ b/data/meson.build @@ -12,6 +12,7 @@ portal_sources = files( 'org.freedesktop.portal.FileChooser.xml', 'org.freedesktop.portal.FileTransfer.xml', 'org.freedesktop.portal.GameMode.xml', + 'org.freedesktop.portal.GlobalShortcuts.xml', 'org.freedesktop.portal.Inhibit.xml', 'org.freedesktop.portal.Location.xml', 'org.freedesktop.portal.MemoryMonitor.xml', @@ -41,6 +42,7 @@ portal_impl_sources = files( 'org.freedesktop.impl.portal.DynamicLauncher.xml', 'org.freedesktop.impl.portal.Email.xml', 'org.freedesktop.impl.portal.FileChooser.xml', + 'org.freedesktop.impl.portal.GlobalShortcuts.xml', 'org.freedesktop.impl.portal.Inhibit.xml', 'org.freedesktop.impl.portal.Lockdown.xml', 'org.freedesktop.impl.portal.Notification.xml', @@ -58,4 +60,4 @@ portal_impl_sources = files( install_data([portal_sources, portal_impl_sources], install_dir: datadir / 'dbus-1' / 'interfaces', -) \ No newline at end of file +) diff --git a/data/org.freedesktop.impl.portal.GlobalShortcuts.xml b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml new file mode 100644 index 000000000..85fcf6259 --- /dev/null +++ b/data/org.freedesktop.impl.portal.GlobalShortcuts.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/org.freedesktop.portal.GlobalShortcuts.xml b/data/org.freedesktop.portal.GlobalShortcuts.xml new file mode 100644 index 000000000..aa4042628 --- /dev/null +++ b/data/org.freedesktop.portal.GlobalShortcuts.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Makefile.am.inc b/src/Makefile.am.inc index 86a8de0b9..d7e07fe78 100644 --- a/src/Makefile.am.inc +++ b/src/Makefile.am.inc @@ -130,6 +130,8 @@ xdg_desktop_portal_SOURCES = \ src/flatpak-instance.h \ src/portal-impl.h \ src/portal-impl.c \ + src/globalshortcuts.h \ + src/globalshortcuts.c \ $(NULL) if HAVE_LIBSYSTEMD diff --git a/src/globalshortcuts.c b/src/globalshortcuts.c new file mode 100644 index 000000000..311124518 --- /dev/null +++ b/src/globalshortcuts.c @@ -0,0 +1,553 @@ +/* + * Copyright © 2022 Aleix Pol Gonzalez + * + * 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 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: + * Aleix Pol Gonzalez + */ + +#include "config.h" + +#include +#include + +#include "globalshortcuts.h" +#include "request.h" +#include "session.h" +#include "permissions.h" +#include "xdp-dbus.h" +#include "xdp-impl-dbus.h" +#include "xdp-utils.h" + +typedef struct _GlobalShortcuts GlobalShortcuts; +typedef struct _GlobalShortcutsClass GlobalShortcutsClass; + +static GQuark quark_request_session; + +struct _GlobalShortcuts +{ + XdpDbusGlobalShortcutsSkeleton parent_instance; +}; + +struct _GlobalShortcutsClass +{ + XdpDbusGlobalShortcutsSkeletonClass parent_class; +}; + +static XdpDbusImplGlobalShortcuts *impl; +static GlobalShortcuts *global_shortcuts; + +GType global_shortcuts_get_type (void) G_GNUC_CONST; +static void global_shortcuts_iface_init (XdpDbusGlobalShortcutsIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GlobalShortcuts, global_shortcuts, XDP_DBUS_TYPE_GLOBAL_SHORTCUTS_SKELETON, + G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_GLOBAL_SHORTCUTS, global_shortcuts_iface_init)); + +typedef struct _GlobalShortcutsSession +{ + Session parent; + + gboolean closed; +} GlobalShortcutsSession; + +typedef struct _GlobalShortcutsSessionClass +{ + SessionClass parent_class; +} GlobalShortcutsSessionClass; + +GType global_shortcuts_session_get_type (void); + +G_DEFINE_TYPE (GlobalShortcutsSession, global_shortcuts_session, session_get_type ()) + +static void +global_shortcuts_session_close (Session *session) +{ + GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + + global_shortcuts_session->closed = TRUE; +} + +static void +global_shortcuts_session_finalize (GObject *object) +{ + G_OBJECT_CLASS (global_shortcuts_session_parent_class)->finalize (object); +} + +static void +global_shortcuts_session_init (GlobalShortcutsSession *global_shortcuts_session) +{ +} + +static void +global_shortcuts_session_class_init (GlobalShortcutsSessionClass *klass) +{ + GObjectClass *object_class; + SessionClass *session_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = global_shortcuts_session_finalize; + + session_class = (SessionClass *)klass; + session_class->close = global_shortcuts_session_close; +} + +static GlobalShortcutsSession * +global_shortcuts_session_new (GVariant *options, + Request *request, + GError **error) +{ + Session *session; + GDBusInterfaceSkeleton *interface_skeleton = + G_DBUS_INTERFACE_SKELETON (request); + const char *session_token; + GDBusConnection *connection = + g_dbus_interface_skeleton_get_connection (interface_skeleton); + GDBusConnection *impl_connection = + g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)); + const char *impl_dbus_name = g_dbus_proxy_get_name (G_DBUS_PROXY (impl)); + + session_token = lookup_session_token (options); + session = g_initable_new (global_shortcuts_session_get_type (), NULL, error, + "sender", request->sender, + "app-id", xdp_app_info_get_id (request->app_info), + "token", session_token, + "connection", connection, + "impl-connection", impl_connection, + "impl-dbus-name", impl_dbus_name, + NULL); + + if (session) + g_debug ("global shortcuts session owned by '%s' created", session->sender); + + return (GlobalShortcutsSession *) session; +} + +static void +create_session_done (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(Request) request = data; + Session *session; + guint response = 2; + GVariant *opts; + gboolean should_close_session; + GVariantBuilder results_builder; + g_autoptr(GError) error = NULL; + + REQUEST_AUTOLOCK (request); + + session = g_object_get_qdata (G_OBJECT (request), quark_request_session); + SESSION_AUTOLOCK_UNREF (g_object_ref (session)); + g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); + + g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); + + if (!xdp_dbus_impl_global_shortcuts_call_create_session_finish (impl, + &response, + &opts, + res, + &error)) + { + g_dbus_error_strip_remote_error (error); + g_warning ("A backend call failed: %s", error->message); + should_close_session = TRUE; + goto out; + } + + if (request->exported && response == 0) + { + if (!session_export (session, &error)) + { + g_warning ("Failed to export session: %s", error->message); + response = 2; + should_close_session = TRUE; + goto out; + } + + should_close_session = FALSE; + session_register (session); + } + else + { + should_close_session = TRUE; + } + + GVariantDict *dict = g_variant_dict_new (opts); + g_variant_builder_add (&results_builder, "{sv}", + "shortcuts", g_variant_dict_lookup_value(dict, "shortcuts", 0)); + g_variant_builder_add (&results_builder, "{sv}", + "session_handle", g_variant_new ("s", session->id)); + +out: + if (request->exported) + { + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), + response, + g_variant_builder_end (&results_builder)); + request_unexport (request); + } + else + { + g_variant_builder_clear (&results_builder); + } + + if (should_close_session) + session_close (session, FALSE); +} + +static gboolean +handle_create_session (XdpDbusGlobalShortcuts *object, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + Request *request = request_from_invocation (invocation); + g_autoptr(GError) error = NULL; + g_autoptr(XdpDbusImplRequest) impl_request = NULL; + Session *session; + + REQUEST_AUTOLOCK (request); + + impl_request = + xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (impl)), + request->id, + NULL, &error); + if (!impl_request) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + request_set_impl_request (request, impl_request); + request_export (request, g_dbus_method_invocation_get_connection (invocation)); + + session = (Session *)global_shortcuts_session_new (arg_options, request, &error); + if (!session) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_object_set_qdata_full (G_OBJECT (request), + quark_request_session, + g_object_ref (session), + g_object_unref); + + xdp_dbus_impl_global_shortcuts_call_create_session (impl, + request->id, + session->id, + xdp_app_info_get_id (request->app_info), + arg_options, + NULL, + create_session_done, + g_object_ref (request)); + + xdp_dbus_global_shortcuts_complete_create_session (object, invocation, request->id); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +void +bind_shortcuts_done (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(Request) request = data; + Session *session; + guint response = 2; + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) results = NULL; + + REQUEST_AUTOLOCK (request); + + session = g_object_get_qdata (G_OBJECT (request), quark_request_session); + SESSION_AUTOLOCK_UNREF (g_object_ref (session)); + g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); + + if (!xdp_dbus_impl_global_shortcuts_call_bind_shortcuts_finish (impl, &results, res, &error)) + { + g_dbus_error_strip_remote_error (error); + g_warning ("A backend call failed: %s", error->message); + } + + if (request->exported) + { + if (!results) + { + GVariantBuilder results_builder; + + g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); + } + + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); + request_unexport (request); + } +} + +static gboolean +handle_bind_shortcuts (XdpDbusGlobalShortcuts *object, + GDBusMethodInvocation *invocation, + const gchar *arg_session_handle, + const gchar *const *arg_shortcuts, + const gchar *arg_parent_window, + GVariant *arg_options) +{ + Request *request = request_from_invocation (invocation); + Session *session; + g_autoptr(XdpDbusImplRequest) impl_request = NULL; + g_autoptr(GError) error = NULL; + + REQUEST_AUTOLOCK (request); + + session = acquire_session (arg_session_handle, request); + if (!session) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Invalid session"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + SESSION_AUTOLOCK_UNREF (session); + + impl_request = + xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (impl)), + request->id, + NULL, &error); + if (!impl_request) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + request_set_impl_request (request, impl_request); + request_export (request, g_dbus_method_invocation_get_connection (invocation)); + + g_object_set_qdata_full (G_OBJECT (request), + quark_request_session, + g_object_ref (session), + g_object_unref); + + xdp_dbus_impl_global_shortcuts_call_bind_shortcuts (impl, + request->id, + arg_session_handle, + arg_shortcuts, + arg_parent_window, + arg_options, + NULL, + bind_shortcuts_done, + g_object_ref (request)); + + xdp_dbus_global_shortcuts_complete_bind_shortcuts (object, invocation, request->id); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +list_shortcuts_done (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + g_autoptr(Request) request = data; + Session *session; + guint response = 2; + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) results = NULL; + + REQUEST_AUTOLOCK (request); + + session = g_object_get_qdata (G_OBJECT (request), quark_request_session); + SESSION_AUTOLOCK_UNREF (g_object_ref (session)); + g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL); + + if (!xdp_dbus_impl_global_shortcuts_call_list_shortcuts_finish (impl, &results, res, &error)) + { + g_dbus_error_strip_remote_error (error); + g_warning ("A backend call failed: %s", error->message); + } + + if (request->exported) + { + if (!results) + { + GVariantBuilder results_builder; + + g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT); + results = g_variant_ref_sink (g_variant_builder_end (&results_builder)); + } + + xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results); + request_unexport (request); + } +} + +static gboolean +handle_list_shortcuts (XdpDbusGlobalShortcuts *object, + GDBusMethodInvocation *invocation, + const gchar *arg_session_handle) +{ + Request *request = request_from_invocation (invocation); + Session *session; + g_autoptr(XdpDbusImplRequest) impl_request = NULL; + g_autoptr(GError) error = NULL; + + REQUEST_AUTOLOCK (request); + + session = acquire_session (arg_session_handle, request); + if (!session) + { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Invalid session"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + impl_request = + xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)), + G_DBUS_PROXY_FLAGS_NONE, + g_dbus_proxy_get_name (G_DBUS_PROXY (impl)), + request->id, + NULL, &error); + if (!impl_request) + { + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + request_set_impl_request (request, impl_request); + request_export (request, g_dbus_method_invocation_get_connection (invocation)); + + g_object_set_qdata_full (G_OBJECT (request), + quark_request_session, + g_object_ref (session), + g_object_unref); + + xdp_dbus_impl_global_shortcuts_call_list_shortcuts (impl, + request->id, + arg_session_handle, + NULL, + list_shortcuts_done, + g_object_ref (request)); + + xdp_dbus_global_shortcuts_complete_list_shortcuts (object, invocation, request->id); + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +global_shortcuts_iface_init (XdpDbusGlobalShortcutsIface *iface) +{ + iface->handle_create_session = handle_create_session; + iface->handle_bind_shortcuts = handle_bind_shortcuts; + iface->handle_list_shortcuts = handle_list_shortcuts; +} + +static void +global_shortcuts_init (GlobalShortcuts *global_shortcuts) +{ + xdp_dbus_global_shortcuts_set_version (XDP_DBUS_GLOBAL_SHORTCUTS (global_shortcuts), 1); +} + +static void +global_shortcuts_class_init (GlobalShortcutsClass *klass) +{ + quark_request_session = + g_quark_from_static_string ("-xdp-request-global-shortcuts-session"); +} + +static void +activated_cb (XdpDbusImplGlobalShortcuts *impl, + const char *session_id, + const char *shortcut_id, + guint64 timestamp, + GVariant *options, + gpointer data) +{ + g_autoptr(Session) session = lookup_session (session_id); + GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + + g_debug ("Received activated %s for %s", session_id, shortcut_id); + + if (global_shortcuts_session && !global_shortcuts_session->closed) { + xdp_dbus_global_shortcuts_emit_activated (data, session_id, shortcut_id, timestamp, options); + } +} + +static void +deactivated_cb (XdpDbusImplGlobalShortcuts *impl, + const char *session_id, + const char *shortcut_id, + guint64 timestamp, + GVariant *options, + gpointer data) +{ + g_autoptr(Session) session = lookup_session (session_id); + GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + + g_debug ("Received deactivated %s for %s", session_id, shortcut_id); + + if (global_shortcuts_session && !global_shortcuts_session->closed) + { + xdp_dbus_global_shortcuts_emit_deactivated (data, session_id, shortcut_id, timestamp, options); + } +} + +static void +shortcutschanged_cb (XdpDbusImplGlobalShortcuts *impl, + const char *session_id, + GVariant *shortcuts, + gpointer data) +{ + g_autoptr(Session) session = lookup_session (session_id); + GlobalShortcutsSession *global_shortcuts_session = (GlobalShortcutsSession *)session; + + g_debug ("Received ShortcutsChanged %s", session_id); + + if (global_shortcuts_session && !global_shortcuts_session->closed) + xdp_dbus_global_shortcuts_emit_shortcuts_changed (data, session_id, shortcuts); +} + +GDBusInterfaceSkeleton * +global_shortcuts_create (GDBusConnection *connection, + const char *dbus_name) +{ + g_autoptr(GError) error = NULL; + + impl = xdp_dbus_impl_global_shortcuts_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + dbus_name, + "/org/freedesktop/portal/desktop", + NULL, &error); + if (impl == NULL) + { + g_warning ("Failed to create global_shortcuts proxy: %s", error->message); + return NULL; + } + + g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (impl), G_MAXINT); + global_shortcuts = g_object_new (global_shortcuts_get_type (), NULL); + + g_signal_connect (impl, "activated", G_CALLBACK (activated_cb), global_shortcuts); + g_signal_connect (impl, "deactivated", G_CALLBACK (deactivated_cb), global_shortcuts); + g_signal_connect (impl, "shortcuts-changed", G_CALLBACK (shortcutschanged_cb), global_shortcuts); + + return G_DBUS_INTERFACE_SKELETON (global_shortcuts); +} diff --git a/src/globalshortcuts.h b/src/globalshortcuts.h new file mode 100644 index 000000000..beab1a26e --- /dev/null +++ b/src/globalshortcuts.h @@ -0,0 +1,27 @@ +/* + * Copyright © 2022 Aleix Pol Gonzalez + * + * 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 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: + * Aleix Pol Gonzalez + */ + +#pragma once + +#include + +GDBusInterfaceSkeleton * +global_shortcuts_create (GDBusConnection *connection, + const char *dbus_name); diff --git a/src/meson.build b/src/meson.build index 24227fe4f..08587a32a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -49,6 +49,7 @@ xdg_desktop_portal_sources = files( 'flatpak-instance.c', 'gamemode.c', 'glib-backports.c', + 'globalshortcuts.c', 'inhibit.c', 'memory-monitor.c', 'network-monitor.c', diff --git a/src/request.c b/src/request.c index 5bd5016de..8a95d842d 100644 --- a/src/request.c +++ b/src/request.c @@ -343,6 +343,22 @@ get_token (GDBusMethodInvocation *invocation) { options = g_variant_get_child_value (parameters, 1); } + else if (strcmp (interface, "org.freedesktop.portal.GlobalShortcuts") == 0) + { + if (strcmp (method, "CreateSession") == 0 ) + { + options = g_variant_get_child_value (parameters, 0); + } + else if (strcmp (method, "BindShortcuts") == 0 ) + { + options = g_variant_get_child_value (parameters, 3); + } + else + { + g_warning ("Support for %s::%s missing in %s", + interface, method, G_STRLOC); + } + } else { g_print ("Support for %s missing in " G_STRLOC, interface); diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c index a5a3baa86..7cf99344b 100644 --- a/src/xdg-desktop-portal.c +++ b/src/xdg-desktop-portal.c @@ -40,6 +40,7 @@ #include "email.h" #include "file-chooser.h" #include "gamemode.h" +#include "globalshortcuts.h" #include "inhibit.h" #include "location.h" #include "memory-monitor.h" @@ -327,6 +328,11 @@ on_bus_acquired (GDBusConnection *connection, export_portal_implementation (connection, secret_create (connection, implementation->dbus_name)); + implementation = find_portal_implementation ("org.freedesktop.impl.portal.GlobalShortcuts"); + if (implementation != NULL) + export_portal_implementation (connection, + global_shortcuts_create (connection, implementation->dbus_name)); + #ifdef HAVE_GLIB_2_66 implementation = find_portal_implementation ("org.freedesktop.impl.portal.DynamicLauncher"); if (implementation != NULL)