libei: implement org.freedesktop.portal.EmulatedInput support

The current implementation of that portal has two methods: EmulateInput to
authenticate and Connect to get the fd to the EIS implementation. The portal
implementation is in charge of finding EIS and restricting it if need be.

This uses libsystemd because we can integrate that with epoll and our
libei_dispatch() method. GDBus requires a glib mainloop, so it's not really
suitable here. Given how simple this is anyway, it's easy to just do the DBus
bits in the caller and then hand the fd to ei_setup_backend_fd().

A eis-fake-portal is provided for testing, this "portal" can use the custom
portal bus name and connect the eis-demo-client to the eis-demo-server.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2020-08-11 20:54:01 +10:00
parent f6c2f24a22
commit 6b3d9255cf
14 changed files with 724 additions and 17 deletions

View file

@ -232,6 +232,14 @@ build-no-libxkcommon-nodeps@fedora:32:
before_script:
- dnf remove -y libxkcommon-devel
build-no-portal@fedora:32:
extends:
- .fedora-build@template
before_script:
- dnf remove -y libsystemd-devel
variables:
MESON_ARGS: "-Dportal=false"
valgrind@fedora:32:
extends:
- .fedora-build@template

View file

@ -253,6 +253,14 @@ build-no-libxkcommon-nodeps@{{distro.name}}:{{version}}:
before_script:
- dnf remove -y libxkcommon-devel
build-no-portal@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template
before_script:
- dnf remove -y libsystemd-devel
variables:
MESON_ARGS: "-Dportal=false"
valgrind@{{distro.name}}:{{version}}:
extends:
- .{{distro.name}}-build@template

View file

