evdev-mt-touchpad-thumb.c 11.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * Copyright © 2019 Matt Mayfield
 * Copyright © 2019 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "config.h"
#include "evdev-mt-touchpad.h"

28 29 30
/* distance between fingers to assume it is not a scroll */
#define SCROLL_MM_X 35
#define SCROLL_MM_Y 25
31 32 33 34 35

static inline const char*
thumb_state_to_str(enum tp_thumb_state state)
{
	switch(state){
36 37 38 39 40 41 42
	CASE_RETURN_STRING(THUMB_STATE_FINGER);
	CASE_RETURN_STRING(THUMB_STATE_JAILED);
	CASE_RETURN_STRING(THUMB_STATE_PINCH);
	CASE_RETURN_STRING(THUMB_STATE_SUPPRESSED);
	CASE_RETURN_STRING(THUMB_STATE_REVIVED);
	CASE_RETURN_STRING(THUMB_STATE_REVIVED_JAILED);
	CASE_RETURN_STRING(THUMB_STATE_DEAD);
43 44 45 46 47
	}

	return NULL;
}

48
static void
49 50 51
tp_thumb_set_state(struct tp_dispatch *tp,
		   struct tp_touch *t,
		   enum tp_thumb_state state)
52
{
53 54 55
	unsigned int index = t ? t->index : UINT_MAX;

	if (tp->thumb.state == state && tp->thumb.index == index)
56 57 58 59
		return;

	evdev_log_debug(tp->device,
			"thumb: touch %d, %s → %s\n",
60 61
			(int)index,
			thumb_state_to_str(tp->thumb.state),
62 63
			thumb_state_to_str(state));

64 65
	tp->thumb.state = state;
	tp->thumb.index = index;
66
}
67

68
void
69 70 71 72 73 74 75 76 77
tp_thumb_reset(struct tp_dispatch *tp)
{
	tp->thumb.state = THUMB_STATE_FINGER;
	tp->thumb.index = UINT_MAX;
	tp->thumb.pinch_eligible = true;
}

static void
tp_thumb_lift(struct tp_dispatch *tp)
78
{
79 80
	tp->thumb.state = THUMB_STATE_FINGER;
	tp->thumb.index = UINT_MAX;
81 82
}

83 84
static bool
tp_thumb_in_exclusion_area(const struct tp_dispatch *tp,
85
			   const struct tp_touch *t)
86 87
{
	return (t->point.y > tp->thumb.lower_thumb_line &&
88
		tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE);
89 90 91 92 93

}

static bool
tp_thumb_detect_pressure_size(const struct tp_dispatch *tp,
94
			      const struct tp_touch *t)
95 96 97 98 99
{
	bool is_thumb = false;

	if (tp->thumb.use_pressure &&
	    t->pressure > tp->thumb.pressure_threshold &&
100
	    tp_thumb_in_exclusion_area(tp, t)) {
101 102 103 104 105 106 107 108 109 110 111 112
		is_thumb = true;
	}

	if (tp->thumb.use_size &&
	    (t->major > tp->thumb.size_threshold) &&
	    (t->minor < (tp->thumb.size_threshold * 0.6))) {
		is_thumb = true;
	}

	return is_thumb;
}

113 114 115
static bool
tp_thumb_needs_jail(const struct tp_dispatch *tp, const struct tp_touch *t)
{
116 117
	if (t->point.y < tp->thumb.upper_thumb_line ||
	    tp->scroll.method == LIBINPUT_CONFIG_SCROLL_EDGE)
118 119 120
		return false;

	if (!tp_thumb_in_exclusion_area(tp, t) &&
121
           (tp->thumb.use_size || tp->thumb.use_pressure) &&
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
	    !tp_thumb_detect_pressure_size(tp, t))
		return false;

	if (t->speed.exceeded_count >= 10)
		return false;

	return true;
}

bool
tp_thumb_ignored(const struct tp_dispatch *tp, const struct tp_touch *t)
{
	return (tp->thumb.detect_thumbs &&
		tp->thumb.index == t->index &&
		(tp->thumb.state == THUMB_STATE_JAILED ||
		 tp->thumb.state == THUMB_STATE_PINCH ||
		 tp->thumb.state == THUMB_STATE_SUPPRESSED ||
		 tp->thumb.state == THUMB_STATE_REVIVED_JAILED ||
		 tp->thumb.state == THUMB_STATE_DEAD));
}

bool
tp_thumb_ignored_for_tap(const struct tp_dispatch *tp,
			 const struct tp_touch *t)
{
	return (tp->thumb.detect_thumbs &&
		tp->thumb.index == t->index &&
		(tp->thumb.state == THUMB_STATE_PINCH ||
		 tp->thumb.state == THUMB_STATE_SUPPRESSED ||
		 tp->thumb.state == THUMB_STATE_DEAD));
}

