# HG changeset patch # User František Kučera # Date 1616958703 -7200 # Node ID 72384bb5c66e4e5d2a63d4239bc6ac328c1fcf42 # Parent 8d44cba0a3d14e59b0511951f9d089921fdf4d97 list input events: first working version diff -r 8d44cba0a3d1 -r 72384bb5c66e src/CMakeLists.txt --- a/src/CMakeLists.txt Sat Mar 27 00:29:10 2021 +0100 +++ b/src/CMakeLists.txt Sun Mar 28 21:11:43 2021 +0200 @@ -17,7 +17,7 @@ # Relpipe libraries: INCLUDE(FindPkgConfig) -pkg_check_modules (RELPIPE_LIBS relpipe-lib-writer.cpp relpipe-lib-cli.cpp x11 xi xtst) +pkg_check_modules (RELPIPE_LIBS relpipe-lib-writer.cpp relpipe-lib-cli.cpp x11 xi) include_directories(${RELPIPE_LIBS_INCLUDE_DIRS}) link_directories(${RELPIPE_LIBS_LIBRARY_DIRS}) diff -r 8d44cba0a3d1 -r 72384bb5c66e src/X11Command.h --- 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> convertor; // TODO: support also other encodings and use platform encoding as default + std::wstring_convert> 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) { + void listInputDevices(const Display& display, Configuration& configuration, std::shared_ptr writer, std::function 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) { + void listInputEvents(Display& display, Configuration& configuration, std::shared_ptr writer, std::function 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) { + void process(Configuration& configuration, std::shared_ptr writer, std::function 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."); } diff -r 8d44cba0a3d1 -r 72384bb5c66e src/relpipe-in-x11.cpp --- a/src/relpipe-in-x11.cpp Sat Mar 27 00:29:10 2021 +0100 +++ b/src/relpipe-in-x11.cpp Sun Mar 28 21:11:43 2021 +0200 @@ -16,6 +16,7 @@ */ #include #include +#include #include #include @@ -46,7 +47,7 @@ Configuration configuration = cliParser.parse(cli.arguments()); X11Command command; std::shared_ptr writer(Factory::create(std::cout)); - command.process(configuration, writer); + command.process(configuration, writer, std::bind(fflush, stdout)); // std::bind(fflush, XXX) Factory::create(XXX) must be the same stream XXX resultCode = CLI::EXIT_CODE_SUCCESS; } catch (RelpipeWriterException& e) { fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessge().c_str());