@ -157,13 +157,43 @@ Open questions
### Flatpak integration
Where flatpak portals are in use, `libei` will communicate with
the portal and `libeis` with the portal implementation (e.g.
`xdg-desktop-portal-gdk`). The portal is reponsible for
allowing the client to connect and restrictions on the devices a client may
create. `libeis` will run in a private namespace of the compositor.
Where flatpak portals are in use, `libei` can communicate with
the portal through a custom backend. The above diagram modified for
Flatpak would be:
The portal **may** control suspending/resuming devices (in addition to the
```
+--------------------+
| Wayland compositor |_
+--------------------+ \
| libinput | libeis | \_wayland______
+----------+---------+ \
| [eis-0.socket] \
/dev/input/ / \\ +-------+------------------+
| ======>| libei | Wayland client A |
| after +-------+------------------+
initial| handover /
connection| / initial request
| / dbus[org.freedesktop.portal.EmulatedInput]
+--------------------+
| xdg-desktop-portal |
+--------------------+
```
The current approach works so that
- the compositor starts an `libeis` socket backend at `$XDG_RUNTIME_DIR/eis-0`
- `xdg-desktop-portal` provides `org.freedesktop.portal.EmulatedInput`
- a client connects to the `xdg-desktop-portal` to request emulated input
- `xdg-desktop-portal` authenticates a client and opens the initial
connection to the `libeis` socket. It restricts the capabilities available
on that socket (e.g. sets the client name based on `app-id` using
`libreis`).
- `xdg-desktop-portal` hands over the file descriptor to the client which
can initialize a `libei` context
- from then on, `libei` and `libeis` talk directly to each other, the portal
has no further influence.
This describes the **current** implementation. Changes to this approach are
likely, e.g. the portal **may** control suspending/resuming devices (in addition to the
server). The UI for this is not yet sorted.
### Authentication

View file

@ -50,12 +50,30 @@ src_libei = [
'src/libei-fd.c',
'src/libei-proto.h',
'src/libei-proto.c',
'src/libei-stubs.c',
proto_headers,
]
if get_option('portal')
dep_systemd = dependency('libsystemd')
config_h.set10('ENABLE_LIBEI_PORTAL', 1)
src_libei += [
'src/libei-portal.c',
]
else
dep_systemd = dependency('', required: false)
endif
deps_libei = [
dep_libutil,
dep_protobuf,
dep_systemd,
]
lib_libei = shared_library('ei',
src_libei,
dependencies: [dep_libutil, dep_protobuf],
dependencies: deps_libei,
install: true
)
install_headers('src/libei.h')
@ -69,6 +87,7 @@ pkgconfig.generate(lib_libei,
description: 'Emulated Input client library',
version: meson.project_version(),
libraries: lib_libei,
variables: [ 'ei_portal=' + get_option('portal').to_string() ],
)
src_libeis = [
@ -136,6 +155,13 @@ executable('ei-demo-client',
'tools/ei-demo-client.c',
dependencies: [dep_libutil, dep_libei, dep_libxkbcommon])
if get_option('portal')
executable('eis-fake-portal',
'tools/eis-fake-portal.c',
include_directories: 'src',
dependencies: [dep_libutil, dep_systemd])
endif
# tests
subproject('munit')
@ -166,7 +192,7 @@ test('unit-tests-ei',
src_libei,
include_directories: 'src',
c_args: ['-D_enable_tests_'],
dependencies: [dep_unittest, dep_libutil, dep_protobuf]))
dependencies: deps_libei + [dep_unittest]))
test('unit-tests-eis',
executable('unit-tests-eis',

1
meson_options.txt Normal file
View file

@ -0,0 +1 @@
option('portal', type: 'boolean', value: 'true', description: 'Enable/disable org.freedesktop.portal support')

291
src/libei-portal.c Normal file
View file

@ -0,0 +1,291 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include "libei.h"
#include "libei-private.h"
#include "util-io.h"
#include "util-macros.h"
#include "util-mem.h"
#include "util-object.h"
#include "util-strings.h"
struct ei_portal {
struct object object;
struct source *bus_source;
sd_bus *bus;
sd_bus_slot *slot;
char *busname;
};
static void
ei_portal_destroy(struct ei_portal *portal)
{
free(portal->busname);
sd_bus_unref(portal->bus);
}
OBJECT_IMPLEMENT_CREATE(ei_portal);
static
OBJECT_IMPLEMENT_PARENT(ei_portal, ei);
static
OBJECT_IMPLEMENT_REF(ei_portal);
static
OBJECT_IMPLEMENT_UNREF(ei_portal);
#define _cleanup_ei_portal_ _cleanup_(ei_portal_cleanup)
static void
interface_portal_destroy(struct ei *ei, void *backend)
{
struct ei_portal *portal = backend;
ei_portal_unref(portal);
}
static const struct ei_backend_interface interface = {
.destroy = interface_portal_destroy,
};
static char *
xdp_token(sd_bus *bus, char **token_out)
{
_cleanup_free_ char *sender = NULL;
const char *name = NULL;
if (sd_bus_get_unique_name(bus, &name) != 0)
return NULL;
sender = xstrdup(name + 1); /* drop initial : */
for (unsigned i = 0; sender[i]; i++) {
if (sender[i] == '.')
sender[i] = '_';
}
char *token = xaprintf("ei_%d", rand());
*token_out = token;
return xaprintf("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
}
static void
portal_connect(struct ei_portal *portal)
{
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *response = NULL;
struct sd_bus *bus = portal->bus;
struct ei *ei = ei_portal_parent(portal);
int eisfd;
int rc = sd_bus_call_method(bus, portal->busname,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.EmulatedInput",
"Connect",
&error,
&response,
"a{sv}", 0);
if (rc < 0) {
log_error(ei, "Failed to call method: %s\n", strerror(-rc));
goto out;
}
rc = sd_bus_message_read(response, "h", &eisfd);
if (rc < 0) {
log_error(ei, "Failed to extract fd: %s\n", strerror(-rc));
goto out;
}
/* the fd is owned by the message */
rc = xerrno(dup(eisfd));
if (rc < 0) {
log_error(ei, "Failed to dup fd: %s\n", strerror(-rc));
goto out;
} else {
eisfd = rc;
int flags = fcntl(eisfd, F_GETFL, 0);
fcntl(eisfd, F_SETFL, flags | O_NONBLOCK);
}
log_debug(ei, "Initiating ei context with fd %d from portal\n", eisfd);
/* We're done with DBus, lets clean up */
source_remove(portal->bus_source);
source_unref(portal->bus_source);
portal->bus = sd_bus_unref(portal->bus);
rc = ei_set_connection(ei, eisfd);
out:
if (rc < 0) {
log_error(ei, "Failed to set the connection: %s\n", strerror(-rc));
ei_disconnect(ei);
}
}
static int
portal_response_received(sd_bus_message *m, void *userdata, sd_bus_error *error)
{
struct ei_portal *portal = userdata;
struct ei *ei = ei_portal_parent(portal);
unsigned response;
assert(m);
assert(portal);
/* We'll only get this signal once */
portal->slot = sd_bus_slot_unref(portal->slot);
int rc = sd_bus_message_read(m, "u", &response);
if (rc < 0) {
log_error(ei, "Failed to read response from signal: %s\n", strerror(-rc));
ei_disconnect(ei);
return 0;
}
log_debug(ei, "Portal EmulateInput reponse is %d\n", response);
if (response != 0) {
ei_disconnect(ei);
return 0;
}
portal_connect(portal);
return 0;
}
static void
dbus_dispatch(struct source *source, void *data)
{
struct ei_portal *portal = data;
/* We need to ref the bus here, portal_connect() may remove
* portal->bus but that needs to stay valid here until the end of
* the loop.
*/
_cleanup_(sd_bus_unrefp) struct sd_bus *bus = sd_bus_ref(portal->bus);
int rc;
do {
rc = sd_bus_process(bus, NULL);
} while (rc > 0);
if (rc != 0) {
log_error(ei_portal_parent(portal), "dbus processing failed with %s", strerror(-rc));
}
}
static int
portal_init(struct ei *ei, const char *busname)
{
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *response = NULL;
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
_cleanup_ei_portal_ struct ei_portal *portal = ei_portal_create(&ei->object);
const char *path = NULL;
int rc = sd_bus_open_user(&bus);
if (rc < 0 ) {
log_error(ei, "Failed to init dbus: %s\n", strerror(-rc));
return -ECONNREFUSED;
}
_cleanup_free_ char *token = NULL;
_cleanup_free_ char *handle = xdp_token(bus, &token);
rc = sd_bus_match_signal(bus, &slot,
busname,
handle,
"org.freedesktop.portal.Request",
"Response",
portal_response_received,
portal);
if (rc < 0) {
log_error(ei, "Failed to subscribe to signal: %s\n", strerror(-rc));
return -ECONNREFUSED;
}
rc = sd_bus_call_method(bus,
busname,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.EmulatedInput",
"EmulateInput",
&error,
&response,
"a{sv}", 1,
"handle_token", /* string key */
"s", token /* variant string */
);
if (rc < 0) {
log_error(ei, "Failed to call method: %s\n", strerror(-rc));
return -ECONNREFUSED;
}
rc = sd_bus_message_read(response, "o", &path);
if (rc < 0) {
log_error(ei, "Failed to parse response: %s\n", strerror(-rc));
return -ECONNREFUSED;
}
log_debug(ei, "portal Response object is %s\n", path);
struct source *s = source_new(sd_bus_get_fd(bus), dbus_dispatch, portal);
source_never_close_fd(s); /* the bus object handles the fd */
rc = sink_add_source(ei->sink, s);
if (rc == 0) {
portal->bus_source = source_ref(s);
portal->bus = sd_bus_ref(bus);
portal->slot = sd_bus_slot_ref(slot);
}
portal->busname = xstrdup(busname);
ei->backend = ei_portal_ref(portal);
ei->backend_interface = interface;
source_unref(s);
return 0;
}
_public_ int
ei_setup_backend_portal(struct ei *ei)
{
return portal_init(ei, "org.freedesktop.portal.Desktop");
}
_public_ int
ei_setup_backend_portal_busname(struct ei *ei, const char *busname)
{
return portal_init(ei, busname);
}

View file

@ -113,6 +113,9 @@ ei_init(struct ei *ei);
int
ei_set_connection(struct ei *ei, int fd);
void
ei_disconnect(struct ei *ei);
int
ei_add_device(struct ei_device *device);

38
src/libei-stubs.c Normal file
View file

@ -0,0 +1,38 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include "libei.h"
#include "util-macros.h"
#if !ENABLE_LIBEI_PORTAL
_public_ int
ei_setup_backend_portal(struct ei *ei)
{
return -ENOSYS;
}
#endif

View file

@ -66,9 +66,6 @@ OBJECT_IMPLEMENT_GETTER(ei_event, type, enum ei_event_type);
_public_
OBJECT_IMPLEMENT_GETTER(ei_event, device, struct ei_device*);
static void
ei_disconnect(struct ei *ei);
static void
ei_destroy(struct ei *ei)
{
@ -276,7 +273,7 @@ connection_send_key(struct ei *ei, struct ei_device *device,
return ei_proto_send_key(ei, device, k, is_press);
}
static void
void
ei_disconnect(struct ei *ei)
{
if (ei->state == EI_STATE_DISCONNECTED ||

View file

@ -238,6 +238,17 @@ ei_unref(struct ei *ei);
void
ei_set_user_data(struct ei *ei, void *user_data);
/**
* Connect to the org.freedesktop.portal.Desktop portal
*
* @return 0 on success or a negative errno on failure
*/
int
ei_setup_backend_portal(struct ei *ei);
int
ei_setup_backend_portal_busname(struct ei *ei, const char *busname);
/**
* Return the custom data pointer for this context. libei will not look at or
* modify the pointer. Use ei_set_user_data() to change the user data.

View file

@ -114,6 +114,12 @@ source_new(int sourcefd, source_dispatch_t dispatch, void *user_data)
}
void
source_never_close_fd(struct source *s)
{
s->close_behavior = SOURCE_CLOSE_FD_NEVER;
}
static void
sink_destroy(struct sink *sink)
{

View file

@ -82,6 +82,9 @@ source_set_user_data(struct source *source, void *user_data);
struct source *
source_new(int fd, source_dispatch_t dispatch, void *user_data);
void
source_never_close_fd(struct source *s);
struct sink *
sink_new(void);

View file

@ -135,7 +135,7 @@ static void
usage(FILE *fp, const char *argv0)
{
fprintf(fp,
"Usage: %s [--verbose] [--socket] [--layout=us]\n"
"Usage: %s [--verbose] [--socket|--portal] [--busname=a.b.c.d] [--layout=us]\n"
"\n"
"Start an EI demo client. The client will connect to EIS\n"
"with the chosen backend (default: socket) and emulate pointer\n"
@ -144,8 +144,11 @@ usage(FILE *fp, const char *argv0)
"Options:\n"
" --socket Use the socket backend. The socket path is $LIBEI_SOCKET if set, \n"
" otherwise XDG_RUNTIME/eis-0\n"
" --portal Use the portal backend.\n"
" --busname Use the given busname (default: org.freedesktop.portal.Desktop)\n"
" --layout Use the given XKB layout (requires libxkbcommon). Default: none\n"
" --verbose Enable debugging output\n",
" --verbose Enable debugging output\n"
"",
argv0);
}
@ -153,21 +156,28 @@ int main(int argc, char **argv)
{
enum {
SOCKET,
PORTAL,
} backend = SOCKET;
bool verbose = false;
const char *layout = NULL;
_cleanup_free_ char *busname = xstrdup("org.freedesktop.portal.Desktop");
while (1) {
enum {
OPT_BACKEND_SOCKET,
OPT_BACKEND_PORTAL,
OPT_BUSNAME,
OPT_LAYOUT,
OPT_VERBOSE,
};
static struct option long_opts[] = {
{"socket", no_argument, 0, OPT_BACKEND_SOCKET},
{"layout", required_argument, 0, OPT_LAYOUT},
{"verbose", no_argument, 0, OPT_VERBOSE},
{"help", no_argument, 0, 'h'},
{ "portal", no_argument, 0, OPT_BACKEND_PORTAL},
{ "busname", required_argument, 0, OPT_BUSNAME},
{ "layout", required_argument, 0, OPT_LAYOUT},
{ "verbose", no_argument, 0, OPT_VERBOSE},
{ "help", no_argument, 0, 'h'},
{NULL},
};
@ -186,6 +196,13 @@ int main(int argc, char **argv)
case OPT_BACKEND_SOCKET:
backend = SOCKET;
break;
case OPT_BACKEND_PORTAL:
backend = PORTAL;
break;
case OPT_BUSNAME:
free(busname);
busname = xstrdup(optarg);
break;
case OPT_LAYOUT:
layout = optarg;
break;
@ -209,6 +226,11 @@ int main(int argc, char **argv)
const char SOCKETNAME[] = "eis-0";
colorprint("connecting to %s\n", SOCKETNAME);
rc = ei_setup_backend_socket(ei, getenv("LIBEI_SOCKET") ? NULL : SOCKETNAME);
} else if (backend == PORTAL) {
#if ENABLE_LIBEI_PORTAL
colorprint("connecting to %s\n", busname);
rc = ei_setup_backend_portal_busname(ei, busname);
#endif
}
if (rc != 0) {

263
tools/eis-fake-portal.c Normal file
View file

@ -0,0 +1,263 @@
/*
* Copyright © 2020 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include "util-io.h"
#include "util-mem.h"
#include "util-logger.h"
#include "util-strings.h"
#include <systemd/sd-bus.h>
#define _cleanup_sd_bus_ _cleanup_(sd_bus_unrefp)
#define _cleanup_sd_bus_slot_ _cleanup_(sd_bus_slot_unrefp)
#define _cleanup_sd_event_ _cleanup_(sd_event_unrefp)
struct portal {
struct logger *logger;
char *busname;
} portal;
#define call(_call) do { \
int _rc = _call; \
if (_rc < 0) { \
log_error(portal, "Failed with %s %s:%d\n", strerror(-_rc), __func__, __LINE__); \
return _rc; \
} } while(0)
static int
request_close(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
/* We don't live long enough for this to be called */
return 0;
}
static const sd_bus_vtable request_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Close", "", "", request_close, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("Response", "ua{sv}", 0),
SD_BUS_VTABLE_END,
};
static int
create_request_object(struct portal *portal,
sd_bus *bus,
const char *objectpath)
{
_cleanup_sd_bus_slot_ sd_bus_slot *slot = NULL;
call(sd_bus_add_object_vtable(bus, &slot,
objectpath,
"org.freedesktop.portal.Request",
request_vtable,
NULL));
int response = 0;
log_debug(portal, "emitting Response %d on %s\n", response, objectpath);
return sd_bus_emit_signal(bus,
objectpath,
"org.freedesktop.portal.Request",
"Response",
"ua{sv}",
response,
0 /* Array size */
);
/* note: _cleanup_ removes object immediately */
}
static char *
sender_token(const char *input)
{
if (!input)
return NULL;
char *token = strstrip(input, ":");
for (size_t idx = 0; token[idx]; idx++) {
if (token[idx] == '.')
token[idx] = '_';
}
return token;
}
static int
portal_emulate_input(sd_bus_message *m, void *userdata,
sd_bus_error *ret_error)
{
struct portal *portal = userdata;
call(sd_bus_message_enter_container(m, 'a', "{sv}"));
const char *key, *handle_token;
call(sd_bus_message_read(m, "{sv}", &key, "s", &handle_token));
/* we only have a handle token in the dict so far */
if (!streq(key, "handle_token"))
return -EINVAL;
call(sd_bus_message_exit_container(m));
_cleanup_free_ char *sender = sender_token(sd_bus_message_get_sender(m));
if (!sender)
return -ENOMEM;
/* Send back the object path of the object we're about to create. We
* then create the object, so if that fails we have a problem but
* meh, this is for testing only .*/
_cleanup_free_ char *objpath = xaprintf("%s/request/%s/%s",
"/org/freedesktop/portal/desktop",
sender,
handle_token);
call(sd_bus_reply_method_return(m, "o", objpath));
/* now create the object */
return create_request_object(portal, sd_bus_message_get_bus(m), objpath);
}
static int
portal_connect(sd_bus_message *m, void *userdata,
sd_bus_error *ret_error)
{
struct portal *portal = userdata;
const char *xdg = getenv("XDG_RUNTIME_DIR");
if (!xdg)
return -ENOENT;
_cleanup_free_ char *sockpath = xaprintf("%s/eis-0", xdg);
int handle = xconnect(sockpath);
log_debug(portal, "passing Handle %d\n", handle);
return sd_bus_reply_method_return(m, "h", handle);
}
static const sd_bus_vtable portal_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("EmulateInput", "a{sv}", "o", portal_emulate_input, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Connect", "a{sv}", "h", portal_connect, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
static int
run(struct portal *portal)
{
_cleanup_sd_bus_ sd_bus *bus = NULL;
_cleanup_sd_bus_slot_ sd_bus_slot *slot = NULL;
int rc = sd_bus_open_user(&bus);
if (rc < 0)
return rc;
rc = sd_bus_add_object_vtable(bus, &slot,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.EmulatedInput",
portal_vtable,
portal);
if (rc < 0)
return rc;
log_debug(portal, "Portal object at: /org/freedesktop/portal/desktop\n");
rc = sd_bus_request_name(bus, portal->busname, 0);
if (rc < 0)
return rc;
log_debug(portal, "Portal DBus name: %s\n", portal->busname);
_cleanup_sd_event_ struct sd_event *event = NULL;
rc = sd_event_default(&event);
if (rc < 0)
return rc;
rc = sd_event_set_watchdog(event, true);
if (rc < 0)
return rc;
rc = sd_bus_attach_event(bus, event, 0);
if (rc < 0)
return rc;
return sd_event_loop(event);
}
static void
usage(FILE *fp, const char *argv0)
{
fprintf(fp,
"Usage: %s [--busname=a.b.c.d]\n"
"\n"
"Emulates an XDG Desktop portal for the org.freedesktop.portal.EmulatedInput interface\n"
"\n"
"Options:\n"
" --busname use the given busname instead of the default org.freedesktop.libei.Desktop\n"
"",
basename(argv0));
}
int
main(int argc, char **argv)
{
_cleanup_free_ char *busname = xstrdup("org.freedesktop.portal.Desktop");
while (1) {
enum opts {
OPT_BUSNAME,
};
static struct option long_opts[] = {
{ "busname", required_argument, 0, OPT_BUSNAME},
{ "help", no_argument, 0, 'h'},
{ NULL},
};
int optind = 0;
int c = getopt_long(argc, argv, "h", long_opts, &optind);
if (c == -1)
break;
switch(c) {
case 'h':
usage(stdout, argv[0]);
return EXIT_SUCCESS;
case OPT_BUSNAME:
free(busname);
busname = xstrdup(optarg);
break;
default:
usage(stderr, argv[0]);
return EXIT_FAILURE;
}
}
portal.busname = steal(&busname);
portal.logger = logger_new("portal", NULL);
logger_set_priority(portal.logger, LOGGER_DEBUG);
int rc = run(&portal);
if (rc < 0)
fprintf(stderr, "Failed to start fake portal: %s\n", strerror(-rc));
logger_unref(portal.logger);
free(portal.busname);
return rc == 0;
}