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 Jul 7, 2023
1 parent d1916e7 commit 37ea85a
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 3 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 @@ -350,6 +350,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
31 changes: 31 additions & 0 deletions data/org.freedesktop.portal.RemoteDesktop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,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 return an error.
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
106 changes: 105 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 @@ -88,6 +89,8 @@ typedef struct _RemoteDesktopSession
gboolean sources_selected;

gboolean clipboard_enabled;

gboolean uses_eis;
} RemoteDesktopSession;

typedef struct _RemoteDesktopSessionClass
Expand Down Expand Up @@ -695,7 +698,7 @@ check_notify (Session *session,
{
RemoteDesktopSession *remote_desktop_session = (RemoteDesktopSession *)session;

if (!remote_desktop_session->devices_selected)
if (!remote_desktop_session->devices_selected || remote_desktop_session->uses_eis)
return FALSE;

switch (remote_desktop_session->state)
Expand Down Expand Up @@ -1357,6 +1360,105 @@ 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:
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_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 @@ -1374,6 +1476,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 Down
2 changes: 1 addition & 1 deletion src/xdg-desktop-portal.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,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
56 changes: 56 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 @@ -29,6 +30,7 @@ def load(mock, parameters):
mock.force_clipoboard_enabled: bool = parameters.get(
"force-clipboard-enabled", False
)
mock.fail_connect_to_eis: bool = parameters.get("fail-connect-to-eis", False)
mock.AddProperties(
MAIN_IFACE,
dbus.Dictionary(
Expand All @@ -37,6 +39,7 @@ def load(mock, parameters):
}
),
)
mock.sessions: dict[str, ImplSession] = {}


@dbus.service.method(
Expand All @@ -50,6 +53,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 @@ -85,6 +89,33 @@ def force_close():
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})")

assert session_handle in self.sessions
response = Response(self.response, {})
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}",
Expand All @@ -99,6 +130,7 @@ def Start(
f"Start({handle}, {session_handle}, {parent_window}, {app_id}, {options})"
)

assert session_handle in self.sessions
response = Response(self.response, {})

if self.force_clipoboard_enabled:
Expand Down Expand Up @@ -127,3 +159,27 @@ def 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 37ea85a

Please sign in to comment.