bool
tp_thumb_ignored_for_gesture(const struct tp_dispatch *tp,
			     const struct tp_touch *t)
{
	return (tp->thumb.detect_thumbs &&
		tp->thumb.index == t->index &&
		(tp->thumb.state == THUMB_STATE_JAILED ||
		 tp->thumb.state == THUMB_STATE_SUPPRESSED ||
		 tp->thumb.state == THUMB_STATE_REVIVED_JAILED ||
		 tp->thumb.state == THUMB_STATE_DEAD));
}

166 167 168
void
tp_thumb_suppress(struct tp_dispatch *tp, struct tp_touch *t)
{
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
	if(tp->thumb.state == THUMB_STATE_FINGER ||
	   tp->thumb.state == THUMB_STATE_JAILED ||
	   tp->thumb.state == THUMB_STATE_PINCH ||
	   tp->thumb.index != t->index) {
		tp_thumb_set_state(tp, t, THUMB_STATE_SUPPRESSED);
		return;
	}

	tp_thumb_set_state(tp, t, THUMB_STATE_DEAD);
}

static void
tp_thumb_pinch(struct tp_dispatch *tp, struct tp_touch *t)
{
	if(tp->thumb.state == THUMB_STATE_FINGER ||
	   tp->thumb.state == THUMB_STATE_JAILED ||
	   tp->thumb.index != t->index)
		tp_thumb_set_state(tp, t, THUMB_STATE_PINCH);
	else if (tp->thumb.state != THUMB_STATE_PINCH)
		tp_thumb_suppress(tp, t);
}

static void
tp_thumb_revive(struct tp_dispatch *tp, struct tp_touch *t)
{
	if((tp->thumb.state != THUMB_STATE_SUPPRESSED &&
	    tp->thumb.state != THUMB_STATE_PINCH) ||
	   (tp->thumb.index != t->index))
		return;

	if(tp_thumb_needs_jail(tp, t))
		tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED_JAILED);
	else
		tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED);
203 204
}

205
void
206 207 208
tp_thumb_update_touch(struct tp_dispatch *tp,
		      struct tp_touch *t,
		      uint64_t time)
209
{
210
	if (!tp->thumb.detect_thumbs)
211
		return;
212

213 214
	/* Once any active touch exceeds the speed threshold, don't
	 * try to detect pinches until all touches lift.
215
	 */
216 217 218 219 220 221 222 223 224 225 226
	if (t->speed.exceeded_count >= 10 &&
	    tp->thumb.pinch_eligible &&
	    tp->gesture.state == GESTURE_STATE_NONE) {
		tp->thumb.pinch_eligible = false;
		if(tp->thumb.state == THUMB_STATE_PINCH) {
			struct tp_touch *thumb;
			tp_for_each_touch(tp, thumb) {
				if (thumb->index != tp->thumb.index)
					continue;

				tp_thumb_set_state(tp, thumb, THUMB_STATE_SUPPRESSED);
227 228 229 230 231
				break;
			}
		}
	}

232 233 234 235 236 237 238 239
	/* Handle the thumb lifting off the touchpad */
	if (t->state == TOUCH_END && t->index == tp->thumb.index) {
		tp_thumb_lift(tp);
		return;
	}

	/* If this touch is not the only one, thumb updates happen by context
	 * instead of here
240
	 */
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
	if (tp->nfingers_down > 1)
		return;

	/* If we arrived here by other fingers lifting off, revive current touch
	 * if appropriate
	 */
	tp_thumb_revive(tp, t);

	/* First new touch below the lower_thumb_line, or below the upper_thumb_
	 * line if hardware can't verify it's a finger, starts as JAILED.
	 */
	if (t->state == TOUCH_BEGIN && tp_thumb_needs_jail(tp, t)) {
		tp_thumb_set_state(tp, t, THUMB_STATE_JAILED);
		return;
	}

	/* If a touch breaks the speed threshold, or leaves the thumb area
	 * (upper or lower, depending on HW detection), it "escapes" jail.
259
	 */
260 261 262 263 264 265
	if (tp->thumb.state == THUMB_STATE_JAILED &&
	    !(tp_thumb_needs_jail(tp, t)))
		tp_thumb_set_state(tp, t, THUMB_STATE_FINGER);
	if (tp->thumb.state == THUMB_STATE_REVIVED_JAILED &&
	    !(tp_thumb_needs_jail(tp, t)))
		tp_thumb_set_state(tp, t, THUMB_STATE_REVIVED);
266 267
}

