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 @@ -15,6 +15,7 @@ add_executable(logid
util/log.cpp
config/config.cpp
InputDevice.cpp
TouchpadDevice.cpp
DeviceManager.cpp
Device.cpp
Receiver.cpp
Expand All @@ -35,6 +36,7 @@ add_executable(logid
actions/GestureAction.cpp
actions/ChangeHostAction.cpp
actions/ChangeProfile.cpp
actions/TouchpadAction.cpp
actions/gesture/Gesture.cpp
actions/gesture/ReleaseGesture.cpp
actions/gesture/ThresholdGesture.cpp
Expand Down
12 changes: 12 additions & 0 deletions src/logid/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@ std::shared_ptr<InputDevice> Device::virtualInput() const {
}
}

std::shared_ptr<TouchpadDevice> Device::virtualTouchpad() const {
if (auto manager = _manager.lock()) {
return manager->virtualTouchpad();
} else {
logPrintf(ERROR, "Device manager lost");
logPrintf(ERROR,
"Fatal error occurred, file a bug report,"
" the program will now exit.");
std::terminate();
}
}

std::shared_ptr<ipcgull::node> Device::ipcNode() const {
return _ipc_node;
}
Expand Down
2 changes: 2 additions & 0 deletions src/logid/Device.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace logid {
class Receiver;

class InputDevice;
class TouchpadDevice;

class DeviceNickname {
public:
Expand Down Expand Up @@ -100,6 +101,7 @@ namespace logid {
void reset();

[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<TouchpadDevice> virtualTouchpad() const;

[[nodiscard]] std::shared_ptr<ipcgull::node> ipcNode() const;

Expand Down
16 changes: 16 additions & 0 deletions src/logid/DeviceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
*/

#include <DeviceManager.h>
#include <TouchpadDevice.h>
#include <backend/Error.h>
#include <util/log.h>
#include <thread>
#include <sstream>
#include <utility>
#include <ipc_defs.h>
#include <system_error>

using namespace logid;
using namespace logid::backend;
Expand Down Expand Up @@ -52,6 +54,20 @@ std::shared_ptr<InputDevice> DeviceManager::virtualInput() const {
return _virtual_input;
}

std::shared_ptr<TouchpadDevice> DeviceManager::virtualTouchpad() const {
static constexpr auto virtual_touchpad_name = "LogiOps Virtual Touchpad";
std::lock_guard<std::mutex> lock(_touchpad_lock);
if (!_virtual_touchpad) {
try {
_virtual_touchpad = std::make_shared<TouchpadDevice>(virtual_touchpad_name);
} catch (std::system_error& e) {
logPrintf(ERROR, "Could not create touchpad device: %s", e.what());
}
}

return _virtual_touchpad;
}

std::shared_ptr<const ipcgull::node> DeviceManager::devicesNode() const {
return _device_node;
}
Expand Down
4 changes: 4 additions & 0 deletions src/logid/DeviceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@

namespace logid {
class InputDevice;
class TouchpadDevice;

class DeviceManager : public backend::raw::DeviceMonitor {
public:

[[nodiscard]] std::shared_ptr<Configuration> config() const;

[[nodiscard]] std::shared_ptr<InputDevice> virtualInput() const;
[[nodiscard]] std::shared_ptr<TouchpadDevice> virtualTouchpad() const;

[[nodiscard]] std::shared_ptr<const ipcgull::node> devicesNode() const;

Expand Down Expand Up @@ -83,6 +85,8 @@ namespace logid {
std::shared_ptr<ipcgull::server> _server;
std::shared_ptr<Configuration> _config;
std::shared_ptr<InputDevice> _virtual_input;
mutable std::shared_ptr<TouchpadDevice> _virtual_touchpad;
mutable std::mutex _touchpad_lock;

std::shared_ptr<ipcgull::node> _root_node;

Expand Down
238 changes: 238 additions & 0 deletions src/logid/TouchpadDevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
#include <TouchpadDevice.h>
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing GPL license header. All other files in this codebase include a copyright notice and GPL v3 license header at the top of the file. This file should include the same standard header for consistency.

Copilot uses AI. Check for mistakes.
#include <system_error>
#include <algorithm>

extern "C"
{
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#include <linux/input.h>
}

using namespace logid;

namespace {
int clamp_value(int value, int min_value, int max_value) {
return std::max(min_value, std::min(value, max_value));
}
}

TouchpadDevice::TouchpadDevice(const char* name, int width, int height, int slots) :
_name(name),
_range_x(width > 0 ? width : default_width),
_range_y(height > 0 ? height : default_height),
_max_slots(slots > 0 ? slots : default_slots),
_active(false),
_active_fingers(0),
_next_tracking_id(1) {
_createDevice();
}

TouchpadDevice::~TouchpadDevice() {
_destroyDevice();
}

void TouchpadDevice::setRange(int width, int height) {
const int next_width = width > 0 ? width : default_width;
const int next_height = height > 0 ? height : default_height;

std::lock_guard<std::mutex> lock(_input_mutex);
if (_range_x == next_width && _range_y == next_height)
return;

_endContactsLocked();
_range_x = next_width;
_range_y = next_height;
_destroyDevice();
_createDevice();
Comment on lines +44 to +47
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential use-after-free or crash if _createDevice() fails during setRange(). The method calls _destroyDevice() which sets _ui_device to nullptr, then calls _createDevice() which may throw an exception. If _createDevice() throws, _ui_device remains null, but subsequent calls to _sendEvent() will dereference the null pointer through libevdev_uinput_write_event() without checking. Consider wrapping the _createDevice() call in a try-catch block to restore the device to a valid state or mark it as invalid.

Suggested change
_range_x = next_width;
_range_y = next_height;
_destroyDevice();
_createDevice();
const int prev_width = _range_x;
const int prev_height = _range_y;
_range_x = next_width;
_range_y = next_height;
_destroyDevice();
try {
_createDevice();
} catch (...) {
// Roll back to the previous range and try to restore the original device.
_range_x = prev_width;
_range_y = prev_height;
try {
_createDevice();
} catch (...) {
// Leave the exception visible to the caller.
throw;
}
}

Copilot uses AI. Check for mistakes.
}

int TouchpadDevice::width() const {
return _range_x;
}

int TouchpadDevice::height() const {
return _range_y;
}

int TouchpadDevice::slots() const {
return _max_slots;
}

void TouchpadDevice::beginContacts(int fingers, int start_x, int start_y) {
std::lock_guard<std::mutex> lock(_input_mutex);
if (fingers < 1)
return;

if (fingers > _max_slots)
fingers = _max_slots;

_endContactsLocked();
_active = true;
_active_fingers = fingers;
_contacts.clear();
_contacts.reserve(fingers);

const int x = _clampX(start_x);
const int y = _clampY(start_y);

for (int slot = 0; slot < fingers; ++slot) {
if (_next_tracking_id > 65535)
_next_tracking_id = 1;
Contact contact{ x, y, _next_tracking_id++ };
_contacts.emplace_back(contact);
_sendEvent(EV_ABS, ABS_MT_SLOT, slot);
_sendEvent(EV_ABS, ABS_MT_TRACKING_ID, contact.tracking_id);
_sendEvent(EV_ABS, ABS_MT_POSITION_X, contact.x);
_sendEvent(EV_ABS, ABS_MT_POSITION_Y, contact.y);
}

_sendEvent(EV_ABS, ABS_X, x);
_sendEvent(EV_ABS, ABS_Y, y);
_setToolButtons(fingers, true);
_sendSyn();
}

void TouchpadDevice::moveContacts(int dx, int dy) {
std::lock_guard<std::mutex> lock(_input_mutex);
if (!_active || _contacts.empty())
return;

for (std::size_t slot = 0; slot < _contacts.size(); ++slot) {
auto& contact = _contacts[slot];
contact.x = _clampX(contact.x + dx);
contact.y = _clampY(contact.y + dy);
_sendEvent(EV_ABS, ABS_MT_SLOT, static_cast<int>(slot));
_sendEvent(EV_ABS, ABS_MT_POSITION_X, contact.x);
_sendEvent(EV_ABS, ABS_MT_POSITION_Y, contact.y);
}

_sendEvent(EV_ABS, ABS_X, _contacts.front().x);
_sendEvent(EV_ABS, ABS_Y, _contacts.front().y);
_sendSyn();
}

void TouchpadDevice::endContacts() {
std::lock_guard<std::mutex> lock(_input_mutex);
_endContactsLocked();
}

void TouchpadDevice::_createDevice() {
_device = libevdev_new();
libevdev_set_name(_device, _name.c_str());

libevdev_enable_event_type(_device, EV_KEY);
libevdev_enable_event_code(_device, EV_KEY, BTN_TOOL_FINGER, nullptr);
libevdev_enable_event_code(_device, EV_KEY, BTN_TOOL_DOUBLETAP, nullptr);
libevdev_enable_event_code(_device, EV_KEY, BTN_TOOL_TRIPLETAP, nullptr);
#ifdef BTN_TOOL_QUADTAP
libevdev_enable_event_code(_device, EV_KEY, BTN_TOOL_QUADTAP, nullptr);
#endif
#ifdef BTN_TOOL_QUINTTAP
libevdev_enable_event_code(_device, EV_KEY, BTN_TOOL_QUINTTAP, nullptr);
#endif
libevdev_enable_event_code(_device, EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(_device, EV_KEY, BTN_LEFT, nullptr);
libevdev_enable_property(_device, INPUT_PROP_POINTER);

libevdev_enable_event_type(_device, EV_ABS);

input_absinfo abs_x{};
abs_x.minimum = 0;
abs_x.maximum = _range_x;
abs_x.value = _range_x / 2;
libevdev_enable_event_code(_device, EV_ABS, ABS_X, &abs_x);

input_absinfo abs_y{};
abs_y.minimum = 0;
abs_y.maximum = _range_y;
abs_y.value = _range_y / 2;
libevdev_enable_event_code(_device, EV_ABS, ABS_Y, &abs_y);

input_absinfo mt_slot{};
mt_slot.minimum = 0;
mt_slot.maximum = _max_slots - 1;
libevdev_enable_event_code(_device, EV_ABS, ABS_MT_SLOT, &mt_slot);

input_absinfo mt_tracking{};
mt_tracking.minimum = -1;
mt_tracking.maximum = 65535;
libevdev_enable_event_code(_device, EV_ABS, ABS_MT_TRACKING_ID, &mt_tracking);

libevdev_enable_event_code(_device, EV_ABS, ABS_MT_POSITION_X, &abs_x);
libevdev_enable_event_code(_device, EV_ABS, ABS_MT_POSITION_Y, &abs_y);

int err = libevdev_uinput_create_from_device(_device,
LIBEVDEV_UINPUT_OPEN_MANAGED,
&_ui_device);
if (err != 0) {
libevdev_free(_device);
_device = nullptr;
throw std::system_error(-err, std::generic_category());
}
}

void TouchpadDevice::_destroyDevice() {
if (_ui_device) {
libevdev_uinput_destroy(_ui_device);
_ui_device = nullptr;
}
if (_device) {
libevdev_free(_device);
_device = nullptr;
}
}

void TouchpadDevice::_sendEvent(uint type, uint code, int value) {
libevdev_uinput_write_event(_ui_device, type, code, value);
}

void TouchpadDevice::_sendSyn() {
libevdev_uinput_write_event(_ui_device, EV_SYN, SYN_REPORT, 0);
}

void TouchpadDevice::_setToolButtons(int fingers, bool down) {
const int pressed = down ? 1 : 0;
_sendEvent(EV_KEY, BTN_TOUCH, pressed);
const bool one = down && fingers == 1;
const bool two = down && fingers == 2;
const bool three = down && fingers == 3;
const bool four = down && fingers == 4;
const bool five = down && fingers >= 5;
_sendEvent(EV_KEY, BTN_TOOL_FINGER, one ? 1 : 0);
_sendEvent(EV_KEY, BTN_TOOL_DOUBLETAP, two ? 1 : 0);
_sendEvent(EV_KEY, BTN_TOOL_TRIPLETAP, three ? 1 : 0);
#ifdef BTN_TOOL_QUADTAP
_sendEvent(EV_KEY, BTN_TOOL_QUADTAP, four ? 1 : 0);
#endif
#ifdef BTN_TOOL_QUINTTAP
_sendEvent(EV_KEY, BTN_TOOL_QUINTTAP, five ? 1 : 0);
#endif
}

int TouchpadDevice::_clampX(int x) const {
return clamp_value(x, 0, _range_x);
}

int TouchpadDevice::_clampY(int y) const {
return clamp_value(y, 0, _range_y);
}

void TouchpadDevice::_endContactsLocked() {
if (!_active || _contacts.empty()) {
_active = false;
_contacts.clear();
_active_fingers = 0;
return;
}

for (std::size_t slot = 0; slot < _contacts.size(); ++slot) {
_sendEvent(EV_ABS, ABS_MT_SLOT, static_cast<int>(slot));
_sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
}
_setToolButtons(_active_fingers, false);
_sendSyn();
_contacts.clear();
_active = false;
_active_fingers = 0;
}
Loading
Loading