mirror of
https://github.com/KDE/krfb
synced 2026-07-01 07:41:17 -07:00
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:
@@ -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)
|
||||
|
||||
109
cmake/modules/FindPipeWire.cmake
Normal file
109
cmake/modules/FindPipeWire.cmake
Normal 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"
|
||||
)
|
||||
374
dbus/xdp_dbus_remotedesktop_interface.xml
Normal file
374
dbus/xdp_dbus_remotedesktop_interface.xml
Normal 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>
|
||||
186
dbus/xdp_dbus_screencast_interface.xml
Normal file
186
dbus/xdp_dbus_screencast_interface.xml
Normal 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
6
events/CMakeLists.txt
Normal 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
18
events/x11/CMakeLists.txt
Normal 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)
|
||||
13
events/x11/krfb_events_x11.desktop
Normal file
13
events/x11/krfb_events_x11.desktop
Normal 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
|
||||
15
events/x11/krfb_events_x11.json
Normal file
15
events/x11/krfb_events_x11.json
Normal 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
212
events/x11/x11events.cpp
Normal 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
41
events/x11/x11events.h
Normal 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
|
||||
|
||||
45
events/x11/x11eventsplugin.cpp
Normal file
45
events/x11/x11eventsplugin.cpp
Normal 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"
|
||||
|
||||
43
events/x11/x11eventsplugin.h
Normal file
43
events/x11/x11eventsplugin.h
Normal 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
27
events/xdp/CMakeLists.txt
Normal 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
|
||||
)
|
||||
14
events/xdp/krfb_events_xdp.desktop
Normal file
14
events/xdp/krfb_events_xdp.desktop
Normal 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
|
||||
|
||||
16
events/xdp/krfb_events_xdp.json
Normal file
16
events/xdp/krfb_events_xdp.json
Normal 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
128
events/xdp/xdpevents.cpp
Normal 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
37
events/xdp/xdpevents.h
Normal 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
|
||||
|
||||
|
||||
43
events/xdp/xdpeventsplugin.cpp
Normal file
43
events/xdp/xdpeventsplugin.cpp
Normal 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"
|
||||
|
||||
46
events/xdp/xdpeventsplugin.h
Normal file
46
events/xdp/xdpeventsplugin.h
Normal 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
|
||||
|
||||
@@ -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()
|
||||
|
||||
38
framebuffers/pipewire/CMakeLists.txt
Normal file
38
framebuffers/pipewire/CMakeLists.txt
Normal 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
|
||||
)
|
||||
15
framebuffers/pipewire/krfb_framebuffer_pw.json
Normal file
15
framebuffers/pipewire/krfb_framebuffer_pw.json
Normal 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"
|
||||
}
|
||||
}
|
||||
800
framebuffers/pipewire/pw_framebuffer.cpp
Normal file
800
framebuffers/pipewire/pw_framebuffer.cpp
Normal 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;
|
||||
}
|
||||
59
framebuffers/pipewire/pw_framebuffer.h
Normal file
59
framebuffers/pipewire/pw_framebuffer.h
Normal 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
|
||||
53
framebuffers/pipewire/pw_framebufferplugin.cpp
Normal file
53
framebuffers/pipewire/pw_framebufferplugin.cpp
Normal 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"
|
||||
45
framebuffers/pipewire/pw_framebufferplugin.h
Normal file
45
framebuffers/pipewire/pw_framebufferplugin.h
Normal 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
|
||||
@@ -1,3 +0,0 @@
|
||||
[Project]
|
||||
Manager=KDevCMakeManager
|
||||
Name=krfb
|
||||
@@ -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
|
||||
|
||||
173
krfb/events.cpp
173
krfb/events.cpp
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
120
krfb/eventsmanager.cpp
Normal 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
62
krfb/eventsmanager.h
Normal 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
34
krfb/eventsplugin.cpp
Normal 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
42
krfb/eventsplugin.h
Normal 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
|
||||
|
||||
@@ -50,6 +50,11 @@ void FrameBuffer::getServerFormat(rfbPixelFormat &)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant FrameBuffer::customProperty(const QString &property) const
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int FrameBuffer::depth()
|
||||
{
|
||||
return 32;
|
||||
|
||||
@@ -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
8
krfb/krfb-events.desktop
Normal 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
7
krfb/krfb-events.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"KPlugin": {
|
||||
"Description": "Events plugins for KRfb",
|
||||
"Description[x-test]": "xxFrame Buffer plugins for KRfbxx"
|
||||
},
|
||||
"X-KDE-ServiceType": "krfb/events"
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user