Skip to content
Merged
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ but reports the affected capability as unavailable and returns
The Linux backend uses `libevdev` internally to construct uinput keyboard,
mouse, touchscreen, trackpad, and pen tablet devices. Consumers still use the
same platform-neutral C++ API; `libevdev` is a Linux build dependency, not a
public API dependency.
public API dependency. UTF-8 keyboard text submission is supported through the
same Unicode compose sequence for both uinput keyboards and the XTest fallback.

The Linux packaging model needs `/dev/uinput` and `/dev/uhid` access. Install a udev rules file such
as `/etc/udev/rules.d/60-libvirtualhid.rules` with:
Expand Down
116 changes: 60 additions & 56 deletions src/core/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,30 @@ namespace lvh {
});
}

template<class BackendAction, class DeviceUpdate>
OperationStatus submit_touch_event(
const auto &device_ptr,
const char *closed_message,
BackendAction backend_action,
DeviceUpdate device_update
) {
return with_device(device_ptr, [closed_message, backend_action, device_update](auto &device) {
if (!device.open) {
return OperationStatus::failure(ErrorCode::device_closed, closed_message);
}

if (device.backend) {
if (const auto status = backend_action(*device.backend); !status.ok()) {
return status;
}
}

device_update(device);
++device.submitted_events;
return OperationStatus::success();
});
}

