diff --git a/base/project.js b/base/project.js index 50a08648..e2221049 100644 --- a/base/project.js +++ b/base/project.js @@ -265,6 +265,8 @@ if (flags.with_nfd && (platform == "windows" || platform == "linux" || platform project.add_lib("gtk-3"); project.add_lib("gobject-2.0"); project.add_lib("glib-2.0"); + project.add_lib("X11"); + project.add_lib("Xi"); } else if (platform == "macos") { project.add_cfiles("sources/libs/nfd.m"); diff --git a/base/sources/backends/linux_system.c b/base/sources/backends/linux_system.c index 174e16bf..e07279f5 100644 --- a/base/sources/backends/linux_system.c +++ b/base/sources/backends/linux_system.c @@ -33,8 +33,8 @@ static bool saveInitialized = false; static const char *videoFormats[] = {"ogv", NULL}; static struct timeval start; static bool mouse_hidden = false; +static int xi_extension = -1; -static void init_pen_device(XDeviceInfo *info, struct x11_pen_device *pen, bool eraser); bool iron_x11_init(); void iron_display_init() { @@ -275,10 +275,16 @@ void iron_window_create(iron_window_options_t *win) { iron_window_set_title(win->title); if (x11_ctx.pen.id != -1) { - xlib.XSelectExtensionEvent(x11_ctx.display, window->window, &x11_ctx.pen.motionClass, 1); - } - if (x11_ctx.eraser.id != -1) { - xlib.XSelectExtensionEvent(x11_ctx.display, window->window, &x11_ctx.eraser.motionClass, 1); + XIEventMask mask; + unsigned char mask_data[XIMaskLen(XI_LASTEVENT)]; + memset(mask_data, 0, sizeof(mask_data)); + mask.deviceid = x11_ctx.pen.id; + mask.mask_len = sizeof(mask_data); + mask.mask = mask_data; + XISetMask(mask.mask, XI_Motion); + XISetMask(mask.mask, XI_ButtonPress); + XISetMask(mask.mask, XI_ButtonRelease); + XISelectEvents(x11_ctx.display, window->window, &mask, 1); } gpu_init(win->depth_bits, win->vsync); @@ -312,9 +318,7 @@ bool iron_internal_call_close_callback() { if (iron_internal_window_callbacks[0].close_callback != NULL) { return iron_internal_window_callbacks[0].close_callback(iron_internal_window_callbacks[0].close_data); } - else { - return true; - } + return true; } iron_window_mode_t iron_window_get_mode() { @@ -464,77 +468,86 @@ bool iron_x11_init() { xlib.XInternAtoms(x11_ctx.display, atom_names, sizeof atom_names / sizeof atom_names[0], False, (Atom *)&x11_ctx.atoms); clipboardString = (char *)malloc(clipboardStringSize); + int event, error; + XQueryExtension(x11_ctx.display, "XInputExtension", &xi_extension, &event, &error); + int num_devices; + XIDeviceInfo *devices = XIQueryDevice(x11_ctx.display, XIAllDevices, &num_devices); x11_ctx.pen.id = -1; x11_ctx.eraser.id = -1; - - int count; - XDeviceInfoPtr devices = (XDeviceInfoPtr)xlib.XListInputDevices(x11_ctx.display, &count); - for (int i = 0; i < count; i++) { - strncpy(buffer, devices[i].name, 1023); + for (int i = 0; i < num_devices; i++) { + XIDeviceInfo *info = &devices[i]; + strncpy(buffer, info->name, 1023); buffer[1023] = 0; for (int j = 0; buffer[j]; j++) { buffer[j] = tolower(buffer[j]); } - if (strstr(buffer, "stylus") || strstr(buffer, "pen") || strstr(buffer, "wacom")) { - init_pen_device(&devices[i], &x11_ctx.pen, false); - } - if (strstr(buffer, "eraser")) { - init_pen_device(&devices[i], &x11_ctx.eraser, true); - } - } - if (devices != NULL) { - xlib.XFreeDeviceList(devices); + bool is_pen = strstr(buffer, "stylus") || strstr(buffer, "pen") || strstr(buffer, "wacom"); + bool is_eraser = strstr(buffer, "eraser"); + if (!is_pen && !is_eraser) { + continue; + } + + int pressure_index = -1; + double max = 0; + for (int j = 0; j < info->num_classes; j++) { + if (info->classes[j]->type == XIValuatorClass) { + XIValuatorClassInfo *val = (XIValuatorClassInfo *)info->classes[j]; + if (val->label == XInternAtom(x11_ctx.display, "Abs Pressure", False)) { + pressure_index = val->number; + max = val->max; + break; + } + } + } + if (pressure_index == -1) continue; + + struct x11_pen_device *d = is_pen ? &x11_ctx.pen : &x11_ctx.eraser; + d->id = info->deviceid; + d->pressure_index = pressure_index; + d->pressure_max = max; + d->press = is_pen ? iron_internal_pen_trigger_press : iron_internal_eraser_trigger_press; + d->move = is_pen ? iron_internal_pen_trigger_move : iron_internal_eraser_trigger_move; + d->release = is_pen ? iron_internal_pen_trigger_release : iron_internal_eraser_trigger_release; } + XIFreeDeviceInfo(devices); return true; } -static void init_pen_device(XDeviceInfo *info, struct x11_pen_device *pen, bool eraser) { - XDevice *device = xlib.XOpenDevice(x11_ctx.display, info->id); - XAnyClassPtr c = info->inputclassinfo; - for (int j = 0; j < device->num_classes; j++) { - if (c->class == ValuatorClass) { - XValuatorInfo *valuator_info = (XValuatorInfo *)c; - if (valuator_info->num_axes > 2) { - pen->maxPressure = valuator_info->axes[2].max_value; - } - pen->id = info->id; - DeviceMotionNotify(device, pen->motionEvent, pen->motionClass); - if (eraser) { - pen->press = iron_internal_eraser_trigger_press; - pen->move = iron_internal_eraser_trigger_move; - pen->release = iron_internal_eraser_trigger_release; - } - else { - pen->press = iron_internal_pen_trigger_press; - pen->move = iron_internal_pen_trigger_move; - pen->release = iron_internal_pen_trigger_release; - } - return; - } - c = (XAnyClassPtr)((uint8_t *)c + c->length); - } - xlib.XCloseDevice(x11_ctx.display, device); -} - static void check_pen_device(struct iron_x11_window *window, XEvent *event, struct x11_pen_device *pen) { - if (event->type == pen->motionEvent) { - XDeviceMotionEvent *motion = (XDeviceMotionEvent *)(event); - if (motion->deviceid == pen->id) { - float p = (float)motion->axis_data[2] / (float)pen->maxPressure; - if (p > 0 && x11_ctx.pen.current_pressure == 0) { - pen->press(motion->x, motion->y, p); - } - else if (p == 0 && pen->current_pressure > 0) { - pen->release(motion->x, motion->y, p); - } - else if (p > 0) { - pen->move(motion->x, motion->y, p); - } - pen->current_pressure = p; - } + if (!XGetEventData(x11_ctx.display, &event->xcookie)) { + return; } + + XIDeviceEvent *dev_event = (XIDeviceEvent *)event->xcookie.data; + if (dev_event->event != window->window || dev_event->deviceid != pen->id) { + XFreeEventData(x11_ctx.display, &event->xcookie); + return; + } + + float p = pen->pressure_current; + if (XIMaskIsSet(dev_event->valuators.mask, pen->pressure_index)) { + double *values = dev_event->valuators.values; + int idx = 0; + for (int i = 0; i < pen->pressure_index; i++) { + if (XIMaskIsSet(dev_event->valuators.mask, i)) idx++; + } + p = values[idx] / pen->pressure_max; + } + + if (event->xcookie.evtype == XI_ButtonPress && dev_event->detail == 1) { + pen->press(dev_event->event_x, dev_event->event_y, p); + } + else if (event->xcookie.evtype == XI_ButtonRelease && dev_event->detail == 1) { + pen->release(dev_event->event_x, dev_event->event_y, p); + } + else if (event->xcookie.evtype == XI_Motion && p > 0) { + pen->move(dev_event->event_x, dev_event->event_y, p); + } + pen->pressure_current = p; + + XFreeEventData(x11_ctx.display, &event->xcookie); } struct iron_x11_window *window_from_window(Window window) { @@ -706,13 +719,19 @@ static bool _handle_messages() { while (xlib.XPending(x11_ctx.display)) { XEvent event; xlib.XNextEvent(x11_ctx.display, &event); + + if (event.type == GenericEvent && event.xcookie.extension == xi_extension) { + check_pen_device(&x11_ctx.windows[0], &event, &x11_ctx.pen); + check_pen_device(&x11_ctx.windows[0], &event, &x11_ctx.eraser); + continue; + } + Window window = event.xclient.window; struct iron_x11_window *k_window = window_from_window(window); if (k_window == NULL) { continue; } - check_pen_device(k_window, &event, &x11_ctx.pen); - check_pen_device(k_window, &event, &x11_ctx.eraser); + switch (event.type) { case MappingNotify: { xlib.XRefreshKeyboardMapping(&event.xmapping); @@ -1025,10 +1044,7 @@ void iron_load_url(const char *url) { if (strncmp(url, "http://", sizeof("http://") - 1) == 0 || strncmp(url, "https://", sizeof("https://") - 1) == 0) { char openUrlCommand[256]; snprintf(openUrlCommand, 256, "xdg-open %s", url); - int err = system(openUrlCommand); - if (err != 0) { - iron_log("Error opening url %s", url); - } + system(openUrlCommand); } } @@ -1136,18 +1152,6 @@ void iron_copy_to_clipboard(const char *text) { strcpy(clipboardString, text); } -static int parse_number_at_end_of_line(char *line) { - char *end = &line[strlen(line) - 2]; - int num = 0; - int multi = 1; - while (*end >= '0' && *end <= '9') { - num += (*end - '0') * multi; - multi *= 10; - --end; - } - return num; -} - int iron_hardware_threads(void) { return sysconf(_SC_NPROCESSORS_ONLN); } diff --git a/base/sources/backends/linux_system.h b/base/sources/backends/linux_system.h index ce4a0f4d..19f3ed9e 100644 --- a/base/sources/backends/linux_system.h +++ b/base/sources/backends/linux_system.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #define MAXIMUM_DISPLAYS 8 @@ -140,12 +141,10 @@ struct iron_x11_procs { }; struct x11_pen_device { - XID id; - uint32_t motionEvent; - XEventClass motionClass; - uint32_t maxPressure; - float current_pressure; - + int id; + int pressure_index; + double pressure_max; + float pressure_current; void (*press)(int /*x*/, int /*y*/, float /*pressure*/); void (*move)(int /*x*/, int /*y*/, float /*pressure*/); void (*release)(int /*x*/, int /*y*/, float /*pressure*/);