/**
* Relational pipes
* Copyright © 2021 František Kučera (Frantovo.cz, GlobalCode.info)
*
* 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, version 3 of the License.
*
* 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/>.
*/
#pragma once
#include <codecvt>
#include <memory>
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <X11/extensions/XInput.h>
#include "Configuration.h"
namespace relpipe {
namespace in {
namespace x11 {
class X11Command {
private:
class Display {
public:
::Display* display = nullptr;
// TODO: more OOP
virtual ~Display() {
if (display) XCloseDisplay(display);
}
};
class Device {
public:
XDevice* device = nullptr;
// TODO: more OOP
virtual ~Device() {
if (device) XFree(device);
}
};
class DeviceInfoList {
public:
XDeviceInfo* items = nullptr;
int size = 0;
// TODO: more OOP
XDeviceInfo* operator[](int index) const {
if (index < size) return items + index;
else throw std::out_of_range("invalid index of XDeviceInfo");
}
virtual ~DeviceInfoList() {
if (items) XFreeDeviceList(items);
}
};
std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: use platform encoding as default
relpipe::common::type::StringX getDeviceType(const Display& display, const XDeviceInfo* device) {
if (device && device->type) {
char* raw = XGetAtomName(display.display, device->type);
if (raw) {
relpipe::common::type::StringX type = convertor.from_bytes(raw);
XFree(raw);
transform(type.begin(), type.end(), type.begin(), ::tolower);
return type;
}
}
return L"";
}
void listInputDevices(const Display& display, Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer, std::function<void() > relationalWriterFlush) {
writer->startRelation(L"x11_input_device",{
{L"id", relpipe::writer::TypeId::INTEGER},
{L"name", relpipe::writer::TypeId::STRING},
{L"type", relpipe::writer::TypeId::STRING},
}, true);
DeviceInfoList devices;
devices.items = XListInputDevices(display.display, &devices.size);
for (int i = 0; i < devices.size; i++) {
relpipe::common::type::Integer id = devices[i]->id;
relpipe::common::type::StringX name = convertor.from_bytes(devices[i]->name);
relpipe::common::type::StringX type = getDeviceType(display, devices[i]);
writer->writeAttribute(&id, typeid (id));
writer->writeAttribute(&name, typeid (name));
writer->writeAttribute(&type, typeid (type));
}
relationalWriterFlush();
}
/**
* Fields of this structure are filled in DeviceKeyPress(), DeviceButtonPress() … macros.
*/
class EventType {
private:
const static int UNUSED = -1;
public:
int motion = UNUSED;
int buttonPress = UNUSED;
int buttonRelease = UNUSED;
int keyPress = UNUSED;
int keyRelease = UNUSED;
int proximityIn = UNUSED;
int proximityOut = UNUSED;
} eventType;
void registerEvents(const Display& display, const XDeviceInfo* deviceInfo) {
bool registerProximityEvents = false; // TODO: proximity?
Device device;
Window window;
int screen;
int eventCount = 0;
XEventClass events[7]; // TODO: check array size
XInputClassInfo* classInfo;
int classIndex;
screen = DefaultScreen(display.display);
window = RootWindow(display.display, screen);
// TODO: configurable window from which we capture the events or optionally open our own window (can also provide some visual feedback/info)
// Currently we can do something like:
// Xephyr :5 -screen 1920x1080
// DISPLAY=:5 relpipe-in-x11 --list-input-devices false --list-input-events true | …
device.device = XOpenDevice(display.display, deviceInfo->id);
if (device.device) {
if (device.device->num_classes > 0) {
for (classInfo = device.device->classes, classIndex = 0; classIndex < deviceInfo->num_classes; classInfo++, classIndex++) {
if (classInfo->input_class == KeyClass) {
DeviceKeyPress(device.device, eventType.keyPress, events[eventCount]);
eventCount++;
DeviceKeyRelease(device.device, eventType.keyRelease, events[eventCount]);
eventCount++;
} else if (classInfo->input_class == ButtonClass) {
DeviceButtonPress(device.device, eventType.buttonPress, events[eventCount]);
eventCount++;
DeviceButtonRelease(device.device, eventType.buttonRelease, events[eventCount]);
eventCount++;
} else if (classInfo->input_class == ValuatorClass) {
DeviceMotionNotify(device.device, eventType.motion, events[eventCount]);
eventCount++;
if (registerProximityEvents) {
ProximityIn(device.device, eventType.proximityIn, events[eventCount]);
eventCount++;
ProximityOut(device.device, eventType.proximityOut, events[eventCount]);
eventCount++;
}
}
}
int result = XSelectExtensionEvent(display.display, window, events, eventCount);
if (result != Success) throw std::logic_error("Unable to register events from device: " + std::to_string(deviceInfo->id) + " Result: " + std::to_string(result));
}
} else {
throw std::invalid_argument("Unable to open the device: " + std::to_string(deviceInfo->id));
}
}
void listInputEvents(Display& display, Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer, std::function<void() > relationalWriterFlush) {
writer->startRelation(L"x11_input_event",{
{L"device", relpipe::writer::TypeId::INTEGER},
{L"type", relpipe::writer::TypeId::STRING},
{L"state", relpipe::writer::TypeId::STRING},
{L"key", relpipe::writer::TypeId::INTEGER},
{L"button", relpipe::writer::TypeId::INTEGER},
{L"x", relpipe::writer::TypeId::INTEGER},
{L"y", relpipe::writer::TypeId::INTEGER},
// {L"x_root", relpipe::writer::TypeId::INTEGER},
// {L"y_root", relpipe::writer::TypeId::INTEGER},
}, true);
{
DeviceInfoList devices;
devices.items = XListInputDevices(display.display, &devices.size);
for (int i = 0; i < devices.size; i++) {
if (devices[i]->type) {
registerEvents(display, devices[i]);
}
}
}
for (XEvent event; true;) {
XNextEvent(display.display, &event);
relpipe::common::type::Integer device = -1; // TODO: null
relpipe::common::type::StringX type;
relpipe::common::type::StringX state;
relpipe::common::type::Integer button = -1; // TODO: null
relpipe::common::type::Integer key = -1; // TODO: null
relpipe::common::type::Integer x = -1; // TODO: null
relpipe::common::type::Integer y = -1; // TODO: null
// relpipe::common::type::Integer xRoot = -1; // TODO: null
// relpipe::common::type::Integer yRoot = -1; // TODO: null
if (event.type == eventType.motion) {
XDeviceMotionEvent* e = (XDeviceMotionEvent*) & event;
device = e->deviceid;
type = L"motion";
x = e->x;
y = e->y;
// xRoot = e->x_root;
// yRoot = e->y_root;
} else if (event.type == eventType.buttonPress || event.type == eventType.buttonRelease) {
XDeviceButtonEvent* e = (XDeviceButtonEvent*) & event;
device = e->deviceid;
type = L"button";
state = event.type == eventType.buttonPress ? L"pressed" : L"released";
button = e->button;
x = e->x;
y = e->y;
// xRoot = e->x_root;
// yRoot = e->y_root;
} else if (event.type == eventType.keyPress || event.type == eventType.keyRelease) {
XDeviceKeyEvent* e = (XDeviceKeyEvent*) & event;
device = e->deviceid;
type = L"key";
state = event.type == eventType.keyPress ? L"pressed" : L"released";
key = e->keycode;
x = e->x;
y = e->y;
// xRoot = e->x_root;
// yRoot = e->y_root;
} else if (event.type == eventType.proximityIn || event.type == eventType.proximityOut) {
XProximityNotifyEvent* e = (XProximityNotifyEvent*) & event;
device = e->deviceid;
type = L"proximity";
}
writer->writeAttribute(&device, typeid (device));
writer->writeAttribute(type);
writer->writeAttribute(state);
writer->writeAttribute(&key, typeid (key));
writer->writeAttribute(&button, typeid (button));
writer->writeAttribute(&x, typeid (x));
writer->writeAttribute(&y, typeid (y));
// writer->writeAttribute(&xRoot, typeid (xRoot));
// writer->writeAttribute(&yRoot, typeid (yRoot));
relationalWriterFlush();
}
}
static int handleXError(::Display* display, XErrorEvent* errorEvent) {
// FIXME: print error
return 0;
}
public:
void process(Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer, std::function<void() > relationalWriterFlush) {
XSetErrorHandler(handleXError);
Display display;
display.display = XOpenDisplay(nullptr);
if (display.display) {
if (configuration.listInputDevices) listInputDevices(display, configuration, writer, relationalWriterFlush);
if (configuration.listInputEvents) listInputEvents(display, configuration, writer, relationalWriterFlush);
} else {
throw std::invalid_argument("Unable to open display. Please check the $DISPLAY variable.");
}
}
};
}
}
}