/* * Loosely based on a mix of: * - xorg-xinput * https://github.com/freedesktop/xorg-xinput/blob/master/src/property.c::print_property_xi2 * - xkbcat * https://github.com/anko/xkbcat */ #define _GNU_SOURCE #include #include #include #include #include #include #include char* get_property_xi2(Display* dpy, int deviceid, Atom property, const char* property_name) { Atom act_type; char* name; int act_format; unsigned long nitems, bytes_after; unsigned char* data; unsigned char* ptr; int j, done = False; char* ret = NULL; name = XGetAtomName(dpy, property); bool interested_in_property = (strcmp(name, property_name) == 0); XFree(name); if (!interested_in_property) return ret; if (XIGetProperty(dpy, deviceid, property, 0, 1000, False, AnyPropertyType, &act_type, &act_format, &nitems, &bytes_after, &data) == Success) { ptr = data; for (j = 0; j < nitems; j++) { switch(act_type) { case XA_STRING: if (act_format != 8) { //Unknown string format done = True; break; } int len = strlen((char*)ptr); ret = strdup((const char*)ptr); j += len; // The loop's j++ jumps over the terminating 0 ptr += len; // ptr += size below jumps over the terminating 0 break; default: // Unknown type done = True; break; } ptr += act_format/8; if (done == True) break; } XFree(data); } return ret; } int is_xinput2_available(Display* disp) { int xiOpcode; int queryEvent, queryError; if (! XQueryExtension(disp, "XInputExtension", &xiOpcode, &queryEvent, &queryError)) { fprintf(stderr, "XInput extension not available\n"); return -1; } int major = 2, minor = 0; int queryResult = XIQueryVersion(disp, &major, &minor); if (queryResult == BadRequest) { fprintf(stderr, "XInput 2.0 support required (got %d.%d)\n", major, minor); return -2; } else if (queryResult != Success) { fprintf(stderr, "XIQueryVersion failed\n"); return -3; } return 0; } typedef struct SlaveInfo { char* slave_names; char* dev_nodes; } SlaveInfo; SlaveInfo new_slave_info() { SlaveInfo si; si.slave_names = NULL; si.dev_nodes = NULL; return si; } void free_slave_info(SlaveInfo si) { free(si.slave_names); free(si.dev_nodes); si.slave_names = NULL; si.dev_nodes = NULL; } bool is_empty_slave_info(SlaveInfo si) { return (si.slave_names == NULL || si.dev_nodes == NULL); } void replace(char* str, char old, char new) { int i = 0; while (str[i] != '\0') { if (str[i] == old) str[i] = new; i++; } } int append_as_line(char** dst, char* line) { uint len_newstr = strlen(line) + 2; if (*dst != NULL) len_newstr += strlen(*dst); char* newstr = (char*)malloc(len_newstr); if (newstr == NULL) return -1; newstr[0] = '\0'; replace(line, '\n', ' '); if (*dst != NULL) strcat(newstr, *dst); strcat(newstr, line); strcat(newstr, "\n"); free(*dst); *dst = newstr; return 0; } SlaveInfo get_slave_info(Display* disp) { int num_slaves; XIDeviceInfo* all_slaves = XIQueryDevice(disp, XIAllDevices, &num_slaves); SlaveInfo slaveinfo = new_slave_info(); bool fail = false; for (int i = 0; i < num_slaves; i++) { XIDeviceInfo* current_slave = &all_slaves[i]; int nprops; Atom* props = XIListProperties(disp, current_slave->deviceid, &nprops); while(nprops-- && !fail) { char* devnode = get_property_xi2(disp, current_slave->deviceid, props[nprops], "Device Node"); if (current_slave->name != NULL && devnode != NULL) { int append_slave_success = append_as_line(&slaveinfo.slave_names, current_slave->name); int append_devnode_success = append_as_line(&slaveinfo.dev_nodes, devnode); if (append_slave_success != 0 || append_devnode_success != 0) fail = true; } free(devnode); } XFree(props); if (fail) { free_slave_info(slaveinfo); break; } } XIFreeDeviceInfo(all_slaves); return slaveinfo; } Display* connect_to_x(const char* xDisplayName) { Display* disp = XOpenDisplay(xDisplayName); if (NULL == disp) { fprintf(stderr, "Cannot open X display '%s'\n", xDisplayName); return NULL; } if (is_xinput2_available(disp) != 0) { XCloseDisplay(disp); return NULL; } return disp; } int main(int argc, char * argv[]) { char* xDisplayName = getenv("DISPLAY"); if (xDisplayName == NULL) { fprintf(stderr, "DISPLAY environment variable not found\n"); exit(1); } Display* disp = connect_to_x(xDisplayName); if (disp == NULL) exit(1); SlaveInfo si = get_slave_info(disp); if (!is_empty_slave_info(si)) { printf("%s", si.slave_names); printf("%s", si.dev_nodes); } free_slave_info(si); XCloseDisplay(disp); exit(0); }