From c05707884c17995bf22c5756075d0ad7c1046794 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 20 May 2019 08:10:30 +0200 Subject: [PATCH] Implement Wayland support using PipeWire and xdg-desktop-portal Summary: Adds a new framebuffer implementation, which uses xdg-desktop-portal to support remote desktop on Wayland and uses PipeWire to deliver the screen content. So far only mouse support is implemented, because keyboard support is missing on KWin side. Reviewers: Kanedias, romangg Reviewed By: Kanedias Subscribers: asturmlechner, pino, ngraham, romangg Differential Revision: https://phabricator.kde.org/D20402 --- CMakeLists.txt | 10 + cmake/modules/FindPipeWire.cmake | 109 +++ dbus/xdp_dbus_remotedesktop_interface.xml | 374 ++++++++ dbus/xdp_dbus_screencast_interface.xml | 186 ++++ events/CMakeLists.txt | 6 + events/x11/CMakeLists.txt | 18 + events/x11/krfb_events_x11.desktop | 13 + events/x11/krfb_events_x11.json | 15 + events/x11/x11events.cpp | 212 +++++ events/x11/x11events.h | 41 + events/x11/x11eventsplugin.cpp | 45 + events/x11/x11eventsplugin.h | 43 + events/xdp/CMakeLists.txt | 27 + events/xdp/krfb_events_xdp.desktop | 14 + events/xdp/krfb_events_xdp.json | 16 + events/xdp/xdpevents.cpp | 128 +++ events/xdp/xdpevents.h | 37 + events/xdp/xdpeventsplugin.cpp | 43 + events/xdp/xdpeventsplugin.h | 46 + framebuffers/CMakeLists.txt | 6 +- framebuffers/pipewire/CMakeLists.txt | 38 + .../pipewire/krfb_framebuffer_pw.json | 15 + framebuffers/pipewire/pw_framebuffer.cpp | 800 ++++++++++++++++++ framebuffers/pipewire/pw_framebuffer.h | 59 ++ .../pipewire/pw_framebufferplugin.cpp | 53 ++ framebuffers/pipewire/pw_framebufferplugin.h | 45 + krfb.kdev4 | 3 - krfb/CMakeLists.txt | 9 +- krfb/events.cpp | 171 +--- krfb/events.h | 19 +- krfb/eventsmanager.cpp | 120 +++ krfb/eventsmanager.h | 62 ++ krfb/eventsplugin.cpp | 34 + krfb/eventsplugin.h | 42 + krfb/framebuffer.cpp | 5 + krfb/framebuffer.h | 5 + krfb/krfb-events.desktop | 8 + krfb/krfb-events.json | 7 + krfb/main.cpp | 3 - krfb/rfbclient.cpp | 9 +- krfb/rfbserver.cpp | 19 +- krfb/rfbserver.h | 1 + krfb/rfbservermanager.cpp | 15 +- krfb/rfbservermanager.h | 3 + 44 files changed, 2751 insertions(+), 183 deletions(-) create mode 100644 cmake/modules/FindPipeWire.cmake create mode 100644 dbus/xdp_dbus_remotedesktop_interface.xml create mode 100644 dbus/xdp_dbus_screencast_interface.xml create mode 100644 events/CMakeLists.txt create mode 100644 events/x11/CMakeLists.txt create mode 100644 events/x11/krfb_events_x11.desktop create mode 100644 events/x11/krfb_events_x11.json create mode 100644 events/x11/x11events.cpp create mode 100644 events/x11/x11events.h create mode 100644 events/x11/x11eventsplugin.cpp create mode 100644 events/x11/x11eventsplugin.h create mode 100644 events/xdp/CMakeLists.txt create mode 100644 events/xdp/krfb_events_xdp.desktop create mode 100644 events/xdp/krfb_events_xdp.json create mode 100644 events/xdp/xdpevents.cpp create mode 100644 events/xdp/xdpevents.h create mode 100644 events/xdp/xdpeventsplugin.cpp create mode 100644 events/xdp/xdpeventsplugin.h create mode 100644 framebuffers/pipewire/CMakeLists.txt create mode 100644 framebuffers/pipewire/krfb_framebuffer_pw.json create mode 100644 framebuffers/pipewire/pw_framebuffer.cpp create mode 100644 framebuffers/pipewire/pw_framebuffer.h create mode 100644 framebuffers/pipewire/pw_framebufferplugin.cpp create mode 100644 framebuffers/pipewire/pw_framebufferplugin.h delete mode 100644 krfb.kdev4 create mode 100644 krfb/eventsmanager.cpp create mode 100644 krfb/eventsmanager.h create mode 100644 krfb/eventsplugin.cpp create mode 100644 krfb/eventsplugin.h create mode 100644 krfb/krfb-events.desktop create mode 100644 krfb/krfb-events.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 213128c1..bafacdbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,9 @@ include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMSetupVersion) include(FeatureSummary) +include(CheckIncludeFile) + +check_include_file("linux/input.h" HAVE_LINUX_INPUT_H) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core DBus Widgets X11Extras) @@ -73,6 +76,12 @@ include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ) find_package(LibVNCServer REQUIRED) +find_package(PipeWire) +set_package_properties(PipeWire PROPERTIES + TYPE OPTIONAL + PURPOSE "Required for pipewire screencast plugin" +) + ecm_setup_version(PROJECT VARIABLE_PREFIX KRFB VERSION_HEADER "krfb_version.h") @@ -88,6 +97,7 @@ if(Q_WS_X11) endif(NOT X11_XTest_FOUND) endif(Q_WS_X11) +add_subdirectory(events) add_subdirectory(krfb) add_subdirectory(framebuffers) add_subdirectory(doc) diff --git a/cmake/modules/FindPipeWire.cmake b/cmake/modules/FindPipeWire.cmake new file mode 100644 index 00000000..2775aa95 --- /dev/null +++ b/cmake/modules/FindPipeWire.cmake @@ -0,0 +1,109 @@ +#.rst: +# FindPipeWire +# ------- +# +# Try to find PipeWire on a Unix system. +# +# This will define the following variables: +# +# ``PipeWire_FOUND`` +# True if (the requested version of) PipeWire is available +# ``PipeWire_VERSION`` +# The version of PipeWire +# ``PipeWire_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the ``PipeWire::PipeWire`` +# target +# ``PipeWire_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``PipeWire_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``PipeWire_FOUND`` is TRUE, it will also define the following imported target: +# +# ``PipeWire::PipeWire`` +# The PipeWire library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +#============================================================================= +# Copyright 2014 Alex Merry +# Copyright 2014 Martin Gräßlin +# Copyright 2018 Jan Grulich +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +# Use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls +find_package(PkgConfig QUIET) +pkg_check_modules(PKG_PipeWire QUIET libpipewire-0.2 libpipewire-0.3) + +set(PipeWire_DEFINITIONS "${PKG_PipeWire_CFLAGS_OTHER}") +set(PipeWire_VERSION "${PKG_PipeWire_VERSION}") + +find_path(PipeWire_INCLUDE_DIRS + NAMES + pipewire/pipewire.h + HINTS + ${PKG_PipeWire_INCLUDE_DIRS} +) + +find_library(PipeWire_LIBRARIES + NAMES + pipewire-0.2 pipewire-0.3 + HINTS + ${PKG_PipeWire_LIBRARIES_DIRS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PipeWire + FOUND_VAR + PipeWire_FOUND + REQUIRED_VARS + PipeWire_LIBRARIES + PipeWire_INCLUDE_DIRS + VERSION_VAR + PipeWire_VERSION +) + +if(PipeWire_FOUND AND NOT TARGET PipeWire::PipeWire) + add_library(PipeWire::PipeWire UNKNOWN IMPORTED) + set_target_properties(PipeWire::PipeWire PROPERTIES + IMPORTED_LOCATION "${PipeWire_LIBRARIES}" + INTERFACE_COMPILE_OPTIONS "${PipeWire_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${PipeWire_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced(PipeWire_LIBRARIES PipeWire_INCLUDE_DIRS) + +include(FeatureSummary) +set_package_properties(PipeWire PROPERTIES + URL "http://www.pipewire.org" + DESCRIPTION "PipeWire - multimedia processing" +) diff --git a/dbus/xdp_dbus_remotedesktop_interface.xml b/dbus/xdp_dbus_remotedesktop_interface.xml new file mode 100644 index 00000000..054ea2b6 --- /dev/null +++ b/dbus/xdp_dbus_remotedesktop_interface.xml @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dbus/xdp_dbus_screencast_interface.xml b/dbus/xdp_dbus_screencast_interface.xml new file mode 100644 index 00000000..0cd062f1 --- /dev/null +++ b/dbus/xdp_dbus_screencast_interface.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/events/CMakeLists.txt b/events/CMakeLists.txt new file mode 100644 index 00000000..b9d2502f --- /dev/null +++ b/events/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(x11) + +# Makes sense to use only when PW framebuffer is used +if (${PipeWire_FOUND}) + add_subdirectory(xdp) +endif() diff --git a/events/x11/CMakeLists.txt b/events/x11/CMakeLists.txt new file mode 100644 index 00000000..2f21d35a --- /dev/null +++ b/events/x11/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories (${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +set (krfb_events_x11_SRCS + x11events.cpp + x11eventsplugin.cpp +) + +add_library (krfb_events_x11 MODULE ${krfb_events_x11_SRCS}) + +target_link_libraries (krfb_events_x11 + ${X11_XTest_LIB} + KF5::CoreAddons + krfbprivate +) + +install (TARGETS krfb_events_x11 DESTINATION ${PLUGIN_INSTALL_DIR}/krfb) diff --git a/events/x11/krfb_events_x11.desktop b/events/x11/krfb_events_x11.desktop new file mode 100644 index 00000000..7c2fb05e --- /dev/null +++ b/events/x11/krfb_events_x11.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Encoding=UTF-8 +Comment=X11 XFakeInput based event handler for KRfb +Name=X11 XFakeInput based event handler for KRfb +Type=Service +ServiceTypes=krfb/events + +X-KDE-Library=krfb_events_x11 +X-KDE-PluginInfo-Name=x11 +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true diff --git a/events/x11/krfb_events_x11.json b/events/x11/krfb_events_x11.json new file mode 100644 index 00000000..8d9e9350 --- /dev/null +++ b/events/x11/krfb_events_x11.json @@ -0,0 +1,15 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Description": "X11 XFakeInput based event handler for KRfb", + "EnabledByDefault": true, + "Id": "x11", + "License": "GPL", + "Name": "X11 Event handler for KRfb", + "ServiceTypes": [ + "krfb/events" + ], + "Version": "0.1", + "Website": "http://www.kde.org" + } +} diff --git a/events/x11/x11events.cpp b/events/x11/x11events.cpp new file mode 100644 index 00000000..e61fde54 --- /dev/null +++ b/events/x11/x11events.cpp @@ -0,0 +1,212 @@ +/* + This file is part of the KDE project + + Copyright (C) 2016 by Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "x11events.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +enum { + LEFTSHIFT = 1, + RIGHTSHIFT = 2, + ALTGR = 4 +}; + +class EventData +{ +public: + EventData(); + + //keyboard + Display *dpy; + signed char modifiers[0x100]; + KeyCode keycodes[0x100]; + KeyCode leftShiftCode; + KeyCode rightShiftCode; + KeyCode altGrCode; + char modifierState; + + //mouse + int buttonMask; + int x; + int y; + +private: + void init(); +}; + +Q_GLOBAL_STATIC(EventData, data) + +EventData::EventData() +{ + init(); +} + +void EventData::init() +{ + buttonMask = 0; + + dpy = QX11Info::display(); + //initialize keycodes + KeySym key, *keymap; + int i, j, minkey, maxkey, syms_per_keycode; + + memset(modifiers, -1, sizeof(modifiers)); + + XDisplayKeycodes(dpy, &minkey, &maxkey); + Q_ASSERT(minkey >= 8); + Q_ASSERT(maxkey < 256); + keymap = (KeySym *) XGetKeyboardMapping(dpy, minkey, + (maxkey - minkey + 1), + &syms_per_keycode); + Q_ASSERT(keymap); + + for (i = minkey; i <= maxkey; i++) { + for (j = 0; j < syms_per_keycode; j++) { + key = keymap[(i-minkey)*syms_per_keycode+j]; + + if (key >= ' ' && key < 0x100 && i == XKeysymToKeycode(dpy, key)) { + keycodes[key] = i; + modifiers[key] = j; + } + } + } + + leftShiftCode = XKeysymToKeycode(dpy, XK_Shift_L); + rightShiftCode = XKeysymToKeycode(dpy, XK_Shift_R); + altGrCode = XKeysymToKeycode(dpy, XK_Mode_switch); + + XFree((char *)keymap); +} + +/* this function adjusts the modifiers according to mod (as from modifiers) and data->modifierState */ +static void tweakModifiers(signed char mod, bool down) +{ + bool isShift = data->modifierState & (LEFTSHIFT | RIGHTSHIFT); + + if (mod < 0) { + return; + } + + if (isShift && mod != 1) { + if (data->modifierState & LEFTSHIFT) { + XTestFakeKeyEvent(data->dpy, data->leftShiftCode, + down, CurrentTime); + } + + if (data->modifierState & RIGHTSHIFT) { + XTestFakeKeyEvent(data->dpy, data->rightShiftCode, + down, CurrentTime); + } + } + + if (!isShift && mod == 1) { + XTestFakeKeyEvent(data->dpy, data->leftShiftCode, + down, CurrentTime); + } + + if ((data->modifierState & ALTGR) && mod != 2) { + XTestFakeKeyEvent(data->dpy, data->altGrCode, + !down, CurrentTime); + } + + if (!(data->modifierState & ALTGR) && mod == 2) { + XTestFakeKeyEvent(data->dpy, data->altGrCode, + down, CurrentTime); + } +} + +void X11EventHandler::handleKeyboard(bool down, rfbKeySym keySym) +{ +#define ADJUSTMOD(sym,state) \ + if(keySym==sym) { if(down) data->modifierState|=state; else data->modifierState&=~state; } + + if (QX11Info::isPlatformX11()) { + ADJUSTMOD(XK_Shift_L, LEFTSHIFT); + ADJUSTMOD(XK_Shift_R, RIGHTSHIFT); + ADJUSTMOD(XK_Mode_switch, ALTGR); + + if (keySym >= ' ' && keySym < 0x100) { + KeyCode k; + + if (down) { + tweakModifiers(data->modifiers[keySym], True); + } + + k = data->keycodes[keySym]; + + if (k != NoSymbol) { + XTestFakeKeyEvent(data->dpy, k, down, CurrentTime); + } + + if (down) { + tweakModifiers(data->modifiers[keySym], False); + } + } else { + KeyCode k = XKeysymToKeycode(data->dpy, keySym); + + if (k != NoSymbol) { + XTestFakeKeyEvent(data->dpy, k, down, CurrentTime); + } + } + } +/* + // Wayland platform and pipweire plugin in use + if (KrfbConfig::preferredFrameBufferPlugin() == QStringLiteral("pw")) { + + }*/ +} + +void X11EventHandler::handlePointer(int buttonMask, int x, int y) +{ + if (QX11Info::isPlatformX11()) { + QDesktopWidget *desktopWidget = QApplication::desktop(); + + int screen = desktopWidget->screenNumber(); + + if (screen < 0) { + screen = 0; + } + + XTestFakeMotionEvent(data->dpy, screen, x, y, CurrentTime); + + for (int i = 0; i < 5; i++) { + if ((data->buttonMask&(1 << i)) != (buttonMask&(1 << i))) { + XTestFakeButtonEvent(data->dpy, + i + 1, + (buttonMask&(1 << i)) ? True : False, + CurrentTime); + } + } + + data->buttonMask = buttonMask; + } +} + +#include "x11events.moc" diff --git a/events/x11/x11events.h b/events/x11/x11events.h new file mode 100644 index 00000000..a82c2625 --- /dev/null +++ b/events/x11/x11events.h @@ -0,0 +1,41 @@ +/* + This file is part of the KDE project + + Copyright (C) 2016 by Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef EVENTS_X11EVENTS_H +#define EVENTS_X11EVENTS_H + +#include "../../krfb/events.h" + +class X11EventHandler : public EventHandler +{ + Q_OBJECT +public: + explicit X11EventHandler(QObject *parent = nullptr) + : EventHandler(parent) + { + }; + + void handleKeyboard(bool down, rfbKeySym key) override; + void handlePointer(int buttonMask, int x, int y) override; +}; + +#endif + diff --git a/events/x11/x11eventsplugin.cpp b/events/x11/x11eventsplugin.cpp new file mode 100644 index 00000000..696ece53 --- /dev/null +++ b/events/x11/x11eventsplugin.cpp @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (C) 2016 Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "x11eventsplugin.h" + +#include "x11events.h" + +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(X11EventsPluginFactory, "krfb_events_x11.json", + registerPlugin();) + +X11EventsPlugin::X11EventsPlugin(QObject *parent, const QVariantList &args) + : EventsPlugin(parent, args) +{ +} + +EventHandler *X11EventsPlugin::eventHandler() +{ + // works only under X11 + if(!QX11Info::isPlatformX11()) + return nullptr; + + return new X11EventHandler(); +} + +#include "x11eventsplugin.moc" + diff --git a/events/x11/x11eventsplugin.h b/events/x11/x11eventsplugin.h new file mode 100644 index 00000000..6f96f484 --- /dev/null +++ b/events/x11/x11eventsplugin.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + Copyright (C) 2016 Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KRFB_EVENTS_X11_X11EVENTSPLUGIN_H +#define KRFB_EVENTS_X11_X11EVENTSPLUGIN_H + +#include "eventsplugin.h" + +#include + +class EventHandler; + +class X11EventsPlugin : public EventsPlugin +{ + Q_OBJECT +public: + X11EventsPlugin(QObject *parent, const QVariantList &args); + virtual ~X11EventsPlugin() = default; + + EventHandler *eventHandler() override; + +private: + Q_DISABLE_COPY(X11EventsPlugin) +}; + + +#endif // Header guard diff --git a/events/xdp/CMakeLists.txt b/events/xdp/CMakeLists.txt new file mode 100644 index 00000000..77d19b28 --- /dev/null +++ b/events/xdp/CMakeLists.txt @@ -0,0 +1,27 @@ +include_directories (${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +set (krfb_events_xdp_SRCS + xdpevents.cpp + xdpeventsplugin.cpp +) + +qt5_add_dbus_interface( + krfb_events_xdp_SRCS + ${CMAKE_SOURCE_DIR}/dbus/xdp_dbus_remotedesktop_interface.xml + xdp_dbus_remotedesktop_interface +) + +add_library (krfb_events_xdp MODULE ${krfb_events_xdp_SRCS}) + +target_link_libraries (krfb_events_xdp + KF5::CoreAddons + KF5::I18n + Qt5::DBus + krfbprivate +) + +install (TARGETS krfb_events_xdp + DESTINATION ${PLUGIN_INSTALL_DIR}/krfb +) diff --git a/events/xdp/krfb_events_xdp.desktop b/events/xdp/krfb_events_xdp.desktop new file mode 100644 index 00000000..f2622712 --- /dev/null +++ b/events/xdp/krfb_events_xdp.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Encoding=UTF-8 +Comment=Xdg-desktop-portal based event handler for KRfb +Name=Xdg-desktop-portal based event handler for KRfb +Type=Service +ServiceTypes=krfb/events + +X-KDE-Library=krfb_events_xdp +X-KDE-PluginInfo-Name=xdp +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website=http://www.kde.org +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=true + diff --git a/events/xdp/krfb_events_xdp.json b/events/xdp/krfb_events_xdp.json new file mode 100644 index 00000000..8656433e --- /dev/null +++ b/events/xdp/krfb_events_xdp.json @@ -0,0 +1,16 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Description": "Xdg-desktop-portal based event handler for KRfb", + "EnabledByDefault": true, + "Id": "xdp", + "License": "GPL", + "Name": "Xdg-desktop-portal Event handler for KRfb", + "ServiceTypes": [ + "krfb/events" + ], + "Version": "0.1", + "Website": "http://www.kde.org" + } +} + diff --git a/events/xdp/xdpevents.cpp b/events/xdp/xdpevents.cpp new file mode 100644 index 00000000..4665992a --- /dev/null +++ b/events/xdp/xdpevents.cpp @@ -0,0 +1,128 @@ +/* + This file is part of the KDE project + + Copyright (C) 2018-2019 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "xdpevents.h" + +#include "rfbservermanager.h" +#include "xdp_dbus_remotedesktop_interface.h" + +#include + +#include +#include +#include + +class EventData +{ +public: + EventData(); + + //mouse + int buttonMask; + int x; + int y; + + QScopedPointer dbusXdpRemoteDesktopService; + +private: + void init(); +}; + +Q_GLOBAL_STATIC(EventData, data) + +EventData::EventData() +{ + init(); +} + +void EventData::init() +{ + buttonMask = 0; + + dbusXdpRemoteDesktopService.reset(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), QDBusConnection::sessionBus())); +} + +void XdpEventHandler::handleKeyboard(bool down, rfbKeySym keySym) +{ + // TODO: implement button handling + // both in FakeInput interface and here + Q_UNUSED(down) + Q_UNUSED(keySym) +} + +void XdpEventHandler::handlePointer(int buttonMask, int x, int y) +{ + const uint streamNodeId = frameBuffer()->customProperty(QLatin1String("stream_node_id")).toUInt(); + const QDBusObjectPath sessionHandle = frameBuffer()->customProperty(QLatin1String("session_handle")).value(); + + if (streamNodeId == 0 || sessionHandle.path().isEmpty()) { + return; + } + + if (x != data->x || y != data->y) { + data->dbusXdpRemoteDesktopService->NotifyPointerMotionAbsolute(sessionHandle, QVariantMap(), streamNodeId, x, y); + data->x = x; + data->y = y; + } + + if (buttonMask != data->buttonMask) { + int i = 0; + QVector buttons = { BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, 0, 0, 0, 0, BTN_SIDE, BTN_EXTRA }; + for (auto it = buttons.constBegin(); it != buttons.constEnd(); ++it) { + int prevButtonState = (data->buttonMask >> i) & 0x01; + int currentButtonState = (buttonMask >> i) & 0x01; + + if (prevButtonState != currentButtonState) { + if (*it) { + data->dbusXdpRemoteDesktopService->NotifyPointerButton(sessionHandle, QVariantMap(), *it, buttonMask); + } else { + int axis = 0; + int steps = 0; + switch (i) { + case 3: + axis = 0; // Vertical + steps = -1; + break; + case 4: + axis = 0; // Vertical + steps = 1; + break; + case 5: + axis = 1; // Horizontal + steps = -1; + break; + case 6: + axis = 1; // Horizontal + steps = 1; + break; + } + + data->dbusXdpRemoteDesktopService->NotifyPointerAxisDiscrete(sessionHandle, QVariantMap(), axis, steps); + } + } + ++i; + } + data->buttonMask = buttonMask; + } +} + +#include "xdpevents.moc" diff --git a/events/xdp/xdpevents.h b/events/xdp/xdpevents.h new file mode 100644 index 00000000..616dba5b --- /dev/null +++ b/events/xdp/xdpevents.h @@ -0,0 +1,37 @@ +/* + This file is part of the KDE project + + Copyright (C) 2018-2019 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef EVENTS_XDPEVENTS_H +#define EVENTS_XDPEVENTS_H + +#include "../../krfb/events.h" + +class XdpEventHandler : public EventHandler +{ + Q_OBJECT +public: + void handleKeyboard(bool down, rfbKeySym key) override; + void handlePointer(int buttonMask, int x, int y) override; +}; + +#endif + + diff --git a/events/xdp/xdpeventsplugin.cpp b/events/xdp/xdpeventsplugin.cpp new file mode 100644 index 00000000..bb4c44e4 --- /dev/null +++ b/events/xdp/xdpeventsplugin.cpp @@ -0,0 +1,43 @@ +/* + This file is part of the KDE project + + Copyright (C) 2018-2019 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "xdpeventsplugin.h" + +#include "xdpevents.h" + +#include + +K_PLUGIN_FACTORY_WITH_JSON(XdpEventsPluginFactory, "krfb_events_xdp.json", + registerPlugin();) + +XdpEventsPlugin::XdpEventsPlugin(QObject *parent, const QVariantList &args) + : EventsPlugin(parent, args) +{ +} + +EventHandler *XdpEventsPlugin::eventHandler() +{ + // works only under Wayland + return new XdpEventHandler(); +} + +#include "xdpeventsplugin.moc" + diff --git a/events/xdp/xdpeventsplugin.h b/events/xdp/xdpeventsplugin.h new file mode 100644 index 00000000..5d23bc68 --- /dev/null +++ b/events/xdp/xdpeventsplugin.h @@ -0,0 +1,46 @@ +/* + This file is part of the KDE project + + Copyright (C) 2018-2019 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KRFB_EVENTS_XDP_XDPEVENTSPLUGIN_H +#define KRFB_EVENTS_XDP_XDPEVENTSPLUGIN_H + +#include "eventsplugin.h" + +#include + +class EventHandler; + +class XdpEventsPlugin : public EventsPlugin +{ + Q_OBJECT +public: + XdpEventsPlugin(QObject *parent, const QVariantList &args); + virtual ~XdpEventsPlugin() = default; + + EventHandler *eventHandler() override; + +private: + Q_DISABLE_COPY(XdpEventsPlugin) +}; + + +#endif // Header guard + diff --git a/framebuffers/CMakeLists.txt b/framebuffers/CMakeLists.txt index e0d9dc8d..51a751db 100644 --- a/framebuffers/CMakeLists.txt +++ b/framebuffers/CMakeLists.txt @@ -1,5 +1,9 @@ add_subdirectory (qt) if (${XCB_DAMAGE_FOUND} AND ${XCB_SHM_FOUND} AND ${XCB_IMAGE_FOUND}) -add_subdirectory (xcb) + add_subdirectory (xcb) +endif() + +if (${PipeWire_FOUND}) + add_subdirectory(pipewire) endif() diff --git a/framebuffers/pipewire/CMakeLists.txt b/framebuffers/pipewire/CMakeLists.txt new file mode 100644 index 00000000..76b4453c --- /dev/null +++ b/framebuffers/pipewire/CMakeLists.txt @@ -0,0 +1,38 @@ +include_directories (${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +set (krfb_framebuffer_pw_SRCS + pw_framebuffer.cpp + pw_framebufferplugin.cpp +) + +qt5_add_dbus_interface( + krfb_framebuffer_pw_SRCS + ${CMAKE_SOURCE_DIR}/dbus/xdp_dbus_screencast_interface.xml + xdp_dbus_screencast_interface +) + +qt5_add_dbus_interface( + krfb_framebuffer_pw_SRCS + ${CMAKE_SOURCE_DIR}/dbus/xdp_dbus_remotedesktop_interface.xml + xdp_dbus_remotedesktop_interface +) + +add_library(krfb_framebuffer_pw + MODULE + ${krfb_framebuffer_pw_SRCS} +) + +target_link_libraries (krfb_framebuffer_pw + Qt5::Core + Qt5::Gui + Qt5::DBus + KF5::CoreAddons + krfbprivate + PipeWire::PipeWire +) + +install (TARGETS krfb_framebuffer_pw + DESTINATION ${PLUGIN_INSTALL_DIR}/krfb +) diff --git a/framebuffers/pipewire/krfb_framebuffer_pw.json b/framebuffers/pipewire/krfb_framebuffer_pw.json new file mode 100644 index 00000000..82ca201e --- /dev/null +++ b/framebuffers/pipewire/krfb_framebuffer_pw.json @@ -0,0 +1,15 @@ +{ + "Encoding": "UTF-8", + "KPlugin": { + "Description": "PipeWire based Framebuffer for KRfb.", + "EnabledByDefault": true, + "Id": "pw", + "License": "GPL3", + "Name": "PipeWire Framebuffer for KRfb", + "ServiceTypes": [ + "krfb/framebuffer" + ], + "Version": "0.1", + "Website": "http://www.kde.org" + } +} diff --git a/framebuffers/pipewire/pw_framebuffer.cpp b/framebuffers/pipewire/pw_framebuffer.cpp new file mode 100644 index 00000000..f0b6f74f --- /dev/null +++ b/framebuffers/pipewire/pw_framebuffer.cpp @@ -0,0 +1,800 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Oleg Chernovskiy + Copyright (C) 2018 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. +*/ + +// system +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) +#include +#endif + +// pipewire +#include + +#if !PW_CHECK_VERSION(0, 2, 9) +#include +#include +#include +#include +#endif +#include +#include + +#include +#include +#include +#include + +#include + +#include "pw_framebuffer.h" +#include "xdp_dbus_screencast_interface.h" +#include "xdp_dbus_remotedesktop_interface.h" + +static const uint MIN_SUPPORTED_XDP_KDE_SC_VERSION = 1; + +Q_DECLARE_METATYPE(PWFrameBuffer::Stream); +Q_DECLARE_METATYPE(PWFrameBuffer::Streams); + +const QDBusArgument &operator >> (const QDBusArgument &arg, PWFrameBuffer::Stream &stream) +{ + arg.beginStructure(); + arg >> stream.nodeId; + + arg.beginMap(); + while (!arg.atEnd()) { + QString key; + QVariant map; + arg.beginMapEntry(); + arg >> key >> map; + arg.endMapEntry(); + stream.map.insert(key, map); + } + arg.endMap(); + arg.endStructure(); + + return arg; +} + +#if !PW_CHECK_VERSION(0, 2, 9) +/** + * @brief The PwType class - helper class to contain pointers to raw C pipewire media mappings + */ +class PwType { +public: + spa_type_media_type media_type; + spa_type_media_subtype media_subtype; + spa_type_format_video format_video; + spa_type_video_format video_format; +}; +#endif + +/** + * @brief The PWFrameBuffer::Private class - private counterpart of PWFramebuffer class. This is the entity where + * whole logic resides, for more info search for "d-pointer pattern" information. + */ +class PWFrameBuffer::Private { +public: + Private(PWFrameBuffer *q); + ~Private(); + +private: + friend class PWFrameBuffer; + + static void onStateChanged(void *data, pw_remote_state old, pw_remote_state state, const char *error); + static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); + static void onStreamFormatChanged(void *data, const struct spa_pod *format); + static void onStreamProcess(void *data); + + void initDbus(); + void initPw(); +#if !PW_CHECK_VERSION(0, 2, 9) + void initializePwTypes(); +#endif + + // dbus handling + void handleSessionCreated(quint32 &code, QVariantMap &results); + void handleDevicesSelected(quint32 &code, QVariantMap &results); + void handleSourcesSelected(quint32 &code, QVariantMap &results); + void handleRemoteDesktopStarted(quint32 &code, QVariantMap &results); + + // pw handling + void createReceivingStream(); + void handleFrame(pw_buffer *pwBuffer); + + // link to public interface + PWFrameBuffer *q; + + // pipewire stuff +#if PW_CHECK_VERSION(0, 2, 9) + struct pw_core *pwCore = nullptr; + struct pw_loop *pwLoop = nullptr; + struct pw_stream *pwStream = nullptr; + struct pw_remote *pwRemote = nullptr; + struct pw_thread_loop *pwMainLoop = nullptr; +#else + pw_core *pwCore = nullptr; + pw_loop *pwLoop = nullptr; + pw_stream *pwStream = nullptr; + pw_remote *pwRemote = nullptr; + pw_thread_loop *pwMainLoop = nullptr; + pw_type *pwCoreType = nullptr; + PwType *pwType = nullptr; +#endif + + uint pwStreamNodeId = 0; + + // event handlers + pw_remote_events pwRemoteEvents = {}; + pw_stream_events pwStreamEvents = {}; + + // wayland-like listeners + // ...of events that happen in pipewire server + spa_hook remoteListener = {}; + // ...of events that happen with the stream we consume + spa_hook streamListener = {}; + + // negotiated video format + spa_video_info_raw *videoFormat = nullptr; + + // requests a session from XDG Desktop Portal + // auto-generated and compiled from xdp_dbus_interface.xml file + QScopedPointer dbusXdpScreenCastService; + QScopedPointer dbusXdpRemoteDesktopService; + + // XDP screencast session handle + QDBusObjectPath sessionPath; + // Pipewire file descriptor + QDBusUnixFileDescriptor pipewireFd; + + // screen geometry holder + struct { + quint32 width; + quint32 height; + } screenGeometry; + + // Allowed devices + uint devices; + + // sanity indicator + bool isValid = true; +}; + +PWFrameBuffer::Private::Private(PWFrameBuffer *q) : q(q) +{ + // initialize event handlers, remote end and stream-related + pwRemoteEvents.version = PW_VERSION_REMOTE_EVENTS; + pwRemoteEvents.state_changed = &onStateChanged; + + pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; + pwStreamEvents.state_changed = &onStreamStateChanged; + pwStreamEvents.format_changed = &onStreamFormatChanged; + pwStreamEvents.process = &onStreamProcess; +} + +/** + * @brief PWFrameBuffer::Private::initDbus - initialize D-Bus connectivity with XDG Desktop Portal. + * Based on XDG_CURRENT_DESKTOP environment variable it will give us implementation that we need, + * in case of KDE it is xdg-desktop-portal-kde binary. + */ +void PWFrameBuffer::Private::initDbus() +{ + qInfo() << "Initializing D-Bus connectivity with XDG Desktop Portal"; + dbusXdpScreenCastService.reset(new OrgFreedesktopPortalScreenCastInterface(QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), + QDBusConnection::sessionBus())); + dbusXdpRemoteDesktopService.reset(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"), + QLatin1String("/org/freedesktop/portal/desktop"), + QDBusConnection::sessionBus())); + auto version = dbusXdpScreenCastService->version(); + if (version < MIN_SUPPORTED_XDP_KDE_SC_VERSION) { + qWarning() << "Unsupported XDG Portal screencast interface version:" << version; + isValid = false; + return; + } + + // create session + auto sessionParameters = QVariantMap { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + { QLatin1String("session_handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) }, + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } +#else + { QLatin1String("session_handle_token"), QStringLiteral("krfb%1").arg(qrand()) }, + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } +#endif + }; + auto sessionReply = dbusXdpRemoteDesktopService->CreateSession(sessionParameters); + sessionReply.waitForFinished(); + if (!sessionReply.isValid()) { + qWarning("Couldn't initialize XDP-KDE screencast session"); + isValid = false; + return; + } + + qInfo() << "DBus session created: " << sessionReply.value().path(); + QDBusConnection::sessionBus().connect(QString(), + sessionReply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this->q, + SLOT(handleXdpSessionCreated(uint, QVariantMap))); +} + +void PWFrameBuffer::handleXdpSessionCreated(quint32 code, QVariantMap results) +{ + d->handleSessionCreated(code, results); +} + +/** + * @brief PWFrameBuffer::Private::handleSessionCreated - handle creation of ScreenCast session. + * XDG Portal answers with session path if it was able to successfully create the screencast. + * + * @param code return code for dbus call. Zero is success, non-zero means error + * @param results map with results of call. + */ +void PWFrameBuffer::Private::handleSessionCreated(quint32 &code, QVariantMap &results) +{ + if (code != 0) { + qWarning() << "Failed to create session: " << code; + isValid = false; + return; + } + + sessionPath = QDBusObjectPath(results.value(QLatin1String("session_handle")).toString()); + + // select sources for the session + auto selectionOptions = QVariantMap { + // We have to specify it's an uint, otherwise xdg-desktop-portal will not forward it to backend implementation + { QLatin1String("types"),QVariant::fromValue(7) }, // request all (KeyBoard, Pointer, TouchScreen) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } +#else + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } +#endif + }; + auto selectorReply = dbusXdpRemoteDesktopService->SelectDevices(sessionPath, selectionOptions); + selectorReply.waitForFinished(); + if (!selectorReply.isValid()) { + qWarning() << "Couldn't select devices for the remote-desktop session"; + isValid = false; + return; + } + QDBusConnection::sessionBus().connect(QString(), + selectorReply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this->q, + SLOT(handleXdpDevicesSelected(uint, QVariantMap))); +} + +void PWFrameBuffer::handleXdpDevicesSelected(quint32 code, QVariantMap results) +{ + d->handleDevicesSelected(code, results); +} + +/** + * @brief PWFrameBuffer::Private::handleDevicesCreated - handle selection of devices we want to use for remote desktop + * + * @param code return code for dbus call. Zero is success, non-zero means error + * @param results map with results of call. + */ +void PWFrameBuffer::Private::handleDevicesSelected(quint32 &code, QVariantMap &results) +{ + if (code != 0) { + qWarning() << "Failed to select devices: " << code; + isValid = false; + return; + } + + // select sources for the session + auto selectionOptions = QVariantMap { + { QLatin1String("types"), 1 }, // only MONITOR is supported + { QLatin1String("multiple"), false }, +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } +#else + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } +#endif + }; + auto selectorReply = dbusXdpScreenCastService->SelectSources(sessionPath, selectionOptions); + selectorReply.waitForFinished(); + if (!selectorReply.isValid()) { + qWarning() << "Couldn't select sources for the screen-casting session"; + isValid = false; + return; + } + QDBusConnection::sessionBus().connect(QString(), + selectorReply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this->q, + SLOT(handleXdpSourcesSelected(uint, QVariantMap))); +} + +void PWFrameBuffer::handleXdpSourcesSelected(quint32 code, QVariantMap results) +{ + d->handleSourcesSelected(code, results); +} + +/** + * @brief PWFrameBuffer::Private::handleSourcesSelected - handle Screencast sources selection. + * XDG Portal shows a dialog at this point which allows you to select monitor from the list. + * This function is called after you make a selection. + * + * @param code return code for dbus call. Zero is success, non-zero means error + * @param results map with results of call. + */ +void PWFrameBuffer::Private::handleSourcesSelected(quint32 &code, QVariantMap &) +{ + if (code != 0) { + qWarning() << "Failed to select sources: " << code; + isValid = false; + return; + } + + // start session + auto startParameters = QVariantMap { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(QRandomGenerator::global()->generate()) } +#else + { QLatin1String("handle_token"), QStringLiteral("krfb%1").arg(qrand()) } +#endif + }; + auto startReply = dbusXdpRemoteDesktopService->Start(sessionPath, QString(), startParameters); + startReply.waitForFinished(); + QDBusConnection::sessionBus().connect(QString(), + startReply.value().path(), + QLatin1String("org.freedesktop.portal.Request"), + QLatin1String("Response"), + this->q, + SLOT(handleXdpRemoteDesktopStarted(uint, QVariantMap))); +} + + +void PWFrameBuffer::handleXdpRemoteDesktopStarted(quint32 code, QVariantMap results) +{ + d->handleRemoteDesktopStarted(code, results); +} + +/** + * @brief PWFrameBuffer::Private::handleScreencastStarted - handle Screencast start. + * At this point there shall be ready pipewire stream to consume. + * + * @param code return code for dbus call. Zero is success, non-zero means error + * @param results map with results of call. + */ +void PWFrameBuffer::Private::handleRemoteDesktopStarted(quint32 &code, QVariantMap &results) +{ + if (code != 0) { + qWarning() << "Failed to start screencast: " << code; + isValid = false; + return; + } + + // there should be only one stream + Streams streams = qdbus_cast(results.value(QLatin1String("streams"))); + if (streams.isEmpty()) { + // maybe we should check deeper with qdbus_cast but this suffices for now + qWarning() << "Failed to get screencast streams"; + isValid = false; + return; + } + + auto streamReply = dbusXdpScreenCastService->OpenPipeWireRemote(sessionPath, QVariantMap()); + streamReply.waitForFinished(); + if (!streamReply.isValid()) { + qWarning() << "Couldn't open pipewire remote for the screen-casting session"; + isValid = false; + return; + } + + pipewireFd = streamReply.value(); + if (!pipewireFd.isValid()) { + qWarning() << "Couldn't get pipewire connection file descriptor"; + isValid = false; + return; + } + + QSize streamResolution = qdbus_cast(streams.first().map.value(QLatin1String("size"))); + screenGeometry.width = streamResolution.width(); + screenGeometry.height = streamResolution.height(); + + devices = results.value(QLatin1String("types")).toUInt(); + + pwStreamNodeId = streams.first().nodeId; + + // Reallocate our buffer with actual needed size + q->fb = static_cast(malloc(screenGeometry.width * screenGeometry.height * 4)); + + if (!q->fb) { + qWarning() << "Failed to allocate buffer"; + isValid = false; + return; + } + + Q_EMIT q->frameBufferChanged(); + + initPw(); +} + +/** + * @brief PWFrameBuffer::Private::initPw - initialize Pipewire socket connectivity. + * pipewireFd should be pointing to existing file descriptor that was passed by D-Bus at this point. + */ +void PWFrameBuffer::Private::initPw() { + qInfo() << "Initializing Pipewire connectivity"; + + // init pipewire (required) + pw_init(nullptr, nullptr); // args are not used anyways + + // initialize our source + pwLoop = pw_loop_new(nullptr); + pwMainLoop = pw_thread_loop_new(pwLoop, "pipewire-main-loop"); + + // create PipeWire core object (required) + pwCore = pw_core_new(pwLoop, nullptr); +#if !PW_CHECK_VERSION(0, 2, 9) + pwCoreType = pw_core_get_type(pwCore); + + // init type maps + initializePwTypes(); +#endif + + // pw_remote should be initialized before type maps or connection error will happen + pwRemote = pw_remote_new(pwCore, nullptr, 0); + + // init PipeWire remote, add listener to handle events + pw_remote_add_listener(pwRemote, &remoteListener, &pwRemoteEvents, this); + pw_remote_connect_fd(pwRemote, pipewireFd.fileDescriptor()); + + if (pw_thread_loop_start(pwMainLoop) < 0) { + qWarning() << "Failed to start main PipeWire loop"; + isValid = false; + } +} + +#if !PW_CHECK_VERSION(0, 2, 9) +/** + * @brief PWFrameBuffer::Private::initializePwTypes - helper method to initialize and map all needed + * Pipewire types from core to type structure. + */ +void PWFrameBuffer::Private::initializePwTypes() +{ + // raw C-like PipeWire type map + spa_type_map *map = pwCoreType->map; + pwType = new PwType(); + + spa_type_media_type_map(map, &pwType->media_type); + spa_type_media_subtype_map(map, &pwType->media_subtype); + spa_type_format_video_map(map, &pwType->format_video); + spa_type_video_format_map(map, &pwType->video_format); +} +#endif + +/** + * @brief PWFrameBuffer::Private::onStateChanged - global state tracking for pipewire connection + * @param data pointer that you have set in pw_remote_add_listener call's last argument + * @param state new state that connection has changed to + * @param error optional error message, is set to non-null if state is error + */ +void PWFrameBuffer::Private::onStateChanged(void *data, pw_remote_state /*old*/, pw_remote_state state, const char *error) +{ + qInfo() << "remote state: " << pw_remote_state_as_string(state); + + PWFrameBuffer::Private *d = static_cast(data); + + switch (state) { + case PW_REMOTE_STATE_ERROR: + qWarning() << "remote error: " << error; + break; + case PW_REMOTE_STATE_CONNECTED: + d->createReceivingStream(); + break; + default: + qInfo() << "remote state: " << pw_remote_state_as_string(state); + break; + } +} + +/** + * @brief PWFrameBuffer::Private::onStreamStateChanged - called whenever stream state changes on pipewire server + * @param data pointer that you have set in pw_stream_add_listener call's last argument + * @param state new state that stream has changed to + * @param error_message optional error message, is set to non-null if state is error + */ +void PWFrameBuffer::Private::onStreamStateChanged(void *data, pw_stream_state /*old*/, pw_stream_state state, const char *error_message) +{ + qInfo() << "Stream state changed: " << pw_stream_state_as_string(state); + + auto *d = static_cast(data); + + switch (state) { + case PW_STREAM_STATE_ERROR: + qWarning() << "pipewire stream error: " << error_message; + break; + case PW_STREAM_STATE_CONFIGURE: + pw_stream_set_active(d->pwStream, true); + break; + default: + break; + } +} + +/** + * @brief PWFrameBuffer::Private::onStreamFormatChanged - being executed after stream is set to active + * and after setup has been requested to connect to it. The actual video format is being negotiated here. + * @param data pointer that you have set in pw_stream_add_listener call's last argument + * @param format format that's being proposed + */ +#if defined(PW_API_PRE_0_2_0) +void PWFrameBuffer::Private::onStreamFormatChanged(void *data, struct spa_pod *format) +#else +void PWFrameBuffer::Private::onStreamFormatChanged(void *data, const struct spa_pod *format) +#endif // defined(PW_API_PRE_0_2_0) +{ + qInfo() << "Stream format changed"; + auto *d = static_cast(data); + + const int bpp = 4; + + if (!format) { + pw_stream_finish_format(d->pwStream, 0, nullptr, 0); + return; + } + + d->videoFormat = new spa_video_info_raw(); +#if PW_CHECK_VERSION(0, 2, 9) + spa_format_video_raw_parse(format, d->videoFormat); +#else + spa_format_video_raw_parse(format, d->videoFormat, &d->pwType->format_video); +#endif + auto width = d->videoFormat->size.width; + auto height = d->videoFormat->size.height; + auto stride = SPA_ROUND_UP_N(width * bpp, 4); + auto size = height * stride; + + uint8_t buffer[1024]; + auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + // setup buffers and meta header for new format + const struct spa_pod *params[2]; + +#if PW_CHECK_VERSION(0, 2, 9) + params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + ":", SPA_PARAM_BUFFERS_size, "i", size, + ":", SPA_PARAM_BUFFERS_stride, "i", stride, + ":", SPA_PARAM_BUFFERS_buffers, "?ri", SPA_CHOICE_RANGE(8, 1, 32), + ":", SPA_PARAM_BUFFERS_align, "i", 16)); + params[1] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + ":", SPA_PARAM_META_type, "I", SPA_META_Header, + ":", SPA_PARAM_META_size, "i", sizeof(struct spa_meta_header))); +#else + params[0] = reinterpret_cast(spa_pod_builder_object(&builder, + d->pwCoreType->param.idBuffers, d->pwCoreType->param_buffers.Buffers, + ":", d->pwCoreType->param_buffers.size, "i", size, + ":", d->pwCoreType->param_buffers.stride, "i", stride, + ":", d->pwCoreType->param_buffers.buffers, "iru", 8, SPA_POD_PROP_MIN_MAX(1, 32), + ":", d->pwCoreType->param_buffers.align, "i", 16)); + params[1] = reinterpret_cast(spa_pod_builder_object(&builder, + d->pwCoreType->param.idMeta, d->pwCoreType->param_meta.Meta, + ":", d->pwCoreType->param_meta.type, "I", d->pwCoreType->meta.Header, + ":", d->pwCoreType->param_meta.size, "i", sizeof(struct spa_meta_header))); +#endif + + pw_stream_finish_format(d->pwStream, 0, params, 2); +} + +/** + * @brief PWFrameBuffer::Private::onNewBuffer - called when new buffer is available in pipewire stream + * @param data pointer that you have set in pw_stream_add_listener call's last argument + * @param id + */ +void PWFrameBuffer::Private::onStreamProcess(void *data) +{ + auto *d = static_cast(data); + + pw_buffer *buf; + if (!(buf = pw_stream_dequeue_buffer(d->pwStream))) { + return; + } + + d->handleFrame(buf); + + pw_stream_queue_buffer(d->pwStream, buf); +} + +void PWFrameBuffer::Private::handleFrame(pw_buffer *pwBuffer) +{ + auto *spaBuffer = pwBuffer->buffer; + void *src = nullptr; + + src = spaBuffer->datas[0].data; + if (!src) { + return; + } + + quint32 maxSize = spaBuffer->datas[0].maxsize; + qint32 srcStride = spaBuffer->datas[0].chunk->stride; + if (srcStride != q->paddedWidth()) { + qWarning() << "Got buffer with stride different from screen stride" << srcStride << "!=" << q->paddedWidth(); + return; + } + + q->tiles.append(QRect(0, 0, q->width(), q->height())); + std::memcpy(q->fb, src, maxSize); +} + +/** + * @brief PWFrameBuffer::Private::createReceivingStream - create a stream that will consume Pipewire buffers + * and copy the framebuffer to the existing image that we track. The state of the stream and configuration + * are later handled by the corresponding listener. + */ +void PWFrameBuffer::Private::createReceivingStream() +{ + spa_rectangle pwMinScreenBounds = SPA_RECTANGLE(1, 1); + spa_rectangle pwMaxScreenBounds = SPA_RECTANGLE(screenGeometry.width, screenGeometry.height); + + spa_fraction pwFramerateMin = SPA_FRACTION(0, 1); + spa_fraction pwFramerateMax = SPA_FRACTION(60, 1); + + auto reuseProps = pw_properties_new("pipewire.client.reuse", "1", nullptr); // null marks end of varargs + pwStream = pw_stream_new(pwRemote, "krfb-fb-consume-stream", reuseProps); + + uint8_t buffer[1024] = {}; + const spa_pod *params[1]; + auto builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + +#if PW_CHECK_VERSION(0, 2, 9) + params[0] = reinterpret_cast(spa_pod_builder_add_object(&builder, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + ":", SPA_FORMAT_mediaType, "I", SPA_MEDIA_TYPE_video, + ":", SPA_FORMAT_mediaSubtype, "I", SPA_MEDIA_SUBTYPE_raw, + ":", SPA_FORMAT_VIDEO_format, "I", SPA_VIDEO_FORMAT_RGBx, + ":", SPA_FORMAT_VIDEO_size, "?rR", SPA_CHOICE_RANGE(&pwMaxScreenBounds, &pwMinScreenBounds, &pwMaxScreenBounds), + ":", SPA_FORMAT_VIDEO_framerate, "F", &pwFramerateMin, + ":", SPA_FORMAT_VIDEO_maxFramerate, "?rF", SPA_CHOICE_RANGE(&pwFramerateMax, &pwFramerateMin, &pwFramerateMax))); +#else + params[0] = reinterpret_cast(spa_pod_builder_object(&builder, + pwCoreType->param.idEnumFormat, pwCoreType->spa_format, + "I", pwType->media_type.video, + "I", pwType->media_subtype.raw, + ":", pwType->format_video.format, "I", pwType->video_format.RGBx, + ":", pwType->format_video.size, "Rru", &pwMaxScreenBounds, SPA_POD_PROP_MIN_MAX(&pwMinScreenBounds, &pwMaxScreenBounds), + ":", pwType->format_video.framerate, "F", &pwFramerateMin, + ":", pwType->format_video.max_framerate, "Fru", &pwFramerateMax, 2, &pwFramerateMin, &pwFramerateMax)); +#endif + + pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this); + auto flags = static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS); +#if PW_CHECK_VERSION(0, 2, 9) + if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, pwStreamNodeId, flags, params, 1) != 0) { +#else + if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, nullptr, flags, params, 1) != 0) { +#endif + qWarning() << "Could not connect receiving stream"; + isValid = false; + } +} + +PWFrameBuffer::Private::~Private() +{ + if (pwMainLoop) { + pw_thread_loop_stop(pwMainLoop); + } + +#if !PW_CHECK_VERSION(0, 2, 9) + if (pwType) { + delete pwType; + } +#endif + + if (pwStream) { + pw_stream_destroy(pwStream); + } + + if (pwRemote) { + pw_remote_destroy(pwRemote); + } + + if (pwCore) + pw_core_destroy(pwCore); + + if (pwMainLoop) { + pw_thread_loop_destroy(pwMainLoop); + } + + if (pwLoop) { + pw_loop_destroy(pwLoop); + } +} + +PWFrameBuffer::PWFrameBuffer(WId winid, QObject *parent) + : FrameBuffer (winid, parent), + d(new Private(this)) +{ + // D-Bus is most important in init chain, no toys for us if something is wrong with XDP + // PipeWire connectivity is initialized after D-Bus session is started + d->initDbus(); + + // FIXME: for now use some initial size, later on we will reallocate this with the actual size we get from portal + d->screenGeometry.width = 800; + d->screenGeometry.height = 600; + fb = nullptr; +} + +PWFrameBuffer::~PWFrameBuffer() +{ + free(fb); + fb = nullptr; +} + +int PWFrameBuffer::depth() +{ + return 32; +} + +int PWFrameBuffer::height() +{ + return static_cast(d->screenGeometry.height); +} + +int PWFrameBuffer::width() +{ + return static_cast(d->screenGeometry.width); +} + +int PWFrameBuffer::paddedWidth() +{ + return width() * 4; +} + +void PWFrameBuffer::getServerFormat(rfbPixelFormat &format) +{ + format.bitsPerPixel = 32; + format.depth = 32; + format.trueColour = true; + format.bigEndian = false; +} + +void PWFrameBuffer::startMonitor() +{ + +} + +void PWFrameBuffer::stopMonitor() +{ + +} + +QVariant PWFrameBuffer::customProperty(const QString &property) const +{ + if (property == QLatin1String("stream_node_id")) { + return QVariant::fromValue(d->pwStreamNodeId); + } if (property == QLatin1String("session_handle")) { + return QVariant::fromValue(d->sessionPath); + } + + return FrameBuffer::customProperty(property); +} + +bool PWFrameBuffer::isValid() const +{ + return d->isValid; +} diff --git a/framebuffers/pipewire/pw_framebuffer.h b/framebuffers/pipewire/pw_framebuffer.h new file mode 100644 index 00000000..8b4e03b6 --- /dev/null +++ b/framebuffers/pipewire/pw_framebuffer.h @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Oleg Chernovskiy + Copyright (C) 2018 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. +*/ +#ifndef KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H +#define KRFB_FRAMEBUFFER_XCB_XCB_FRAMEBUFFER_H + +#include "framebuffer.h" +#include +#include + +/** + * @brief The PWFrameBuffer class - framebuffer implementation based on XDG Desktop Portal ScreenCast interface. + * The design relies heavily on a presence of XDG D-Bus service and PipeWire daemon. + * + * @author Oleg Chernovskiy + */ +class PWFrameBuffer: public FrameBuffer +{ + Q_OBJECT +public: + typedef struct { + uint nodeId; + QVariantMap map; + } Stream; + typedef QList Streams; + + PWFrameBuffer(WId winid, QObject *parent = nullptr); + virtual ~PWFrameBuffer() override; + + int depth() override; + int height() override; + int width() override; + int paddedWidth() override; + void getServerFormat(rfbPixelFormat &format) override; + void startMonitor() override; + void stopMonitor() override; + + QVariant customProperty(const QString &property) const override; + + bool isValid() const; + +private Q_SLOTS: + void handleXdpSessionCreated(quint32 code, QVariantMap results); + void handleXdpDevicesSelected(quint32 code, QVariantMap results); + void handleXdpSourcesSelected(quint32 code, QVariantMap results); + void handleXdpRemoteDesktopStarted(quint32 code, QVariantMap results); + +private: + class Private; + const QScopedPointer d; +}; + +#endif diff --git a/framebuffers/pipewire/pw_framebufferplugin.cpp b/framebuffers/pipewire/pw_framebufferplugin.cpp new file mode 100644 index 00000000..791b239d --- /dev/null +++ b/framebuffers/pipewire/pw_framebufferplugin.cpp @@ -0,0 +1,53 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "pw_framebufferplugin.h" +#include "pw_framebuffer.h" +#include + + +K_PLUGIN_FACTORY_WITH_JSON(PWFrameBufferPluginFactory, "krfb_framebuffer_pw.json", + registerPlugin();) + +PWFrameBufferPlugin::PWFrameBufferPlugin(QObject *parent, const QVariantList &args) + : FrameBufferPlugin(parent, args) +{ +} + + +PWFrameBufferPlugin::~PWFrameBufferPlugin() +{ +} + + +FrameBuffer *PWFrameBufferPlugin::frameBuffer(WId id) +{ + auto pwfb = new PWFrameBuffer(id); + + // sanity check for dbus/wayland/pipewire errors + if (!pwfb->isValid()) { + delete pwfb; + return nullptr; + } + + return pwfb; +} + +#include "pw_framebufferplugin.moc" diff --git a/framebuffers/pipewire/pw_framebufferplugin.h b/framebuffers/pipewire/pw_framebufferplugin.h new file mode 100644 index 00000000..e071585a --- /dev/null +++ b/framebuffers/pipewire/pw_framebufferplugin.h @@ -0,0 +1,45 @@ + /* This file is part of the KDE project + Copyright (C) 2018 Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KRFB_FRAMEBUFFER_PW_PWFRAMEBUFFERPLUGIN_H +#define KRFB_FRAMEBUFFER_PW_PWFRAMEBUFFERPLUGIN_H + + +#include "framebufferplugin.h" +#include + + +class FrameBuffer; + +class PWFrameBufferPlugin: public FrameBufferPlugin +{ + Q_OBJECT + +public: + PWFrameBufferPlugin(QObject *parent, const QVariantList &args); + virtual ~PWFrameBufferPlugin() override; + + FrameBuffer *frameBuffer(WId id) override; + +private: + Q_DISABLE_COPY(PWFrameBufferPlugin) +}; + + +#endif // Header guard diff --git a/krfb.kdev4 b/krfb.kdev4 deleted file mode 100644 index b521cf3c..00000000 --- a/krfb.kdev4 +++ /dev/null @@ -1,3 +0,0 @@ -[Project] -Manager=KDevCMakeManager -Name=krfb diff --git a/krfb/CMakeLists.txt b/krfb/CMakeLists.txt index 1064a010..1041c97d 100644 --- a/krfb/CMakeLists.txt +++ b/krfb/CMakeLists.txt @@ -11,6 +11,8 @@ include(GenerateExportHeader) set (krfbprivate_SRCS framebuffer.cpp framebufferplugin.cpp + events.cpp + eventsplugin.cpp ) add_library (krfbprivate @@ -19,6 +21,7 @@ add_library (krfbprivate ) generate_export_header(krfbprivate BASE_NAME krfbprivate) + target_link_libraries (krfbprivate Qt5::Core Qt5::Widgets @@ -39,6 +42,7 @@ install (TARGETS krfbprivate install (FILES krfb-framebuffer.desktop + krfb-events.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) @@ -48,8 +52,9 @@ install (FILES set (krfb_SRCS connectiondialog.cpp - events.cpp framebuffermanager.cpp + events.cpp + eventsmanager.cpp main.cpp mainwindow.cpp sockethelpers.cpp @@ -123,4 +128,4 @@ install(FILES org.kde.krfb.appdata.xml install (FILES krfb.notifyrc DESTINATION ${DATA_INSTALL_DIR}/krfb ) - + diff --git a/krfb/events.cpp b/krfb/events.cpp index 24be95df..25cee0d8 100644 --- a/krfb/events.cpp +++ b/krfb/events.cpp @@ -24,177 +24,20 @@ #include "events.h" -#include -#include -#include -#include - -#include -#include -#include - -enum { - LEFTSHIFT = 1, - RIGHTSHIFT = 2, - ALTGR = 4 -}; - -class EventData +EventHandler::EventHandler(QObject *parent) + : QObject(parent) { -public: - EventData(); - - //keyboard - Display *dpy; - signed char modifiers[0x100]; - KeyCode keycodes[0x100]; - KeyCode leftShiftCode; - KeyCode rightShiftCode; - KeyCode altGrCode; - char modifierState; - - //mouse - int buttonMask; - -private: - void init(); -}; - -Q_GLOBAL_STATIC(EventData, data) - -EventData::EventData() -{ - init(); } -void EventData::init() +void EventHandler::setFrameBufferPlugin(const QSharedPointer &frameBuffer) { - dpy = QX11Info::display(); - buttonMask = 0; - - //initialize keycodes - KeySym key, *keymap; - int i, j, minkey, maxkey, syms_per_keycode; - - memset(modifiers, -1, sizeof(modifiers)); - - XDisplayKeycodes(dpy, &minkey, &maxkey); - Q_ASSERT(minkey >= 8); - Q_ASSERT(maxkey < 256); - keymap = (KeySym *) XGetKeyboardMapping(dpy, minkey, - (maxkey - minkey + 1), - &syms_per_keycode); - Q_ASSERT(keymap); - - for (i = minkey; i <= maxkey; i++) { - for (j = 0; j < syms_per_keycode; j++) { - key = keymap[(i-minkey)*syms_per_keycode+j]; - - if (key >= ' ' && key < 0x100 && i == XKeysymToKeycode(dpy, key)) { - keycodes[key] = i; - modifiers[key] = j; - } - } - } - - leftShiftCode = XKeysymToKeycode(dpy, XK_Shift_L); - rightShiftCode = XKeysymToKeycode(dpy, XK_Shift_R); - altGrCode = XKeysymToKeycode(dpy, XK_Mode_switch); - - XFree((char *)keymap); + fb = frameBuffer; } -/* this function adjusts the modifiers according to mod (as from modifiers) and data->modifierState */ -static void tweakModifiers(signed char mod, bool down) +QSharedPointer EventHandler::frameBuffer() { - bool isShift = data->modifierState & (LEFTSHIFT | RIGHTSHIFT); - - if (mod < 0) { - return; - } - - if (isShift && mod != 1) { - if (data->modifierState & LEFTSHIFT) { - XTestFakeKeyEvent(data->dpy, data->leftShiftCode, - down, CurrentTime); - } - - if (data->modifierState & RIGHTSHIFT) { - XTestFakeKeyEvent(data->dpy, data->rightShiftCode, - down, CurrentTime); - } - } - - if (!isShift && mod == 1) { - XTestFakeKeyEvent(data->dpy, data->leftShiftCode, - down, CurrentTime); - } - - if ((data->modifierState & ALTGR) && mod != 2) { - XTestFakeKeyEvent(data->dpy, data->altGrCode, - !down, CurrentTime); - } - - if (!(data->modifierState & ALTGR) && mod == 2) { - XTestFakeKeyEvent(data->dpy, data->altGrCode, - down, CurrentTime); - } + return fb; } -void EventHandler::handleKeyboard(bool down, rfbKeySym keySym) -{ -#define ADJUSTMOD(sym,state) \ - if(keySym==sym) { if(down) data->modifierState|=state; else data->modifierState&=~state; } +#include "events.moc" - ADJUSTMOD(XK_Shift_L, LEFTSHIFT); - ADJUSTMOD(XK_Shift_R, RIGHTSHIFT); - ADJUSTMOD(XK_Mode_switch, ALTGR); - - if (keySym >= ' ' && keySym < 0x100) { - KeyCode k; - - if (down) { - tweakModifiers(data->modifiers[keySym], True); - } - - k = data->keycodes[keySym]; - - if (k != NoSymbol) { - XTestFakeKeyEvent(data->dpy, k, down, CurrentTime); - } - - if (down) { - tweakModifiers(data->modifiers[keySym], False); - } - } else { - KeyCode k = XKeysymToKeycode(data->dpy, keySym); - - if (k != NoSymbol) { - XTestFakeKeyEvent(data->dpy, k, down, CurrentTime); - } - } -} - -void EventHandler::handlePointer(int buttonMask, int x, int y) -{ - QDesktopWidget *desktopWidget = QApplication::desktop(); - - int screen = desktopWidget->screenNumber(); - - if (screen < 0) { - screen = 0; - } - - XTestFakeMotionEvent(data->dpy, screen, x, y, CurrentTime); - - for (int i = 0; i < 5; i++) { - if ((data->buttonMask&(1 << i)) != (buttonMask&(1 << i))) { - XTestFakeButtonEvent(data->dpy, - i + 1, - (buttonMask&(1 << i)) ? True : False, - CurrentTime); - } - } - - data->buttonMask = buttonMask; -} diff --git a/krfb/events.h b/krfb/events.h index ac5a230e..babf8a17 100644 --- a/krfb/events.h +++ b/krfb/events.h @@ -25,13 +25,26 @@ #ifndef EVENTS_H #define EVENTS_H +#include "framebuffer.h" #include "rfb.h" +#include "krfbprivate_export.h" -class EventHandler +#include + +class KRFBPRIVATE_EXPORT EventHandler : public QObject { + Q_OBJECT public: - static void handleKeyboard(bool down, rfbKeySym key); - static void handlePointer(int buttonMask, int x, int y); + explicit EventHandler(QObject *parent = nullptr); + virtual ~EventHandler() = default; + virtual void handleKeyboard(bool down, rfbKeySym key) = 0; + virtual void handlePointer(int buttonMask, int x, int y) = 0; + + void setFrameBufferPlugin(const QSharedPointer &frameBuffer); + QSharedPointer frameBuffer(); +private: + // Used to track framebuffer plugin which we need for xdp event plugin + QSharedPointer fb; }; #endif diff --git a/krfb/eventsmanager.cpp b/krfb/eventsmanager.cpp new file mode 100644 index 00000000..e1113a14 --- /dev/null +++ b/krfb/eventsmanager.cpp @@ -0,0 +1,120 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Collabora Ltd + @author George Goldberg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "eventsmanager.h" + +#include "eventsplugin.h" +#include "krfbconfig.h" +#include "rfbservermanager.h" + +#include +#include + +#include +#include +#include + +#include + +class EventsManagerStatic +{ +public: + EventsManager instance; +}; + +Q_GLOBAL_STATIC(EventsManagerStatic, eventsManagerStatic) + +EventsManager::EventsManager() +{ + //qDebug(); + + loadPlugins(); +} + +EventsManager::~EventsManager() +{ + //qDebug(); +} + +EventsManager *EventsManager::instance() +{ + //qDebug(); + + return &eventsManagerStatic->instance; +} + +void EventsManager::loadPlugins() +{ + //qDebug(); + + const QVector plugins = KPluginLoader::findPlugins(QStringLiteral("krfb"), [](const KPluginMetaData & md) { + return md.serviceTypes().contains(QStringLiteral("krfb/events")); + }); + + QVectorIterator i(plugins); + i.toBack(); + QSet unique; + while (i.hasPrevious()) { + KPluginMetaData data = i.previous(); + // only load plugins once, even if found multiple times! + if (unique.contains(data.name())) + continue; + KPluginFactory *factory = KPluginLoader(data.fileName()).factory(); + + if (!factory) { + qDebug() << "KPluginFactory could not load the plugin:" << data.fileName(); + } else { + qDebug() << "found plugin at " << data.fileName(); + } + + EventsPlugin *plugin = factory->create(this); + if (plugin) { + m_plugins.insert(data.pluginId(), plugin); + qDebug() << "Loaded plugin with name " << data.pluginId(); + } else { + qDebug() << "unable to load plugin for " << data.fileName(); + } + unique.insert (data.name()); + } +} + +QSharedPointer EventsManager::eventHandler() +{ + QMap::const_iterator iter = m_plugins.constBegin(); + + while (iter != m_plugins.constEnd()) { + + QSharedPointer eventHandler(iter.value()->eventHandler()); + + if (eventHandler) { + eventHandler->setFrameBufferPlugin(RfbServerManager::instance()->framebuffer()); + return eventHandler; + } + + ++iter; + } + + // No valid events plugin found. + qDebug() << "No valid event handlers found. returning null."; + return QSharedPointer(); +} + + +#include "eventsmanager.moc" diff --git a/krfb/eventsmanager.h b/krfb/eventsmanager.h new file mode 100644 index 00000000..19099baf --- /dev/null +++ b/krfb/eventsmanager.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Collabora Ltd + @author George Goldberg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KRFB_EVENTSMANAGER_H +#define KRFB_EVENTSMANAGER_H + +#include "events.h" + +#include "krfbprivate_export.h" + +#include +#include +#include +#include + +#include + +class EventsPlugin; +class KPluginFactory; + +class KRFBPRIVATE_EXPORT EventsManager : public QObject +{ + Q_OBJECT + friend class EventsManagerStatic; + +public: + static EventsManager *instance(); + + virtual ~EventsManager(); + + QSharedPointer eventHandler(); + +private: + Q_DISABLE_COPY(EventsManager) + + EventsManager(); + + void loadPlugins(); + + QMap m_plugins; + QList > m_eventHandlers; +}; + + +#endif // Header guard diff --git a/krfb/eventsplugin.cpp b/krfb/eventsplugin.cpp new file mode 100644 index 00000000..662de58b --- /dev/null +++ b/krfb/eventsplugin.cpp @@ -0,0 +1,34 @@ + +/* This file is part of the KDE project + Copyright (C) 2016 Oleg Chernovskiy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "eventsplugin.h" + +#include "events.h" + +EventsPlugin::EventsPlugin(QObject *parent, const QVariantList &) + : QObject(parent) +{ +} + +EventsPlugin::~EventsPlugin() +{ +} + +#include "eventsplugin.moc" diff --git a/krfb/eventsplugin.h b/krfb/eventsplugin.h new file mode 100644 index 00000000..9257f9bf --- /dev/null +++ b/krfb/eventsplugin.h @@ -0,0 +1,42 @@ +/* This file is part of the KDE project + Copyright (C) 2009 Collabora Ltd + @author George Goldberg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef LIB_KRFB_EVENTSPLUGIN_H +#define LIB_KRFB_EVENTSPLUGIN_H + +#include "krfbprivate_export.h" + +#include +#include + +class EventHandler; + +class KRFBPRIVATE_EXPORT EventsPlugin : public QObject +{ + Q_OBJECT +public: + EventsPlugin(QObject *parent, const QVariantList &args); + virtual ~EventsPlugin(); + + virtual EventHandler *eventHandler() = 0; +}; + +#endif // Header guard + diff --git a/krfb/framebuffer.cpp b/krfb/framebuffer.cpp index 5eef1b51..507364c0 100644 --- a/krfb/framebuffer.cpp +++ b/krfb/framebuffer.cpp @@ -50,6 +50,11 @@ void FrameBuffer::getServerFormat(rfbPixelFormat &) { } +QVariant FrameBuffer::customProperty(const QString &property) const +{ + return QVariant(); +} + int FrameBuffer::depth() { return 32; diff --git a/krfb/framebuffer.h b/krfb/framebuffer.h index 99828e1a..e7256f03 100644 --- a/krfb/framebuffer.h +++ b/krfb/framebuffer.h @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -44,6 +45,10 @@ public: virtual void getServerFormat(rfbPixelFormat &format); + virtual QVariant customProperty(const QString &property) const; +Q_SIGNALS: + void frameBufferChanged(); + protected: WId win; char *fb; diff --git a/krfb/krfb-events.desktop b/krfb/krfb-events.desktop new file mode 100644 index 00000000..c61b34f2 --- /dev/null +++ b/krfb/krfb-events.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=krfb/events + +Comment=Event plugins for KRfb +Comment[ru]=Модули обработки событий для Krfb +Comment[x-test]=xxEvent plugins for KRfbxx + diff --git a/krfb/krfb-events.json b/krfb/krfb-events.json new file mode 100644 index 00000000..1f9c3667 --- /dev/null +++ b/krfb/krfb-events.json @@ -0,0 +1,7 @@ +{ + "KPlugin": { + "Description": "Events plugins for KRfb", + "Description[x-test]": "xxFrame Buffer plugins for KRfbxx" + }, + "X-KDE-ServiceType": "krfb/events" +} diff --git a/krfb/main.cpp b/krfb/main.cpp index 0b92eab9..a30123f6 100644 --- a/krfb/main.cpp +++ b/krfb/main.cpp @@ -39,7 +39,6 @@ static const char description[] = I18N_NOOP("VNC-compatible server to share " "desktops"); - static bool checkX11Capabilities() { int bp1, bp2, majorv, minorv; @@ -57,7 +56,6 @@ static bool checkX11Capabilities() return true; } - static void checkOldX11PluginConfig() { if (KrfbConfig::preferredFrameBufferPlugin() == QStringLiteral("x11")) { qDebug() << "Detected deprecated configuration: preferredFrameBufferPlugin = x11"; @@ -71,7 +69,6 @@ static void checkOldX11PluginConfig() { } } - int main(int argc, char *argv[]) { QApplication app(argc, argv); diff --git a/krfb/rfbclient.cpp b/krfb/rfbclient.cpp index d2156a43..fcf83598 100644 --- a/krfb/rfbclient.cpp +++ b/krfb/rfbclient.cpp @@ -21,7 +21,7 @@ #include "connectiondialog.h" #include "krfbconfig.h" #include "sockethelpers.h" -#include "events.h" +#include "eventsmanager.h" #include #include #include @@ -37,6 +37,7 @@ struct RfbClient::Private bool controlEnabled; rfbClientPtr client; QSocketNotifier *notifier; + QSharedPointer eventHandler; QString remoteAddressString; }; @@ -49,6 +50,8 @@ RfbClient::RfbClient(rfbClientPtr client, QObject* parent) d->notifier = new QSocketNotifier(client->sock, QSocketNotifier::Read, this); d->notifier->setEnabled(false); connect(d->notifier, &QSocketNotifier::activated, this, &RfbClient::onSocketActivated); + + d->eventHandler = EventsManager::instance()->eventHandler(); } RfbClient::~RfbClient() @@ -110,14 +113,14 @@ rfbClientPtr RfbClient::getRfbClientPtr() void RfbClient::handleKeyboardEvent(bool down, rfbKeySym keySym) { if (d->controlEnabled) { - EventHandler::handleKeyboard(down, keySym); + d->eventHandler->handleKeyboard(down, keySym); } } void RfbClient::handleMouseEvent(int buttonMask, int x, int y) { if (d->controlEnabled) { - EventHandler::handlePointer(buttonMask, x, y); + d->eventHandler->handlePointer(buttonMask, x, y); } } diff --git a/krfb/rfbserver.cpp b/krfb/rfbserver.cpp index d9a813a9..252aa605 100644 --- a/krfb/rfbserver.cpp +++ b/krfb/rfbserver.cpp @@ -24,6 +24,7 @@ #include #include #include +#include struct RfbServer::Private { @@ -143,8 +144,11 @@ bool RfbServer::start() d->ipv6notifier = new QSocketNotifier(d->screen->listen6Sock, QSocketNotifier::Read, this); connect(d->ipv6notifier, &QSocketNotifier::activated, this, &RfbServer::onListenSocketActivated); } - connect(QApplication::clipboard(), &QClipboard::dataChanged, - this, &RfbServer::krfbSendServerCutText); + + if (QX11Info::isPlatformX11()) { + connect(QApplication::clipboard(), &QClipboard::dataChanged, + this, &RfbServer::krfbSendServerCutText); + } return true; } @@ -162,6 +166,17 @@ void RfbServer::stop() } } +void RfbServer::updateFrameBuffer(char *fb, int width, int height, int depth) +{ + int bpp = depth >> 3; + + if (bpp != 1 && bpp != 2 && bpp != 4) { + bpp = 4; + } + + rfbNewFramebuffer(d->screen, fb, width, height, 8, 3, bpp); +} + void RfbServer::updateScreen(const QList & modifiedTiles) { if (d->screen) { diff --git a/krfb/rfbserver.h b/krfb/rfbserver.h index 51b106d1..22de2993 100644 --- a/krfb/rfbserver.h +++ b/krfb/rfbserver.h @@ -43,6 +43,7 @@ public Q_SLOTS: virtual bool start(); virtual void stop(); + void updateFrameBuffer(char *fb, int width, int height, int depth); void updateScreen(const QList & modifiedTiles); void updateCursorPosition(const QPoint & position); diff --git a/krfb/rfbservermanager.cpp b/krfb/rfbservermanager.cpp index 1a40ae85..e7262c4c 100644 --- a/krfb/rfbservermanager.cpp +++ b/krfb/rfbservermanager.cpp @@ -90,7 +90,6 @@ RfbServerManager* RfbServerManager::instance() return &s_instance->server; } - struct RfbServerManager::Private { QSharedPointer fb; @@ -113,6 +112,11 @@ RfbServerManager::~RfbServerManager() delete d; } +QSharedPointer RfbServerManager::framebuffer() const +{ + return d->fb; +} + void RfbServerManager::init() { //qDebug(); @@ -123,10 +127,18 @@ void RfbServerManager::init() d->desktopName = QStringLiteral("%1@%2 (shared desktop)") //FIXME check if we can use utf8 .arg(KUser().loginName(),QHostInfo::localHostName()).toLatin1(); + connect(d->fb.data(), &FrameBuffer::frameBufferChanged, this, &RfbServerManager::updateFrameBuffer); connect(&d->rfbUpdateTimer, &QTimer::timeout, this, &RfbServerManager::updateScreens); connect(qApp, &QApplication::aboutToQuit, this, &RfbServerManager::cleanup); } +void RfbServerManager::updateFrameBuffer() +{ + Q_FOREACH(RfbServer *server, d->servers) { + server->updateFrameBuffer(d->fb->data(), d->fb->width(), d->fb->height(), d->fb->depth()); + } +} + void RfbServerManager::updateScreens() { QList rects = d->fb->modifiedTiles(); @@ -194,7 +206,6 @@ rfbScreenInfoPtr RfbServerManager::newScreen() screen->paddedWidthInBytes = d->fb->paddedWidth(); d->fb->getServerFormat(screen->serverFormat); screen->frameBuffer = d->fb->data(); - screen->desktopName = d->desktopName.constData(); screen->cursor = d->myCursor; } diff --git a/krfb/rfbservermanager.h b/krfb/rfbservermanager.h index a4af4092..4a1a58c7 100644 --- a/krfb/rfbservermanager.h +++ b/krfb/rfbservermanager.h @@ -21,6 +21,7 @@ #define RFBSERVERMANAGER_H #include "rfb.h" +#include "framebuffer.h" #include class RfbClient; @@ -33,12 +34,14 @@ class RfbServerManager : public QObject public: static RfbServerManager *instance(); + QSharedPointer framebuffer() const; Q_SIGNALS: void clientConnected(RfbClient *cc); void clientDisconnected(RfbClient *cc); private Q_SLOTS: void init(); + void updateFrameBuffer(); void updateScreens(); void cleanup();