Skip to content

Commit

Permalink
remote-desktop: add the ability to communicate via an EIS socket
Browse files Browse the repository at this point in the history
This is intended as replacement for the NotifyFoo methods. libei
provides a more flexible and powerful method of sending input events to
the compositor.

A new method ConnectToEIS requests a file descriptor from the compositor
which can then be plugged into libei.

Once established, the communication between compositor and application
is direct, without the need to go through the portal process(es).

To avoid ambiguities between NotifyFoo and input events sent via libei,
any application that uses an EIS connection may not use the NotifyFoo
methods.

Co-authored-by: Olivier Fourdan <ofourdan@redhat.com>
  • Loading branch information
whot and ofourdan committed Jun 8, 2023
1 parent b4f298b commit 4627418
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 4 deletions.
20 changes: 20 additions & 0 deletions data/org.freedesktop.impl.portal.RemoteDesktop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,26 @@
<arg type="a{sv}" name="options" direction="in"/>
<arg type="u" name="slot" direction="in"/>
</method>
<!--
ConnectToEIS:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@app_id: App id of the application
@options: Vardict with optional further information
@fd: A file descriptor to an EIS implementation that can be passed to a
sender libei context
Request a connection to an EIS implementation.
This method is available in version 2 of this interface.
-->
<method name="ConnectToEIS">
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="o" name="session_handle" direction="in"/>
<arg type="s" name="app_id" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="h" name="fd" direction="out"/>
</method>
<!--
AvailableDeviceTypes:
Expand Down
33 changes: 32 additions & 1 deletion data/org.freedesktop.portal.RemoteDesktop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
The Remote desktop portal allows to create remote desktop sessions.
This documentation describes version 1 of this interface.
This documentation describes version 2 of this interface.
-->
<interface name="org.freedesktop.portal.RemoteDesktop">
<!--
Expand Down Expand Up @@ -385,6 +385,37 @@
<arg type="a{sv}" name="options" direction="in"/>
<arg type="u" name="slot" direction="in"/>
</method>

<!--
ConnectToEIS:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@fd: A file descriptor to an EIS implementation that can be passed to a libei sender context
Request a connection to an EIS implementation. The returned handle can
be passed to ei_setup_backend_fd() for a libei sender context to
complete the connection. All information about available devices and the
event flow is subject to libei. Please see the libei documentation for details.
This method may only be called once per session, where the EIS
implementation disconnects the session should be closed.
This method must be called after #org.freedesktop.portal.RemoteDesktop.Start()
Once an EIS connection is established, input events must be sent exclusively via
the EIS connection. Any events submitted via NotifyPointerMotion,
NotifyKeyboardKeycode and other Notify* methods will be silently discarded.
This method was added in version 2 of this interface.
-->
<method name="ConnectToEIS">
<annotation name="org.gtk.GDBus.C.Name" value="connect_to_eis"/>
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="h" name="fd" direction="out"/>
</method>

<!--
AvailableDeviceTypes:
Expand Down
118 changes: 117 additions & 1 deletion src/remote-desktop.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "xdp-impl-dbus.h"
#include "xdp-utils.h"

#include <gio/gunixfdlist.h>
#include <stdint.h>

typedef struct _RemoteDesktop RemoteDesktop;
Expand Down Expand Up @@ -85,6 +86,7 @@ typedef struct _RemoteDesktopSession
DeviceType shared_devices;

GList *streams;
gboolean uses_eis;
} RemoteDesktopSession;

