Compare commits
5 commits
master
...
work/david
Author | SHA1 | Date | |
---|---|---|---|
|
e58c1a1415 | ||
|
8c4c7d7baa | ||
|
5a11df5d7c | ||
|
b7763fc888 | ||
|
551af5fcac |
29 changed files with 2186 additions and 39 deletions
|
@ -30,6 +30,8 @@ KdeConnectPluginConfig::KdeConnectPluginConfig(QObject *parent)
|
||||||
KdeConnectPluginConfig::KdeConnectPluginConfig(const QString &deviceId, const QString &pluginName, QObject *parent)
|
KdeConnectPluginConfig::KdeConnectPluginConfig(const QString &deviceId, const QString &pluginName, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, d(new KdeConnectPluginConfigPrivate())
|
, d(new KdeConnectPluginConfigPrivate())
|
||||||
|
, m_deviceId(deviceId)
|
||||||
|
, m_pluginName(pluginName)
|
||||||
{
|
{
|
||||||
d->m_configDir = KdeConnectConfig::instance().pluginConfigDir(deviceId, pluginName);
|
d->m_configDir = KdeConnectConfig::instance().pluginConfigDir(deviceId, pluginName);
|
||||||
QDir().mkpath(d->m_configDir.path());
|
QDir().mkpath(d->m_configDir.path());
|
||||||
|
|
|
@ -71,6 +71,9 @@ if (UNIX AND NOT APPLE)
|
||||||
geninterface(systeminterfaces/org.mpris.MediaPlayer2.Player.xml generated/systeminterfaces/mprisplayer)
|
geninterface(systeminterfaces/org.mpris.MediaPlayer2.Player.xml generated/systeminterfaces/mprisplayer)
|
||||||
geninterface(systeminterfaces/org.mpris.MediaPlayer2.xml generated/systeminterfaces/mprisroot)
|
geninterface(systeminterfaces/org.mpris.MediaPlayer2.xml generated/systeminterfaces/mprisroot)
|
||||||
geninterface(systeminterfaces/org.freedesktop.portal.RemoteDesktop.xml generated/systeminterfaces/remotedesktop)
|
geninterface(systeminterfaces/org.freedesktop.portal.RemoteDesktop.xml generated/systeminterfaces/remotedesktop)
|
||||||
|
geninterface(systeminterfaces/org.freedesktop.portal.InputCapture.xml generated/systeminterfaces/inputcapture)
|
||||||
|
geninterface(systeminterfaces/org.freedesktop.portal.Request.xml generated/systeminterfaces/request)
|
||||||
|
geninterface(systeminterfaces/org.freedesktop.portal.Session.xml generated/systeminterfaces/session)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(kdeconnectinterfaces STATIC)
|
add_library(kdeconnectinterfaces STATIC)
|
||||||
|
|
|
@ -16,7 +16,7 @@ PluginModel::PluginModel(QObject *parent)
|
||||||
{
|
{
|
||||||
connect(this, &QAbstractItemModel::rowsInserted, this, &PluginModel::rowsChanged);
|
connect(this, &QAbstractItemModel::rowsInserted, this, &PluginModel::rowsChanged);
|
||||||
connect(this, &QAbstractItemModel::rowsRemoved, this, &PluginModel::rowsChanged);
|
connect(this, &QAbstractItemModel::rowsRemoved, this, &PluginModel::rowsChanged);
|
||||||
m_plugins = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"));
|
m_plugins = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"), std::not_fn(&KPluginMetaData::isHidden));
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginModel::~PluginModel()
|
PluginModel::~PluginModel()
|
||||||
|
|
|
@ -0,0 +1,530 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2022 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/>.
|
||||||
|
|
||||||
|
Author: Matthias Clasen <mclasen@redhat.com>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||||
|
<!--
|
||||||
|
org.freedesktop.portal.InputCapture:
|
||||||
|
@short_description: Portal for permitting input capture
|
||||||
|
|
||||||
|
The InputCapture portal allows capture input events from connected
|
||||||
|
physical or logical devices. Capturing input has two distinct states:
|
||||||
|
"enabled" where an application has requested that input should be captured
|
||||||
|
once certain conditions are met (but no input events are being delivered
|
||||||
|
yet) and "active", when input events are being delivered to the application.
|
||||||
|
An application can only control the "enabled" state, the compositor decides
|
||||||
|
when to switch into the "active" state - and which devices to capture.
|
||||||
|
|
||||||
|
Once the compositor activates input capturing, events from physical or
|
||||||
|
logical devices are sent directly to the application instead of using
|
||||||
|
those events to update the pointer position on-screen. The compositor
|
||||||
|
is in control of the input capturing and may filter events and/or stop
|
||||||
|
capturing at any time.
|
||||||
|
|
||||||
|
Input capturing is an asynchronous operation using "triggers". An
|
||||||
|
application sets up a number of triggers but it is the compositor who
|
||||||
|
decides when the trigger conditions are met. Currently, the only defined
|
||||||
|
trigger are pointer barriers: horizontal or vertical "lines" on the screen
|
||||||
|
that should trigger when the cursor moves across those lines.
|
||||||
|
See org.freedesktop.portal.InputCapture.SetPointerBarriers().
|
||||||
|
|
||||||
|
There is currently no way for an application to activate immediate input
|
||||||
|
capture.
|
||||||
|
|
||||||
|
The InputCapture portal merely *manages* the logic when input should be
|
||||||
|
captured. The transport of actual input events is delegated to a
|
||||||
|
transport layer, specifically libei. See org.freedesktop.portal.InputCapture.ConnectToEIS().
|
||||||
|
|
||||||
|
This documentation describes version 1 of this interface.
|
||||||
|
-->
|
||||||
|
<interface name="org.freedesktop.portal.InputCapture">
|
||||||
|
<!--
|
||||||
|
CreateSession:
|
||||||
|
@parent_window: Identifier for the application window, see :doc:`window-identifiers`
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
|
||||||
|
|
||||||
|
Create a capture input session. A successfully created session can at
|
||||||
|
any time be closed using :ref:`org.freedesktop.portal.Session.Close`, or may
|
||||||
|
at any time be closed by the portal implementation, which will be
|
||||||
|
signalled via :ref:`org.freedesktop.portal.Session::Closed`.
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``handle_token`` (``s``)
|
||||||
|
|
||||||
|
A string that will be used as the last element of the @handle. Must be a valid
|
||||||
|
object path element. See the :ref:`org.freedesktop.portal.Request` documentation for
|
||||||
|
more information about the @handle.
|
||||||
|
|
||||||
|
* ``session_handle_token`` (``s``)
|
||||||
|
|
||||||
|
A string that will be used as the last element of the session handle. Must be a valid
|
||||||
|
object path element. See the :ref:`org.freedesktop.portal.Session` documentation for
|
||||||
|
more information about the session handle.
|
||||||
|
|
||||||
|
* ``capabilities`` (``u``)
|
||||||
|
|
||||||
|
Bitmask of requested capabilities, see the SupportedCapabilities property.
|
||||||
|
This value is required and must not be zero.
|
||||||
|
|
||||||
|
The following results get returned via the :ref:`org.freedesktop.portal.Request::Response` signal:
|
||||||
|
|
||||||
|
* ``session_handle`` (``o``)
|
||||||
|
|
||||||
|
The session handle. An object path for the
|
||||||
|
:ref:`org.freedesktop.portal.Session` object representing the created
|
||||||
|
session.
|
||||||
|
|
||||||
|
* ``capabilities`` (``u``)
|
||||||
|
|
||||||
|
The capabilities available to this session. This is always a
|
||||||
|
subset of the requested capabilities.
|
||||||
|
See the SupportedCapabilities property for details. Note that
|
||||||
|
while a capability may be available to a session, there is no
|
||||||
|
guarantee a device with that capability is currently available
|
||||||
|
or if one does become available that it will trigger input capture.
|
||||||
|
|
||||||
|
It is best to view this set as a negative confirmation - a
|
||||||
|
capability that was requested but is missing is an indication that
|
||||||
|
this application may not capture events of that capability.
|
||||||
|
-->
|
||||||
|
<method name="CreateSession">
|
||||||
|
<arg type="s" name="parent_window" 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>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GetZones:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
|
||||||
|
|
||||||
|
Retrieve the set of currently available input zones for this session.
|
||||||
|
The zones may not be continuous and may be a logical representation
|
||||||
|
of the physical screens (e.g. a 4k screen may be represented as
|
||||||
|
low-resolution screen instead). A set of zones is identified by a unique
|
||||||
|
zone_set ID.
|
||||||
|
|
||||||
|
The name "Zone" was chosen to provide distinction with the libei
|
||||||
|
"Region".
|
||||||
|
|
||||||
|
If the zones change (e.g. a monitor is unplugged), the
|
||||||
|
#org.freedesktop.portal.InputCapture::ZonesChanged signal is emitted
|
||||||
|
and the application should re-request the current zones to update its
|
||||||
|
internal state.
|
||||||
|
|
||||||
|
Note that zones are session-specific, there is no guarantee that two
|
||||||
|
applications see the same screen zones. An empty zone list implies
|
||||||
|
that no pointer barriers can be set.
|
||||||
|
|
||||||
|
Whenever the application calls GetZones, the current
|
||||||
|
zone_set ID is returned that references the current set of zones. To
|
||||||
|
establish a pointer barrier, the application must pass this ID to
|
||||||
|
org.freedesktop.portal.InputCapture.SetPointerBarriers(). A mismatch of
|
||||||
|
zone_set IDs implies the application is not using the current zone set and
|
||||||
|
pointer barriers will fail.
|
||||||
|
|
||||||
|
The zone_set ID increases by an unspecified amount with each change to
|
||||||
|
the set of zones. Applications must be able to handle the zone_set ID
|
||||||
|
wrapping around. Implementations of this portal must to increase
|
||||||
|
the zone_set ID by a sensible amount to allow for
|
||||||
|
wrapping detection.
|
||||||
|
|
||||||
|
The following results get returned via the :ref:`org.freedesktop.portal.Request::Response` signal:
|
||||||
|
|
||||||
|
* ``zones`` (``a(uuii)``)
|
||||||
|
|
||||||
|
An array of regions, each specifying that zone's width, height,
|
||||||
|
x/y offset.
|
||||||
|
|
||||||
|
* ``zone_set`` (``u``)
|
||||||
|
|
||||||
|
A unique ID to be used in the
|
||||||
|
org.freedesktop.portal.InputCapture.SetPointerBarriers() method to refer to
|
||||||
|
this set of zones. This id increases by an unspecified
|
||||||
|
amount whenever the zones change and pointer barriers can only be set up
|
||||||
|
if the zone_set matches the most recent returned zone_set.
|
||||||
|
-->
|
||||||
|
<method name="GetZones">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<arg type="o" name="handle" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SetPointerBarriers:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@barriers: An array of vardicts, each specifying one barrier
|
||||||
|
@zone_set: A unique zone_set ID referring to the zone set
|
||||||
|
@handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
|
||||||
|
|
||||||
|
Set up zero or more pointer barriers. Pointer barriers are horizontal
|
||||||
|
or vertical "lines" that should trigger the start of input capture when the cursor moves
|
||||||
|
across the pointer barrier. After a successful
|
||||||
|
org.freedesktop.portal.InputCapture.Enable() call and when the
|
||||||
|
compositor has deemed the pointer barrier to be crossed, input events
|
||||||
|
(from compositor-determined input devices) are sent to the application
|
||||||
|
via the transport layer.
|
||||||
|
|
||||||
|
Pointer barriers are situated on the top (for horizontal barriers) or left
|
||||||
|
(for vertical barriers) edge of their respective pixels and their width or height
|
||||||
|
is inclusive each pixel's width or height. In other words, a barrier spanning
|
||||||
|
x1=0, x2=1 is exactly two pixels wide. A pointer barrier must be situated at the
|
||||||
|
outside boundary of the union of all zones.
|
||||||
|
A pointer barrier must be fully contained within one zone.
|
||||||
|
|
||||||
|
For example, consider two zones of size 1920x1080 with offsets
|
||||||
|
0,0 and 1920,0, respectively. (i.e. a left-right dual-monitor setup).
|
||||||
|
The following pointer barriers are permitted:
|
||||||
|
|
||||||
|
- top edge of left screen: `x1=0, y1=0, x2=1919, y1=0`
|
||||||
|
- bottom edge of left screen: `x1=0, y1=1080, x2=1919, y1=1080`
|
||||||
|
- top edge of right screen: `x1=1920, y1=0, x2=3839, y1=0`
|
||||||
|
- bottom edge of right screen: `x1=1920, y1=1080, x2=3839, y1=1080`
|
||||||
|
- left edge of left screen: `x1=0, y1=0, x2=0, y1=1079`
|
||||||
|
- right edge of right screen: `x1=3840, y1=0, x2=3840, y1=1079`
|
||||||
|
|
||||||
|
A pointer barrier is considered triggered when the pointer would
|
||||||
|
logically move off that zone, even if the actual cusor movement is
|
||||||
|
clipped to the zone.
|
||||||
|
|
||||||
|
A zero-sized array of pointer barriers removes all existing pointer barriers
|
||||||
|
for this session. Setting pointer barriers immediately suspends the
|
||||||
|
current session and the application must call org.freedesktop.portal.InputCapture.Enable()
|
||||||
|
after this method.
|
||||||
|
|
||||||
|
The @zone_set must be equivalent to the last returned zone_set of the
|
||||||
|
org.freedesktop.portal.InputCapture.GetZones() method. A mismatch of ids
|
||||||
|
implies the application is not using the current zone set and
|
||||||
|
pointer barriers will fail.
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``handle_token`` (``s``)
|
||||||
|
|
||||||
|
A string that will be used as the last element of the @handle. Must be a valid
|
||||||
|
object path element. See the :ref:`org.freedesktop.portal.Request` documentation for
|
||||||
|
more information about the @handle.
|
||||||
|
|
||||||
|
Supported keys in the @barriers vardicts include:
|
||||||
|
|
||||||
|
* ``barrier_id`` (``u``)
|
||||||
|
|
||||||
|
The non-zero id of this barrier. This id is used in the
|
||||||
|
#org.freedesktop.portal.InputCapture::Activated signal to inform
|
||||||
|
which barrier triggered input capture.
|
||||||
|
|
||||||
|
* ``position`` (``(iiii)``)
|
||||||
|
|
||||||
|
The x1/y1 x2/y2 position of the pointer barrier. A horizontal
|
||||||
|
pointer barrier must have y1 == y2, a vertical pointer barrier
|
||||||
|
must have x1 == x2. Diagonal pointer barriers are not supported.
|
||||||
|
|
||||||
|
The following results get returned via the :ref:`org.freedesktop.portal.Request::Response` signal:
|
||||||
|
|
||||||
|
* ``failed_barriers`` (``au``)
|
||||||
|
|
||||||
|
An array of barrier_ids of pointer barriers that have been denied. The
|
||||||
|
id matches the barrier_id of the entries in the @barriers argument.
|
||||||
|
-->
|
||||||
|
<method name="SetPointerBarriers">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QList<QVariantMap>"/>
|
||||||
|
<arg type="aa{sv}" name="barriers" direction="in"/>
|
||||||
|
<arg type="u" name="zone_set" direction="in"/>
|
||||||
|
<arg type="o" name="handle" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Enable:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
Enable input capturing. This does not immediately trigger capture, it
|
||||||
|
merely enables the capturing to be triggered at some future point
|
||||||
|
(e.g. by the cursor moving across a barrier). If and when that happens,
|
||||||
|
the #org.freedesktop.portal.InputCapture::Activated signal is emitted.
|
||||||
|
-->
|
||||||
|
<method name="Enable">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Disable:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
Disable input capturing. No
|
||||||
|
#org.freedesktop.portal.InputCapture::Disabled signal will be emitted.
|
||||||
|
|
||||||
|
If input capturing is currently ongoing, no
|
||||||
|
#org.freedesktop.portal.InputCapture::Deactivated signal will be
|
||||||
|
emitted.
|
||||||
|
|
||||||
|
Due to the asynchronous nature of this protocol,
|
||||||
|
#org.freedesktop.portal.InputCapture::Deactivated
|
||||||
|
and/or #org.freedesktop.portal.InputCapture::Deactivated signals may
|
||||||
|
nevertheless be received by the application after a call to
|
||||||
|
org.freedesktop.portal.InputCapture.Enable().
|
||||||
|
|
||||||
|
Input events will not be captured until a subsequent
|
||||||
|
org.freedesktop.portal.InputCapture.Enable() call.
|
||||||
|
-->
|
||||||
|
<method name="Disable">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Release:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
Release any ongoing input capture. No
|
||||||
|
#org.freedesktop.portal.InputCapture::Deactivated signal will be emitted.
|
||||||
|
|
||||||
|
Due to the asynchronous nature of this protocol, a
|
||||||
|
#org.freedesktop.portal.InputCapture::Deactivated
|
||||||
|
signal may
|
||||||
|
nevertheless be received by the application after a call to
|
||||||
|
org.freedesktop.portal.InputCapture.Release().
|
||||||
|
|
||||||
|
The activation_id provided in the @options vardict specifies which
|
||||||
|
currently ongoing input capture should be terminated. The asynchronous
|
||||||
|
nature of this portal allows for an input capture to be Deactivated and
|
||||||
|
a new input capture to be Activated before the client requests the
|
||||||
|
Release for the previous input capture.
|
||||||
|
|
||||||
|
A compositor should ignore a
|
||||||
|
org.freedesktop.portal.InputCapture.Release() request for a no longer active
|
||||||
|
activation_id.
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``activation_id`` (``u``)
|
||||||
|
|
||||||
|
The same activation_id number as in the
|
||||||
|
#org.freedesktop.portal.InputCapture::Activated signal.
|
||||||
|
|
||||||
|
* ``cursor_position`` (``(dd)``)
|
||||||
|
|
||||||
|
The suggested cursor position within the Zones available in
|
||||||
|
this session.
|
||||||
|
|
||||||
|
This is a suggestion to the compositor to place the cursor in
|
||||||
|
the correct position to allow for fluent movement between virtual
|
||||||
|
screens. The compositor is not required to honor this suggestion.
|
||||||
|
-->
|
||||||
|
<method name="Release">
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ConnectToEIS:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
@fd: A file descriptor to an active EIS implementation that can be passed to a passive libei context
|
||||||
|
|
||||||
|
Set up the connection to an active EIS implementation. Once input capturing starts,
|
||||||
|
input events are sent via the EI protocol between the compositor and the application.
|
||||||
|
This call must be invoked before org.freedesktop.portal.InputCapture.Enable().
|
||||||
|
|
||||||
|
A session only needs to set this up once, the EIS implementation is not affected by
|
||||||
|
calls to org.freedesktop.portal.InputCapture.Disable() and org.freedesktop.portal.InputCapture.Enable() -
|
||||||
|
the same connection can be re-used until the session is closed.
|
||||||
|
-->
|
||||||
|
<method name="ConnectToEIS">
|
||||||
|
<annotation name="org.gtk.GDBus.C.Name" value="connect_to_eis"/>
|
||||||
|
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
|
||||||
|
<arg type="o" name="session_handle" direction="in"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="in"/>
|
||||||
|
<arg type="h" name="fd" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Disabled:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
The Disabled signal is emitted when the application will no longer
|
||||||
|
receive captured input. If input capturing is currently ongoing, the
|
||||||
|
#org.freedesktop.portal.InputCapture::Deactivated signal is emitted
|
||||||
|
before this signal.
|
||||||
|
|
||||||
|
Applications must call org.freedesktop.portal.InputCapture.Enable() to
|
||||||
|
request future input capturing for this session.
|
||||||
|
-->
|
||||||
|
<signal name="Disabled">
|
||||||
|
<arg type="o" name="session_handle" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Activated:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
The Activated signal is emitted when input capture starts and input events
|
||||||
|
are about to be sent to the application.
|
||||||
|
|
||||||
|
This signal is only emitted after a prior call
|
||||||
|
to org.freedesktop.portal.InputCapture.Enable().
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``activation_id`` (``u``)
|
||||||
|
|
||||||
|
A number that can be used to synchronize with the
|
||||||
|
transport-layer. This number has no intrinsic meaning but
|
||||||
|
is guaranteed to increase by an unspecified amount on each call.
|
||||||
|
|
||||||
|
In particular: if the compositor sends a activation_id of N as
|
||||||
|
part of this request it will also set the sequence in EIS'
|
||||||
|
start_emulating event the same value N on the EIS connection
|
||||||
|
before the first event from a device is sent.
|
||||||
|
This allows an application to have a synchronization point and
|
||||||
|
attribute an event sequence to the portal interaction.
|
||||||
|
|
||||||
|
Applications must be able to handle the activation_id number
|
||||||
|
wrapping around. Implementations of this portal must increase
|
||||||
|
the activation_id number by a sensible amount to allow for
|
||||||
|
wrapping detection.
|
||||||
|
|
||||||
|
* ``cursor_position`` (``(dd)``)
|
||||||
|
|
||||||
|
The current cursor position in the same coordinate space as
|
||||||
|
the Zones. Note that this position is usually outside the Zones
|
||||||
|
available to this session as all PointerBarriers are at the edge
|
||||||
|
of their respective Zones.
|
||||||
|
|
||||||
|
For example, a fast movement against a barrier on the right edge
|
||||||
|
of a screen may logically put the cursor dozens of pixels into
|
||||||
|
the (non-existing) screen on the other side of the barrier.
|
||||||
|
It is the application's responsibility to calculate and adjust the
|
||||||
|
cursor position as necessary.
|
||||||
|
|
||||||
|
* ``barrier_id`` (``u``)
|
||||||
|
|
||||||
|
The barrier id of the barrier that triggered. If the value is
|
||||||
|
nonzero, it matches the barrier id as specified in
|
||||||
|
org.freedesktop.portal.InputCapture.SetPointerBarriers().
|
||||||
|
|
||||||
|
If the id is zero, the pointer barrier could not be determined.
|
||||||
|
If the id is missing, the input capture was not triggered by a
|
||||||
|
pointer barrier.
|
||||||
|
|
||||||
|
Where more than one pointer barrier are triggered by the same
|
||||||
|
movement it is up to the compositor to choose one barrier (or use
|
||||||
|
a barrier id of zero).
|
||||||
|
-->
|
||||||
|
<signal name="Activated">
|
||||||
|
<arg type="o" name="session_handle" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Deactivated:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
The Deactivated signal is emitted when input capture stopped and input events
|
||||||
|
are no longer sent to the application. To prevent future input
|
||||||
|
capture, an application must call org.freedesktop.portal.InputCapture.Disable().
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``activation_id`` (``u``)
|
||||||
|
|
||||||
|
The same activation_id number as in the corresponding
|
||||||
|
#org.freedesktop.portal.InputCapture::Activated signal.
|
||||||
|
-->
|
||||||
|
<signal name="Deactivated">
|
||||||
|
<arg type="o" name="session_handle" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ZonesChanged:
|
||||||
|
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
|
||||||
|
@options: Vardict with optional further information
|
||||||
|
|
||||||
|
The ZonesChanged signal is emitted when the set of zones
|
||||||
|
available **to this session** change. An application should immediately
|
||||||
|
call org.freedesktop.portal.InputCapture.GetZones() to update its state
|
||||||
|
of the zones followed by
|
||||||
|
org.freedesktop.portal.InputCapture.SetPointerBarriers() to re-establish
|
||||||
|
the pointer barriers.
|
||||||
|
|
||||||
|
The ZonesChanged signal includes the zone_set ID of the set of zones
|
||||||
|
invalidated, see #org.freedesktop.portal.InputCapture.GetZones().
|
||||||
|
Due to the asynchronous nature of this protocol, the zone_set ID may
|
||||||
|
identify a set of zones that the application has never (or not yet) seen.
|
||||||
|
Applications must be able to handle unknown zone_set IDs. In particular,
|
||||||
|
because the zone_set ID is guaranteed to increment, an application holding
|
||||||
|
a zone_set ID higher than the ID in this signal can usually simply
|
||||||
|
discard the signal.
|
||||||
|
|
||||||
|
Supported keys in the @options vardict include:
|
||||||
|
|
||||||
|
* ``zone_set`` (``u``)
|
||||||
|
|
||||||
|
The zone_set ID of the invalidated zone set as described in
|
||||||
|
org.freedesktop.portal.InputCapture.GetZones()
|
||||||
|
-->
|
||||||
|
<signal name="ZonesChanged">
|
||||||
|
<arg type="o" name="session_handle" direction="out"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
|
||||||
|
<arg type="a{sv}" name="options" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SupportedCapabilities:
|
||||||
|
|
||||||
|
A bitmask of supported capabilities. This list is constant, it is not the list of
|
||||||
|
capabilities currently available but rather which capabilies are
|
||||||
|
implemented by the portal.
|
||||||
|
|
||||||
|
Applications must ignore unknown capabilities.
|
||||||
|
|
||||||
|
Currently defined types are:
|
||||||
|
|
||||||
|
- ``1``: KEYBOARD
|
||||||
|
- ``2``: POINTER
|
||||||
|
- ``4``: TOUCHSCREEN
|
||||||
|
-->
|
||||||
|
<property name="SupportedCapabilities" type="u" access="read"/>
|
||||||
|
<property name="version" type="u" access="read"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2015 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/>.
|
||||||
|
|
||||||
|
Author: Alexander Larsson <alexl@redhat.com>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||||
|
<!--
|
||||||
|
org.freedesktop.portal.Request:
|
||||||
|
@short_description: Shared request interface
|
||||||
|
|
||||||
|
The Request interface is shared by all portal interfaces. When a
|
||||||
|
portal method is called, the reply includes a handle (i.e. object path)
|
||||||
|
for a Request object, which will stay alive for the duration of the
|
||||||
|
user interaction related to the method call.
|
||||||
|
|
||||||
|
The portal indicates that a portal request interaction is over by
|
||||||
|
emitting the #org.freedesktop.portal.Request::Response signal on the
|
||||||
|
Request object.
|
||||||
|
|
||||||
|
The application can abort the interaction calling
|
||||||
|
org.freedesktop.portal.Request.Close() on the Request object.
|
||||||
|
|
||||||
|
Since version 0.9 of xdg-desktop-portal, the handle will be of the form
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
/org/freedesktop/portal/desktop/request/SENDER/TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
where ``SENDER`` is the callers unique name, with the initial ``':'`` removed and
|
||||||
|
all ``'.'`` replaced by ``'_'``, and ``TOKEN`` is a unique token that the caller provided
|
||||||
|
with the handle_token key in the options vardict.
|
||||||
|
|
||||||
|
This change was made to let applications subscribe to the Response signal before
|
||||||
|
making the initial portal call, thereby avoiding a race condition. It is recommended
|
||||||
|
that the caller should verify that the returned handle is what it expected, and update
|
||||||
|
its signal subscription if it isn't. This ensures that applications will work with both
|
||||||
|
old and new versions of xdg-desktop-portal.
|
||||||
|
|
||||||
|
The token that the caller provides should be unique and not guessable. To avoid clashes
|
||||||
|
with calls made from unrelated libraries, it is a good idea to use a per-library prefix
|
||||||
|
combined with a random number.
|
||||||
|
-->
|
||||||
|
<interface name="org.freedesktop.portal.Request">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Close:
|
||||||
|
|
||||||
|
Closes the portal request to which this object refers and ends all
|
||||||
|
related user interaction (dialogs, etc).
|
||||||
|
|
||||||
|
A Response signal will not be emitted in this case.
|
||||||
|
-->
|
||||||
|
<method name="Close">
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Response:
|
||||||
|
@response: Numeric response
|
||||||
|
@results: Vardict with results. The keys and values in the vardict depend on the request.
|
||||||
|
|
||||||
|
Emitted when the user interaction for a portal request is over.
|
||||||
|
|
||||||
|
The @response indicates how the user interaction ended:
|
||||||
|
|
||||||
|
- 0: Success, the request is carried out
|
||||||
|
- 1: The user cancelled the interaction
|
||||||
|
- 2: The user interaction was ended in some other way
|
||||||
|
-->
|
||||||
|
<signal name="Response">
|
||||||
|
<arg type="u" name="response"/>
|
||||||
|
<arg type="a{sv}" name="results"/>
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
</node>
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2017 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/>.
|
||||||
|
|
||||||
|
Author: Jonas Ådahl <jadahl@redhat.com>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
|
||||||
|
<!--
|
||||||
|
org.freedesktop.portal.Session:
|
||||||
|
@short_description: Shared session interface
|
||||||
|
|
||||||
|
The Session interface is shared by all portal interfaces that involve
|
||||||
|
long lived sessions. When a method that creates a session is called, if
|
||||||
|
successful, the reply will include a session handle (i.e. object path) for
|
||||||
|
a Session object, which will stay alive for the duration of the session.
|
||||||
|
|
||||||
|
The duration of the session is defined by the interface that creates it.
|
||||||
|
For convenience, the interface contains a method
|
||||||
|
org.freedesktop.portal.Session.Close(), and a signal
|
||||||
|
#org.freedesktop.portal.Session::Closed. Whether it is allowed to directly
|
||||||
|
call org.freedesktop.portal.Session.Close() depends on the interface.
|
||||||
|
|
||||||
|
The handle of a session will be of the form
|
||||||
|
``/org/freedesktop/portal/desktop/session/SENDER/TOKEN``, where ``SENDER``
|
||||||
|
is the caller's unique name, with the initial ``:`` removed and all ``.``
|
||||||
|
replaced by ``_``, and ``TOKEN`` is a unique token that the caller
|
||||||
|
provided with the ``session_handle_token`` key in the options vardict of
|
||||||
|
the method creating the session.
|
||||||
|
|
||||||
|
The token that the caller provides should be unique and not guessable. To
|
||||||
|
avoid clashes with calls made from unrelated libraries, it is a good idea
|
||||||
|
to use a per-library prefix combined with a random number.
|
||||||
|
|
||||||
|
A client who started a session vanishing from the D-Bus is equivalent to
|
||||||
|
closing all active sessions made by said client.
|
||||||
|
-->
|
||||||
|
<interface name="org.freedesktop.portal.Session">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Close:
|
||||||
|
|
||||||
|
Closes the portal session to which this object refers and ends all
|
||||||
|
related user interaction (dialogs, etc).
|
||||||
|
-->
|
||||||
|
<method name="Close">
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Closed:
|
||||||
|
@details: A key value Vardict with details about the closed session.
|
||||||
|
|
||||||
|
Emitted when a session is closed.
|
||||||
|
|
||||||
|
The content of @details is specified by the interface creating the session.
|
||||||
|
-->
|
||||||
|
<signal name="Closed">
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
|
||||||
|
<arg type="a{sv}" name="details"/>
|
||||||
|
</signal>
|
||||||
|
<property name="version" type="u" access="read"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
|
@ -190,7 +190,7 @@ void KdeConnectKcm::resetDeviceView()
|
||||||
},
|
},
|
||||||
this);
|
this);
|
||||||
|
|
||||||
const QVector<KPluginMetaData> pluginInfo = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"));
|
const QVector<KPluginMetaData> pluginInfo = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"), std::not_fn(&KPluginMetaData::isHidden));
|
||||||
QVector<KPluginMetaData> availablePluginInfo;
|
QVector<KPluginMetaData> availablePluginInfo;
|
||||||
|
|
||||||
m_oldSupportedPluginNames = currentDevice->supportedPlugins();
|
m_oldSupportedPluginNames = currentDevice->supportedPlugins();
|
||||||
|
|
|
@ -43,6 +43,8 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
if (TARGET KF6::ModemManagerQt)
|
if (TARGET KF6::ModemManagerQt)
|
||||||
add_subdirectory(mmtelephony)
|
add_subdirectory(mmtelephony)
|
||||||
endif()
|
endif()
|
||||||
|
add_subdirectory(shareinputdevices)
|
||||||
|
add_subdirectory(shareinputdevicesremote)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp)
|
kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp)
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
|
pkg_check_modules(PKG_libei REQUIRED IMPORTED_TARGET libei-1.0)
|
||||||
target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp ${SRCS})
|
target_sources(kdeconnect_mousepad PUBLIC waylandremoteinput.cpp ${SRCS})
|
||||||
target_sources(kdeconnect_mousepad PRIVATE ${wayland_SRCS})
|
target_sources(kdeconnect_mousepad PRIVATE ${wayland_SRCS})
|
||||||
target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient PkgConfig::XkbCommon)
|
target_link_libraries(kdeconnect_mousepad Wayland::Client Qt::WaylandClient PkgConfig::XkbCommon PkgConfig::PKG_libei)
|
||||||
|
|
||||||
|
|
||||||
if (WITH_X11)
|
if (WITH_X11)
|
||||||
find_package(LibFakeKey REQUIRED)
|
find_package(LibFakeKey REQUIRED)
|
||||||
|
|
|
@ -71,6 +71,8 @@ bool MacOSRemoteInput::handlePacket(const NetworkPacket& np)
|
||||||
|
|
||||||
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
||||||
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
||||||
|
float x = np.get<float>(QStringLiteral("x"), 0);
|
||||||
|
float y = np.get<float>(QStringLiteral("y"), 0);
|
||||||
|
|
||||||
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
||||||
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||||
|
@ -138,7 +140,7 @@ bool MacOSRemoteInput::handlePacket(const NetworkPacket& np)
|
||||||
bool alt = np.get<bool>(QStringLiteral("alt"), false);
|
bool alt = np.get<bool>(QStringLiteral("alt"), false);
|
||||||
bool shift = np.get<bool>(QStringLiteral("shift"), false);
|
bool shift = np.get<bool>(QStringLiteral("shift"), false);
|
||||||
bool super = np.get<bool>(QStringLiteral("super"), false);
|
bool super = np.get<bool>(QStringLiteral("super"), false);
|
||||||
|
|
||||||
|
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
CGEventRef ctrlEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Control, true);
|
CGEventRef ctrlEvent = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)kVK_Control, true);
|
||||||
|
@ -214,8 +216,12 @@ bool MacOSRemoteInput::handlePacket(const NetworkPacket& np)
|
||||||
|
|
||||||
}
|
}
|
||||||
} else { //Is a mouse move event
|
} else { //Is a mouse move event
|
||||||
QPoint point = QCursor::pos();
|
if (dx || dy) {
|
||||||
QCursor::setPos(point.x() + (int)dx, point.y() + (int)dy);
|
QPoint point = QCursor::pos();
|
||||||
|
QCursor::setPos(point.x() + (int)dx, point.y() + (int)dy);
|
||||||
|
} else if (np.has(QStringLiteral("x")) || np.has(QStringLiteral("y"))) {
|
||||||
|
QCursor::setPos(x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@
|
||||||
#include <KSharedConfig>
|
#include <KSharedConfig>
|
||||||
#include <QDBusPendingCallWatcher>
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include <libei.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -60,12 +63,67 @@ int SpecialKeysMap[] = {
|
||||||
|
|
||||||
Q_GLOBAL_STATIC(RemoteDesktopSession, s_session);
|
Q_GLOBAL_STATIC(RemoteDesktopSession, s_session);
|
||||||
|
|
||||||
|
class Xkb
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Xkb()
|
||||||
|
{
|
||||||
|
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
|
||||||
|
}
|
||||||
|
Xkb(int keymapFd, int size)
|
||||||
|
{
|
||||||
|
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
|
||||||
|
char *map = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, keymapFd, 0));
|
||||||
|
if (map != MAP_FAILED) {
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_string(m_xkbcontext.get(), map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
munmap(map, size);
|
||||||
|
}
|
||||||
|
close(keymapFd);
|
||||||
|
if (!m_xkbkeymap) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Failed to create keymap";
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
}
|
||||||
|
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> keycodeFromKeysym(xkb_keysym_t keysym)
|
||||||
|
{
|
||||||
|
auto layout = xkb_state_serialize_layout(m_xkbstate.get(), XKB_STATE_LAYOUT_EFFECTIVE);
|
||||||
|
const xkb_keycode_t max = xkb_keymap_max_keycode(m_xkbkeymap.get());
|
||||||
|
for (xkb_keycode_t keycode = xkb_keymap_min_keycode(m_xkbkeymap.get()); keycode < max; keycode++) {
|
||||||
|
uint levelCount = xkb_keymap_num_levels_for_key(m_xkbkeymap.get(), keycode, layout);
|
||||||
|
for (uint currentLevel = 0; currentLevel < levelCount; currentLevel++) {
|
||||||
|
const xkb_keysym_t *syms;
|
||||||
|
uint num_syms = xkb_keymap_key_get_syms_by_level(m_xkbkeymap.get(), keycode, layout, currentLevel, &syms);
|
||||||
|
for (uint sym = 0; sym < num_syms; sym++) {
|
||||||
|
if (syms[sym] == keysym) {
|
||||||
|
return {keycode - 8};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<auto D>
|
||||||
|
using deleter = std::integral_constant<decltype(D), D>;
|
||||||
|
std::unique_ptr<xkb_context, deleter<xkb_context_unref>> m_xkbcontext;
|
||||||
|
std::unique_ptr<xkb_keymap, deleter<xkb_keymap_unref>> m_xkbkeymap;
|
||||||
|
std::unique_ptr<xkb_state, deleter<xkb_state_unref>> m_xkbstate;
|
||||||
|
};
|
||||||
|
|
||||||
RemoteDesktopSession::RemoteDesktopSession()
|
RemoteDesktopSession::RemoteDesktopSession()
|
||||||
: iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
|
: iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
|
||||||
QLatin1String("/org/freedesktop/portal/desktop"),
|
QLatin1String("/org/freedesktop/portal/desktop"),
|
||||||
QDBusConnection::sessionBus(),
|
QDBusConnection::sessionBus(),
|
||||||
this))
|
this))
|
||||||
|
, m_eiNotifier(QSocketNotifier::Read)
|
||||||
|
, m_xkb(new Xkb)
|
||||||
{
|
{
|
||||||
|
connect(&m_eiNotifier, &QSocketNotifier::activated, this, &RemoteDesktopSession::handleEiEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteDesktopSession::createSession()
|
void RemoteDesktopSession::createSession()
|
||||||
|
@ -188,11 +246,184 @@ void RemoteDesktopSession::handleXdpSessionStarted(uint code, const QVariantMap
|
||||||
|
|
||||||
KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
|
KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
|
||||||
stateConfig.writeEntry(QStringLiteral("RestoreToken"), results[QStringLiteral("restore_token")].toString());
|
stateConfig.writeEntry(QStringLiteral("RestoreToken"), results[QStringLiteral("restore_token")].toString());
|
||||||
|
auto call = iface->ConnectToEIS(m_xdpPath, {});
|
||||||
|
connect(new QDBusPendingCallWatcher(call), &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
QDBusReply<QDBusUnixFileDescriptor> reply = *watcher;
|
||||||
|
if (!reply.isValid()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Error connecting to eis" << reply.error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectToEi(reply.value().takeFileDescriptor());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::connectToEi(int fd)
|
||||||
|
{
|
||||||
|
m_eiNotifier.setSocket(fd);
|
||||||
|
m_eiNotifier.setEnabled(true);
|
||||||
|
m_ei = ei_new_sender(this);
|
||||||
|
ei_log_set_handler(m_ei, nullptr);
|
||||||
|
ei_setup_backend_fd(m_ei, fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::handleEiEvents()
|
||||||
|
{
|
||||||
|
ei_dispatch(m_ei);
|
||||||
|
while (auto event = ei_get_event(m_ei)) {
|
||||||
|
const auto type = ei_event_get_type(event);
|
||||||
|
switch (type) {
|
||||||
|
case EI_EVENT_CONNECT:
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Connected to ei";
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DISCONNECT:
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "Disconnected from ei";
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SEAT_ADDED: {
|
||||||
|
auto seat = ei_event_get_seat(event);
|
||||||
|
ei_seat_bind_capabilities(seat,
|
||||||
|
EI_DEVICE_CAP_KEYBOARD,
|
||||||
|
EI_DEVICE_CAP_POINTER,
|
||||||
|
EI_DEVICE_CAP_POINTER_ABSOLUTE,
|
||||||
|
EI_DEVICE_CAP_BUTTON,
|
||||||
|
EI_DEVICE_CAP_SCROLL,
|
||||||
|
nullptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EI_EVENT_SEAT_REMOVED:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DEVICE_ADDED: {
|
||||||
|
auto device = ei_event_get_device(event);
|
||||||
|
if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD) && !m_keyboard) {
|
||||||
|
auto keymap = ei_device_keyboard_get_keymap(device);
|
||||||
|
if (ei_keymap_get_type(keymap) != EI_KEYMAP_TYPE_XKB) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_xkb.reset(new Xkb(ei_keymap_get_fd(keymap), ei_keymap_get_size(keymap)));
|
||||||
|
m_keyboard = device;
|
||||||
|
}
|
||||||
|
if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER) && !m_pointer) {
|
||||||
|
m_pointer = device;
|
||||||
|
}
|
||||||
|
if (ei_device_has_capability(device, EI_DEVICE_CAP_POINTER_ABSOLUTE) && !m_absolutePointer) {
|
||||||
|
m_absolutePointer = device;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case EI_EVENT_DEVICE_REMOVED: {
|
||||||
|
auto device = ei_event_get_device(event);
|
||||||
|
if (device == m_keyboard) {
|
||||||
|
m_keyboard = nullptr;
|
||||||
|
}
|
||||||
|
if (device == m_pointer) {
|
||||||
|
m_pointer = nullptr;
|
||||||
|
}
|
||||||
|
if (device == m_absolutePointer) {
|
||||||
|
m_absolutePointer = nullptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EI_EVENT_DEVICE_PAUSED:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DEVICE_RESUMED:
|
||||||
|
ei_device_start_emulating(ei_event_get_device(event), 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EI_EVENT_FRAME:
|
||||||
|
case EI_EVENT_POINTER_MOTION:
|
||||||
|
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
|
||||||
|
case EI_EVENT_BUTTON_BUTTON:
|
||||||
|
case EI_EVENT_SCROLL_DELTA:
|
||||||
|
case EI_EVENT_SCROLL_DISCRETE:
|
||||||
|
case EI_EVENT_SCROLL_STOP:
|
||||||
|
case EI_EVENT_SCROLL_CANCEL:
|
||||||
|
case EI_EVENT_KEYBOARD_MODIFIERS:
|
||||||
|
case EI_EVENT_KEYBOARD_KEY:
|
||||||
|
case EI_EVENT_DEVICE_START_EMULATING:
|
||||||
|
case EI_EVENT_DEVICE_STOP_EMULATING:
|
||||||
|
case EI_EVENT_TOUCH_DOWN:
|
||||||
|
case EI_EVENT_TOUCH_MOTION:
|
||||||
|
case EI_EVENT_TOUCH_UP:
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Unexpected event of type" << ei_event_get_type(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ei_event_unref(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/)
|
void RemoteDesktopSession::handleXdpSessionFinished(uint /*code*/, const QVariantMap & /*results*/)
|
||||||
{
|
{
|
||||||
m_xdpPath = {};
|
m_xdpPath = {};
|
||||||
|
m_eiNotifier.setEnabled(false);
|
||||||
|
ei_unref(m_ei);
|
||||||
|
m_pointer = nullptr;
|
||||||
|
m_keyboard = nullptr;
|
||||||
|
m_absolutePointer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::pointerButton(int button, bool down)
|
||||||
|
{
|
||||||
|
if (m_ei && m_pointer) {
|
||||||
|
ei_device_button_button(m_pointer, button, down);
|
||||||
|
ei_device_frame(m_pointer, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
iface->NotifyPointerButton(m_xdpPath, {}, BTN_LEFT, down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::pointerAxis(double dx, double dy)
|
||||||
|
{
|
||||||
|
if (m_ei && m_pointer) {
|
||||||
|
// Qt/Kdeconnect use inverted vertical scroll direction compared to libei
|
||||||
|
ei_device_scroll_delta(m_pointer, dx, dy * -1);
|
||||||
|
ei_device_frame(m_pointer, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
iface->NotifyPointerAxis(m_xdpPath, {}, dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::pointerMotion(double dx, double dy)
|
||||||
|
{
|
||||||
|
if (m_ei && m_pointer) {
|
||||||
|
ei_device_pointer_motion(m_pointer, dx, dy);
|
||||||
|
ei_device_frame(m_pointer, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
iface->NotifyPointerMotion(m_xdpPath, {}, dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::pointerMotionAbsolute(double x, double y)
|
||||||
|
{
|
||||||
|
if (m_ei && m_absolutePointer) {
|
||||||
|
qDebug() << x << y;
|
||||||
|
ei_device_pointer_motion_absolute(m_absolutePointer, x, y);
|
||||||
|
ei_device_frame(m_absolutePointer, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
iface->NotifyPointerMotionAbsolute(m_xdpPath, {}, 0, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::keyboardKeycode(int key, bool press)
|
||||||
|
{
|
||||||
|
if (m_ei && m_keyboard) {
|
||||||
|
ei_device_keyboard_key(m_keyboard, key, press);
|
||||||
|
ei_device_frame(m_keyboard, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
iface->NotifyKeyboardKeycode(m_xdpPath, {}, key, press);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteDesktopSession::keyboardKeysym(int sym, bool press)
|
||||||
|
{
|
||||||
|
if (m_ei && m_keyboard) {
|
||||||
|
if (auto code = m_xkb->keycodeFromKeysym(sym)) {
|
||||||
|
ei_device_keyboard_key(m_keyboard, *code, press);
|
||||||
|
ei_device_frame(m_keyboard, ei_now(m_ei));
|
||||||
|
} else {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_MOUSEPAD) << "failed to convert keysym" << sym;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iface->NotifyKeyboardKeysym(m_xdpPath, {}, sym, press).waitForFinished();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WaylandRemoteInput::WaylandRemoteInput(QObject *parent)
|
WaylandRemoteInput::WaylandRemoteInput(QObject *parent)
|
||||||
|
@ -210,6 +441,8 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
|
|
||||||
const float dx = np.get<float>(QStringLiteral("dx"), 0);
|
const float dx = np.get<float>(QStringLiteral("dx"), 0);
|
||||||
const float dy = np.get<float>(QStringLiteral("dy"), 0);
|
const float dy = np.get<float>(QStringLiteral("dy"), 0);
|
||||||
|
const float x = np.get<float>(QStringLiteral("x"), 0);
|
||||||
|
const float y = np.get<float>(QStringLiteral("y"), 0);
|
||||||
|
|
||||||
const bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
const bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
||||||
const bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
const bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||||
|
@ -223,26 +456,26 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
|
|
||||||
if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) {
|
if (isSingleClick || isDoubleClick || isMiddleClick || isRightClick || isSingleHold || isSingleRelease || isScroll || !key.isEmpty() || specialKey) {
|
||||||
if (isSingleClick) {
|
if (isSingleClick) {
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
s_session->pointerButton(BTN_LEFT, true);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
s_session->pointerButton(BTN_LEFT, false);
|
||||||
} else if (isDoubleClick) {
|
} else if (isDoubleClick) {
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
s_session->pointerButton(BTN_LEFT, true);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
s_session->pointerButton(BTN_LEFT, false);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
s_session->pointerButton(BTN_LEFT, true);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
s_session->pointerButton(BTN_LEFT, false);
|
||||||
} else if (isMiddleClick) {
|
} else if (isMiddleClick) {
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1);
|
s_session->pointerButton(BTN_MIDDLE, true);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0);
|
s_session->pointerButton(BTN_MIDDLE, false);
|
||||||
} else if (isRightClick) {
|
} else if (isRightClick) {
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1);
|
s_session->pointerButton(BTN_RIGHT, true);
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0);
|
s_session->pointerButton(BTN_RIGHT, false);
|
||||||
} else if (isSingleHold) {
|
} else if (isSingleHold) {
|
||||||
// For drag'n drop
|
// For drag'n drop
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
s_session->pointerButton(BTN_LEFT, true);
|
||||||
} else if (isSingleRelease) {
|
} else if (isSingleRelease) {
|
||||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
s_session->pointerButton(BTN_LEFT, false);
|
||||||
} else if (isScroll) {
|
} else if (isScroll) {
|
||||||
s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy);
|
s_session->pointerAxis(dx, dy);
|
||||||
} else if (specialKey || !key.isEmpty()) {
|
} else if (specialKey || !key.isEmpty()) {
|
||||||
bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false);
|
bool ctrl = np.get<bool>(QStringLiteral("ctrl"), false);
|
||||||
bool alt = np.get<bool>(QStringLiteral("alt"), false);
|
bool alt = np.get<bool>(QStringLiteral("alt"), false);
|
||||||
|
@ -250,23 +483,23 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
bool super = np.get<bool>(QStringLiteral("super"), false);
|
bool super = np.get<bool>(QStringLiteral("super"), false);
|
||||||
|
|
||||||
if (ctrl)
|
if (ctrl)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 1);
|
s_session->keyboardKeycode(KEY_LEFTCTRL, true);
|
||||||
if (alt)
|
if (alt)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 1);
|
s_session->keyboardKeycode(KEY_LEFTALT, true);
|
||||||
if (shift)
|
if (shift)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 1);
|
s_session->keyboardKeycode(KEY_LEFTSHIFT, true);
|
||||||
if (super)
|
if (super)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 1);
|
s_session->keyboardKeycode(KEY_LEFTMETA, true);
|
||||||
|
|
||||||
if (specialKey) {
|
if (specialKey) {
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 1);
|
s_session->keyboardKeycode(SpecialKeysMap[specialKey], true);
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 0);
|
s_session->keyboardKeycode(SpecialKeysMap[specialKey], false);
|
||||||
} else if (!key.isEmpty()) {
|
} else if (!key.isEmpty()) {
|
||||||
for (const QChar character : key) {
|
for (const QChar character : key) {
|
||||||
const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
|
const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
|
||||||
if (keysym != XKB_KEY_NoSymbol) {
|
if (keysym != XKB_KEY_NoSymbol) {
|
||||||
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 1).waitForFinished();
|
s_session->keyboardKeysym(keysym, true);
|
||||||
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 0).waitForFinished();
|
s_session->keyboardKeysym(keysym, false);
|
||||||
} else {
|
} else {
|
||||||
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
|
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
|
||||||
}
|
}
|
||||||
|
@ -274,16 +507,19 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctrl)
|
if (ctrl)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 0);
|
s_session->keyboardKeycode(KEY_LEFTCTRL, false);
|
||||||
if (alt)
|
if (alt)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 0);
|
s_session->keyboardKeycode(KEY_LEFTALT, false);
|
||||||
if (shift)
|
if (shift)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 0);
|
s_session->keyboardKeycode(KEY_LEFTSHIFT, false);
|
||||||
if (super)
|
if (super)
|
||||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 0);
|
s_session->keyboardKeycode(KEY_LEFTMETA, false);
|
||||||
}
|
}
|
||||||
} else { // Is a mouse move event
|
} else { // Is a mouse move event
|
||||||
s_session->iface->NotifyPointerMotion(s_session->m_xdpPath, {}, dx, dy);
|
if (dx || dy)
|
||||||
|
s_session->pointerMotion(dx, dy);
|
||||||
|
else if ((np.has(QStringLiteral("x")) || np.has(QStringLiteral("y"))))
|
||||||
|
s_session->pointerMotionAbsolute(x, y);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,12 @@
|
||||||
#include "abstractremoteinput.h"
|
#include "abstractremoteinput.h"
|
||||||
#include "generated/systeminterfaces/remotedesktop.h"
|
#include "generated/systeminterfaces/remotedesktop.h"
|
||||||
#include <QDBusObjectPath>
|
#include <QDBusObjectPath>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
class FakeInput;
|
class FakeInput;
|
||||||
|
class Xkb;
|
||||||
|
struct ei;
|
||||||
|
struct ei_device;
|
||||||
|
|
||||||
class RemoteDesktopSession : public QObject
|
class RemoteDesktopSession : public QObject
|
||||||
{
|
{
|
||||||
|
@ -27,6 +31,13 @@ public:
|
||||||
QDBusObjectPath m_xdpPath;
|
QDBusObjectPath m_xdpPath;
|
||||||
bool m_connecting = false;
|
bool m_connecting = false;
|
||||||
|
|
||||||
|
void pointerButton(int button, bool press);
|
||||||
|
void pointerAxis(double dx, double dy);
|
||||||
|
void pointerMotion(double dx, double dy);
|
||||||
|
void pointerMotionAbsolute(double x, double y);
|
||||||
|
void keyboardKeysym(int sym, bool press);
|
||||||
|
void keyboardKeycode(int key, bool press);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void handleXdpSessionCreated(uint code, const QVariantMap &results);
|
void handleXdpSessionCreated(uint code, const QVariantMap &results);
|
||||||
void handleXdpSessionConfigured(uint code, const QVariantMap &results);
|
void handleXdpSessionConfigured(uint code, const QVariantMap &results);
|
||||||
|
@ -34,6 +45,14 @@ private Q_SLOTS:
|
||||||
void handleXdpSessionFinished(uint code, const QVariantMap &results);
|
void handleXdpSessionFinished(uint code, const QVariantMap &results);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void connectToEi(int fd);
|
||||||
|
void handleEiEvents();
|
||||||
|
QSocketNotifier m_eiNotifier;
|
||||||
|
std::unique_ptr<Xkb> m_xkb;
|
||||||
|
ei *m_ei = nullptr;
|
||||||
|
ei_device *m_keyboard = nullptr;
|
||||||
|
ei_device *m_pointer = nullptr;
|
||||||
|
ei_device *m_absolutePointer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class WaylandRemoteInput : public AbstractRemoteInput
|
class WaylandRemoteInput : public AbstractRemoteInput
|
||||||
|
|
|
@ -64,6 +64,8 @@ bool WindowsRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
{
|
{
|
||||||
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
||||||
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
||||||
|
float x = np.get<float>(QStringLiteral("x"), 0);
|
||||||
|
float y = np.get<float>(QStringLiteral("y"), 0);
|
||||||
|
|
||||||
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
||||||
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||||
|
@ -233,11 +235,15 @@ bool WindowsRemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // Is a mouse move event
|
} else { // Is a mouse move event
|
||||||
POINT point;
|
if (dx || dy) {
|
||||||
if (GetCursorPos(&point)) {
|
POINT point;
|
||||||
return SetCursorPos(point.x + (int)dx, point.y + (int)dy);
|
if (GetCursorPos(&point)) {
|
||||||
} else {
|
return SetCursorPos(point.x + (int)dx, point.y + (int)dy);
|
||||||
return false;
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if ((np.has(QStringLiteral("x")) || np.has(QStringLiteral("y")))) {
|
||||||
|
return SetCursorPos((int)x, (int)y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -92,6 +92,8 @@ bool X11RemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
{
|
{
|
||||||
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
float dx = np.get<float>(QStringLiteral("dx"), 0);
|
||||||
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
float dy = np.get<float>(QStringLiteral("dy"), 0);
|
||||||
|
float x = np.get<float>(QStringLiteral("x"), 0);
|
||||||
|
float y = np.get<float>(QStringLiteral("y"), 0);
|
||||||
|
|
||||||
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
bool isSingleClick = np.get<bool>(QStringLiteral("singleclick"), false);
|
||||||
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
bool isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||||
|
@ -196,8 +198,12 @@ bool X11RemoteInput::handlePacket(const NetworkPacket &np)
|
||||||
XFlush(display);
|
XFlush(display);
|
||||||
|
|
||||||
} else { // Is a mouse move event
|
} else { // Is a mouse move event
|
||||||
QPoint point = QCursor::pos();
|
if (dx || dy) {
|
||||||
QCursor::setPos(point.x() + (int)dx, point.y() + (int)dy);
|
QPoint point = QCursor::pos();
|
||||||
|
QCursor::setPos(point.x() + (int)dx, point.y() + (int)dy);
|
||||||
|
} else if (np.has(QStringLiteral("x")) || np.has(QStringLiteral("y"))) {
|
||||||
|
QCursor::setPos(x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
19
plugins/shareinputdevices/CMakeLists.txt
Normal file
19
plugins/shareinputdevices/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
kdeconnect_add_plugin(kdeconnect_shareinputdevices SOURCES shareinputdevicesplugin.cpp inputcapturesession.cpp)
|
||||||
|
|
||||||
|
pkg_check_modules(PKG_libei REQUIRED IMPORTED_TARGET libei-1.0)
|
||||||
|
|
||||||
|
target_link_libraries(kdeconnect_shareinputdevices
|
||||||
|
kdeconnectcore
|
||||||
|
kdeconnectinterfaces
|
||||||
|
KF6::I18n
|
||||||
|
Qt::GuiPrivate
|
||||||
|
PkgConfig::PKG_libei
|
||||||
|
)
|
||||||
|
|
||||||
|
kdeconnect_add_kcm(kdeconnect_shareinputdevices_config SOURCES shareinputdevices_config.cpp)
|
||||||
|
ki18n_wrap_ui(kdeconnect_shareinputdevices_config shareinputdevices_config.ui)
|
||||||
|
target_link_libraries(kdeconnect_shareinputdevices_config
|
||||||
|
kdeconnectpluginkcm
|
||||||
|
kdeconnectinterfaces
|
||||||
|
KF6::I18n
|
||||||
|
)
|
1
plugins/shareinputdevices/README
Normal file
1
plugins/shareinputdevices/README
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This plugin allows sharing keyboard and mouse with another machine.
|
450
plugins/shareinputdevices/inputcapturesession.cpp
Normal file
450
plugins/shareinputdevices/inputcapturesession.cpp
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "inputcapturesession.h"
|
||||||
|
#include "plugin_shareinputdevices_debug.h"
|
||||||
|
|
||||||
|
#include "generated/systeminterfaces/inputcapture.h"
|
||||||
|
#include "generated/systeminterfaces/request.h"
|
||||||
|
#include "generated/systeminterfaces/session.h"
|
||||||
|
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <private/qxkbcommon_p.h>
|
||||||
|
|
||||||
|
#include <libei.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
static QString portalName()
|
||||||
|
{
|
||||||
|
return u"org.freedesktop.portal.Desktop"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString portalPath()
|
||||||
|
{
|
||||||
|
return u"/org/freedesktop/portal/desktop"_s;
|
||||||
|
};
|
||||||
|
|
||||||
|
static QString requestPath(const QString &token)
|
||||||
|
{
|
||||||
|
static QString senderString = QDBusConnection::sessionBus().baseService().remove(0, 1).replace(u'.', u'_');
|
||||||
|
return u"%1/request/%2/%3"_s.arg(portalPath()).arg(senderString).arg(token);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Xkb
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Xkb()
|
||||||
|
{
|
||||||
|
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
|
||||||
|
}
|
||||||
|
Xkb(int keymapFd, int size)
|
||||||
|
{
|
||||||
|
m_xkbcontext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
|
||||||
|
char *map = static_cast<char *>(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, keymapFd, 0));
|
||||||
|
if (map != MAP_FAILED) {
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_string(m_xkbcontext.get(), map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
munmap(map, size);
|
||||||
|
}
|
||||||
|
close(keymapFd);
|
||||||
|
if (!m_xkbkeymap) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Failed to create keymap";
|
||||||
|
m_xkbkeymap.reset(xkb_keymap_new_from_names(m_xkbcontext.get(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS));
|
||||||
|
}
|
||||||
|
m_xkbstate.reset(xkb_state_new(m_xkbkeymap.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateModifiers(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group)
|
||||||
|
{
|
||||||
|
xkb_state_update_mask(m_xkbstate.get(), depressed, latched, locked, 0, 0, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateKey(uint32_t key, bool pressed)
|
||||||
|
{
|
||||||
|
xkb_state_update_key(m_xkbstate.get(), key, pressed ? XKB_KEY_DOWN : XKB_KEY_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
xkb_state *currentState() const
|
||||||
|
{
|
||||||
|
return m_xkbstate.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<auto D>
|
||||||
|
using deleter = std::integral_constant<decltype(D), D>;
|
||||||
|
std::unique_ptr<xkb_context, deleter<xkb_context_unref>> m_xkbcontext;
|
||||||
|
std::unique_ptr<xkb_keymap, deleter<xkb_keymap_unref>> m_xkbkeymap;
|
||||||
|
std::unique_ptr<xkb_state, deleter<xkb_state_unref>> m_xkbstate;
|
||||||
|
};
|
||||||
|
|
||||||
|
InputCaptureSession::InputCaptureSession(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_xkb(new Xkb)
|
||||||
|
, m_inputCapturePortal(new OrgFreedesktopPortalInputCaptureInterface(portalName(), portalPath(), QDBusConnection::sessionBus(), this))
|
||||||
|
|
||||||
|
{
|
||||||
|
connect(m_inputCapturePortal, &OrgFreedesktopPortalInputCaptureInterface::Disabled, this, &InputCaptureSession::disabled);
|
||||||
|
connect(m_inputCapturePortal, &OrgFreedesktopPortalInputCaptureInterface::Activated, this, &InputCaptureSession::activated);
|
||||||
|
connect(m_inputCapturePortal, &OrgFreedesktopPortalInputCaptureInterface::Deactivated, this, &InputCaptureSession::deactivated);
|
||||||
|
connect(m_inputCapturePortal, &OrgFreedesktopPortalInputCaptureInterface::ZonesChanged, this, &InputCaptureSession::zonesChanged);
|
||||||
|
|
||||||
|
const QString token = u"kdeconnect_shareinputdevices%1"_s.arg(QRandomGenerator::global()->generate());
|
||||||
|
auto request = new OrgFreedesktopPortalRequestInterface(portalName(), requestPath(token), QDBusConnection::sessionBus(), this);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, request, &QObject::deleteLater);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, this, &InputCaptureSession::sessionCreated);
|
||||||
|
auto call = m_inputCapturePortal->CreateSession(QString(), {{u"handle_token"_s, token}, {u"session_handle_token"_s, token}, {u"capabilities"_s, 3u}});
|
||||||
|
connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, request, [request](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (watcher->isError()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Error creating input capture session" << watcher->error();
|
||||||
|
request->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
InputCaptureSession::~InputCaptureSession()
|
||||||
|
{
|
||||||
|
if (m_session) {
|
||||||
|
m_session->Close();
|
||||||
|
}
|
||||||
|
if (m_ei) {
|
||||||
|
ei_unref(m_ei);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::sessionCreated(uint response, const QVariantMap &results)
|
||||||
|
{
|
||||||
|
if (response != 0) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Couldn't create input capture session";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_session = std::make_unique<OrgFreedesktopPortalSessionInterface>(portalName(),
|
||||||
|
results[u"session_handle"_s].value<QDBusObjectPath>().path(),
|
||||||
|
QDBusConnection::sessionBus());
|
||||||
|
connect(m_session.get(), &OrgFreedesktopPortalSessionInterface::Closed, this, &InputCaptureSession::sessionClosed);
|
||||||
|
|
||||||
|
auto call = m_inputCapturePortal->ConnectToEIS(QDBusObjectPath(m_session->path()), {});
|
||||||
|
connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
QDBusReply<QDBusUnixFileDescriptor> reply = *watcher;
|
||||||
|
if (!reply.isValid()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Error getting eis fd" << watcher->error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Received ei fd" << reply.value().fileDescriptor();
|
||||||
|
setupEi(reply.value().takeFileDescriptor());
|
||||||
|
});
|
||||||
|
getZones();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::getZones()
|
||||||
|
{
|
||||||
|
const QString token = u"kdeconnect_shareinputdevices%1"_s.arg(QRandomGenerator::global()->generate());
|
||||||
|
auto request = new OrgFreedesktopPortalRequestInterface(portalName(), requestPath(token), QDBusConnection::sessionBus(), this);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, request, &QObject::deleteLater);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, this, &InputCaptureSession::zonesReceived);
|
||||||
|
auto call = m_inputCapturePortal->GetZones(QDBusObjectPath(m_session->path()), {{u"handle_token"_s, token}});
|
||||||
|
connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, request, [request](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (watcher->isError()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Error getting zones" << watcher->error();
|
||||||
|
request->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::zonesReceived(uint response, const QVariantMap &results)
|
||||||
|
{
|
||||||
|
if (response != 0) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Couldn't create input capture session";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_currentZoneSet = results[u"zone_set"_s].toUInt();
|
||||||
|
m_currentZones.clear();
|
||||||
|
const QDBusArgument zoneArgument = results[u"zones"_s].value<QDBusArgument>();
|
||||||
|
zoneArgument.beginArray();
|
||||||
|
while (!zoneArgument.atEnd()) {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
uint x;
|
||||||
|
uint y;
|
||||||
|
zoneArgument.beginStructure();
|
||||||
|
zoneArgument >> width >> height >> x >> y;
|
||||||
|
zoneArgument.endStructure();
|
||||||
|
m_currentZones.push_back(QRect(x, y, width, height));
|
||||||
|
}
|
||||||
|
zoneArgument.endArray();
|
||||||
|
|
||||||
|
setUpBarrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::setUpBarrier()
|
||||||
|
{
|
||||||
|
// Find the left/right/bottom/top-most screen
|
||||||
|
if (m_barrierEdge == Qt::LeftEdge) {
|
||||||
|
std::stable_sort(m_currentZones.begin(), m_currentZones.end(), [](const QRect &lhs, const QRect &rhs) {
|
||||||
|
return lhs.x() < rhs.x();
|
||||||
|
});
|
||||||
|
const auto &zone = m_currentZones.front();
|
||||||
|
// Deliberate QRect::bottom usage, on a 1920x1080 screen this needs to be 1079
|
||||||
|
m_barrier = {zone.x(), zone.y(), zone.x(), zone.bottom()};
|
||||||
|
} else if (m_barrierEdge == Qt::RightEdge) {
|
||||||
|
std::stable_sort(m_currentZones.begin(), m_currentZones.end(), [](const QRect &lhs, const QRect &rhs) {
|
||||||
|
return lhs.x() + lhs.width() > rhs.x() + rhs.width();
|
||||||
|
});
|
||||||
|
const auto &zone = m_currentZones.front();
|
||||||
|
m_barrier = {zone.x() + zone.width(), zone.y(), zone.x() + zone.width(), zone.bottom()};
|
||||||
|
} else if (m_barrierEdge == Qt::TopEdge) {
|
||||||
|
std::stable_sort(m_currentZones.begin(), m_currentZones.end(), [](const QRect &lhs, const QRect &rhs) {
|
||||||
|
return lhs.y() < rhs.y();
|
||||||
|
});
|
||||||
|
const auto &zone = m_currentZones.front();
|
||||||
|
// Same here with QRect::right
|
||||||
|
m_barrier = {zone.x(), zone.y(), zone.right(), zone.y()};
|
||||||
|
} else {
|
||||||
|
std::stable_sort(m_currentZones.begin(), m_currentZones.end(), [](const QRect &lhs, const QRect &rhs) {
|
||||||
|
return lhs.y() + lhs.height() > rhs.y() + rhs.height();
|
||||||
|
});
|
||||||
|
const auto &zone = m_currentZones.front();
|
||||||
|
m_barrier = {zone.x(), zone.y() + zone.height(), zone.right(), zone.y() + zone.height()};
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString token = u"kdeconnect_shareinputdevices%1"_s.arg(QRandomGenerator::global()->generate());
|
||||||
|
auto request = new OrgFreedesktopPortalRequestInterface(portalName(), requestPath(token), QDBusConnection::sessionBus(), this);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, request, &QObject::deleteLater);
|
||||||
|
connect(request, &OrgFreedesktopPortalRequestInterface::Response, this, &InputCaptureSession::barriersSet);
|
||||||
|
auto call = m_inputCapturePortal->SetPointerBarriers(
|
||||||
|
QDBusObjectPath(m_session->path()),
|
||||||
|
{{u"handle_token"_s, token}},
|
||||||
|
{{{u"barrier_id"_s, 1}, {u"position"_s, QVariant::fromValue(QList<int>{m_barrier.x1(), m_barrier.y1(), m_barrier.x2(), m_barrier.y2()})}}},
|
||||||
|
m_currentZoneSet);
|
||||||
|
connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, request, [request](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (watcher->isError()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Error setting barriers" << watcher->error();
|
||||||
|
request->deleteLater();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::barriersSet(uint response, const QVariantMap &results)
|
||||||
|
{
|
||||||
|
if (response != 0) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Couldn't set barriers";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto failedBarriers = qdbus_cast<QList<uint>>(results[u"failed_barriers"_s].value<QDBusArgument>());
|
||||||
|
if (!failedBarriers.empty()) {
|
||||||
|
qCInfo(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Failed barriers" << failedBarriers;
|
||||||
|
}
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::enable()
|
||||||
|
{
|
||||||
|
auto call = m_inputCapturePortal->Enable(QDBusObjectPath(m_session->path()), {});
|
||||||
|
connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) {
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (watcher->isError()) {
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Failed enabling input capture session" << watcher->error();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::setBarrierEdge(Qt::Edge edge)
|
||||||
|
{
|
||||||
|
if (edge != m_barrierEdge) {
|
||||||
|
m_barrierEdge = edge;
|
||||||
|
if (!m_currentZones.empty()) {
|
||||||
|
setUpBarrier();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::release(const QPointF &position)
|
||||||
|
{
|
||||||
|
qDebug() << "releasing with" << position;
|
||||||
|
m_inputCapturePortal->Release(QDBusObjectPath(m_session->path()), {{u"cursor_position"_s, position}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::sessionClosed()
|
||||||
|
{
|
||||||
|
qCCritical(KDECONNECT_PLUGIN_SHAREINPUTDEVICES()) << "input capture session was closed";
|
||||||
|
m_session.reset();
|
||||||
|
m_eiNotifier.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::activated(const QDBusObjectPath &sessionHandle, const QVariantMap &options)
|
||||||
|
{
|
||||||
|
if (!m_session || sessionHandle.path() != m_session->path()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_currentActivationId = options[u"activation_id"_s].toUInt();
|
||||||
|
// uint barrier_id = options[u"barrier_id"].toUInt();
|
||||||
|
auto cursorPosition = qdbus_cast<QPointF>(options[u"cursor_position"_s].value<QDBusArgument>());
|
||||||
|
Q_EMIT started(m_barrier, cursorPosition - m_barrier.p1());
|
||||||
|
for (const auto &event : queuedEiEvents) {
|
||||||
|
handleEiEvent(event);
|
||||||
|
}
|
||||||
|
queuedEiEvents.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::deactivated(const QDBusObjectPath &sessionHandle, const QVariantMap &options)
|
||||||
|
{
|
||||||
|
if (!m_session || sessionHandle.path() != m_session->path()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto deactivatedId = options[u"activation_id"_s].toUInt();
|
||||||
|
Q_UNUSED(deactivatedId)
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::disabled(const QDBusObjectPath &sessionHandle, const QVariantMap &options)
|
||||||
|
{
|
||||||
|
if (!m_session || sessionHandle.path() != m_session->path()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto disabledId = options[u"activation_id"_s].toUInt();
|
||||||
|
Q_UNUSED(disabledId)
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::zonesChanged(const QDBusObjectPath &sessionHandle, const QVariantMap &options)
|
||||||
|
{
|
||||||
|
if (!m_session || sessionHandle.path() != m_session->path()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options[u"zone_set"_s].toUInt() >= m_currentZoneSet) {
|
||||||
|
getZones();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::setupEi(int fd)
|
||||||
|
{
|
||||||
|
m_ei = ei_new_receiver(nullptr);
|
||||||
|
ei_setup_backend_fd(m_ei, fd);
|
||||||
|
m_eiNotifier = std::make_unique<QSocketNotifier>(fd, QSocketNotifier::Read);
|
||||||
|
connect(m_eiNotifier.get(), &QSocketNotifier::activated, this, [this] {
|
||||||
|
ei_dispatch(m_ei);
|
||||||
|
while (auto event = ei_get_event(m_ei)) {
|
||||||
|
handleEiEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputCaptureSession::handleEiEvent(ei_event *event)
|
||||||
|
{
|
||||||
|
const auto type = ei_event_get_type(event);
|
||||||
|
constexpr std::array inputEvents = {
|
||||||
|
EI_EVENT_FRAME,
|
||||||
|
EI_EVENT_POINTER_MOTION,
|
||||||
|
EI_EVENT_POINTER_MOTION_ABSOLUTE,
|
||||||
|
EI_EVENT_BUTTON_BUTTON,
|
||||||
|
EI_EVENT_SCROLL_DELTA,
|
||||||
|
EI_EVENT_SCROLL_DISCRETE,
|
||||||
|
EI_EVENT_SCROLL_STOP,
|
||||||
|
EI_EVENT_SCROLL_CANCEL,
|
||||||
|
EI_EVENT_KEYBOARD_MODIFIERS,
|
||||||
|
EI_EVENT_KEYBOARD_KEY,
|
||||||
|
EI_EVENT_TOUCH_DOWN,
|
||||||
|
EI_EVENT_TOUCH_MOTION,
|
||||||
|
EI_EVENT_TOUCH_UP,
|
||||||
|
};
|
||||||
|
if (m_currentEisSequence > m_currentActivationId && std::find(inputEvents.begin(), inputEvents.end(), type) != inputEvents.end()) {
|
||||||
|
// Wait until DBus activated signal to have correct start position
|
||||||
|
queuedEiEvents.push_back(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case EI_EVENT_CONNECT:
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Connected to ei";
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DISCONNECT:
|
||||||
|
qCWarning(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Disconnected from ei";
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SEAT_ADDED: {
|
||||||
|
auto seat = ei_event_get_seat(event);
|
||||||
|
ei_seat_bind_capabilities(seat, EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_POINTER, EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, nullptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EI_EVENT_SEAT_REMOVED:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DEVICE_ADDED: {
|
||||||
|
auto device = ei_event_get_device(event);
|
||||||
|
if (ei_device_has_capability(device, EI_DEVICE_CAP_KEYBOARD)) {
|
||||||
|
auto keymap = ei_device_keyboard_get_keymap(device);
|
||||||
|
if (ei_keymap_get_type(keymap) != EI_KEYMAP_TYPE_XKB) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_xkb.reset(new Xkb(ei_keymap_get_fd(keymap), ei_keymap_get_size(keymap)));
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case EI_EVENT_DEVICE_REMOVED:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DEVICE_START_EMULATING:
|
||||||
|
m_currentEisSequence = ei_event_emulating_get_sequence(event);
|
||||||
|
break;
|
||||||
|
case EI_EVENT_DEVICE_STOP_EMULATING:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_FRAME:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_POINTER_MOTION:
|
||||||
|
if (m_currentEisSequence < m_currentActivationId) {
|
||||||
|
queuedEiEvents.push_back(event);
|
||||||
|
}
|
||||||
|
Q_EMIT mouseMove(ei_event_pointer_get_dx(event), ei_event_pointer_get_dy(event));
|
||||||
|
break;
|
||||||
|
case EI_EVENT_POINTER_MOTION_ABSOLUTE:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_BUTTON_BUTTON:
|
||||||
|
Q_EMIT mouseButton(ei_event_button_get_button(event), ei_event_button_get_is_press(event));
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SCROLL_DELTA:
|
||||||
|
Q_EMIT scrollDelta(ei_event_scroll_get_dx(event), ei_event_scroll_get_dy(event));
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SCROLL_DISCRETE:
|
||||||
|
Q_EMIT scrollDiscrete(ei_event_scroll_get_discrete_dx(event), ei_event_scroll_get_discrete_dy(event));
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SCROLL_STOP:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_SCROLL_CANCEL:
|
||||||
|
break;
|
||||||
|
case EI_EVENT_KEYBOARD_MODIFIERS:
|
||||||
|
m_xkb->updateModifiers(ei_event_keyboard_get_xkb_mods_depressed(event),
|
||||||
|
ei_event_keyboard_get_xkb_mods_latched(event),
|
||||||
|
ei_event_keyboard_get_xkb_mods_locked(event),
|
||||||
|
ei_event_keyboard_get_xkb_group(event));
|
||||||
|
break;
|
||||||
|
case EI_EVENT_KEYBOARD_KEY: {
|
||||||
|
auto xkbKey = ei_event_keyboard_get_key(event) + 8;
|
||||||
|
m_xkb->updateKey(xkbKey, ei_event_keyboard_get_key_is_press(event));
|
||||||
|
// mousepad plugin does press/release in one, trigger on press like remotekeyboard
|
||||||
|
if (ei_event_keyboard_get_key_is_press(event)) {
|
||||||
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(m_xkb->currentState(), xkbKey);
|
||||||
|
Qt::KeyboardModifiers modifiers = QXkbCommon::modifiers(m_xkb->currentState(), sym);
|
||||||
|
auto qtKey = static_cast<Qt::Key>(QXkbCommon::keysymToQtKey(sym, modifiers));
|
||||||
|
const QString text = QXkbCommon::lookupStringNoKeysymTransformations(sym);
|
||||||
|
Q_EMIT key(qtKey, modifiers, text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EI_EVENT_TOUCH_DOWN:
|
||||||
|
case EI_EVENT_TOUCH_MOTION:
|
||||||
|
case EI_EVENT_TOUCH_UP:
|
||||||
|
case EI_EVENT_DEVICE_PAUSED:
|
||||||
|
case EI_EVENT_DEVICE_RESUMED:
|
||||||
|
qCDebug(KDECONNECT_PLUGIN_SHAREINPUTDEVICES) << "Unexpected event of type" << ei_event_get_type(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ei_event_unref(event);
|
||||||
|
}
|
70
plugins/shareinputdevices/inputcapturesession.h
Normal file
70
plugins/shareinputdevices/inputcapturesession.h
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QLine>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class OrgFreedesktopPortalInputCaptureInterface;
|
||||||
|
class QSocketNotifier;
|
||||||
|
class OrgFreedesktopPortalSessionInterface;
|
||||||
|
class Xkb;
|
||||||
|
struct ei;
|
||||||
|
struct ei_event;
|
||||||
|
|
||||||
|
class InputCaptureSession : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
InputCaptureSession(QObject *parent);
|
||||||
|
~InputCaptureSession();
|
||||||
|
|
||||||
|
void setBarrierEdge(Qt::Edge edge);
|
||||||
|
void release(const QPointF &position);
|
||||||
|
Q_SIGNALS:
|
||||||
|
void started(const QLine &barrier, const QPointF &delta);
|
||||||
|
void mouseMove(double dx, double dy);
|
||||||
|
void mouseButton(int button, bool pressed);
|
||||||
|
void scrollDelta(double dx, double dy);
|
||||||
|
void scrollDiscrete(int dx, int dy);
|
||||||
|
void key(Qt::Key key, Qt::KeyboardModifiers modifiers, const QString &text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void getZones();
|
||||||
|
void setUpBarrier();
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
void sessionCreated(uint response, const QVariantMap &options);
|
||||||
|
void zonesReceived(uint response, const QVariantMap &results);
|
||||||
|
void barriersSet(uint response, const QVariantMap &results);
|
||||||
|
|
||||||
|
void sessionClosed();
|
||||||
|
void disabled(const QDBusObjectPath &sessionHandle, const QVariantMap &options);
|
||||||
|
void activated(const QDBusObjectPath &sessionHandle, const QVariantMap &options);
|
||||||
|
void deactivated(const QDBusObjectPath &sessionHandle, const QVariantMap &options);
|
||||||
|
void zonesChanged(const QDBusObjectPath &sessionHandle, const QVariantMap &options);
|
||||||
|
|
||||||
|
void setupEi(int fd);
|
||||||
|
void handleEiEvent(ei_event *event);
|
||||||
|
|
||||||
|
Qt::Edge m_barrierEdge;
|
||||||
|
QLine m_barrier;
|
||||||
|
|
||||||
|
uint m_currentZoneSet = 0;
|
||||||
|
QList<QRect> m_currentZones;
|
||||||
|
uint m_currentActivationId = 0;
|
||||||
|
uint m_currentEisSequence = 0;
|
||||||
|
QList<ei_event *> queuedEiEvents;
|
||||||
|
std::unique_ptr<QSocketNotifier> m_eiNotifier;
|
||||||
|
std::unique_ptr<OrgFreedesktopPortalSessionInterface> m_session;
|
||||||
|
std::unique_ptr<Xkb> m_xkb;
|
||||||
|
ei *m_ei = nullptr;
|
||||||
|
OrgFreedesktopPortalInputCaptureInterface *m_inputCapturePortal;
|
||||||
|
};
|
23
plugins/shareinputdevices/kdeconnect_shareinputdevices.json
Normal file
23
plugins/shareinputdevices/kdeconnect_shareinputdevices.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "David Redondo",
|
||||||
|
"Email": "kde@david-redondo.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"EnabledByDefault": false,
|
||||||
|
"Icon": "dialog-input-devices",
|
||||||
|
"License": "GPL",
|
||||||
|
"Name": "Share input devices",
|
||||||
|
"Description": "Share mouse and keyboard with the remote device"
|
||||||
|
},
|
||||||
|
"X-KDE-ConfigModule": "kdeconnect/kcms/kdeconnect_shareinputdevices_config",
|
||||||
|
"X-KdeConnect-OutgoingPacketType": [
|
||||||
|
"kdeconnect.mousepad.request",
|
||||||
|
"kdeconnect.shareinputdevices.request"
|
||||||
|
],
|
||||||
|
"X-KdeConnect-SupportedPacketType": [
|
||||||
|
"kdeconnect.shareinputdevices"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQml.Models
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kcmutils as KCMUtils
|
||||||
|
import org.kde.kdeconnect 1.0
|
||||||
|
import org.kde.kitemmodels as KItemModels
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string device
|
||||||
|
readonly property string pluginId: "kdeconnect_shareinputdevices"
|
||||||
|
|
||||||
|
KdeConnectPluginConfig {
|
||||||
|
id: config
|
||||||
|
deviceId: device
|
||||||
|
pluginName: pluginId
|
||||||
|
|
||||||
|
onConfigChanged: edgeComboBox.currentIndex = edgeComboBox.indexOfValue(getInt("edge", Qt.LeftEdge))
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.FormData.label: i18nc("@label:listbox", "Leave Screen at")
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: edgeComboBox
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "value"
|
||||||
|
model: [
|
||||||
|
{
|
||||||
|
text: i18nc("@item:inlistbox top edge of screen", "Top"),
|
||||||
|
value: Qt.TopEdge
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18nc("@item:inlistbox left edge of screen", "Left"),
|
||||||
|
value: Qt.LeftEdge
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18nc("@item:inlistbox right edge of screen", "Right"),
|
||||||
|
value: Qt.RightEdge
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18nc("@item:inlistbox top edge of screen", "Bottom"),
|
||||||
|
value: Qt.BottomEdge
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Component.onCompleted: currentIndex = edgeComboBox.indexOfValue(config.getInt("edge", Qt.LeftEdge))
|
||||||
|
}
|
||||||
|
KCMUtils.ContextualHelpButton {
|
||||||
|
icon.name: "data-warning"
|
||||||
|
toolTipText: i18nc("@info:tooltip", "Another device already reserved this edge. This may lead to unexpected results when both are connected at the same time.")
|
||||||
|
visible: configInstantiator.children.some((deviceConfig) => deviceConfig.getInt("edge", Qt.LeftEdge) == edgeComboBox.currentValue)
|
||||||
|
Instantiator {
|
||||||
|
id: configInstantiator
|
||||||
|
readonly property var children: Array.from({length: configInstantiator.count}, (value, index) => configInstantiator.objectAt(index))
|
||||||
|
delegate: KdeConnectPluginConfig {
|
||||||
|
deviceId: model.deviceId
|
||||||
|
pluginName: pluginId
|
||||||
|
|
||||||
|
}
|
||||||
|
model: KItemModels.KSortFilterProxyModel {
|
||||||
|
sourceModel: DevicesModel {
|
||||||
|
}
|
||||||
|
filterRowCallback: function(source_row, source_parent) {
|
||||||
|
const device = sourceModel.getDevice(source_row)
|
||||||
|
return device.id() !== root.device && device.isPluginEnabled(pluginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
plugins/shareinputdevices/shareinputdevices_config.cpp
Normal file
80
plugins/shareinputdevices/shareinputdevices_config.cpp
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shareinputdevices_config.h"
|
||||||
|
|
||||||
|
#include <interfaces/dbusinterfaces.h>
|
||||||
|
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
K_PLUGIN_CLASS(ShareInputDevicesConfig)
|
||||||
|
|
||||||
|
constexpr auto defaultEdge = Qt::LeftEdge;
|
||||||
|
constexpr auto defaultIndex = 1;
|
||||||
|
|
||||||
|
ShareInputDevicesConfig::ShareInputDevicesConfig(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
|
||||||
|
: KdeConnectPluginKcm(parent, data, args)
|
||||||
|
, m_daemon(new DaemonDbusInterface)
|
||||||
|
{
|
||||||
|
m_ui.setupUi(widget());
|
||||||
|
m_ui.edgeContextualWarning->setVisible(false);
|
||||||
|
m_ui.edgeComboBox->setItemData(0, Qt::TopEdge);
|
||||||
|
m_ui.edgeComboBox->setItemData(1, Qt::LeftEdge);
|
||||||
|
m_ui.edgeComboBox->setItemData(2, Qt::RightEdge);
|
||||||
|
m_ui.edgeComboBox->setItemData(3, Qt::BottomEdge);
|
||||||
|
connect(m_ui.edgeComboBox, &QComboBox::currentIndexChanged, this, &ShareInputDevicesConfig::updateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesConfig::checkEdge()
|
||||||
|
{
|
||||||
|
m_ui.edgeContextualWarning->setVisible(false);
|
||||||
|
const QStringList devices = m_daemon->devices();
|
||||||
|
for (const auto &device : devices) {
|
||||||
|
if (device == deviceId()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!DeviceDbusInterface(device).isPluginEnabled(config()->pluginName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (KdeConnectPluginConfig(device, config()->pluginName()).getInt(u"edge"_s, defaultEdge) == m_ui.edgeComboBox->currentData()) {
|
||||||
|
m_ui.edgeContextualWarning->setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesConfig::updateState()
|
||||||
|
{
|
||||||
|
const int current = m_ui.edgeComboBox->currentData().toInt();
|
||||||
|
unmanagedWidgetChangeState(current != config()->getInt(u"edge"_s, defaultEdge));
|
||||||
|
unmanagedWidgetDefaultState(current == defaultEdge);
|
||||||
|
checkEdge();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesConfig::defaults()
|
||||||
|
{
|
||||||
|
KCModule::defaults();
|
||||||
|
m_ui.edgeComboBox->setCurrentIndex(defaultIndex);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesConfig::load()
|
||||||
|
{
|
||||||
|
KCModule::load();
|
||||||
|
const int index = m_ui.edgeComboBox->findData((config()->getInt(u"edge"_s, defaultEdge)));
|
||||||
|
m_ui.edgeComboBox->setCurrentIndex(index != -1 ? index : defaultIndex);
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesConfig::save()
|
||||||
|
{
|
||||||
|
KCModule::save();
|
||||||
|
config()->set(u"edge"_s, m_ui.edgeComboBox->currentData());
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "shareinputdevices_config.moc"
|
29
plugins/shareinputdevices/shareinputdevices_config.h
Normal file
29
plugins/shareinputdevices/shareinputdevices_config.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "kcmplugin/kdeconnectpluginkcm.h"
|
||||||
|
#include "ui_shareinputdevices_config.h"
|
||||||
|
|
||||||
|
class DaemonDbusInterface;
|
||||||
|
|
||||||
|
class ShareInputDevicesConfig : public KdeConnectPluginKcm
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ShareInputDevicesConfig(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
|
||||||
|
|
||||||
|
void save() override;
|
||||||
|
void load() override;
|
||||||
|
void defaults() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateState();
|
||||||
|
void checkEdge();
|
||||||
|
Ui::ShareInputDevicesConfig m_ui;
|
||||||
|
DaemonDbusInterface *m_daemon;
|
||||||
|
};
|
72
plugins/shareinputdevices/shareinputdevices_config.ui
Normal file
72
plugins/shareinputdevices/shareinputdevices_config.ui
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ShareInputDevicesConfig</class>
|
||||||
|
<widget class="QWidget" name="ShareInputDevicesConfig">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>298</width>
|
||||||
|
<height>66</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="edgeLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string comment="@label:listbox">Leave Screen at</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="edgeComboBox">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string comment="@item:inlistbox top edge of screen">Top</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string comment="@item:inlistbox left edge of screen">Left</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string comment="@item:inlistbox right edge of screen">Right</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string comment="@item:inlistbox bottom edge of screen">Bottom</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="KContextualHelpButton" name="edgeContextualWarning">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="data-warning"/>
|
||||||
|
</property>
|
||||||
|
<property name="contextualHelpText">
|
||||||
|
<string comment="@info:tooltip">Another device already reserved this edge. This may lead to unexpected results when both are connected at the same time.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>KContextualHelpButton</class>
|
||||||
|
<extends>QToolButton</extends>
|
||||||
|
<header>kcontextualhelpbutton.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
145
plugins/shareinputdevices/shareinputdevicesplugin.cpp
Normal file
145
plugins/shareinputdevices/shareinputdevicesplugin.cpp
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
#include "shareinputdevicesplugin.h"
|
||||||
|
|
||||||
|
#include "inputcapturesession.h"
|
||||||
|
#include "plugin_shareinputdevices_debug.h"
|
||||||
|
|
||||||
|
#include <core/daemon.h>
|
||||||
|
#include <core/device.h>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QPointF>
|
||||||
|
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
#define PACKET_TYPE_MOUSEPAD_REQUEST u"kdeconnect.mousepad.request"_s
|
||||||
|
#define PACKET_TYPE_SHAREINPUTDEVICES u"kdeconnect.shareinputdevices"_s
|
||||||
|
#define PACKET_TYPE_SHAREINPUTDEVICES_REQUEST u"kdeconnect.shareinputdevices.request"_s
|
||||||
|
|
||||||
|
QMap<int, int> specialKeysMap = {
|
||||||
|
// 0, // Invalid
|
||||||
|
{Qt::Key_Backspace, 1},
|
||||||
|
{Qt::Key_Tab, 2},
|
||||||
|
// XK_Linefeed, // 3
|
||||||
|
{Qt::Key_Left, 4},
|
||||||
|
{Qt::Key_Up, 5},
|
||||||
|
{Qt::Key_Right, 6},
|
||||||
|
{Qt::Key_Down, 7},
|
||||||
|
{Qt::Key_PageUp, 8},
|
||||||
|
{Qt::Key_PageDown, 9},
|
||||||
|
{Qt::Key_Home, 10},
|
||||||
|
{Qt::Key_End, 11},
|
||||||
|
{Qt::Key_Return, 12},
|
||||||
|
{Qt::Key_Enter, 12},
|
||||||
|
{Qt::Key_Delete, 13},
|
||||||
|
{Qt::Key_Escape, 14},
|
||||||
|
{Qt::Key_SysReq, 15},
|
||||||
|
{Qt::Key_ScrollLock, 16},
|
||||||
|
// 0, // 17
|
||||||
|
// 0, // 18
|
||||||
|
// 0, // 19
|
||||||
|
// 0, // 20
|
||||||
|
{Qt::Key_F1, 21},
|
||||||
|
{Qt::Key_F2, 22},
|
||||||
|
{Qt::Key_F3, 23},
|
||||||
|
{Qt::Key_F4, 24},
|
||||||
|
{Qt::Key_F5, 25},
|
||||||
|
{Qt::Key_F6, 26},
|
||||||
|
{Qt::Key_F7, 27},
|
||||||
|
{Qt::Key_F8, 28},
|
||||||
|
{Qt::Key_F9, 29},
|
||||||
|
{Qt::Key_F10, 30},
|
||||||
|
{Qt::Key_F11, 31},
|
||||||
|
{Qt::Key_F12, 32},
|
||||||
|
};
|
||||||
|
|
||||||
|
K_PLUGIN_CLASS_WITH_JSON(ShareInputDevicesPlugin, "kdeconnect_shareinputdevices.json")
|
||||||
|
|
||||||
|
ShareInputDevicesPlugin::ShareInputDevicesPlugin(QObject *parent, const QVariantList &args)
|
||||||
|
: KdeConnectPlugin(parent, args)
|
||||||
|
, m_inputCaptureSession(new InputCaptureSession(this))
|
||||||
|
{
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::started, this, [this](const QLine &barrier, const QPointF &delta) {
|
||||||
|
m_activatedBarrier = barrier;
|
||||||
|
NetworkPacket packet(PACKET_TYPE_SHAREINPUTDEVICES_REQUEST, {{u"startEdge"_s, configuredEdge()}, {u"deltax"_s, delta.x()}, {u"deltay"_s, delta.y()}});
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::mouseMove, this, [this](double x, double y) {
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST, {{u"dx"_s, x}, {u"dy"_s, y}});
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::mouseButton, this, [this](int button, bool pressed) {
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST);
|
||||||
|
// mousepad only supports separate press and release for left click currently, so send event on release even though not entirely correct
|
||||||
|
if (button == BTN_LEFT) {
|
||||||
|
packet.set(pressed ? u"singlehold"_s : u"singlerelease"_s, true);
|
||||||
|
} else if (button == BTN_RIGHT && pressed) {
|
||||||
|
packet.set(u"rightclick"_s, true);
|
||||||
|
} else if (button == BTN_MIDDLE) {
|
||||||
|
packet.set(u"middleclick"_s, true);
|
||||||
|
}
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::scrollDelta, this, [this](double x, double y) {
|
||||||
|
qDebug() << "scrollDelta" << x << y;
|
||||||
|
// scrollDirection in kdeconnect is inverted compared to what we get here
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST, {{u"scroll"_s, true}, {u"dx"_s, x}, {u"dy"_s, y}});
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::scrollDiscrete, this, [this](int x, int y) {
|
||||||
|
qDebug() << "scolldiscrete" << x << y;
|
||||||
|
constexpr auto anglePer120Step = 15 / 120.0;
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST, {{u"scroll"_s, true}, {u"dx"_s, x * anglePer120Step}, {u"dy"_s, -y * anglePer120Step}});
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
connect(m_inputCaptureSession, &InputCaptureSession::key, this, [this](Qt::Key key, Qt::KeyboardModifiers modifiers, const QString &text) {
|
||||||
|
qDebug() << "sending key" << text << modifiers << specialKeysMap.value(key);
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST,
|
||||||
|
{
|
||||||
|
{u"key"_s, text},
|
||||||
|
{u"specialKey"_s, specialKeysMap.value(key)},
|
||||||
|
{u"shift"_s, static_cast<bool>(modifiers & Qt::ShiftModifier)},
|
||||||
|
{u"ctrl"_s, static_cast<bool>(modifiers & Qt::ControlModifier)},
|
||||||
|
{u"alt"_s, static_cast<bool>(modifiers & Qt::AltModifier)},
|
||||||
|
{u"super"_s, static_cast<bool>(modifiers & Qt::MetaModifier)},
|
||||||
|
});
|
||||||
|
sendPacket(packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_inputCaptureSession->setBarrierEdge(configuredEdge());
|
||||||
|
connect(config(), &KdeConnectPluginConfig::configChanged, this, [this] {
|
||||||
|
m_inputCaptureSession->setBarrierEdge(configuredEdge());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::Edge ShareInputDevicesPlugin::configuredEdge() const
|
||||||
|
{
|
||||||
|
return static_cast<Qt::Edge>(config()->getInt(u"edge"_s, Qt::LeftEdge));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesPlugin::receivePacket(const NetworkPacket &np)
|
||||||
|
{
|
||||||
|
if (np.type() == PACKET_TYPE_SHAREINPUTDEVICES) {
|
||||||
|
if (np.has(u"releaseDeltax"_s)) {
|
||||||
|
const QPointF releaseDelta{np.get<double>(u"releaseDeltax"_s), np.get<double>(u"releaseDeltay"_s)};
|
||||||
|
const QPointF releasePosition = m_activatedBarrier.p1() + releaseDelta;
|
||||||
|
m_inputCaptureSession->release(releasePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ShareInputDevicesPlugin::dbusPath() const
|
||||||
|
{
|
||||||
|
return QLatin1String("/modules/kdeconnect/devices/%1/shareinputdevices").arg(device()->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "shareinputdevicesplugin.moc"
|
31
plugins/shareinputdevices/shareinputdevicesplugin.h
Normal file
31
plugins/shareinputdevices/shareinputdevicesplugin.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QLine>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointF>
|
||||||
|
|
||||||
|
#include <core/kdeconnectplugin.h>
|
||||||
|
|
||||||
|
class InputCaptureSession;
|
||||||
|
class QScreen;
|
||||||
|
|
||||||
|
class ShareInputDevicesPlugin : public KdeConnectPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ShareInputDevicesPlugin(QObject *parent, const QVariantList &args);
|
||||||
|
|
||||||
|
void receivePacket(const NetworkPacket &np) override;
|
||||||
|
QString dbusPath() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Qt::Edge configuredEdge() const;
|
||||||
|
InputCaptureSession *m_inputCaptureSession;
|
||||||
|
QLine m_activatedBarrier;
|
||||||
|
};
|
7
plugins/shareinputdevicesremote/CMakeLists.txt
Normal file
7
plugins/shareinputdevicesremote/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
kdeconnect_add_plugin(kdeconnect_shareinputdevicesremote SOURCES shareinputdevicesremoteplugin.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(kdeconnect_shareinputdevicesremote
|
||||||
|
kdeconnectcore
|
||||||
|
Qt::Gui
|
||||||
|
KF6::I18n
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "David Redondo",
|
||||||
|
"Email": "kde@david-redondo.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"EnabledByDefault": true,
|
||||||
|
"Icon": "dialog-input-devices",
|
||||||
|
"License": "GPL",
|
||||||
|
"Hidden": true
|
||||||
|
|
||||||
|
},
|
||||||
|
"X-KdeConnect-OutgoingPacketType": [
|
||||||
|
"kdeconnect.shareinputdevices"
|
||||||
|
],
|
||||||
|
"X-KdeConnect-SupportedPacketType": [
|
||||||
|
"kdeconnect.mousepad.request",
|
||||||
|
"kdeconnect.shareinputdevices.request"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
#include "shareinputdevicesremoteplugin.h"
|
||||||
|
|
||||||
|
#include <core/daemon.h>
|
||||||
|
#include <core/device.h>
|
||||||
|
|
||||||
|
#include <KPluginFactory>
|
||||||
|
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
#define PACKET_TYPE_MOUSEPAD_REQUEST u"kdeconnect.mousepad.request"_s
|
||||||
|
#define PACKET_TYPE_SHAREINPUTDEVICES u"kdeconnect.shareinputdevices"_s
|
||||||
|
#define PACKET_TYPE_SHAREINPUTDEVICES_REQUEST u"kdeconnect.shareinputdevices.request"_s
|
||||||
|
|
||||||
|
K_PLUGIN_CLASS_WITH_JSON(ShareInputDevicesRemotePlugin, "kdeconnect_shareinputdevicesremote.json")
|
||||||
|
|
||||||
|
ShareInputDevicesRemotePlugin::ShareInputDevicesRemotePlugin(QObject *parent, const QVariantList &args)
|
||||||
|
: KdeConnectPlugin(parent, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareInputDevicesRemotePlugin::receivePacket(const NetworkPacket &np)
|
||||||
|
{
|
||||||
|
if (np.type() == PACKET_TYPE_SHAREINPUTDEVICES_REQUEST) {
|
||||||
|
if (np.has(u"startEdge"_s)) {
|
||||||
|
const Qt::Edge startEdge = np.get<Qt::Edge>(u"startEdge"_s);
|
||||||
|
const QPointF delta = {np.get<double>(u"deltax"_s), np.get<double>(u"deltay"_s)};
|
||||||
|
auto screens = QGuiApplication::screens();
|
||||||
|
auto mousePadPlugin = device()->plugin(u"kdeconnect_mousepad"_s);
|
||||||
|
if (screens.empty() || !mousePadPlugin) {
|
||||||
|
NetworkPacket packet(PACKET_TYPE_SHAREINPUTDEVICES, {{u"releaseDelta"_s, QPointF(0, 0)}});
|
||||||
|
sendPacket(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This is opposite to the sorting in inputcaptureession because here the opposite edge is wanted.
|
||||||
|
// For example if on the source side a left barrier was crossed, this is one is entered from the right.
|
||||||
|
if (startEdge == Qt::LeftEdge) {
|
||||||
|
std::stable_sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
|
||||||
|
return lhs->geometry().x() + lhs->geometry().width() > rhs->geometry().x() + rhs->geometry().width();
|
||||||
|
});
|
||||||
|
m_enterEdge = Qt::RightEdge;
|
||||||
|
m_currentPosition = screens.front()->geometry().topRight() + delta;
|
||||||
|
} else if (startEdge == Qt::RightEdge) {
|
||||||
|
std::stable_sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
|
||||||
|
return lhs->geometry().x() < rhs->geometry().x();
|
||||||
|
});
|
||||||
|
m_enterEdge = Qt::LeftEdge;
|
||||||
|
m_currentPosition = screens.front()->geometry().topLeft() + delta;
|
||||||
|
} else if (startEdge == Qt::TopEdge) {
|
||||||
|
std::stable_sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
|
||||||
|
return lhs->geometry().y() + lhs->geometry().height() > rhs->geometry().y() + rhs->geometry().height();
|
||||||
|
});
|
||||||
|
m_enterEdge = Qt::BottomEdge;
|
||||||
|
m_currentPosition = screens.front()->geometry().bottomLeft() + delta;
|
||||||
|
} else {
|
||||||
|
std::stable_sort(screens.begin(), screens.end(), [](QScreen *lhs, QScreen *rhs) {
|
||||||
|
return lhs->geometry().y() < rhs->geometry().y();
|
||||||
|
});
|
||||||
|
m_enterEdge = Qt::TopEdge;
|
||||||
|
m_currentPosition = screens.front()->geometry().topLeft() + delta;
|
||||||
|
}
|
||||||
|
m_enterScreen = screens.front();
|
||||||
|
// Send a packet from this local device to the mousepad plugin because the position could only
|
||||||
|
// be calculated here
|
||||||
|
NetworkPacket packet(PACKET_TYPE_MOUSEPAD_REQUEST, {{u"x"_s, m_currentPosition.x()}, {u"y"_s, m_currentPosition.y()}});
|
||||||
|
mousePadPlugin->receivePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (np.type() == PACKET_TYPE_MOUSEPAD_REQUEST) {
|
||||||
|
if (np.has(u"dx"_s) && m_enterScreen) {
|
||||||
|
m_currentPosition += QPointF(np.get<double>(u"dx"_s), np.get<double>(u"dy"_s));
|
||||||
|
auto sendRelease = [this](const QPointF &delta) {
|
||||||
|
NetworkPacket packet(PACKET_TYPE_SHAREINPUTDEVICES, {{u"releaseDeltax"_s, delta.x()}, {u"releaseDeltay"_s, delta.y()}});
|
||||||
|
sendPacket(packet);
|
||||||
|
};
|
||||||
|
if (m_enterEdge == Qt::LeftEdge && m_currentPosition.x() < m_enterScreen->geometry().x()) {
|
||||||
|
const QPointF delta = m_currentPosition - m_enterScreen->geometry().topLeft();
|
||||||
|
sendRelease(delta);
|
||||||
|
} else if (m_enterEdge == Qt::RightEdge && m_currentPosition.x() > m_enterScreen->geometry().x() + m_enterScreen->geometry().width()) {
|
||||||
|
const QPointF delta = m_currentPosition - m_enterScreen->geometry().topRight();
|
||||||
|
sendRelease(delta);
|
||||||
|
} else if (m_enterEdge == Qt::TopEdge && m_currentPosition.y() < m_enterScreen->geometry().y()) {
|
||||||
|
const QPointF delta = m_currentPosition - m_enterScreen->geometry().topLeft();
|
||||||
|
sendRelease(delta);
|
||||||
|
} else if (m_enterEdge == Qt::BottomEdge && m_currentPosition.y() > m_enterScreen->geometry().y() + m_enterScreen->geometry().height()) {
|
||||||
|
const QPointF delta = m_currentPosition - m_enterScreen->geometry().bottomLeft();
|
||||||
|
sendRelease(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ShareInputDevicesRemotePlugin::dbusPath() const
|
||||||
|
{
|
||||||
|
return QLatin1String("/modules/kdeconnect/devices/%1/shareinputdevicesremote").arg(device()->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "shareinputdevicesremoteplugin.moc"
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2024 David Redondo <kde@david-redondo.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QLine>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointF>
|
||||||
|
|
||||||
|
#include <core/kdeconnectplugin.h>
|
||||||
|
|
||||||
|
class InputCaptureSession;
|
||||||
|
class QScreen;
|
||||||
|
|
||||||
|
class ShareInputDevicesRemotePlugin : public KdeConnectPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ShareInputDevicesRemotePlugin(QObject *parent, const QVariantList &args);
|
||||||
|
|
||||||
|
void receivePacket(const NetworkPacket &np) override;
|
||||||
|
QString dbusPath() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScreen *m_enterScreen;
|
||||||
|
Qt::Edge m_enterEdge;
|
||||||
|
QPointF m_enterPosition;
|
||||||
|
QPointF m_currentPosition;
|
||||||
|
};
|
Loading…
Reference in a new issue