tools: update the portal for the latest version

The latest version of the portal communication adds session capabilities
and wraps the calls a bit differently.

This now  also includes a helper tool for the impl.portal part so we can
run xdg-desktop-portal against that without the need for a mutter
implementation.
Use:
- run the eis-demo-server
- run the eis-fake-impl-portal
- run the xdg-desktop-portal (it'll use the fake impl portal)
- run an ei client with the portal enabled

Note that the ei-fake-portal is a shortcut, it will open eis directly
without going through the impl.portal.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2021-08-30 17:20:34 +10:00
parent 43be3ddc89
commit df5237a7ea
4 changed files with 563 additions and 25 deletions

View file

@ -186,6 +186,10 @@ if get_option('portal')
'tools/eis-fake-portal.c',
include_directories: 'src',
dependencies: [dep_libutil, dep_systemd, dep_libreis])
executable('eis-fake-impl-portal',
'tools/eis-fake-impl-portal.c',
include_directories: 'src',
dependencies: [dep_libutil, dep_systemd, dep_libreis])
endif
# tests

View file

@ -96,7 +96,7 @@ xdp_token(sd_bus *bus, char **token_out)
}
static void
portal_connect(struct ei_portal *portal)
portal_connect(struct ei_portal *portal, const char *session_handle)
{
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_unref_(sd_bus_message) *response = NULL;
@ -107,19 +107,40 @@ portal_connect(struct ei_portal *portal)
int rc = sd_bus_call_method(bus, portal->busname,
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.EmulatedInput",
"Connect",
"ConnectToEIS",
&error,
&response,
"a{sv}", 0);
"oa{sv}",
session_handle,
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);
int status;
rc = sd_bus_message_read(response, "u", &status);
if (rc < 0) {
log_error(ei, "Failed to extract fd: %s\n", strerror(-rc));
log_error(ei, "Failed to extract status, invalid message format: %s\n", strerror(-rc));
goto out;
}
if (status != 0) {
log_info(ei, "Unable to get fd from portal\n");
ei_disconnect(ei);
return;
}
const char *key;
rc = sd_bus_message_read(response, "a{sv}", 1, &key, "h", &eisfd);
if (rc < 0) {
log_error(ei, "Failed to extract fd, invalid message format: %s\n", strerror(-rc));
goto out;
}
if (!streq(key, "fd")) {
log_error(ei, "Invalid key '%s', expected 'fd'\n", key);
goto out;
}
@ -162,25 +183,54 @@ portal_response_received(sd_bus_message *m, void *userdata, sd_bus_error *error)
/* We'll only get this signal once */
portal->slot = sd_bus_slot_unref(portal->slot);
int rc = sd_bus_message_read(m, "u", &response);
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);
log_debug(ei, "Portal CreateSession reponse is %d\n", response);
const char *session_handle = NULL;
if (response == 0) {
const char *key;
rc = sd_bus_message_read(m, "a{sv}", 1, &key, "s", &session_handle);
if (rc < 0) {
log_error(ei, "Failed to read session handle from signal: %s\n", strerror(-rc));
ei_disconnect(ei);
return 0;
}
if (!streq(key, "session_handle")) {
log_error(ei, "Invalid or unhandled option: %ss\n", key);
ei_disconnect(ei);
return 0;
}
}
/* FIXME: we need to subscribe to the sessions Close signal */
if (response != 0) {
ei_disconnect(ei);
return 0;
}
portal_connect(portal);
portal_connect(portal, session_handle);
return 0;
}
static int
session_closed_received(sd_bus_message *m, void *userdata, sd_bus_error *error)
{
struct ei_portal *portal = userdata;
struct ei *ei = ei_portal_parent(portal);
log_error(ei, "Session closed received");
return 0;
}
static void
dbus_dispatch(struct source *source, void *data)
{
@ -221,6 +271,8 @@ portal_init(struct ei *ei, const char *busname)
_cleanup_free_ char *token = NULL;
_cleanup_free_ char *handle = xdp_token(bus, &token);
_cleanup_free_ char *session_token = xdp_token(bus, &session_token);
_cleanup_free_ char *session_handle = xdp_token(bus, &session_token);
rc = sd_bus_match_signal(bus, &slot,
busname,
@ -234,16 +286,30 @@ portal_init(struct ei *ei, const char *busname)
return -ECONNREFUSED;
}
rc = sd_bus_match_signal(bus, &slot,
busname,
session_handle,
"org.freedesktop.portal.Session",
"Closed",
session_closed_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",
"CreateSession",
&error,
&response,
"a{sv}", 1,
"a{sv}", 2,
"handle_token", /* string key */
"s", token /* variant string */
"s", token, /* variant string */
"session_handle_token", /* string key */
"s", session_token /* variant string */
);
if (rc < 0) {

View file

@ -0,0 +1,371 @@
/*
* Copyright © 2021 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 <getopt.h>
#include <stdbool.h>
#include <unistd.h>
#include "util-io.h"
#include "util-list.h"
#include "util-mem.h"
#include "util-logger.h"
#include "util-strings.h"
#include "libreis.h"
#include <systemd/sd-bus.h>
DEFINE_UNREF_CLEANUP_FUNC(reis);
struct session {
unsigned int version;
struct list link;
struct sd_bus_slot *slot;
char *handle;
};
struct portal {
unsigned int version;
struct logger *logger;
char *busname;
struct list sessions;
} 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
session_close(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
sd_bus_reply_method_return(m, "");
/* Not implemented */
return 0;
}
static const sd_bus_vtable session_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Close", "", "", session_close, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("version", "u", NULL, offsetof(struct session, version), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END,
};
static int
create_session_object(struct portal *portal,
sd_bus *bus,
const char *objectpath)
{
struct session *session = calloc(sizeof *session, 1);
session->handle = xstrdup(objectpath);
call(sd_bus_add_object_vtable(bus, &session->slot,
objectpath,
"org.freedesktop.impl.portal.Session",
session_vtable,
session));
list_append(&portal->sessions, &session->link);
log_debug(portal, "Session created on %s\n", objectpath);
return 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_VTABLE_END,
};
static int
create_request_object(struct portal *portal,
sd_bus *bus,
const char *objectpath,
const char *session_path)
{
_unref_(sd_bus_slot) *slot = NULL;
create_session_object(portal, bus, session_path);
call(sd_bus_add_object_vtable(bus, &slot,
objectpath,
"org.freedesktop.impl.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.impl.portal.Request",
"Response",
"ua{sv}",
response,
1,
"session_handle",/* string key */
"s", session_path /* variant string */
);
/* note: _unref_ removes object immediately */
}
static int
portal_create_session(sd_bus_message *m, void *userdata,
sd_bus_error *ret_error)
{
struct portal *portal = userdata;
static int session_id = 1234;
const char *handle;
const char *session_handle;
const char *app_id;
sd_bus_message_read(m, "oosa{sv}",
&handle,
&session_handle,
&app_id, 0);
log_debug(portal, "CreateSession: app-id %s, session %s, handle %s\n", app_id, session_handle, handle);
_cleanup_free_ char *sid = xaprintf("%d", session_id++);
call(sd_bus_reply_method_return(m, "ua{sv}", 0, 1, "session_id", "s", sid));
return create_request_object(portal, sd_bus_message_get_bus(m), handle, session_handle);
}
static void
set_prop_cmdline(struct reis *reis)
{
char path[PATH_MAX];
xsnprintf(path, sizeof(path), "/proc/%u/cmdline", getpid());
_cleanup_close_ int fd = open(path, O_RDONLY);
if (fd < 0)
return;
int len;
if ((len = read(fd, path, sizeof(path) - 1)) < 0)
return;
path[len] = '\0';
reis_set_property_with_permissions(reis, "ei.application.cmdline", path, REIS_PROPERTY_PERM_NONE);
}
static void
set_prop_pid(struct reis *reis)
{
char pid[64];
xsnprintf(pid, sizeof(pid), "%u", getpid());
reis_set_property_with_permissions(reis, "ei.application.pid", pid, REIS_PROPERTY_PERM_NONE);
}
static void
set_prop_type(struct reis *reis)
{
reis_set_property_with_permissions(reis, "ei.connection.type", "portal", REIS_PROPERTY_PERM_NONE);
}
static int
portal_connect_to_eis(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
struct portal *portal = userdata;
log_debug(portal, "Got a connect request\n");
const char *xdg = getenv("XDG_RUNTIME_DIR");
if (!xdg)
return -ENOENT;
const char *session_handle;
const char *app_id;
call(sd_bus_message_read(m, "os", &session_handle, &app_id));
log_debug(portal, "Connect request session %s appid %s\n", session_handle, app_id);
struct session *session;
bool found = false;
list_for_each_safe(session, &portal->sessions, link) {
if (streq(session->handle, session_handle)) {
found = true;
break;
}
}
/* We're aborting here because it's a client bug and this is just a
* fake portal */
if (!found) {
log_error(portal, "Invalid session handle %s\n", session_handle);
abort();
}
log_error(portal, "Valid session %s, connecting to EIS\n", session_handle);
_cleanup_free_ char *sockpath = xaprintf("%s/eis-0", xdg);
int fd = xconnect(sockpath);
if (fd < 0) {
log_error(portal, "Failed to connect to EIS (%s)\n", strerror(-fd));
return sd_bus_reply_method_return(m, "ua{sv}", 1, 0);
}
_unref_(reis) *reis = reis_new(fd);
assert(reis);
set_prop_pid(reis);
set_prop_cmdline(reis);
set_prop_type(reis);
log_debug(portal, "passing fd %d\n", fd);
return sd_bus_reply_method_return(m, "ua{sv}", 0,
1, /* array length */
"fd", "h", fd /* name: "fd", type handle, value fd */);
}
static const sd_bus_vtable portal_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("CreateSession", "oosa{sv}", "ua{sv}", portal_create_session, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ConnectToEIS", "osa{sv}", "ua{sv}", portal_connect_to_eis, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("version", "u", NULL, offsetof(struct portal, version), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END,
};
static int
run(struct portal *portal)
{
_unref_(sd_bus) *bus = NULL;
_unref_(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.impl.portal.EmulatedInput",
portal_vtable,
portal);
if (rc < 0)
return rc;
sd_bus_add_object_manager(bus, NULL, "/org/freedesktop/portal/desktop");
log_debug(portal, "Portal object at: /org/freedesktop/ei/EmulatedInput\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);
_unref_(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.impl.portal.EmulatedInput interface\n"
"\n"
"Options:\n"
" --busname use the given busname instead of the default org.freedesktop.impl.portal.eis.EmulatedInput\n"
"",
basename(argv0));
}
int
main(int argc, char **argv)
{
_cleanup_free_ char *busname = xstrdup("org.freedesktop.impl.portal.eis.EmulatedInput");
while (1) {
enum opts {
OPT_BUSNAME,
};
static struct option long_opts[] = {
{ "busname", required_argument, 0, OPT_BUSNAME},
{ "help", no_argument, 0, 'h'},
{ .name = 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;
}
}
list_init(&portal.sessions);
portal.version = 1;
portal.busname = steal(&busname);
portal.logger = logger_new("impl.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;
}

View file

@ -30,6 +30,7 @@
#include <unistd.h>
#include "util-io.h"
#include "util-list.h"
#include "util-mem.h"
#include "util-logger.h"
#include "util-strings.h"
@ -40,9 +41,17 @@
DEFINE_UNREF_CLEANUP_FUNC(reis);
struct session {
struct list link;
struct sd_bus_slot *slot;
char *handle;
};
struct portal {
struct logger *logger;
char *busname;
struct list sessions;
} portal;
@ -53,6 +62,41 @@ struct portal {
return _rc; \
} } while(0)
static int
session_close(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
/* Not implemented */
return 0;
}
static const sd_bus_vtable session_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Close", "", "", session_close, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("Closed", "ua{sv}", 0),
SD_BUS_VTABLE_END,
};
static int
create_session_object(struct portal *portal,
sd_bus *bus,
const char *objectpath)
{
struct session *session = calloc(sizeof *session, 1);
session->handle = xstrdup(objectpath);
call(sd_bus_add_object_vtable(bus, &session->slot,
objectpath,
"org.freedesktop.portal.Session",
session_vtable,
NULL));
list_append(&portal->sessions, &session->link);
log_debug(portal, "Session created on %s\n", objectpath);
return 0;
}
static int
request_close(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
@ -70,9 +114,13 @@ static const sd_bus_vtable request_vtable[] = {
static int
create_request_object(struct portal *portal,
sd_bus *bus,
const char *objectpath)
const char *objectpath,
const char *session_path)
{
_unref_(sd_bus_slot) *slot = NULL;
create_session_object(portal, bus, session_path);
call(sd_bus_add_object_vtable(bus, &slot,
objectpath,
"org.freedesktop.portal.Request",
@ -87,7 +135,9 @@ create_request_object(struct portal *portal,
"Response",
"ua{sv}",
response,
0 /* Array size */
1,
"session_handle",/* string key */
"s", session_path /* variant string */
);
/* note: _unref_ removes object immediately */
}
@ -108,18 +158,36 @@ sender_token(const char *input)
}
static int
portal_emulate_input(sd_bus_message *m, void *userdata,
sd_bus_error *ret_error)
portal_create_session(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"))
const char *session_token = NULL;
const char *handle_token = NULL;
const char *key, *val;
call(sd_bus_message_read(m, "{sv}", &key, "s", &val));
if (streq(key, "handle_token"))
handle_token = val;
else if (streq(key, "session_handle_token"))
session_token = val;
else
return -EINVAL;
call(sd_bus_message_read(m, "{sv}", &key, "s", &val));
if (streq(key, "handle_token"))
handle_token = val;
else if (streq(key, "session_handle_token"))
session_token = val;
else
return -EINVAL;
assert(handle_token);
assert(session_token);
call(sd_bus_message_exit_container(m));
_cleanup_free_ char *sender = sender_token(sd_bus_message_get_sender(m));
@ -135,8 +203,13 @@ portal_emulate_input(sd_bus_message *m, void *userdata,
handle_token);
call(sd_bus_reply_method_return(m, "o", objpath));
_cleanup_free_ char *session_path = xaprintf("%s/session/%s/%s",
"/org/freedesktop/portal/desktop",
sender,
session_token);
/* now create the object */
return create_request_object(portal, sd_bus_message_get_bus(m), objpath);
return create_request_object(portal, sd_bus_message_get_bus(m), objpath, session_path);
}
static void
@ -173,8 +246,7 @@ set_prop_type(struct reis *reis)
}
static int
portal_connect(sd_bus_message *m, void *userdata,
sd_bus_error *ret_error)
portal_connect_to_eis(sd_bus_message *m, void *userdata, sd_bus_error *ret_error)
{
struct portal *portal = userdata;
@ -182,6 +254,28 @@ portal_connect(sd_bus_message *m, void *userdata,
if (!xdg)
return -ENOENT;
const char *session_handle;
call(sd_bus_message_read(m, "oa{sv}", &session_handle, 0));
struct session *session;
bool found = false;
list_for_each_safe(session, &portal->sessions, link) {
if (streq(session->handle, session_handle)) {
found = true;
break;
}
}
/* We're aborting here because it's a client bug and this is just a
* fake portal */
if (!found) {
log_error(portal, "Invalid session handle %s\n", session_handle);
abort();
}
log_info(portal, "Valid session %s, connecting to EIS\n", session_handle);
_cleanup_free_ char *sockpath = xaprintf("%s/eis-0", xdg);
int handle = xconnect(sockpath);
if (handle < 0) {
@ -196,13 +290,15 @@ portal_connect(sd_bus_message *m, void *userdata,
set_prop_type(reis);
log_debug(portal, "passing Handle %d\n", handle);
return sd_bus_reply_method_return(m, "h", handle);
return sd_bus_reply_method_return(m, "ua{sv}", 0,
1, /* array size */
"fd", "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_METHOD("CreateSession", "a{sv}", "o", portal_create_session, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ConnectToEIS", "oa{sv}", "ua{sv}", portal_connect_to_eis, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END,
};
@ -295,6 +391,7 @@ main(int argc, char **argv)
}
}
list_init(&portal.sessions);
portal.busname = steal(&busname);
portal.logger = logger_new("portal", NULL);
logger_set_priority(portal.logger, LOGGER_DEBUG);