diff --git a/src/evdev-tablet.c b/src/evdev-tablet.c index cc21cc3c..36fd17e4 100644 --- a/src/evdev-tablet.c +++ b/src/evdev-tablet.c @@ -72,6 +72,27 @@ tablet_get_released_buttons(struct tablet_dispatch *tablet, ~(state->bits[i]); } +static struct libinput_tablet_tool_pressure_threshold* +tablet_tool_get_threshold(struct tablet_dispatch *tablet, + struct libinput_tablet_tool *tool) +{ + ARRAY_FOR_EACH(tool->pressure.thresholds, threshold) { + if (threshold->tablet_id == tablet->tablet_id) { + return threshold; + } + } + + /* If we ever get here, we failed detecting the proximity for this tablet + * (or we have too many tablets). Return the first one which will + * make things work incorrectly but we don't need to NULL-check + * everything for an extremely unlikely situtation */ + evdev_log_bug_libinput(tablet->device, + "Failed to find tablet_id %d for pressure offsets\n", + tablet->tablet_id); + + return &tool->pressure.thresholds[0]; +} + /* Merge the previous state with the current one so all buttons look like * they just got pressed in this frame */ static inline void @@ -379,9 +400,8 @@ normalize_distance(const struct input_absinfo *absinfo) } static inline double -normalize_pressure(const struct input_absinfo *absinfo, - int abs_value, - struct libinput_tablet_tool *tool) +normalize_pressure(struct libinput_tablet_tool_pressure_threshold *threshold, + int abs_value) { /** * Note: the upper threshold takes the offset into account so that @@ -394,9 +414,9 @@ normalize_pressure(const struct input_absinfo *absinfo, * The axis is scaled into the range [lower, max] so that the lower * threshold is 0 pressure. */ - struct input_absinfo abs = *absinfo; + struct input_absinfo abs = threshold->abs_pressure; - abs.minimum = tool->pressure.threshold.lower; + abs.minimum = threshold->threshold.lower; return absinfo_normalize_value(&abs, abs_value); } @@ -613,9 +633,9 @@ tablet_update_pressure(struct tablet_dispatch *tablet, return; if (bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) { - tablet->axes.pressure = normalize_pressure(&tool->pressure.abs_pressure, - abs->value, - tool); + struct libinput_tablet_tool_pressure_threshold *threshold = + tablet_tool_get_threshold(tablet, tool); + tablet->axes.pressure = normalize_pressure(threshold, abs->value); } } @@ -1212,43 +1232,48 @@ apply_pressure_range_configuration(struct tablet_dispatch *tablet, lo = axis_range_percentage(&abs, 1); } - tool->pressure.abs_pressure = abs; - tool->pressure.threshold.upper = hi; - tool->pressure.threshold.lower = lo; + struct libinput_tablet_tool_pressure_threshold *threshold = + tablet_tool_get_threshold(tablet, tool); + threshold->abs_pressure = abs; + threshold->threshold.upper = hi; + threshold->threshold.lower = lo; tool->pressure.range.min = tool->pressure.wanted_range.min; tool->pressure.range.max = tool->pressure.wanted_range.max; /* Disable any heuristics */ if (tool->pressure.has_configured_range) { - tool->pressure.has_offset = true; - tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; + threshold->has_offset = true; + threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; } } static inline void tool_init_pressure_thresholds(struct tablet_dispatch *tablet, - struct libinput_tablet_tool *tool) + struct libinput_tablet_tool *tool, + struct libinput_tablet_tool_pressure_threshold *threshold) { struct evdev_device *device = tablet->device; const struct input_absinfo *pressure, *distance; - tool->pressure.offset = 0; - tool->pressure.has_offset = false; + threshold->tablet_id = tablet->tablet_id; + threshold->offset = 0; + threshold->has_offset = false; pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); if (!pressure) { - tool->pressure.threshold.upper = 1; - tool->pressure.threshold.lower = 0; + threshold->threshold.upper = 1; + threshold->threshold.lower = 0; return; } + threshold->abs_pressure = *pressure; distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); if (distance) { - tool->pressure.offset = pressure->minimum; - tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; + threshold->offset = pressure->minimum; + threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_DONE; } else { - tool->pressure.offset = pressure->maximum; - tool->pressure.heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1; + threshold->offset = pressure->maximum; + threshold->heuristic_state = PRESSURE_HEURISTIC_STATE_PROXIN1; } apply_pressure_range_configuration(tablet, tool); @@ -1312,19 +1337,7 @@ tablet_new_tool(struct tablet_dispatch *tablet, .config.pressure_range.get_default = pressure_range_get_default, }; - /* Copy the pressure axis for configuring the range later */ - struct evdev_device *device = tablet->device; - const struct input_absinfo *abs = libevdev_get_abs_info(device->evdev, - ABS_PRESSURE); - if (abs) - tool->pressure.abs_pressure = *abs; - - /* FIXME: known bug - the pressure threshold is only set once on the - * first tablet, if a tool is used across multiple tablets with - * different pressure ranges this will be wrong. This case is niche - * enough that we can fix it if we ever run into it. - */ - tool_init_pressure_thresholds(tablet, tool); + tool_init_pressure_thresholds(tablet, tool, &tool->pressure.thresholds[0]); tool_set_bits(tablet, tool); return tool; @@ -1382,7 +1395,16 @@ tablet_get_tool(struct tablet_dispatch *tablet, if (!tool) { tool = tablet_new_tool(tablet, type, tool_id, serial); list_insert(tool_list, &tool->link); - } + } else { + ARRAY_FOR_EACH(tool->pressure.thresholds, t) { + if (t->tablet_id == tablet->tablet_id) + break; + if (t->tablet_id == 0) { + tool_init_pressure_thresholds(tablet, tool, t); + break; + } + } + } return tool; } @@ -1468,7 +1490,9 @@ sanitize_pressure_distance(struct tablet_dispatch *tablet, /* Note: this is an arbitrary "in contact" decision rather than "tip * down". We use the lower threshold as minimum pressure value, * anything less than that gets filtered away */ - tool_in_contact = (pressure->value > tool->pressure.threshold.lower); + struct libinput_tablet_tool_pressure_threshold* threshold = + tablet_tool_get_threshold(tablet, tool); + tool_in_contact = (pressure->value > threshold->threshold.lower); /* Keep distance and pressure mutually exclusive */ if (distance && @@ -1514,18 +1538,18 @@ sanitize_tablet_axes(struct tablet_dispatch *tablet, } static void -set_pressure_offset(struct libinput_tablet_tool *tool, int offset) +set_pressure_offset(struct libinput_tablet_tool_pressure_threshold *threshold, int offset) { - tool->pressure.offset = offset; - tool->pressure.has_offset = true; + threshold->offset = offset; + threshold->has_offset = true; /* Adjust the tresholds accordingly - we use the same gap (4% in * device coordinates) between upper and lower as before which isn't * technically correct (our range shrunk) but it's easy to calculate. */ - int gap = tool->pressure.threshold.upper - tool->pressure.threshold.lower; - tool->pressure.threshold.lower = offset; - tool->pressure.threshold.upper = offset + gap; + int gap = threshold->threshold.upper - threshold->threshold.lower; + threshold->threshold.lower = offset; + threshold->threshold.upper = offset + gap; } static void @@ -1549,11 +1573,13 @@ update_pressure_offset(struct tablet_dispatch *tablet, * offset value, don't actually set it to have an offset. */ int offset = pressure->value; - if (tool->pressure.has_offset) { - if (offset < tool->pressure.offset) - set_pressure_offset(tool, offset); - } else if (tool->pressure.heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) { - tool->pressure.offset = min(offset, tool->pressure.offset); + struct libinput_tablet_tool_pressure_threshold *threshold = + tablet_tool_get_threshold(tablet, tool); + if (threshold->has_offset) { + if (offset < threshold->offset) + set_pressure_offset(threshold, offset); + } else if (threshold->heuristic_state != PRESSURE_HEURISTIC_STATE_DONE) { + threshold->offset = min(offset, threshold->offset); } } @@ -1565,10 +1591,15 @@ detect_pressure_offset(struct tablet_dispatch *tablet, const struct input_absinfo *pressure, *distance; int offset; - if (tool->pressure.has_offset || tool->pressure.has_configured_range || + if (tool->pressure.has_configured_range || !bit_is_set(tablet->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_PRESSURE)) return; + struct libinput_tablet_tool_pressure_threshold *threshold = + tablet_tool_get_threshold(tablet, tool); + if (threshold->has_offset) + return; + pressure = libevdev_get_abs_info(device->evdev, ABS_PRESSURE); distance = libevdev_get_abs_info(device->evdev, ABS_DISTANCE); @@ -1592,16 +1623,16 @@ detect_pressure_offset(struct tablet_dispatch *tablet, * deciding prox-in arrives we should know the minimum offset. */ if (offset > pressure->minimum) - tool->pressure.offset = min(offset, tool->pressure.offset); + threshold->offset = min(offset, threshold->offset); - switch (tool->pressure.heuristic_state) { + switch (threshold->heuristic_state) { case PRESSURE_HEURISTIC_STATE_PROXIN1: case PRESSURE_HEURISTIC_STATE_PROXIN2: - tool->pressure.heuristic_state++; + threshold->heuristic_state++; return; case PRESSURE_HEURISTIC_STATE_DECIDE: - tool->pressure.heuristic_state++; - offset = tool->pressure.offset; + threshold->heuristic_state++; + offset = threshold->offset; break; case PRESSURE_HEURISTIC_STATE_DONE: return; @@ -1628,7 +1659,7 @@ detect_pressure_offset(struct tablet_dispatch *tablet, tool->serial, HTTP_DOC_LINK); - set_pressure_offset(tool, offset); + set_pressure_offset(threshold, offset); } static void @@ -1659,10 +1690,12 @@ detect_tool_contact(struct tablet_dispatch *tablet, } pressure = p->value; - if (pressure <= tool->pressure.threshold.lower && + struct libinput_tablet_tool_pressure_threshold *threshold = + tablet_tool_get_threshold(tablet, tool); + if (pressure <= threshold->threshold.lower && tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { tablet_set_status(tablet, TABLET_TOOL_LEAVING_CONTACT); - } else if (pressure >= tool->pressure.threshold.upper && + } else if (pressure >= threshold->threshold.upper && !tablet_has_status(tablet, TABLET_TOOL_IN_CONTACT)) { tablet_set_status(tablet, TABLET_TOOL_ENTERING_CONTACT); } @@ -2913,6 +2946,7 @@ static int tablet_init(struct tablet_dispatch *tablet, struct evdev_device *device) { + static unsigned int tablet_ids = 0; struct libinput *li = evdev_libinput_context(device); struct libevdev *evdev = device->evdev; enum libinput_tablet_tool_axis axis; @@ -2943,6 +2977,7 @@ tablet_init(struct tablet_dispatch *tablet, } #endif + tablet->tablet_id = ++tablet_ids; tablet->base.dispatch_type = DISPATCH_TABLET; tablet->base.interface = &tablet_interface; tablet->device = device; diff --git a/src/evdev-tablet.h b/src/evdev-tablet.h index de0349ef..aa9fcdc0 100644 --- a/src/evdev-tablet.h +++ b/src/evdev-tablet.h @@ -61,6 +61,8 @@ struct button_state { struct tablet_dispatch { struct evdev_dispatch base; struct evdev_device *device; + unsigned int tablet_id; /* incremental ID */ + unsigned int status; unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)]; struct tablet_axes axes; /* for assembling the current state */ diff --git a/src/evdev-totem.c b/src/evdev-totem.c index 412192de..6707c18e 100644 --- a/src/evdev-totem.c +++ b/src/evdev-totem.c @@ -88,10 +88,13 @@ totem_new_tool(struct totem_dispatch *totem) .refcount = 1, }; - tool->pressure.offset = 0; - tool->pressure.has_offset = false; - tool->pressure.threshold.lower = 0; - tool->pressure.threshold.upper = 1; + ARRAY_FOR_EACH(tool->pressure.thresholds, t) { + t->tablet_id = 0; + t->offset = 0; + t->has_offset = false; + t->threshold.lower = 0; + t->threshold.upper = 1; + } set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X); set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y); diff --git a/src/libinput-private.h b/src/libinput-private.h index 266a61e6..16955cee 100644 --- a/src/libinput-private.h +++ b/src/libinput-private.h @@ -484,6 +484,21 @@ struct libinput_tablet_tool_config_pressure_range { void (*get_default)(struct libinput_tablet_tool *tool, double *min, double *max); }; +struct libinput_tablet_tool_pressure_threshold { + unsigned int tablet_id; + + /* The configured axis we actually work with */ + struct input_absinfo abs_pressure; + struct threshold threshold; /* in device coordinates */ + int offset; /* in device coordinates */ + bool has_offset; + + /* This gives us per-tablet heuristic state which is arguably + * wrong but >99% of users have one tablet and it's easier to + * implement it this way */ + enum pressure_heuristic_state heuristic_state; +}; + struct libinput_tablet_tool { struct list link; uint32_t serial; @@ -495,17 +510,16 @@ struct libinput_tablet_tool { void *user_data; struct { - /* The configured axis we actually work with */ - struct input_absinfo abs_pressure; - struct normalized_range range; + /* We're assuming that the *configured* pressure range is per + * tool, not per tablet. The *adjusted* thresholds are then + * per-tablet. */ + struct normalized_range range; struct normalized_range wanted_range; bool has_configured_range; - struct threshold threshold; /* in device coordinates */ - int offset; /* in device coordinates */ - bool has_offset; - - enum pressure_heuristic_state heuristic_state; + /* Hard-coded because I doubt we have users with more + * than 4 tablets at the same time */ + struct libinput_tablet_tool_pressure_threshold thresholds[4]; } pressure; struct { diff --git a/test/test-tablet.c b/test/test-tablet.c index fe1b5aa6..e32d277f 100644 --- a/test/test-tablet.c +++ b/test/test-tablet.c @@ -5066,6 +5066,88 @@ START_TEST(tablet_pressure_offset_none_for_small_distance) } END_TEST +START_TEST(tablet_pressure_across_multiple_tablets) +{ + struct litest_device *cintiq12wx = litest_current_device(); + struct libinput *li = cintiq12wx->libinput; + + struct litest_device *mobilestudio = litest_add_device(li, LITEST_WACOM_CINTIQ_PRO16_PEN); + + bool direction = litest_test_param_get_bool(test_env->params, "8k-to-1k"); + struct litest_device *first = direction ? mobilestudio : cintiq12wx; + struct litest_device *second = direction ? cintiq12wx : mobilestudio; + + struct axis_replacement axes[] = { + { ABS_DISTANCE, 20 }, + { ABS_PRESSURE, 0 }, + { -1, -1 }, + }; + + bool have_cintiq12wx = false; + bool have_mobilestudio = false; + + libinput_dispatch(li); + + while (!have_cintiq12wx || !have_mobilestudio) { + litest_wait_for_event_of_type(li, LIBINPUT_EVENT_DEVICE_ADDED); + struct libinput_event *ev = libinput_get_event(li); + litest_assert_event_type(ev, LIBINPUT_EVENT_DEVICE_ADDED); + if (libinput_event_get_device(ev) == cintiq12wx->libinput_device) + have_cintiq12wx = true; + if (libinput_event_get_device(ev) == mobilestudio->libinput_device) + have_mobilestudio = true; + litest_checkpoint("Have Cintiq 12WX: %s, MobileStudio: %s", yesno(have_cintiq12wx), yesno(have_mobilestudio)); + libinput_event_destroy(ev); + libinput_dispatch(li); + } + + litest_drain_events(li); + + /* Proximity in followed by pressure up to 70%, on the first + * device, then on the second one. They have different pressure + * ranges but we expect the normalized range to be the same + * proportionate range */ + struct litest_device *dev = first; + for (int i = 0; i < 2; i++, dev = second) { + litest_checkpoint("Putting pen into proximity on %s", libinput_device_get_name(dev->libinput_device)); + litest_tablet_proximity_in(dev, 50, 50, axes); + + litest_axis_set_value(axes, ABS_DISTANCE, 0); + litest_axis_set_value(axes, ABS_PRESSURE, 10); + litest_tablet_motion(dev, 50, 50, axes); + litest_dispatch(li); + + for (size_t pressure = 10; pressure <= 70; pressure += 10) { + litest_axis_set_value(axes, ABS_PRESSURE, pressure); + litest_tablet_motion(dev, 50, 50, axes); + litest_dispatch(li); + } + litest_tablet_proximity_out(dev); + litest_timeout_tablet_proxout(); + libinput_dispatch(li); + + litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN); + litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_DOWN); + do { + struct libinput_event *ev = libinput_get_event(li); + struct libinput_event_tablet_tool *tev = litest_is_tablet_event(ev, LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + double pressure = libinput_event_tablet_tool_get_pressure(tev); + /* We start at device range 10% but we always have a small threshold */ + litest_assert_double_gt(pressure, 0.09); + litest_assert_double_le(pressure, 0.7); + + libinput_event_destroy(ev); + } while (libinput_next_event_type(li) == LIBINPUT_EVENT_TABLET_TOOL_AXIS); + + litest_assert_tablet_tip_event(li, LIBINPUT_TABLET_TOOL_TIP_UP); + litest_assert_tablet_proximity_event(li, LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT); + } + + litest_delete_device(mobilestudio); +} +END_TEST + START_TEST(tablet_distance_range) { struct litest_device *dev = litest_current_device(); @@ -7164,6 +7246,8 @@ TEST_COLLECTION(tablet) litest_add_for_device(tablet_pressure_offset_decrease, LITEST_WACOM_HID4800_PEN); litest_add_for_device(tablet_pressure_offset_increase, LITEST_WACOM_HID4800_PEN); litest_add_for_device(tablet_pressure_offset_exceed_threshold, LITEST_WACOM_HID4800_PEN); + litest_with_parameters(params, "8k-to-1k", 'b') + litest_add_parametrized_for_device(tablet_pressure_across_multiple_tablets, LITEST_WACOM_CINTIQ_12WX, params); litest_add(tablet_pressure_config, LITEST_TABLET, LITEST_TOTEM); litest_add(tablet_pressure_config_set_minimum, LITEST_TABLET, LITEST_TOTEM); @@ -7219,6 +7303,4 @@ TEST_COLLECTION(tablet_left_handed) litest_add_parametrized(tablet_rotation_left_handed_add_touchpad, LITEST_TABLET, LITEST_ANY, params); litest_add_parametrized(tablet_rotation_left_handed_add_tablet, LITEST_TOUCHPAD, LITEST_ANY, params); } - - }