268
void
269
tp_thumb_update_multifinger(struct tp_dispatch *tp)
270 271 272
{
	struct tp_touch *t;
	struct tp_touch *first = NULL,
273 274
			*second = NULL,
			*newest = NULL;
275 276
	struct device_coords distance;
	struct phys_coords mm;
277
	unsigned int speed_exceeded_count = 0;
278

279 280 281
	/* Get the first and second bottom-most touches, the max speed exceeded
	 * count overall, and the newest touch (or one of them, if more).
	 */
282 283 284 285 286
	tp_for_each_touch(tp, t) {
		if (t->state == TOUCH_NONE ||
		    t->state == TOUCH_HOVERING)
			continue;

287 288 289 290 291 292 293 294 295 296 297 298 299
		if (t->state == TOUCH_BEGIN)
			newest = t;

		speed_exceeded_count = max(speed_exceeded_count,
		                           t->speed.exceeded_count);

		if (!first) {
			first = t;
			continue;
		}

		if (t->point.y > first->point.y) {
			second = first;
300
			first = t;
301 302
			continue;
		}
303

304 305 306
		if (!second || t->point.y > second->point.y ) {
			second = t;
		}
307 308
	}

309 310 311
	if (!first || !second)
		return;

312 313 314
	assert(first);
	assert(second);

315 316 317
	distance.x = abs(first->point.x - second->point.x);
	distance.y = abs(first->point.y - second->point.y);
	mm = evdev_device_unit_delta_to_mm(tp->device, &distance);
318

319
	/* Speed-based thumb detection: if an existing finger is moving, and
320 321 322 323
	 * a new touch arrives, mark it as a thumb if it doesn't qualify as a
	 * 2-finger scroll.
	 */
	if (newest &&
324
	    tp->thumb.state == THUMB_STATE_FINGER &&
325 326 327 328 329 330 331 332 333
	    tp->nfingers_down == 2 &&
	    speed_exceeded_count > 5 &&
	    (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_2FG ||
	     (mm.x > SCROLL_MM_X || mm.y > SCROLL_MM_Y))) {
		evdev_log_debug(tp->device,
				"touch %d is speed-based thumb\n",
				newest->index);
		tp_thumb_suppress(tp, newest);
		return;
334 335
	}

336 337
	/* Position-based thumb detection: When a new touch arrives, check the
	 * two lowest touches. If they qualify for 2-finger scrolling, clear
338 339 340 341 342
	 * thumb status.
	 *
	 * If they were in distinct diagonal position, then mark the lower
	 * touch (based on pinch_eligible) as either PINCH or SUPPRESSED. If
	 * we're too close together for a thumb, lift that.
343
	 */
344
	if (mm.y > SCROLL_MM_Y && mm.x > SCROLL_MM_X) {
345 346 347 348
		if (tp->thumb.pinch_eligible)
			tp_thumb_pinch(tp, first);
		else
			tp_thumb_suppress(tp, first);
349
	} else if (mm.x < SCROLL_MM_X && mm.y < SCROLL_MM_Y) {
350 351
		tp_thumb_lift(tp);
	}
352 353
}

354 355 356 357 358 359 360 361 362 363 364
void
tp_init_thumb(struct tp_dispatch *tp)
{
	struct evdev_device *device = tp->device;
	double w = 0.0, h = 0.0;
	struct device_coords edges;
	struct phys_coords mm = { 0.0, 0.0 };
	uint32_t threshold;
	struct quirks_context *quirks;
	struct quirks *q;

365 366
	tp->thumb.detect_thumbs = false;

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
	if (!tp->buttons.is_clickpad)
		return;

	/* if the touchpad is less than 50mm high, skip thumb detection.
	 * it's too small to meaningfully interact with a thumb on the
	 * touchpad */
	evdev_device_get_size(device, &w, &h);
	if (h < 50)
		return;

	tp->thumb.detect_thumbs = true;
	tp->thumb.use_pressure = false;
	tp->thumb.pressure_threshold = INT_MAX;

	/* detect thumbs by pressure in the bottom 15mm, detect thumbs by
	 * lingering in the bottom 8mm */
	mm.y = h * 0.85;
	edges = evdev_device_mm_to_units(device, &mm);
	tp->thumb.upper_thumb_line = edges.y;

	mm.y = h * 0.92;
	edges = evdev_device_mm_to_units(device, &mm);
	tp->thumb.lower_thumb_line = edges.y;

	quirks = evdev_libinput_context(device)->quirks;
	q = quirks_fetch_for_device(quirks, device->udev_device);

	if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_PRESSURE)) {
		if (quirks_get_uint32(q,
				      QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD,
				      &threshold)) {
			tp->thumb.use_pressure = true;
			tp->thumb.pressure_threshold = threshold;
		}
	}

	if (libevdev_has_event_code(device->evdev, EV_ABS, ABS_MT_TOUCH_MAJOR)) {
		if (quirks_get_uint32(q,
				      QUIRK_ATTR_THUMB_SIZE_THRESHOLD,
				      &threshold)) {
			tp->thumb.use_size = true;
			tp->thumb.size_threshold = threshold;
		}
	}

412 413
	tp_thumb_reset(tp);

414 415 416 417 418 419 420
	quirks_unref(q);

	evdev_log_debug(device,
			"thumb: enabled thumb detection (area%s%s)\n",
			tp->thumb.use_pressure ? ", pressure" : "",
			tp->thumb.use_size ? ", size" : "");
}