--- a/src/X11Command.h Sat Mar 27 00:29:10 2021 +0100
+++ b/src/X11Command.h Sun Mar 28 21:11:43 2021 +0200
@@ -41,25 +41,38 @@
virtual ~Display() {
if (display) XCloseDisplay(display);
}
-
};
- class DeviceList {
+ class Device {
+ public:
+ XDevice* device = nullptr;
+ // TODO: more OOP
+
+ virtual ~Device() {
+ if (device) XFree(device);
+ }
+ };
+
+ class DeviceInfoList {
public:
XDeviceInfo* items = nullptr;
- int count = 0;
+ int size = 0;
// TODO: more OOP
- virtual ~DeviceList() {
+ 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: support also other encodings and use platform encoding as default
+ std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: use platform encoding as default
- relpipe::common::type::StringX getDeviceType(Display& display, XDeviceInfo* device) {
+ relpipe::common::type::StringX getDeviceType(const Display& display, const XDeviceInfo* device) {
if (device && device->type) {
char* raw = XGetAtomName(display.display, device->type);
if (raw) {
@@ -72,44 +85,203 @@
return L"";
}
- void listInputDevices(Display& display, Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer) {
+ 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);
- DeviceList devices;
- devices.items = XListInputDevices(display.display, &devices.count);
+ DeviceInfoList devices;
+ devices.items = XListInputDevices(display.display, &devices.size);
- for (int i = 0; i < devices.count; i++) {
- relpipe::common::type::Integer id = (devices.items + i)->id;
- relpipe::common::type::StringX name = convertor.from_bytes((devices.items + i)->name);
- relpipe::common::type::StringX type = getDeviceType(display, devices.items + i);
+ 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) {
+ void listInputEvents(Display& display, Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer, std::function<void() > relationalWriterFlush) {
writer->startRelation(L"x11_input_event",{
- {L"device_id", relpipe::writer::TypeId::INTEGER},
+ {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);
- // TODO: list events
+
+ {
+ DeviceInfoList devices;
+ devices.items = XListInputDevices(display.display, &devices.size);
+ for (int i = 0; i < devices.size; i++) {
+ try {
+ registerEvents(display, devices[i]);
+ } catch (...) {
+ // exception "Unable to open device: …"
+ // TODO: do not call registerEvents() for some devices, skip them
+ }
+ }
+ }
+
+ 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) {
+ 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);
- if (configuration.listInputEvents) listInputEvents(display, configuration, writer);
+ 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.");
}