feat: handle input capture session persistence

Use the new libportal API to start a CreateSession2, then set the
session persistence and read/write the tokens as required. This allows
us to start a server without a permission dialog - provided the portal
supports it and the user is ok with that.
This commit is contained in:
Peter Hutterer 2026-02-02 11:10:24 +10:00 committed by Nick Bolton
parent 7b614215a1
commit 1c789fd6a9
5 changed files with 122 additions and 12 deletions

View file

@ -145,6 +145,7 @@ This section contains options used when in server mode it will begin with `[serv
| externalConfig | `true` or `false` | When true use the external config path |
| externalConfigFile | Filepath | Path the server config file if it does not exist the GUI will it generated based on the `internalConfig` section.|
| protocol | `barrier` or `synergy` | The protocol to use when saying hello to clients. Can be set to barrier or synergy. If not set barrier is used as the default |
| xdpRestoreToken | UUID | Restore token provided by XDG portals |
### InternalConfig

View file

@ -106,6 +106,7 @@ public:
inline static const auto ExternalConfig = QStringLiteral("server/externalConfig");
inline static const auto ExternalConfigFile = QStringLiteral("server/externalConfigFile");
inline static const auto Protocol = QStringLiteral("server/protocol");
inline static const auto XdpRestoreToken = QStringLiteral("server/xdpRestoreToken");
};
// Enums types used in settings
@ -253,6 +254,7 @@ private:
, Settings::Server::ExternalConfig
, Settings::Server::ExternalConfigFile
, Settings::Server::Protocol
, Settings::Server::XdpRestoreToken
};
// When checking the default values this list contains the ones that default to false.

View file

@ -9,6 +9,15 @@ if(UNIX AND NOT APPLE)
pkg_check_modules(LIBPORTAL REQUIRED QUIET "libportal >= ${REQUIRED_LIBPORTAL_VERSION}")
message(STATUS "libportal version: ${LIBPORTAL_VERSION}")
# Check if we have the restore ability in the IC portal
include(CMakePushCheckState)
include(CheckSymbolExists)
cmake_push_check_state(RESET)
set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES};${LIBPORTAL_INCLUDE_DIRS};${GLIB2_INCLUDE_DIRS}")
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};${LIBPORTAL_LINK_LIBRARIES};${GLIB2_LINK_LIBRARIES}")
check_symbol_exists(xdp_input_capture_session_get_restore_token "libportal/inputcapture.h" HAVE_IC_RESTORE)
cmake_pop_check_state()
endif()
if(WIN32)
@ -178,6 +187,9 @@ if(UNIX)
target_compile_definitions(platform PRIVATE HAVE_XKB_KEYMAP_MOD_GET_MASK=1)
endif()
target_compile_definitions(platform PUBLIC WINAPI_LIBEI WINAPI_LIBPORTAL)
if (HAVE_IC_RESTORE)
target_compile_definitions(platform PUBLIC HAVE_LIBPORTAL_INPUTCAPTURE_RESTORE)
endif()
target_include_directories(platform PUBLIC ${LIBEI_INCLUDE_DIRS} ${LIBPORTAL_INCLUDE_DIRS})
target_link_libraries(
platform

View file

@ -12,6 +12,10 @@
#include "base/Log.h"
#include "base/TMethodJob.h"
#ifdef HAVE_LIBPORTAL_INPUTCAPTURE_RESTORE
#include "common/Settings.h"
#endif
#include <sys/socket.h> // for EIS fd hack, remove
#include <sys/un.h> // for EIS fd hack, remove
@ -20,6 +24,7 @@ namespace deskflow {
PortalInputCapture::PortalInputCapture(EiScreen *screen, IEventQueue *events)
: m_screen{screen},
m_events{events},
m_portalVersion(0),
m_portal{xdp_portal_new()}
{
m_glibMainLoop = g_main_loop_new(nullptr, true);
@ -79,21 +84,10 @@ void PortalInputCapture::handleSessionClosed(XdpSession *session)
m_signals.at(Signal::SessionClosed) = 0;
}
void PortalInputCapture::handleInitSession(GObject *object, GAsyncResult *res)
void PortalInputCapture::setupSession(XdpInputCaptureSession *session)
{
LOG_DEBUG("portal input capture session initialized");
g_autoptr(GError) error = nullptr;
auto session = xdp_portal_create_input_capture_session_finish(XDP_PORTAL(object), res, &error);
if (!session) {
LOG_ERR("failed to initialize input capture session, quitting: %s", error->message);
g_main_loop_quit(m_glibMainLoop);
m_events->addEvent(Event(EventTypes::Quit));
return;
}
m_session = session;
auto fd = xdp_input_capture_session_connect_to_eis(session, &error);
if (fd < 0) {
LOG_ERR("failed to connect to eis: %s", error->message);
@ -115,6 +109,46 @@ void PortalInputCapture::handleInitSession(GObject *object, GAsyncResult *res)
handleZonesChanged(m_session, nullptr);
}
void PortalInputCapture::handleInitSession(GObject *object, GAsyncResult *res)
{
g_autoptr(GError) error = nullptr;
LOG_DEBUG("portal input capture session initialized");
auto session = xdp_portal_create_input_capture_session_finish(XDP_PORTAL(object), res, &error);
if (!session) {
LOG_ERR("failed to initialize input capture session, quitting: %s", error->message);
g_main_loop_quit(m_glibMainLoop);
m_events->addEvent(Event(EventTypes::Quit));
return;
}
m_session = session;
setupSession(session);
}
void PortalInputCapture::handleStart(GObject *object, GAsyncResult *res)
{
g_autoptr(GError) error = nullptr;
#ifdef HAVE_LIBPORTAL_INPUTCAPTURE_RESTORE
LOG_DEBUG("portal input capture session initialized");
if (!xdp_input_capture_session_start_finish(m_session, res, &error)) {
LOG_ERR("failed to start input capture session, quitting: %s", error->message);
g_main_loop_quit(m_glibMainLoop);
m_events->addEvent(Event(EventTypes::Quit));
return;
}
auto restoreToken = xdp_input_capture_session_get_restore_token(m_session);
if (restoreToken) {
Settings::setValue(Settings::Server::XdpRestoreToken, QString(restoreToken));
}
#endif
setupSession(m_session);
}
void PortalInputCapture::handleSetPointerBarriers(const GObject *, GAsyncResult *res)
{
g_autoptr(GError) error = nullptr;
@ -151,6 +185,63 @@ void PortalInputCapture::handleSetPointerBarriers(const GObject *, GAsyncResult
gboolean PortalInputCapture::initSession()
{
LOG_DEBUG("setting up input capture session");
XdpInputCaptureSession *session;
#ifdef HAVE_LIBPORTAL_INPUTCAPTURE_RESTORE
g_autoptr(GError) error = nullptr;
m_portalVersion = xdp_portal_get_input_capture_version_sync(
m_portal,
nullptr, // Cancellable
nullptr
);
LOG_DEBUG("input capture version %d", m_portalVersion);
switch (m_portalVersion) {
case 0:
LOG_WARN("portal bug: input capture version is %d", m_portalVersion);
// fallthrough
case 1:
xdp_portal_create_input_capture_session(
m_portal,
nullptr, // parent
static_cast<XdpInputCapability>(XDP_INPUT_CAPABILITY_KEYBOARD | XDP_INPUT_CAPABILITY_POINTER),
nullptr, // cancellable
[](GObject *obj, GAsyncResult *res, gpointer data) {
static_cast<PortalInputCapture *>(data)->handleInitSession(obj, res);
},
this
);
break;
default:
session = xdp_portal_create_input_capture_session2_sync(
m_portal,
nullptr, // Cancellable
&error
);
if (!session) {
LOG_ERR("failed to initialize input capture session, quitting: %s", error->message);
g_main_loop_quit(m_glibMainLoop);
m_events->addEvent(Event(EventTypes::Quit));
return FALSE;
}
m_session = session;
xdp_input_capture_session_set_session_persistence(session, XDP_INPUT_CAPTURE_SESSION_PERSISTENCE_PERSISTENT);
if (auto sessionToken = Settings::value(Settings::Server::XdpRestoreToken).toByteArray(); !sessionToken.isEmpty()) {
xdp_input_capture_session_set_restore_token(session, strdup(sessionToken.data()));
}
xdp_input_capture_session_start(
m_session,
nullptr, // parent
static_cast<XdpInputCapability>(XDP_INPUT_CAPABILITY_KEYBOARD | XDP_INPUT_CAPABILITY_POINTER),
nullptr, // cancellable
[](GObject *obj, GAsyncResult *res, gpointer data) {
static_cast<PortalInputCapture *>(data)->handleStart(obj, res);
},
this
);
break;
}
#else
xdp_portal_create_input_capture_session(
m_portal,
nullptr, // parent
@ -161,6 +252,7 @@ gboolean PortalInputCapture::initSession()
},
this
);
#endif
return false;
}

View file

@ -35,6 +35,8 @@ private:
void glibThread(const void *);
gboolean timeoutHandler() const;
gboolean initSession();
void setupSession(XdpInputCaptureSession *session);
void handleStart(GObject *object, GAsyncResult *res);
void handleInitSession(GObject *object, GAsyncResult *res);
void handleSetPointerBarriers(const GObject *object, GAsyncResult *res);
void handleSessionClosed(XdpSession *session);
@ -83,6 +85,7 @@ private:
EiScreen *m_screen = nullptr;
IEventQueue *m_events = nullptr;
int m_portalVersion = 0;
Thread *m_glibThread;
GMainLoop *m_glibMainLoop = nullptr;