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
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
qt5-default libqt5x11extras5-dev

- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
Expand Down
4 changes: 2 additions & 2 deletions 55-projecteur.rules.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ SUBSYSTEMS=="hid", KERNELS=="0005:046D:B503.*", MODE="0660", TAG+="uaccess"

# Additional supported Bluetooth devices @EXTRA_BLUETOOTH_UDEV_RULES@

# Rules for uninput: Essential for creating a virtual input device that
# Projecteur use for forwarding device events to the system after grabbing it
# Rules for uinput: Essential for creating a virtual input device that
# Projecteur uses to forward device events to the system after grabbing it
KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput"
14 changes: 10 additions & 4 deletions src/aboutdlg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
#include <QTextBrowser>

namespace {
// -------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------
/// Contributor (name, github_name, email, url)
struct Contributor
{
explicit Contributor(const QString& name = {}, const QString& github_name = {},
Expand All @@ -27,8 +28,10 @@ namespace {

QString toHtml() const
{
auto html = QString("<b>%1</b>").arg(name.isEmpty() ? QString("<a href=\"https://github.com/%1\">%1</a>").arg(github_name)
: name);
auto html = QString("<b>%1</b>").arg(name.isEmpty()
? QString("<a href=\"https://github.com/%1\">%1</a>").arg(github_name)
: name);

if (email.size()) {
html += QString(" &lt;%1&gt;").arg(email);
}
Expand All @@ -48,7 +51,7 @@ namespace {
QString url;
};

// -------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------
QString getContributorsHtml()
{
static std::vector<Contributor> contributors =
Expand All @@ -66,6 +69,9 @@ namespace {
Contributor("Stuart Prescott", "llimeht"),
Contributor("Crista Renouard", "Lumnicence"),
Contributor("freddii", "freddii"),
Contributor("Matthias Blümel", "Blaimi"),
Contributor("Grzegorz Szymaszek", "gszy"),
Contributor("TheAssassin", "TheAssassin"),
};

static std::mt19937 g(std::random_device{}());
Expand Down
7 changes: 4 additions & 3 deletions src/device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ const char* toString(ConnectionMode cm, bool withClass)

// -------------------------------------------------------------------------------------------------
DeviceConnection::DeviceConnection(const DeviceId& id, const QString& name,
std::shared_ptr<VirtualDevice> vdev)
std::shared_ptr<VirtualDevice> vmouse,
std::shared_ptr<VirtualDevice> vkeyboard)
: m_deviceId(id)
, m_deviceName(name)
, m_inputMapper(std::make_shared<InputMapper>(std::move(vdev)))
, m_inputMapper(std::make_shared<InputMapper>(std::move(vmouse), std::move(vkeyboard)))
{
}

Expand Down Expand Up @@ -245,7 +246,7 @@ std::shared_ptr<SubEventConnection> SubEventConnection::create(const DeviceScan:
connection->m_details.grabbed = [&dc, evfd, &sd]()
{
// Grab device inputs if a virtual device exists.
if (dc.inputMapper()->virtualDevice())
if (dc.inputMapper()->hasVirtualDevice())
{
const int res = ioctl(evfd, EVIOCGRAB, 1);
if (res == 0) { return true; }
Expand Down
4 changes: 3 additions & 1 deletion src/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class DeviceConnection : public QObject
Q_OBJECT

public:
DeviceConnection(const DeviceId& id, const QString& name, std::shared_ptr<VirtualDevice> vdev);
DeviceConnection(const DeviceId& id, const QString& name,
std::shared_ptr<VirtualDevice> vmouse, std::shared_ptr<VirtualDevice> vkeyboard);

~DeviceConnection();

const auto& deviceName() const { return m_deviceName; }
Expand Down
128 changes: 107 additions & 21 deletions src/deviceinput.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,29 @@ namespace {

return KeyEventSequence{std::move(pressed)};
};

// -----------------------------------------------------------------------------------------------
bool isMouseEvent(const input_event* input_events, size_t num)
{
if (num < 2) {
// no events, or single SYN event
return false;
}

auto const& ev = [&]() -> input_event const& {
if (input_events[0].type == EV_MSC) {
return input_events[1];
}
return input_events[0];
}();

if (ev.type == EV_KEY && ev.code >= BTN_MISC && ev.code < KEY_OK) {
return true;
}

return false;
}

} // end anonymous namespace

// -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -547,16 +570,26 @@ const char* toString(Action::Type at, bool withClass)
// -------------------------------------------------------------------------------------------------
struct InputMapper::Impl
{
Impl(InputMapper* parent, std::shared_ptr<VirtualDevice> vdev);
Impl(InputMapper* parent,
std::shared_ptr<VirtualDevice> virtualMouse,
std::shared_ptr<VirtualDevice> virtualKeybaord);

void sequenceTimeout();
void resetState();
void record(const struct input_event input_events[], size_t num);
void emitNativeKeySequence(const NativeKeySequence& ks);
void execAction(const std::shared_ptr<Action>& action, DeviceKeyMap::Result r);
bool hasVirtualDevices() const;

void forwardEvents(const struct input_event input_events[], size_t num);
void forwardEvents(const std::vector<struct input_event>& input_events);

InputMapper* m_parent = nullptr;
std::shared_ptr<VirtualDevice> m_vdev; // can be a nullptr if application is started without uinput

// virtual devices can be empty shared_ptr's if app is started without uinput
std::shared_ptr<VirtualDevice> m_vmouse;
std::shared_ptr<VirtualDevice> m_vkeyboard;

QTimer* m_seqTimer = nullptr;
DeviceKeyMap m_keymap;

Expand All @@ -569,9 +602,12 @@ struct InputMapper::Impl
};

// -------------------------------------------------------------------------------------------------
InputMapper::Impl::Impl(InputMapper* parent, std::shared_ptr<VirtualDevice> vdev)
InputMapper::Impl::Impl(InputMapper* parent
, std::shared_ptr<VirtualDevice> virtualMouse
, std::shared_ptr<VirtualDevice> virtualKeyboard)
: m_parent(parent)
, m_vdev(std::move(vdev))
, m_vmouse(std::move(virtualMouse))
, m_vkeyboard(std::move(virtualKeyboard))
, m_seqTimer(new QTimer(parent))
{
constexpr int defaultSequenceIntervalMs = 250;
Expand All @@ -580,6 +616,12 @@ InputMapper::Impl::Impl(InputMapper* parent, std::shared_ptr<VirtualDevice> vdev
connect(m_seqTimer, &QTimer::timeout, parent, [this](){ sequenceTimeout(); });
}

// -------------------------------------------------------------------------------------------------
bool InputMapper::Impl::hasVirtualDevices() const
{
return (m_vmouse && m_vkeyboard);
}

// -------------------------------------------------------------------------------------------------
void InputMapper::Impl::execAction(const std::shared_ptr<Action>& action, DeviceKeyMap::Result r)
{
Expand Down Expand Up @@ -612,9 +654,9 @@ void InputMapper::Impl::sequenceTimeout()
if (m_lastState.first == DeviceKeyMap::Result::Valid) {
// Last input event was part of a valid key sequence, but timeout hit
// So we emit our stored event so far to the virtual device
if (m_vdev && !m_events.empty())
if (hasVirtualDevices() && !m_events.empty())
{
m_vdev->emitEvents(m_events);
forwardEvents(m_events);
}
resetState();
}
Expand All @@ -625,9 +667,10 @@ void InputMapper::Impl::sequenceTimeout()
{
execAction(m_lastState.second->action, DeviceKeyMap::Result::PartialHit);
}
else if (m_vdev && !m_events.empty())
else if (hasVirtualDevices() && !m_events.empty())
{
m_vdev->emitEvents(m_events);
// TODO differentiate between mouse and keyboard events
forwardEvents(m_events);
m_events.resize(0);
}
resetState();
Expand All @@ -644,7 +687,7 @@ void InputMapper::Impl::resetState()
// -------------------------------------------------------------------------------------------------
void InputMapper::Impl::emitNativeKeySequence(const NativeKeySequence& ks)
{
if (!m_vdev) { return; }
if (!m_vkeyboard) { return; }

std::vector<input_event> events;
events.reserve(5); // up to 3 modifier keys + 1 key + 1 syn event
Expand All @@ -653,7 +696,7 @@ void InputMapper::Impl::emitNativeKeySequence(const NativeKeySequence& ks)
for (const auto& ie : ke) {
events.emplace_back(input_event{{}, ie.type, ie.code, ie.value});
}
m_vdev->emitEvents(events);
m_vkeyboard->emitEvents(events);
events.resize(0);
}
}
Expand All @@ -671,25 +714,67 @@ void InputMapper::Impl::record(const struct input_event input_events[], size_t n
}

// -------------------------------------------------------------------------------------------------
void InputMapper::Impl::forwardEvents(const std::vector<struct input_event>& input_events)
{
forwardEvents(input_events.data(), input_events.size());
}

// -------------------------------------------------------------------------------------------------
InputMapper::InputMapper(std::shared_ptr<VirtualDevice> virtualDevice, QObject* parent)
void InputMapper::Impl::forwardEvents(const struct input_event input_events[], size_t num)
{
input_event const* beg = input_events;
input_event const* end = input_events + num;

auto predicate = [](input_event const& e){
return e.type == EV_SYN;
};

// handle each part separated by a SYN event
input_event const* syn = std::find_if(beg, end, predicate);

while (syn != end) {
auto const len = std::distance(beg, syn) + 1;

if (isMouseEvent(beg, len)) {
m_vmouse->emitEvents(beg, len);
} else {
m_vkeyboard->emitEvents(beg, len);
}

beg = syn + 1;
syn = std::find_if(beg, end, predicate);
}
}

// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
InputMapper::InputMapper(
std::shared_ptr<VirtualDevice> virtualMouse
, std::shared_ptr<VirtualDevice> virtualKeyboard
, QObject* parent)
: QObject(parent)
, impl(std::make_unique<Impl>(this, std::move(virtualDevice)))
, impl(std::make_unique<Impl>(this, std::move(virtualMouse), std::move(virtualKeyboard)))
{}

// -------------------------------------------------------------------------------------------------
InputMapper::~InputMapper() = default;

// -------------------------------------------------------------------------------------------------
std::shared_ptr<VirtualDevice> InputMapper::virtualDevice() const
std::shared_ptr<VirtualDevice> InputMapper::virtualMouse() const
{
return impl->m_vdev;
return impl->m_vmouse;
}

// -------------------------------------------------------------------------------------------------
std::shared_ptr<VirtualDevice> InputMapper::virtualKeyboard() const
{
return impl->m_vkeyboard;
}

// -------------------------------------------------------------------------------------------------
bool InputMapper::hasVirtualDevice() const
{
return !!(impl->m_vdev);
return impl->hasVirtualDevices();
}

// -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -728,12 +813,12 @@ void InputMapper::setKeyEventInterval(int interval)
// -------------------------------------------------------------------------------------------------
void InputMapper::addEvents(const input_event* input_events, size_t num)
{
if (num == 0 || (!impl->m_vdev)) { return; }
if (num == 0 || (!hasVirtualDevice())) { return; }

// If no key mapping is configured ...
if (!impl->m_recordingMode && !impl->m_keymap.hasConfig()) {
// ... forward events to virtual device if it exists...
impl->m_vdev->emitEvents(input_events, num);
// ... forward events to virtual device
impl->forwardEvents(input_events, num);
return;
}

Expand Down Expand Up @@ -774,7 +859,8 @@ void InputMapper::addEvents(const input_event* input_events, size_t num)
if (res == DeviceKeyMap::Result::Miss)
{ // key sequence miss, send all buffered events so far
impl->m_seqTimer->stop();
impl->m_vdev->emitEvents(impl->m_events);

impl->forwardEvents(impl->m_events);

impl->resetState();
}
Expand All @@ -785,7 +871,7 @@ void InputMapper::addEvents(const input_event* input_events, size_t num)
impl->execAction(pos->action, res);
}
else {
impl->m_vdev->emitEvents(impl->m_events);
impl->forwardEvents(impl->m_events);
}

impl->resetState();
Expand All @@ -808,7 +894,7 @@ void InputMapper::addEvents(const KeyEvent& key_event)
return ie;
};

// // Check if key_event does have SYN event at end
// Check if key_event does have SYN event at end
const bool hasLastSYN = (key_event.back().type == EV_SYN);

std::vector<struct input_event> events;
Expand Down
11 changes: 8 additions & 3 deletions src/deviceinput.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ QDebug operator<<(QDebug debug, const KeyEvent &ke);
// Some inputs from Logitech Spotlight device (like Next Hold and Back Hold events) are not a valid
// input event (input_event in linux/input.h) in a conventional sense. They are communicated
// via HID++ messages from the device. Using the input mapper we need to
// reserve some KeyEventSequence for theese events. These KeyEventSequence should be designed in
// reserve some KeyEventSequence for these events. These KeyEventSequence should be designed in
// such a way that they cannot interfere with other valid input events from the device.
namespace SpecialKeys
{
Expand Down Expand Up @@ -288,7 +288,11 @@ class InputMapper : public QObject
Q_OBJECT

public:
InputMapper(std::shared_ptr<VirtualDevice> virtualDevice, QObject* parent = nullptr);
InputMapper(
std::shared_ptr<VirtualDevice> virtualMouse,
std::shared_ptr<VirtualDevice> virtualKeyboard,
QObject* parent = nullptr);

~InputMapper();

void resetState(); // Reset any stored sequence state.
Expand All @@ -307,7 +311,8 @@ class InputMapper : public QObject
const SpecialMoveInputs& specialMoveInputs();
void setSpecialMoveInputs(SpecialMoveInputs moveInputs);

std::shared_ptr<VirtualDevice> virtualDevice() const;
std::shared_ptr<VirtualDevice> virtualMouse() const;
std::shared_ptr<VirtualDevice> virtualKeyboard() const;
bool hasVirtualDevice() const;

void setConfiguration(const InputMapConfig& config);
Expand Down
Loading