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
This commit is contained in:
Jan Grulich
2019-05-20 08:10:30 +02:00
parent 7624aee8e2
commit c05707884c
44 changed files with 2751 additions and 183 deletions

View File

@@ -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)

View File

@@ -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 <alex.merry@kde.org>
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
# Copyright 2018 Jan Grulich <jgrulich@redhat.com>
#
# 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"
)

View File

@@ -0,0 +1,374 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2017-2018 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.portal.RemoteDesktop:
@short_description: Remote desktop portal
The Remote desktop portal allows to create remote desktop sessions.
This documentation describes version 1 of this interface.
-->
<interface name="org.freedesktop.portal.RemoteDesktop">
<!--
CreateSession:
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Create a remote desktop session.
A remote desktop session is used to allow remote controlling a desktop
session. It can also be used together with a screen cast session (see
org.freedesktop.portal.ScreenCast), but may only be started and stopped
with this interface.
To also get a screen content, call the
#org.freedesktop.ScreenCast.SelectSources with the
#org.freedesktop.Session object created with this method.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
<varlistentry>
<term>session_handle_token s</term>
<listitem><para>
A string that will be used as the last element of the session handle. Must be a valid
object path element. See the #org.freedesktop.portal.Session documentation for
more information about the session handle.
</para></listitem>
</varlistentry>
</variablelist>
The following results get returned via the #org.freedesktop.portal.Request::Response signal:
<variablelist>
<varlistentry>
<term>session_handle o</term>
<listitem><para>
The session handle. An object path for the
#org.freedesktop.portal.Session object representing the created
session.
</para></listitem>
</varlistentry>
</variablelist>
-->
<method name="CreateSession">
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
SelectDevices:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Select input devices to remote control.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
<varlistentry>
<term>type u</term>
<listitem><para>
Bitmask of what device types to request remote controlling of.
Default is all.
</para></listitem>
</varlistentry>
</variablelist>
For available source types, see the AvailableDeviceTypes property.
-->
<method name="SelectDevices">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
Start:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link>
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Start the remote desktop session. This will typically result in the portal
presenting a dialog letting the user select what to share, including
devices and optionally screen content if screen cast sources was
selected.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
</variablelist>
The following results get returned via the
#org.freedesktop.portal.Request::Response signal:
<variablelist>
<varlistentry>
<term>devices u</term>
<listitem><para>
A bitmask of the devices selected by the user.
</para></listitem>
</varlistentry>
</variablelist>
If a screen cast source was selected, the results of the
#org.freedesktop.portal.ScreenCast.Start response signal may be
included.
-->
<method name="Start">
<arg type="o" name="session_handle" direction="in"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
NotifyPointerMotion:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@dx: Relative movement on the x axis
@dy: Relative movement on the y axis
Notify about a new relative pointer motion event. The (dx, dy) vector
represents the new pointer position in the streams logical coordinate
space.
-->
<method name="NotifyPointerMotion">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="d" name="dx" direction="in"/>
<arg type="d" name="dy" direction="in"/>
</method>
<!--
NotifyPointerMotionAbsolute:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@stream: The PipeWire stream node the coordinate is relative to
@x: Pointer motion x coordinate
@y: Pointer motion y coordinate
Notify about a new absolute pointer motion event. The (x, y) position
represents the new pointer position in the streams logical coordinate
space (see the logical_size stream property in
#org.freedesktop.portal.ScreenCast).
-->
<method name="NotifyPointerMotionAbsolute">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="u" name="stream" direction="in"/>
<arg type="d" name="x" direction="in"/>
<arg type="d" name="y" direction="in"/>
</method>
<!--
NotifyPointerButton:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@button: The pointer button was pressed or released
@state: The new state of the button
The pointer button is encoded according to Linux Evdev button codes.
May only be called if POINTER access was provided after starting the
session.
Available button states:
<simplelist>
<member>0: Released</member>
<member>1: Pressed</member>
</simplelist>
-->
<method name="NotifyPointerButton">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="i" name="button" direction="in"/>
<arg type="u" name="state" direction="in"/>
</method>
<!--
NotifyPointerAxis:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@dx: Relative axis movement on the x axis
@dy: Relative axis movement on the y axis
The axis movement from a 'smooth scroll' device, such as a touchpad.
When applicable, the size of the motion delta should be equivalent to
the motion vector of a pointer motion done using the same advice.
May only be called if POINTER access was provided after starting the
session.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>finish b</term>
<listitem><para>
If set to true, this is the last axis event in a series, for
example as a result of the fingers being lifted from a touchpad
after a two-finger scroll. Default is false.
</para></listitem>
</varlistentry>
</variablelist>
-->
<method name="NotifyPointerAxis">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="d" name="dx" direction="in"/>
<arg type="d" name="dy" direction="in"/>
</method>
<!--
NotifyPointerAxisDiscrete:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@axis: The axis that was scrolled
@steps: The number of steps scrolled
May only be called if POINTER access was provided after starting the
session.
Available axes:
<simplelist>
<member>0: Vertical scroll</member>
<member>1: Horizontal scroll</member>
</simplelist>
-->
<method name="NotifyPointerAxisDiscrete">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="u" name="axis" direction="in"/>
<arg type="i" name="steps" direction="in"/>
</method>
<!--
NotifyKeyboardKeycode:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@keycode: Keyboard code that was pressed or released
@state: New state of keyboard keysym
May only be called if KEYBOARD access was provided after starting the
session.
Available keyboard keysym states:
<simplelist>
<member>0: Released</member>
<member>1: Pressed</member>
</simplelist>
-->
<method name="NotifyKeyboardKeycode">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="i" name="keycode" direction="in"/>
<arg type="u" name="state" direction="in"/>
</method>
<!--
NotifyKeyboardKeysym:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@keysym: Keyboard symbol that was pressed or released
@state: New state of keyboard keysym
May only be called if KEYBOARD access was provided after starting the
session.
Available keyboard keysym states:
<simplelist>
<member>0: Released</member>
<member>1: Pressed</member>
</simplelist>
-->
<method name="NotifyKeyboardKeysym">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="i" name="keysym" direction="in"/>
<arg type="u" name="state" direction="in"/>
</method>
<!--
NotifyTouchDown:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@stream: The PipeWire stream node the coordinate is relative to
@slot: Touch slot where touch point appeared
@x: Touch down x coordinate
@y: Touch down y coordinate
May only be called if TOUCHSCREEN access was provided after starting the
session.
Notify about a new touch down event. The (x, y) position
represents the new touch point position in the streams logical
coordinate space (see the logical_size stream property in
#org.freedesktop.portal.ScreenCast).
-->
<method name="NotifyTouchDown">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="u" name="stream" direction="in"/>
<arg type="u" name="slot" direction="in"/>
<arg type="d" name="x" direction="in"/>
<arg type="d" name="y" direction="in"/>
</method>
<!--
NotifyTouchMotion:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@stream: The PipeWire stream node the coordinate is relative to
@slot: Touch slot where touch point appeared
@x: Touch motion x coordinate
@y: Touch motion y coordinate
May only be called if TOUCHSCREEN access was provided after starting the
session.
Notify about a new touch motion event. The (x, y) position
represents where the touch point position in the streams logical
coordinate space moved (see the logical_size stream property in
#org.freedesktop.portal.ScreenCast).
-->
<method name="NotifyTouchMotion">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="u" name="stream" direction="in"/>
<arg type="u" name="slot" direction="in"/>
<arg type="d" name="x" direction="in"/>
<arg type="d" name="y" direction="in"/>
</method>
<!--
NotifyTouchUp:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@slot: Touch slot where touch point appeared
May only be called if TOUCHSCREEN access was provided after starting the
session.
Notify about a new touch up event.
-->
<method name="NotifyTouchUp">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="u" name="slot" direction="in"/>
</method>
<!--
AvailableDeviceTypes:
A bitmask of available source types. Currently defined types are:
<simplelist>
<member>1: KEYBOARD</member>
<member>2: POINTER</member>
<member>4: TOUCHSCREEN</member>
</simplelist>
-->
<property name="AvailableDeviceTypes" type="u" access="read"/>
<property name="version" type="u" access="read"/>
</interface>
</node>

View File

@@ -0,0 +1,186 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2017-2018 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.portal.ScreenCast:
@short_description: Screen cast portal
-->
<interface name="org.freedesktop.portal.ScreenCast">
<!--
CreateSession:
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Create a screen cast session. A successfully created session can at
any time be closed using org.freedesktop.portal.Session::Close, or may
at any time be closed by the portal implementation, which will be
signalled via org.freedesktop.portal.Session::Closed.
The following results get returned via the #org.freedesktop.portal.Request::Response signal:
<variablelist>
<varlistentry>
<term>session_handle o</term>
<listitem><para>
The session handle. An object path for the
#org.freedesktop.portal.Session object representing the created
session.
</para></listitem>
</varlistentry>
</variablelist>
-->
<method name="CreateSession">
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
SelectSources:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Configure what the screen cast session should record. This method must
be called before starting the session.
Passing invalid input to this method will cause the session to be
closed. An application may only attempt to select sources once per
session.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>types u</term>
<listitem><para>
Bitmask of what types of content to record. Default is MONITOR.
</para></listitem>
</varlistentry>
<varlistentry>
<term>multiple b</term>
<listitem><para>
Whether to allow selecting multiple sources. Default is no.
</para></listitem>
</varlistentry>
</variablelist>
For available source types, see the AvailableSourceTypes property.
-->
<method name="SelectSources">
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
Start:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@parent_window: Identifier for the application window
@options: Vardict with optional further information
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Start the screen cast session. This will typically result the portal
presenting a dialog letting the user do the selection set up by
SelectSources. An application can only attempt start a session once.
A screen cast session may only be started after having selected sources
using org.freedesktop.portal.ScreenCast::SelectSources.
The @parent_window identifier must be of the form "x11:$XID" for an X11
window. Support for other window systems may be added in the future.
The following results get returned via the
#org.freedesktop.portal.Request::Response signal:
<variablelist>
<varlistentry>
<term>streams a(ua{sv})</term>
<listitem><para>
An array of PipeWire streams. Each stream consists of a PipeWire
node ID (the first element in the tuple, and a Vardict of
properties.
The array will contain a single stream if 'multiple' (see
SelectSources) was set to 'false', or at least one stream if
'multiple' was set to 'true' as part of the SelectSources method.
</para></listitem>
</varlistentry>
</variablelist>
Stream properties include:
<variablelist>
<varlistentry>
<term>position (ii)</term>
<listitem><para>
A tuple consisting of the position (x, y) in the compositor
coordinate space. Note that the position may not be equivalent to a
position in a pixel coordinate space. Only available for monitor
streams.
</para></listitem>
</varlistentry>
<varlistentry>
<term>size (ii)</term>
<listitem><para>
A tuple consisting of (width, height). The size represents the size
of the stream as it is displayed in the compositor coordinate
space. Note that this size may not be equivalent to a size in a
pixel coordinate space. The size may differ from the size of the
stream.
</para></listitem>
</varlistentry>
</variablelist>
-->
<method name="Start">
<arg type="o" name="session_handle" direction="in"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
OpenPipeWireRemote:
@session_handle: Object path for the #org.freedesktop.portal.Session object
@options: Vardict with optional further information
@fd: File descriptor of an open PipeWire remote.
Open a file descriptor to the PipeWire remote where the screen cast
streams are available. The file descriptor should be used to create a
<classname>pw_remote</classname> object, by using
<function>pw_remote_connect_fd</function>. Only the screen cast stream
nodes will be available from this PipeWire node.
-->
<method name="OpenPipeWireRemote">
<annotation name="org.gtk.GDBus.C.Name" value="open_pipewire_remote"/>
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="o" name="session_handle" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
<arg type="h" name="fd" direction="out"/>
</method>
<!--
AvailableSourceTypes:
A bitmask of available source types. Currently defined types are:
<simplelist>
<member>1: MONITOR</member>
<member>2: WINDOW</member>
</simplelist>
-->
<property name="AvailableSourceTypes" type="u" access="read"/>
<property name="version" type="u" access="read"/>
</interface>
</node>

6
events/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
add_subdirectory(x11)
# Makes sense to use only when PW framebuffer is used
if (${PipeWire_FOUND})
add_subdirectory(xdp)
endif()

18
events/x11/CMakeLists.txt Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -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"
}
}

212
events/x11/x11events.cpp Normal file
View File

@@ -0,0 +1,212 @@
/*
This file is part of the KDE project
Copyright (C) 2016 by Oleg Chernovskiy <kanedias@xaker.ru>
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 <QApplication>
#include <QX11Info>
#include <QDesktopWidget>
#include <QGlobalStatic>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/XTest.h>
#include <QX11Info>
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"

41
events/x11/x11events.h Normal file
View File

@@ -0,0 +1,41 @@
/*
This file is part of the KDE project
Copyright (C) 2016 by Oleg Chernovskiy <kanedias@xaker.ru>
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

View File

@@ -0,0 +1,45 @@
/* This file is part of the KDE project
Copyright (C) 2016 Oleg Chernovskiy <kanedias@xaker.ru>
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 <KPluginFactory>
#include <QX11Info>
K_PLUGIN_FACTORY_WITH_JSON(X11EventsPluginFactory, "krfb_events_x11.json",
registerPlugin<X11EventsPlugin>();)
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"

View File

@@ -0,0 +1,43 @@
/* This file is part of the KDE project
Copyright (C) 2016 Oleg Chernovskiy <kanedias@xaker.ru>
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 <QWidget>
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

27
events/xdp/CMakeLists.txt Normal file
View File

@@ -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
)

View File

@@ -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

View File

@@ -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"
}
}

128
events/xdp/xdpevents.cpp Normal file
View File

@@ -0,0 +1,128 @@
/*
This file is part of the KDE project
Copyright (C) 2018-2019 Jan Grulich <jgrulich@redhat.com>
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 <linux/input.h>
#include <QApplication>
#include <QDesktopWidget>
#include <QGlobalStatic>
class EventData
{
public:
EventData();
//mouse
int buttonMask;
int x;
int y;
QScopedPointer<OrgFreedesktopPortalRemoteDesktopInterface> 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<QDBusObjectPath>();
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<int> 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"

37
events/xdp/xdpevents.h Normal file
View File

@@ -0,0 +1,37 @@
/*
This file is part of the KDE project
Copyright (C) 2018-2019 Jan Grulich <jgrulich@redhat.com>
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

View File

@@ -0,0 +1,43 @@
/*
This file is part of the KDE project
Copyright (C) 2018-2019 Jan Grulich <jgrulich@redhat.com>
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 <KPluginFactory>
K_PLUGIN_FACTORY_WITH_JSON(XdpEventsPluginFactory, "krfb_events_xdp.json",
registerPlugin<XdpEventsPlugin>();)
XdpEventsPlugin::XdpEventsPlugin(QObject *parent, const QVariantList &args)
: EventsPlugin(parent, args)
{
}
EventHandler *XdpEventsPlugin::eventHandler()
{
// works only under Wayland
return new XdpEventHandler();
}
#include "xdpeventsplugin.moc"

View File

@@ -0,0 +1,46 @@
/*
This file is part of the KDE project
Copyright (C) 2018-2019 Jan Grulich <jgrulich@redhat.com>
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 <QWidget>
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

View File

@@ -3,3 +3,7 @@ add_subdirectory (qt)
if (${XCB_DAMAGE_FOUND} AND ${XCB_SHM_FOUND} AND ${XCB_IMAGE_FOUND})
add_subdirectory (xcb)
endif()
if (${PipeWire_FOUND})
add_subdirectory(pipewire)
endif()

View File

@@ -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
)

View File

@@ -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"
}
}

View File

@@ -0,0 +1,800 @@
/* This file is part of the KDE project
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
Copyright (C) 2018 Jan Grulich <jgrulich@redhat.com>
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 <sys/mman.h>
#include <cstring>
// Qt
#include <QX11Info>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QScreen>
#include <QSocketNotifier>
#include <QDebug>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
#include <QRandomGenerator>
#endif
// pipewire
#include <pipewire/version.h>
#if !PW_CHECK_VERSION(0, 2, 9)
#include <spa/support/type-map.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include <spa/param/video/raw-utils.h>
#endif
#include <spa/param/video/format-utils.h>
#include <spa/param/props.h>
#include <pipewire/factory.h>
#include <pipewire/pipewire.h>
#include <pipewire/remote.h>
#include <pipewire/stream.h>
#include <limits.h>
#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<OrgFreedesktopPortalScreenCastInterface> dbusXdpScreenCastService;
QScopedPointer<OrgFreedesktopPortalRemoteDesktopInterface> 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<uint>(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<Streams>(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<QSize>(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<char*>(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<PWFrameBuffer::Private*>(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<PWFrameBuffer::Private *>(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<PWFrameBuffer::Private *>(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 *>(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 *>(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 *>(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 *>(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<PWFrameBuffer::Private *>(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 *>(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 *>(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_flags>(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<qint32>(d->screenGeometry.height);
}
int PWFrameBuffer::width()
{
return static_cast<qint32>(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<uint>(d->pwStreamNodeId);
} if (property == QLatin1String("session_handle")) {
return QVariant::fromValue<QDBusObjectPath>(d->sessionPath);
}
return FrameBuffer::customProperty(property);
}
bool PWFrameBuffer::isValid() const
{
return d->isValid;
}

View File

@@ -0,0 +1,59 @@
/* This file is part of the KDE project
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
Copyright (C) 2018 Jan Grulich <jgrulich@redhat.com>
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 <QWidget>
#include <QVariantMap>
/**
* @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 <kanedias@xaker.ru>
*/
class PWFrameBuffer: public FrameBuffer
{
Q_OBJECT
public:
typedef struct {
uint nodeId;
QVariantMap map;
} Stream;
typedef QList<Stream> 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<Private> d;
};
#endif

View File

@@ -0,0 +1,53 @@
/* This file is part of the KDE project
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
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 <KPluginFactory>
K_PLUGIN_FACTORY_WITH_JSON(PWFrameBufferPluginFactory, "krfb_framebuffer_pw.json",
registerPlugin<PWFrameBufferPlugin>();)
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"

View File

@@ -0,0 +1,45 @@
/* This file is part of the KDE project
Copyright (C) 2018 Oleg Chernovskiy <kanedias@xaker.ru>
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 <QWidget>
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

View File

@@ -1,3 +0,0 @@
[Project]
Manager=KDevCMakeManager
Name=krfb

View File

@@ -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

View File

@@ -24,177 +24,20 @@
#include "events.h"
#include <QApplication>
#include <QX11Info>
#include <QDesktopWidget>
#include <QGlobalStatic>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/XTest.h>
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()
void EventHandler::setFrameBufferPlugin(const QSharedPointer<FrameBuffer> &frameBuffer)
{
init();
fb = frameBuffer;
}
void EventData::init()
QSharedPointer<FrameBuffer> EventHandler::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;
}
}
return fb;
}
leftShiftCode = XKeysymToKeycode(dpy, XK_Shift_L);
rightShiftCode = XKeysymToKeycode(dpy, XK_Shift_R);
altGrCode = XKeysymToKeycode(dpy, XK_Mode_switch);
#include "events.moc"
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 EventHandler::handleKeyboard(bool down, rfbKeySym keySym)
{
#define ADJUSTMOD(sym,state) \
if(keySym==sym) { if(down) data->modifierState|=state; else data->modifierState&=~state; }
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;
}

View File

@@ -25,13 +25,26 @@
#ifndef EVENTS_H
#define EVENTS_H
#include "framebuffer.h"
#include "rfb.h"
#include "krfbprivate_export.h"
class EventHandler
#include <QObject>
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> &frameBuffer);
QSharedPointer<FrameBuffer> frameBuffer();
private:
// Used to track framebuffer plugin which we need for xdp event plugin
QSharedPointer<FrameBuffer> fb;
};
#endif

120
krfb/eventsmanager.cpp Normal file
View File

@@ -0,0 +1,120 @@
/* This file is part of the KDE project
Copyright (C) 2009 Collabora Ltd <info@collabora.co.uk>
@author George Goldberg <george.goldberg@collabora.co.uk>
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 <QDebug>
#include <QGlobalStatic>
#include <KPluginFactory>
#include <KPluginLoader>
#include <KPluginMetaData>
#include <QtCore/QSharedPointer>
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<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("krfb"), [](const KPluginMetaData & md) {
return md.serviceTypes().contains(QStringLiteral("krfb/events"));
});
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> 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<EventsPlugin>(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<EventHandler> EventsManager::eventHandler()
{
QMap<QString, EventsPlugin *>::const_iterator iter = m_plugins.constBegin();
while (iter != m_plugins.constEnd()) {
QSharedPointer<EventHandler> 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<EventHandler>();
}
#include "eventsmanager.moc"

62
krfb/eventsmanager.h Normal file
View File

@@ -0,0 +1,62 @@
/* This file is part of the KDE project
Copyright (C) 2009 Collabora Ltd <info@collabora.co.uk>
@author George Goldberg <george.goldberg@collabora.co.uk>
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 <QtCore/QMap>
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <QtCore/QWeakPointer>
#include <QWidget>
class EventsPlugin;
class KPluginFactory;
class KRFBPRIVATE_EXPORT EventsManager : public QObject
{
Q_OBJECT
friend class EventsManagerStatic;
public:
static EventsManager *instance();
virtual ~EventsManager();
QSharedPointer<EventHandler> eventHandler();
private:
Q_DISABLE_COPY(EventsManager)
EventsManager();
void loadPlugins();
QMap<QString, EventsPlugin *> m_plugins;
QList<QWeakPointer<EventHandler> > m_eventHandlers;
};
#endif // Header guard

34
krfb/eventsplugin.cpp Normal file
View File

@@ -0,0 +1,34 @@
/* This file is part of the KDE project
Copyright (C) 2016 Oleg Chernovskiy <kanedias@xaker.ru>
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"

42
krfb/eventsplugin.h Normal file
View File

@@ -0,0 +1,42 @@
/* This file is part of the KDE project
Copyright (C) 2009 Collabora Ltd <info@collabora.co.uk>
@author George Goldberg <george.goldberg@collabora.co.uk>
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 <QtCore/QVariantList>
#include <QWidget>
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

View File

@@ -50,6 +50,11 @@ void FrameBuffer::getServerFormat(rfbPixelFormat &)
{
}
QVariant FrameBuffer::customProperty(const QString &property) const
{
return QVariant();
}
int FrameBuffer::depth()
{
return 32;

View File

@@ -17,6 +17,7 @@
#include <QObject>
#include <QRect>
#include <QList>
#include <QVariant>
#include <QWidget>
@@ -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;

8
krfb/krfb-events.desktop Normal file
View File

@@ -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

7
krfb/krfb-events.json Normal file
View File

@@ -0,0 +1,7 @@
{
"KPlugin": {
"Description": "Events plugins for KRfb",
"Description[x-test]": "xxFrame Buffer plugins for KRfbxx"
},
"X-KDE-ServiceType": "krfb/events"
}

View File

@@ -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);

View File

@@ -21,7 +21,7 @@
#include "connectiondialog.h"
#include "krfbconfig.h"
#include "sockethelpers.h"
#include "events.h"
#include "eventsmanager.h"
#include <QSocketNotifier>
#include <QDebug>
#include <poll.h>
@@ -37,6 +37,7 @@ struct RfbClient::Private
bool controlEnabled;
rfbClientPtr client;
QSocketNotifier *notifier;
QSharedPointer<EventHandler> 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);
}
}

View File

@@ -24,6 +24,7 @@
#include <QClipboard>
#include <QPointer>
#include <QDebug>
#include <QX11Info>
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);
}
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<QRect> & modifiedTiles)
{
if (d->screen) {

View File

@@ -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<QRect> & modifiedTiles);
void updateCursorPosition(const QPoint & position);

View File

@@ -90,7 +90,6 @@ RfbServerManager* RfbServerManager::instance()
return &s_instance->server;
}
struct RfbServerManager::Private
{
QSharedPointer<FrameBuffer> fb;
@@ -113,6 +112,11 @@ RfbServerManager::~RfbServerManager()
delete d;
}
QSharedPointer<FrameBuffer> 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<QRect> 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;
}

View File

@@ -21,6 +21,7 @@
#define RFBSERVERMANAGER_H
#include "rfb.h"
#include "framebuffer.h"
#include <QObject>
class RfbClient;
@@ -33,12 +34,14 @@ class RfbServerManager : public QObject
public:
static RfbServerManager *instance();
QSharedPointer<FrameBuffer> framebuffer() const;
Q_SIGNALS:
void clientConnected(RfbClient *cc);
void clientDisconnected(RfbClient *cc);
private Q_SLOTS:
void init();
void updateFrameBuffer();
void updateScreens();
void cleanup();