list input events: first working version v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 28 Mar 2021 21:11:43 +0200
branchv_0
changeset 3 72384bb5c66e
parent 2 8d44cba0a3d1
child 4 820c4a4a3ed8
list input events: first working version
src/CMakeLists.txt
src/X11Command.h
src/relpipe-in-x11.cpp
--- 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})
 
--- 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.");
 		}
--- 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 <cstdlib>
 #include <memory>
+#include <functional>
 
 #include <relpipe/writer/RelationalWriter.h>
 #include <relpipe/writer/RelpipeWriterException.h>
@@ -46,7 +47,7 @@
 		Configuration configuration = cliParser.parse(cli.arguments());
 		X11Command command;
 		std::shared_ptr<RelationalWriter> 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());