hapticsplugin: Port to feedbackd

This is an initial port to feedbackd for the haptics plugin.

This implementation is a simple port to have the motor enabled for a certain duration. We will eventually want to use feedbackd events to trigger these instead.

Related MR for qtfeedback: https://invent.kde.org/jbbgameich/ktactilefeedback/-/merge_requests/2

https://invent.kde.org/teams/plasma-mobile/issues/-/issues/10
This commit is contained in:
Devin Lin 2025-05-22 11:45:44 -04:00
parent 73b5595139
commit 72284989f8
8 changed files with 184 additions and 44 deletions

View file

@ -45,7 +45,7 @@ Dependencies:
* Milou (for search)
* Kirigami
* Kirigami Addons
* hfd-service (optional: for vibrations)
* feedbackd (optional: for vibrations)
To start the shell in a window, run:

View file

@ -1,12 +1,15 @@
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
qt_add_dbus_interfaces(DBUS_SRCS dbus/com.lomiri.hfd.xml)
set_source_files_properties(dbus/org.sigxcpu.Feedback.Haptic.xml PROPERTIES INCLUDE vibrationevent.h)
qt_add_dbus_interfaces(dbusinterface_SRCS
dbus/org.sigxcpu.Feedback.Haptic.xml)
ecm_add_qml_module(hapticsplugin URI org.kde.plasma.private.mobileshell.hapticsplugin GENERATE_PLUGIN_SOURCE)
target_sources(hapticsplugin PRIVATE
vibrationevent.h
vibrationmanager.cpp
${DBUS_SRCS}
${dbusinterface_SRCS}
)
target_link_libraries(hapticsplugin PRIVATE
@ -14,6 +17,7 @@ target_link_libraries(hapticsplugin PRIVATE
Qt::DBus
KF6::CoreAddons
KF6::I18n
QCoro::DBus
)
ecm_finalize_qml_module(hapticsplugin)

View file

@ -1,32 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
SPDX-FileCopyrightText: 2020 Marius Gripsgard <marius@ubports.com>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<node name="/com/lomiri/hfd">
<interface name="com.lomiri.hfd.Vibrator">
<method name="vibrate"/>
<method name="vibrate">
<arg name="durationMs" type="i" direction="in" />
</method>
<method name="rumble">
<arg name="durationMs" type="i" direction="in" />
<arg name="repeat" type="i" direction="in" />
</method>
</interface>
<interface name="com.lomiri.hfd.Leds">
<method name="setState">
<arg name="state" type="i" direction="in" />
</method>
<method name="setColor">
<arg name="color" type="u" direction="in" />
</method>
<method name="setOnMs">
<arg name="onMs" type="i" direction="in" />
</method>
<method name="setOffMs">
<arg name="offMs" type="i" direction="in" />
</method>
</interface>
</node>

View file

@ -0,0 +1,37 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
- SPDX-FileCopyrightText: 2025 Guido Günther <agx@sigxcpu.org>
- SPDX-License-Identifier: GPL-3.0-or-later
-->
<node>
<!-- org.sigxcpu.Feedback.Haptic
@short_description: Interface to make a device vibrate
This D-Bus interface is used to make a device's haptic motor
vibrate. This is can be useful e.g. for games.
To provider user feedback the event based interface should be
preferred.
-->
<interface name="org.sigxcpu.Feedback.Haptic">
<!--
Vibrate:
@app_id: The application id usually in "reverse DNS" format
@pattern: The vibration pattern.
@success: Whether vibration was triggered
Triggers the given vibration pattern on the haptic device. The
pattern is a sequence of relative amplitude and duration pairs.
The amplitude must be between 0.0 and 1.0, durations are in
milliseconds.
-->
<method name="Vibrate">
<arg direction="in" name="app_id" type="s"/>
<arg direction="in" name="pattern" type="a(du)"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VibrationEventList" />
<arg direction="out" name="success" type="b"/>
</method>
</interface>
</node>

View file

@ -0,0 +1,80 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
- SPDX-FileCopyrightText: 2025 Guido Günther <agx@sigxcpu.org>
- SPDX-License-Identifier: GPL-3.0-or-later
-->
<node>
<!-- org.sigxcpu.Feedback
@short_description: haptic/audio/visual feedback interface
This D-Bus interface is used to get the current feedback theme
and to give feedback on events.
-->
<interface name="org.sigxcpu.Feedback">
<!--
Profile: The currently used profile.
The currently used feedback profile name. Applications should
usually not change this value.
-->
<property name="Profile" type="s" access="readwrite" />
<!--
TriggerFeedback:
@app_id: The application id usually in "reverse DNS" format
@event: The event name from the Event naming spec
@hints: Additional hints. Currently known hints
- profile: Override the profile used for this event with the given profile name
- important: Override the current global feedback level.
Together with the 'profile' hint this allows to trigger feedback for events
that would otherwise be disabled. A typical use case is an alarm clock. Note
that the feedback daemon (depending on it's configuration) might ignore this flag.
- sound-file: A custom sound file to play. This file will be used instead of any
sound event specified in the "full" profile. The sound will only be played if
appropriate for the feedback level of the event.
@timeout: When the feedbacks for this event should end latest in seconds. The special
values '-1' (just run each feedback once) and '0' (endless loop) are also supported.
@id: Event id for future reference
Give user feedback for an event by triggering feedbacks
defined in the daemon. The method call returns an event id
that can be used later on to e.g. cancel the triggered
feedbacks early.
Depending on the event, theme and profile several forms of
feedback will be triggered such as an audio ring tone and a
haptic motor.
-->
<method name="TriggerFeedback">
<arg direction="in" name="app_id" type="s"/>
<arg direction="in" name="event" type="s"/>
<arg direction="in" name="hints" type="a{sv}"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap" />
<arg direction="in" name="timeout" type="i"/>
<arg direction="out" name="id" type="u"/>
</method>
<!--
EndFeedback:
@id: The id of the event
End all feedbacks triggered by the event with the given id.
-->
<method name="EndFeedback">
<arg direction="in" name="id" type="u"/>
</method>
<!--
FeedbackEnded:
@id: The id of the event
@reason: The reason why feedback was ended (currently unused).
Emitted when all feedbacks for an event have ended.
-->
<signal name="FeedbackEnded">
<arg name="id" type="u"/>
<arg name="reason" type="u"/>
</signal>
</interface>
</node>

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDBusArgument>
#include <QList>
class VibrationEvent
{
public:
double amplitude;
quint32 duration;
};
using VibrationEventList = QList<VibrationEvent>;
Q_DECLARE_METATYPE(VibrationEvent)
Q_DECLARE_METATYPE(VibrationEventList)
inline QDBusArgument &operator<<(QDBusArgument &argument, const VibrationEvent &e)
{
argument.beginStructure();
argument << e.amplitude;
argument << e.duration;
argument.endStructure();
return argument;
}
inline const QDBusArgument &operator>>(const QDBusArgument &argument, VibrationEvent &e)
{
argument.beginStructure();
argument >> e.amplitude;
argument >> e.duration;
argument.endStructure();
return argument;
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-FileCopyrightText: 2023-2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "vibrationmanager.h"
@ -6,14 +6,24 @@
VibrationManager::VibrationManager(QObject *parent)
: QObject{parent}
{
qDBusRegisterMetaType<VibrationEvent>();
qDBusRegisterMetaType<VibrationEventList>();
}
void VibrationManager::vibrate(int durationMs)
QCoro::Task<void> VibrationManager::vibrate(int durationMs)
{
// Only create interface when needed.
if (!m_interface) {
const auto objectPath = QStringLiteral("/com/lomiri/hfd");
m_interface = new com::lomiri::hfd::Vibrator("com.lomiri.hfd", objectPath, QDBusConnection::systemBus(), this);
const auto objectPath = QStringLiteral("/org/sigxcpu/Feedback");
m_interface = new OrgSigxcpuFeedbackHapticInterface("org.sigxcpu.Feedback", objectPath, QDBusConnection::sessionBus(), this);
}
const QString appId = QStringLiteral("org.kde.plasmashell");
const VibrationEvent event{1.0, static_cast<quint32>(durationMs)};
const VibrationEventList pattern = {event};
QDBusPendingReply<bool> reply = co_await m_interface->Vibrate(appId, pattern);
if (!reply.isValid() || !reply.value()) {
qWarning() << "feedbackd vibration failed";
}
m_interface->vibrate(durationMs);
}

View file

@ -3,10 +3,14 @@
#pragma once
#include <QList>
#include <QObject>
#include <qqmlregistration.h>
#include "hfdinterface.h"
#include "hapticinterface.h"
#include "vibrationevent.h"
#include <QCoroDBusPendingReply>
class VibrationManager : public QObject
{
@ -17,8 +21,8 @@ class VibrationManager : public QObject
public:
VibrationManager(QObject *parent = nullptr);
Q_INVOKABLE void vibrate(int durationMs);
Q_INVOKABLE QCoro::Task<void> vibrate(int durationMs);
private:
com::lomiri::hfd::Vibrator *m_interface{nullptr};
OrgSigxcpuFeedbackHapticInterface *m_interface{nullptr};
};