template<class DeviceList>
std::size_t count_open_devices(const DeviceList &devices) {
std::size_t count = 0;
Expand Down Expand Up @@ -707,43 +731,33 @@ namespace lvh {
return validation;
}

return with_device(device_, [&contact](auto &device) {
if (!device.open) {
return OperationStatus::failure(ErrorCode::device_closed, "touchscreen is closed");
}

if (device.backend) {
if (const auto status = device.backend->place_contact(contact); !status.ok()) {
return status;
}
return submit_touch_event(
device_,
"touchscreen is closed",
[&contact](auto &backend) {
return backend.place_contact(contact);
},
[&contact](auto &device) {
device.last_contact = contact;
}

device.last_contact = contact;
++device.submitted_events;
return OperationStatus::success();
});
);
}

OperationStatus Touchscreen::release_contact(std::int32_t contact_id) {
if (contact_id < 0) {
return OperationStatus::failure(ErrorCode::invalid_argument, "touch contact id must not be negative");
}

return with_device(device_, [contact_id](auto &device) {
if (!device.open) {
return OperationStatus::failure(ErrorCode::device_closed, "touchscreen is closed");
return submit_touch_event(
device_,
"touchscreen is closed",
[contact_id](auto &backend) {
return backend.release_contact(contact_id);
},
[contact_id](auto &device) {
device.last_contact.id = contact_id;
}

if (device.backend) {
if (const auto status = device.backend->release_contact(contact_id); !status.ok()) {
return status;
}
}

device.last_contact.id = contact_id;
++device.submitted_events;
return OperationStatus::success();
});
);
}

TouchContact Touchscreen::last_submitted_contact() const {
Expand Down Expand Up @@ -814,43 +828,33 @@ namespace lvh {
return validation;
}

return with_device(device_, [&contact](auto &device) {
if (!device.open) {
return OperationStatus::failure(ErrorCode::device_closed, "trackpad is closed");
return submit_touch_event(
device_,
"trackpad is closed",
[&contact](auto &backend) {
return backend.place_contact(contact);
},
[&contact](auto &device) {
device.last_contact = contact;
}

if (device.backend) {
if (const auto status = device.backend->place_contact(contact); !status.ok()) {
return status;
}
}

device.last_contact = contact;
++device.submitted_events;
return OperationStatus::success();
});
);
}

OperationStatus Trackpad::release_contact(std::int32_t contact_id) {
if (contact_id < 0) {
return OperationStatus::failure(ErrorCode::invalid_argument, "touch contact id must not be negative");
}

return with_device(device_, [contact_id](auto &device) {
if (!device.open) {
return OperationStatus::failure(ErrorCode::device_closed, "trackpad is closed");
}

if (device.backend) {
if (const auto status = device.backend->release_contact(contact_id); !status.ok()) {
return status;
}
return submit_touch_event(
device_,
"trackpad is closed",
[contact_id](auto &backend) {
return backend.release_contact(contact_id);
},
[contact_id](auto &device) {
device.last_contact.id = contact_id;
}

device.last_contact.id = contact_id;
++device.submitted_events;
return OperationStatus::success();
});
);
}

OperationStatus Trackpad::button(bool pressed) {
Expand Down
139 changes: 57 additions & 82 deletions src/platform/linux/uhid_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,57 @@ namespace lvh::detail {
return static_cast<KeyboardKeyCode>(0x41 + (digit - 'A'));
}

template<std::size_t Count, class SubmitKeyEvent>
OperationStatus submit_keyboard_events(const std::array<KeyboardEvent, Count> &events, SubmitKeyEvent &submit_key_event) {
for (const auto &event : events) {
if (const auto status = submit_key_event(event); !status.ok()) {
return status;
}
}
return OperationStatus::success();
}

template<class SubmitKeyEvent>
OperationStatus type_text_with_unicode_hex(std::string_view text, SubmitKeyEvent submit_key_event) {
static constexpr std::array<KeyboardEvent, 6> unicode_hex_prefix {{
{.key_code = 0xA2, .pressed = true},
{.key_code = 0xA0, .pressed = true},
{.key_code = 0x55, .pressed = true},
{.key_code = 0x55, .pressed = false},
{.key_code = 0xA0, .pressed = false},
{.key_code = 0xA2, .pressed = false},
}};
static constexpr std::array<KeyboardEvent, 2> unicode_hex_suffix {{
{.key_code = 0x0D, .pressed = true},
{.key_code = 0x0D, .pressed = false},
}};

for (const auto codepoint : decode_utf8(text)) {
const auto hex = uppercase_hex(codepoint);

if (const auto status = submit_keyboard_events(unicode_hex_prefix, submit_key_event); !status.ok()) {
return status;
}

for (const auto digit : hex) {
const auto key_code = hex_digit_key_code(digit);
const std::array<KeyboardEvent, 2> digit_events {{
{.key_code = key_code, .pressed = true},
{.key_code = key_code, .pressed = false},
}};
if (const auto status = submit_keyboard_events(digit_events, submit_key_event); !status.ok()) {
return status;
}
}

if (const auto status = submit_keyboard_events(unicode_hex_suffix, submit_key_event); !status.ok()) {
return status;
}
}

return OperationStatus::success();
}

[[maybe_unused]] int legacy_scroll_steps(std::int32_t distance) {
if (distance == 0) {
return 0;
Expand Down Expand Up @@ -1158,47 +1209,9 @@ namespace lvh::detail {
}

OperationStatus type_text(const KeyboardTextEvent &event) override {
for (const auto codepoint : decode_utf8(event.text)) {
const auto hex = uppercase_hex(codepoint);

if (const auto status = submit({.key_code = 0xA2, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA0, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x55, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x55, .pressed = false}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA0, .pressed = false}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA2, .pressed = false}); !status.ok()) {
return status;
}

for (const auto digit : hex) {
const auto key_code = hex_digit_key_code(digit);
if (const auto status = submit({.key_code = key_code, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = key_code, .pressed = false}); !status.ok()) {
return status;
}
}

if (const auto status = submit({.key_code = 0x0D, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x0D, .pressed = false}); !status.ok()) {
return status;
}
}

return OperationStatus::success();
return type_text_with_unicode_hex(event.text, [this](const KeyboardEvent &key_event) {
return submit(key_event);
});
}

OperationStatus close() override {
Expand Down Expand Up @@ -1952,47 +1965,9 @@ namespace lvh::detail {
}

OperationStatus type_text(const KeyboardTextEvent &event) override {
for (const auto codepoint : decode_utf8(event.text)) {
const auto hex = uppercase_hex(codepoint);

if (const auto status = submit({.key_code = 0xA2, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA0, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x55, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x55, .pressed = false}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA0, .pressed = false}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0xA2, .pressed = false}); !status.ok()) {
return status;
}

for (const auto digit : hex) {
const auto key_code = hex_digit_key_code(digit);
if (const auto status = submit({.key_code = key_code, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = key_code, .pressed = false}); !status.ok()) {
return status;
}
}

if (const auto status = submit({.key_code = 0x0D, .pressed = true}); !status.ok()) {
return status;
}
if (const auto status = submit({.key_code = 0x0D, .pressed = false}); !status.ok()) {
return status;
}
}

return OperationStatus::success();
return type_text_with_unicode_hex(event.text, [this](const KeyboardEvent &key_event) {
return submit(key_event);
});
}

OperationStatus close() override {
Expand Down
Loading