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)
|
||||
: QObject(parent)
|
||||
, d(new KdeConnectPluginConfigPrivate())
|
||||
, m_deviceId(deviceId)
|
||||
, m_pluginName(pluginName)
|
||||
{
|
||||
d->m_configDir = KdeConnectConfig::instance().pluginConfigDir(deviceId, pluginName);
|
||||
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.xml generated/systeminterfaces/mprisroot)
|
||||
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()
|
||||
|
||||
add_library(kdeconnectinterfaces STATIC)
|
||||
|
|
|
@ -16,7 +16,7 @@ PluginModel::PluginModel(QObject *parent)
|
|||
{
|
||||
connect(this, &QAbstractItemModel::rowsInserted, 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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
const QVector<KPluginMetaData> pluginInfo = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"));
|
||||
const QVector<KPluginMetaData> pluginInfo = KPluginMetaData::findPlugins(QStringLiteral("kdeconnect"), std::not_fn(&KPluginMetaData::isHidden));
|
||||
QVector<KPluginMetaData> availablePluginInfo;
|
||||
|
||||
m_oldSupportedPluginNames = currentDevice->supportedPlugins();
|
||||
|
|
|
@ -43,6 +43,8 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
|||
if (TARGET KF6::ModemManagerQt)
|
||||
add_subdirectory(mmtelephony)
|
||||
endif()
|
||||
add_subdirectory(shareinputdevices)
|
||||
add_subdirectory(shareinputdevicesremote)
|
||||
endif()
|
||||
|
||||
if(NOT APPLE)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
kdeconnect_add_plugin(kdeconnect_mousepad SOURCES mousepadplugin.cpp abstractremoteinput.cpp)
|
||||
|
||||
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 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)
|
||||
find_package(LibFakeKey REQUIRED)
|
||||
|
|
|
@ -71,6 +71,8 @@ bool MacOSRemoteInput::handlePacket(const NetworkPacket& np)
|
|||
|
||||
float dx = np.get<float>(QStringLiteral("dx"), 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 isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||
|
@ -214,8 +216,12 @@ bool MacOSRemoteInput::handlePacket(const NetworkPacket& np)
|
|||
|
||||
}
|
||||
} else { //Is a mouse move event
|
||||
if (dx || 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;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
#include <KSharedConfig>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
|
||||
#include <libei.h>
|
||||
#include <linux/input.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
namespace
|
||||
|
@ -60,12 +63,67 @@ int SpecialKeysMap[] = {
|
|||
|
||||
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()
|
||||
: iface(new OrgFreedesktopPortalRemoteDesktopInterface(QLatin1String("org.freedesktop.portal.Desktop"),
|
||||
QLatin1String("/org/freedesktop/portal/desktop"),
|
||||
QDBusConnection::sessionBus(),
|
||||
this))
|
||||
, m_eiNotifier(QSocketNotifier::Read)
|
||||
, m_xkb(new Xkb)
|
||||
{
|
||||
connect(&m_eiNotifier, &QSocketNotifier::activated, this, &RemoteDesktopSession::handleEiEvents);
|
||||
}
|
||||
|
||||
void RemoteDesktopSession::createSession()
|
||||
|
@ -188,11 +246,184 @@ void RemoteDesktopSession::handleXdpSessionStarted(uint code, const QVariantMap
|
|||
|
||||
KConfigGroup stateConfig = KSharedConfig::openStateConfig()->group(QStringLiteral("mousepad"));
|
||||
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*/)
|
||||
{
|
||||
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)
|
||||
|
@ -210,6 +441,8 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
|||
|
||||
const float dx = np.get<float>(QStringLiteral("dx"), 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 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) {
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
||||
s_session->pointerButton(BTN_LEFT, true);
|
||||
s_session->pointerButton(BTN_LEFT, false);
|
||||
} else if (isDoubleClick) {
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
||||
s_session->pointerButton(BTN_LEFT, true);
|
||||
s_session->pointerButton(BTN_LEFT, false);
|
||||
s_session->pointerButton(BTN_LEFT, true);
|
||||
s_session->pointerButton(BTN_LEFT, false);
|
||||
} else if (isMiddleClick) {
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 1);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_MIDDLE, 0);
|
||||
s_session->pointerButton(BTN_MIDDLE, true);
|
||||
s_session->pointerButton(BTN_MIDDLE, false);
|
||||
} else if (isRightClick) {
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 1);
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_RIGHT, 0);
|
||||
s_session->pointerButton(BTN_RIGHT, true);
|
||||
s_session->pointerButton(BTN_RIGHT, false);
|
||||
} else if (isSingleHold) {
|
||||
// For drag'n drop
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 1);
|
||||
s_session->pointerButton(BTN_LEFT, true);
|
||||
} else if (isSingleRelease) {
|
||||
s_session->iface->NotifyPointerButton(s_session->m_xdpPath, {}, BTN_LEFT, 0);
|
||||
s_session->pointerButton(BTN_LEFT, false);
|
||||
} else if (isScroll) {
|
||||
s_session->iface->NotifyPointerAxis(s_session->m_xdpPath, {}, dx, dy);
|
||||
s_session->pointerAxis(dx, dy);
|
||||
} else if (specialKey || !key.isEmpty()) {
|
||||
bool ctrl = np.get<bool>(QStringLiteral("ctrl"), 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);
|
||||
|
||||
if (ctrl)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 1);
|
||||
s_session->keyboardKeycode(KEY_LEFTCTRL, true);
|
||||
if (alt)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 1);
|
||||
s_session->keyboardKeycode(KEY_LEFTALT, true);
|
||||
if (shift)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 1);
|
||||
s_session->keyboardKeycode(KEY_LEFTSHIFT, true);
|
||||
if (super)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTMETA, 1);
|
||||
s_session->keyboardKeycode(KEY_LEFTMETA, true);
|
||||
|
||||
if (specialKey) {
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 1);
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, SpecialKeysMap[specialKey], 0);
|
||||
s_session->keyboardKeycode(SpecialKeysMap[specialKey], true);
|
||||
s_session->keyboardKeycode(SpecialKeysMap[specialKey], false);
|
||||
} else if (!key.isEmpty()) {
|
||||
for (const QChar character : key) {
|
||||
const auto keysym = xkb_utf32_to_keysym(character.toLower().unicode());
|
||||
if (keysym != XKB_KEY_NoSymbol) {
|
||||
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 1).waitForFinished();
|
||||
s_session->iface->NotifyKeyboardKeysym(s_session->m_xdpPath, {}, keysym, 0).waitForFinished();
|
||||
s_session->keyboardKeysym(keysym, true);
|
||||
s_session->keyboardKeysym(keysym, false);
|
||||
} else {
|
||||
qCDebug(KDECONNECT_PLUGIN_MOUSEPAD) << "Cannot send character" << character;
|
||||
}
|
||||
|
@ -274,16 +507,19 @@ bool WaylandRemoteInput::handlePacket(const NetworkPacket &np)
|
|||
}
|
||||
|
||||
if (ctrl)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTCTRL, 0);
|
||||
s_session->keyboardKeycode(KEY_LEFTCTRL, false);
|
||||
if (alt)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTALT, 0);
|
||||
s_session->keyboardKeycode(KEY_LEFTALT, false);
|
||||
if (shift)
|
||||
s_session->iface->NotifyKeyboardKeycode(s_session->m_xdpPath, {}, KEY_LEFTSHIFT, 0);
|
||||
s_session->keyboardKeycode(KEY_LEFTSHIFT, false);
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
#include "abstractremoteinput.h"
|
||||
#include "generated/systeminterfaces/remotedesktop.h"
|
||||
#include <QDBusObjectPath>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
class FakeInput;
|
||||
class Xkb;
|
||||
struct ei;
|
||||
struct ei_device;
|
||||
|
||||
class RemoteDesktopSession : public QObject
|
||||
{
|
||||
|
@ -27,6 +31,13 @@ public:
|
|||
QDBusObjectPath m_xdpPath;
|
||||
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:
|
||||
void handleXdpSessionCreated(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);
|
||||
|
||||
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
|
||||
|
|
|
@ -64,6 +64,8 @@ bool WindowsRemoteInput::handlePacket(const NetworkPacket &np)
|
|||
{
|
||||
float dx = np.get<float>(QStringLiteral("dx"), 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 isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||
|
@ -233,12 +235,16 @@ bool WindowsRemoteInput::handlePacket(const NetworkPacket &np)
|
|||
}
|
||||
|
||||
} else { // Is a mouse move event
|
||||
if (dx || dy) {
|
||||
POINT point;
|
||||
if (GetCursorPos(&point)) {
|
||||
return SetCursorPos(point.x + (int)dx, point.y + (int)dy);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if ((np.has(QStringLiteral("x")) || np.has(QStringLiteral("y")))) {
|
||||
return SetCursorPos((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -92,6 +92,8 @@ bool X11RemoteInput::handlePacket(const NetworkPacket &np)
|
|||
{
|
||||
float dx = np.get<float>(QStringLiteral("dx"), 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 isDoubleClick = np.get<bool>(QStringLiteral("doubleclick"), false);
|
||||
|
@ -196,8 +198,12 @@ bool X11RemoteInput::handlePacket(const NetworkPacket &np)
|
|||
XFlush(display);
|
||||
|
||||
} else { // Is a mouse move event
|
||||
if (dx || 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;
|
||||
}
|
||||
|
|
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