Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/logid/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_executable(logid
Configuration.cpp
features/DPI.cpp
features/SmartShift.cpp
features/HapticFeedback.cpp
features/HiresScroll.cpp
features/RemapButton.cpp
features/DeviceStatus.cpp
Expand Down Expand Up @@ -62,6 +63,7 @@ add_executable(logid
backend/hidpp20/features/AdjustableDPI.cpp
backend/hidpp20/features/SmartShift.cpp
backend/hidpp20/features/ReprogControls.cpp
backend/hidpp20/features/HapticFeedback.cpp
backend/hidpp20/features/HiresScroll.cpp
backend/hidpp20/features/ChangeHost.cpp
backend/hidpp20/features/WirelessDeviceStatus.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/logid/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <features/SmartShift.h>
#include <features/DPI.h>
#include <features/RemapButton.h>
#include <features/HapticFeedback.h>
#include <features/HiresScroll.h>
#include <features/DeviceStatus.h>
#include <features/ThumbWheel.h>
Expand Down Expand Up @@ -151,6 +152,7 @@ void Device::_init() {

_addFeature<features::DPI>("dpi");
_addFeature<features::SmartShift>("smartshift");
_addFeature<features::HapticFeedback>("hapticfeedback");
_addFeature<features::HiresScroll>("hiresscroll");
_addFeature<features::RemapButton>("remapbutton");
_addFeature<features::DeviceStatus>("devicestatus");
Expand Down
3 changes: 2 additions & 1 deletion src/logid/backend/hidpp20/feature_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ namespace logid::backend::hidpp20 {
BACKLIGHT = 0x1981,
BACKLIGHT_V2 = 0x1982,
BACKLIGHT_V3 = 0x1983,
HAPTIC_FEEDBACK = 0x19b0,
PRESENTER_CONTROL = 0x1a00,
SENSOR_3D = 0x1a01,
REPROG_CONTROLS = 0x1b00,
Expand Down Expand Up @@ -126,4 +127,4 @@ namespace logid::backend::hidpp20 {

}

#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS
#endif //LOGID_BACKEND_HIDPP20_FEATUREDEFS
43 changes: 43 additions & 0 deletions src/logid/backend/hidpp20/features/HapticFeedback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 Kristóf Marussy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <backend/hidpp20/features/HapticFeedback.h>

using namespace logid::backend::hidpp20;

static const uint8_t kVibration = 0x01;
static const uint8_t kBatterySaving = 0x02;

HapticFeedback::HapticFeedback(Device* dev) : Feature(dev, ID) {
}

void HapticFeedback::setStrength(uint8_t strength, bool enabled, bool battery_saving) {
uint8_t flags = 0;
if (enabled) {
flags |= kVibration;
}
if (battery_saving) {
flags |= kBatterySaving;
}
std::vector<uint8_t> params = {flags, strength};
callFunction(SetStrength, params);
}

void HapticFeedback::playEffect(uint8_t effect) {
std::vector<uint8_t> params = {effect};
callFunction(PlayEffect, params);
}
44 changes: 44 additions & 0 deletions src/logid/backend/hidpp20/features/HapticFeedback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2025 Kristóf Marussy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef LOGID_BACKEND_HIDPP20_FEATURE_HAPTICFEEDBACK_H
#define LOGID_BACKEND_HIDPP20_FEATURE_HAPTICFEEDBACK_H

#include <backend/hidpp20/Feature.h>
#include <backend/hidpp20/feature_defs.h>

namespace logid::backend::hidpp20 {
class HapticFeedback : public Feature {
public:
static const uint16_t ID = FeatureID::HAPTIC_FEEDBACK;

[[nodiscard]] uint16_t getID() final { return ID; }

enum Function {
SetStrength = 2,
PlayEffect = 4
};

explicit HapticFeedback(Device* dev);

void setStrength(uint8_t strength, bool enabled = true, bool battery_saving = false);

void playEffect(uint8_t effect);
};
}

#endif //LOGID_BACKEND_HIDPP20_FEATURE_HAPTICFEEDBACK_H
15 changes: 13 additions & 2 deletions src/logid/config/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,18 +292,29 @@ namespace logid::config {

typedef map<uint16_t, Button, string_literal_of<keys::cid>> RemapButton;

struct HapticFeedback : public group {
std::optional<bool> enabled;
std::optional<int> strength;
std::optional<bool> battery_saving;

HapticFeedback() : group({"enabled", "strength", "battery_saving"},
&HapticFeedback::enabled, &HapticFeedback::strength,
&HapticFeedback::battery_saving) {}
};

struct Profile : public group {
std::optional<DPI> dpi;
std::optional<SmartShift> smartshift;
std::optional<std::variant<bool, HiresScroll>> hiresscroll;
std::optional<ThumbWheel> thumbwheel;
std::optional<RemapButton> buttons;
std::optional<HapticFeedback> haptic_feedback;

Profile() : group({"dpi", "smartshift", "hiresscroll",
"buttons", "thumbwheel"},
"buttons", "thumbwheel", "haptic_feedback"},
&Profile::dpi, &Profile::smartshift,
&Profile::hiresscroll, &Profile::buttons,
&Profile::thumbwheel) {}
&Profile::thumbwheel, &Profile::haptic_feedback) {}
};

struct Device : public group {
Expand Down
161 changes: 161 additions & 0 deletions src/logid/features/HapticFeedback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright 2025 Kristóf Marussy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config/schema.h"
#include "features/DeviceFeature.h"
#include "ipcgull/interface.h"
#include <cstdint>
#include <features/HapticFeedback.h>
#include <Device.h>
#include <ipc_defs.h>
#include <algorithm>
#include <mutex>
#include <shared_mutex>

using namespace logid::features;
using namespace logid::backend;

static const bool kDefaultEnabled = true;
static const uint8_t kDefaultStrength = 60;
static const int kMinStrength = 1;
static const int kMaxStrength = 100;
static const bool kDefaultBatterySaving = false;
static const uint8_t kMaxEffect = 14;

static uint8_t clampStrength(int strength) {
return std::max(kMinStrength, std::min(strength, kMaxStrength));
}

HapticFeedback::HapticFeedback(Device* device) : DeviceFeature(device), _config(device->activeProfile().haptic_feedback) {
try {
_haptic_feedback = std::make_shared<hidpp20::HapticFeedback>(&device->hidpp20());
} catch (hidpp20::UnsupportedFeature& e) {
throw UnsupportedFeature();
}

_ipc_interface = _device->ipcNode()->make_interface<IPC>(this);
}

void HapticFeedback::configure() {
std::shared_lock lock(_config_mutex);

bool enabled = kDefaultEnabled;
uint8_t strength = kDefaultStrength;
int battery_saving = kDefaultBatterySaving;
if (_config.get().has_value()) {
const auto& config = _config.get().value();
if (config.enabled.has_value()) {
enabled = config.enabled.value();
}
if (config.strength.has_value()) {
strength = clampStrength(config.strength.value());
}
if (config.battery_saving.has_value()) {
battery_saving = config.battery_saving.value();
}
}
setStrength(strength, enabled, battery_saving);
}

void HapticFeedback::listen() {
}

void HapticFeedback::setProfile(config::Profile& profile) {
std::unique_lock lock(_config_mutex);
_config = profile.haptic_feedback;
}

void HapticFeedback::setStrength(uint8_t strength, bool enabled, bool battery_saving) {
_haptic_feedback->setStrength(clampStrength(strength), enabled, battery_saving);
}

void HapticFeedback::playEffect(uint8_t effect) {
if (_isEnabled() && effect <= kMaxEffect) {
_haptic_feedback->playEffect(effect);
}
}

bool HapticFeedback::_isEnabled() const {
std::shared_lock lock(_config_mutex);
if (!_config.get().has_value()) {
return kDefaultEnabled;
}
return _config.get().value().enabled.value_or(kDefaultEnabled);
}

HapticFeedback::IPC::IPC(HapticFeedback* parent) : ipcgull::interface(
SERVICE_ROOT_NAME ".HapticFeedback", {
{"GetEnabled", {this, &IPC::getEnabled, {"enabled"}}},
{"SetEnabled", {this, &IPC::setEnabled, {"enabled"}}},
{"GetStrength", {this, &IPC::getStrength, {"strength"}}},
{"SetStrength", {this, &IPC::setStrength, {"strength"}}},
{"GetBatterySaving", {this, &IPC::getBatterySaving, {"battery_saving"}}},
{"SetBatterySaving", {this, &IPC::setBatterySaving, {"battery_saving"}}},
{"PlayEffect", {this, &IPC::playEffect, {"effect"}}}
}, {}, {}), _parent(*parent) {
}

bool HapticFeedback::IPC::getEnabled() const {
return _parent._isEnabled();
}

void HapticFeedback::IPC::setEnabled(bool enabled) {
std::shared_lock lock(_parent._config_mutex);
if (!_parent._config.get().has_value()) {
_parent._config.get().emplace();
}
_parent._config.get().value().enabled.emplace(enabled);
_parent.configure();
}

uint8_t HapticFeedback::IPC::getStrength() const {
std::unique_lock lock(_parent._config_mutex);
if (!_parent._config.get().has_value()) {
return kDefaultStrength;
}
return _parent._config.get().value().strength.value_or(kDefaultStrength);
}

void HapticFeedback::IPC::setStrength(uint8_t strength) {
std::shared_lock lock(_parent._config_mutex);
if (!_parent._config.get().has_value()) {
_parent._config.get().emplace();
}
_parent._config.get().value().strength.emplace(strength);
_parent.configure();
}

bool HapticFeedback::IPC::getBatterySaving() const {
std::unique_lock lock(_parent._config_mutex);
if (!_parent._config.get().has_value()) {
return kDefaultBatterySaving;
}
return _parent._config.get().value().battery_saving.value_or(kDefaultBatterySaving);
}

void HapticFeedback::IPC::setBatterySaving(bool battery_saving) {
std::shared_lock lock(_parent._config_mutex);
if (!_parent._config.get().has_value()) {
_parent._config.get().emplace();
}
_parent._config.get().value().battery_saving.emplace(battery_saving);
_parent.configure();
}

void HapticFeedback::IPC::playEffect(uint8_t effect) {
_parent.playEffect(effect);
}
Loading