typedef struct _RemoteDesktopSessionClass
Expand Down Expand Up @@ -686,6 +688,9 @@ check_notify (Session *session,
{
RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session;

if (remote_desktop_session->uses_eis)
return FALSE;

switch (remote_desktop_session->state)
{
case REMOTE_DESKTOP_SESSION_STATE_STARTED:
Expand Down Expand Up @@ -1350,6 +1355,115 @@ handle_notify_touch_up (XdpDbusRemoteDesktop *object,
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static XdpOptionKey remote_desktop_connect_to_eis_options[] = {
};

static gboolean
handle_connect_to_eis (XdpDbusRemoteDesktop *object,
GDBusMethodInvocation *invocation,
GUnixFDList *in_fd_list,
const char *arg_session_handle,
GVariant *arg_options)
{
Call *call = call_from_invocation (invocation);
Session *session;
RemoteDesktopSession *remote_desktop_session;
g_autoptr(GUnixFDList) out_fd_list = NULL;
g_autoptr(GError) error = NULL;
GVariantBuilder options_builder;
GVariant *fd;

session = acquire_session_from_call (arg_session_handle, call);
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);

if (!is_remote_desktop_session (session))
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_ACCESS_DENIED,
"Invalid session");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

remote_desktop_session = (RemoteDesktopSession *)session;

if (remote_desktop_session->uses_eis)
{
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session is already connected");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

switch (remote_desktop_session->state)
{
case REMOTE_DESKTOP_SESSION_STATE_STARTED:
break;
case REMOTE_DESKTOP_SESSION_STATE_INIT:
case REMOTE_DESKTOP_SESSION_STATE_DEVICES_SELECTED:
case REMOTE_DESKTOP_SESSION_STATE_SELECTING_DEVICES:
case REMOTE_DESKTOP_SESSION_STATE_SOURCES_SELECTED:
case REMOTE_DESKTOP_SESSION_STATE_SELECTING_SOURCES:
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session is not ready");
return G_DBUS_METHOD_INVOCATION_HANDLED;
case REMOTE_DESKTOP_SESSION_STATE_STARTING:
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session is already started");
return G_DBUS_METHOD_INVOCATION_HANDLED;
case REMOTE_DESKTOP_SESSION_STATE_CLOSED:
g_dbus_method_invocation_return_error (invocation,
G_DBUS_ERROR,
G_DBUS_ERROR_FAILED,
"Session is already closed");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
if (!xdp_filter_options (arg_options, &options_builder,
remote_desktop_connect_to_eis_options,
G_N_ELEMENTS (remote_desktop_connect_to_eis_options),
&error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

if (!xdp_dbus_impl_remote_desktop_call_connect_to_eis_sync (impl,
arg_session_handle,
xdp_app_info_get_id (call->app_info),
g_variant_builder_end (&options_builder),
in_fd_list,
&fd,
&out_fd_list,
NULL,
&error))
{
g_warning ("Failed to ConnectToEIS: %s", error->message);
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

remote_desktop_session->uses_eis = TRUE;

xdp_dbus_remote_desktop_complete_connect_to_eis (object, invocation, out_fd_list, fd);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static void
remote_desktop_iface_init (XdpDbusRemoteDesktopIface *iface)
{
Expand All @@ -1367,6 +1481,8 @@ remote_desktop_iface_init (XdpDbusRemoteDesktopIface *iface)
iface->handle_notify_touch_down = handle_notify_touch_down;
iface->handle_notify_touch_motion = handle_notify_touch_motion;
iface->handle_notify_touch_up = handle_notify_touch_up;

iface->handle_connect_to_eis = handle_connect_to_eis;
}

static void
Expand All @@ -1391,7 +1507,7 @@ on_supported_device_types_changed (GObject *gobject,
static void
remote_desktop_init (RemoteDesktop *remote_desktop)
{
xdp_dbus_remote_desktop_set_version (XDP_DBUS_REMOTE_DESKTOP (remote_desktop), 1);
xdp_dbus_remote_desktop_set_version (XDP_DBUS_REMOTE_DESKTOP (remote_desktop), 2);

g_signal_connect (impl, "notify::supported-device-types",
G_CALLBACK (on_supported_device_types_changed),
Expand Down
2 changes: 1 addition & 1 deletion src/xdg-desktop-portal.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ method_needs_request (GDBusMethodInvocation *invocation)
}
else if (strcmp (interface, "org.freedesktop.portal.RemoteDesktop") == 0)
{
if (strstr (method, "Notify") == method)
if (strstr (method, "Notify") == method || strcmp (method, "ConnectToEIS") == 0)
return FALSE;
else
return TRUE;
Expand Down
84 changes: 84 additions & 0 deletions tests/templates/remotedesktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from tests.templates import Response, init_template_logger, ImplRequest, ImplSession
import dbus
import dbus.service
import socket

from gi.repository import GLib

Expand All @@ -26,6 +27,7 @@ def load(mock, parameters):
mock.response: int = parameters.get("response", 0)
mock.expect_close: bool = parameters.get("expect-close", False)
mock.force_close: int = parameters.get("force-close", 0)
mock.fail_connect_to_eis: bool = parameters.get("fail-connect-to-eis", False)
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
Expand All @@ -34,6 +36,7 @@ def load(mock, parameters):
}
),
)
mock.sessions: dict[str, ImplSession] = {}


@dbus.service.method(
Expand All @@ -47,6 +50,7 @@ def CreateSession(self, handle, session_handle, app_id, options, cb_success, cb_
logger.debug(f"CreateSession({handle}, {session_handle}, {app_id}, {options})")

session = ImplSession(self, BUS_NAME, session_handle).export()
self.sessions[session_handle] = session

response = Response(self.response, {"session_handle": session.handle})

Expand Down Expand Up @@ -80,3 +84,83 @@ def force_close():
except Exception as e:
logger.critical(e)
cb_error(e)


@dbus.service.method(
MAIN_IFACE,
in_signature="oosa{sv}",
out_signature="ua{sv}",
async_callbacks=("cb_success", "cb_error"),
)
def SelectDevices(self, handle, session_handle, app_id, options, cb_success, cb_error):
try:
logger.debug(f"SelectDevices({handle}, {session_handle}, {app_id}, {options})")

session = self.sessions[session_handle]
response = Response(self.response, {"session_handle": session.handle})
request = ImplRequest(self, BUS_NAME, handle)
request.export()

def reply():
logger.debug(f"SelectDevices with response {response}")
cb_success(response.response, response.results)

logger.debug(f"scheduling delay of {self.delay}")
GLib.timeout_add(self.delay, reply)

except Exception as e:
logger.critical(e)
cb_error(e)


@dbus.service.method(
MAIN_IFACE,
in_signature="oossa{sv}",
out_signature="ua{sv}",
async_callbacks=("cb_success", "cb_error"),
)
def Start(
self, handle, session_handle, app_id, parent_window, options, cb_success, cb_error
):
try:
logger.debug(f"Start({handle}, {session_handle}, {app_id}, {options})")

session = self.sessions[session_handle]
response = Response(self.response, {"session_handle": session.handle})
request = ImplRequest(self, BUS_NAME, handle)
request.export()

def reply():
logger.debug(f"Start with response {response}")
cb_success(response.response, response.results)

logger.debug(f"scheduling delay of {self.delay}")
GLib.timeout_add(self.delay, reply)

except Exception as e:
logger.critical(e)
cb_error(e)


@dbus.service.method(
MAIN_IFACE,
in_signature="osa{sv}",
out_signature="h",
)
def ConnectToEIS(self, session_handle, app_id, options):
try:
logger.debug(f"ConnectToEIS({session_handle}, {app_id}, {options})")

assert session_handle in self.sessions

if self.fail_connect_to_eis:
raise dbus.exceptions.DBusException(f"Purposely failing ConnectToEIS")

sockets = socket.socketpair()
self.eis_socket = sockets[0]
assert self.eis_socket.send(b"HELLO") == 5

return dbus.types.UnixFd(sockets[1])
except Exception as e:
logger.critical(e)
raise e
Loading

0 comments on commit 4627418

Please sign in to comment.