Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 108 additions & 7 deletions drivers/hid/hid-magicmouse.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,46 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie

#define J314_TP_MAX_FINGER_ORIENTATION 16384

/*
* Maximum frame-to-frame displacement (in mm) allowed when matching a
* multitouch contact to an existing tracking-ID slot. Without this, passing
* dmax=0 to input_mt_assign_slots() makes the kernel happily reuse a
* tracking ID across two physically distinct contacts that landed far
* apart, causing the cursor to teleport across the trackpad. 10mm matches
* drivers/input/mouse/synaptics.c and is far larger than any frame-to-frame
* motion a real finger can make.
*/
#define MAGICMOUSE_TP_DMAX 10

/*
* If a brand-new touch arrives with an approach area (WIDTH_MAJOR) larger
* than this threshold, flag it as MT_TOOL_PALM at its first frame, before
* TOUCH_MAJOR has time to ramp up high enough for libinput's size-based
* palm detection to fire. This eliminates the 5-10mm of cursor drift per
* palm contact that would otherwise occur during the classification
* window while typing.
*
* Measurement (M1 MacBook Pro 16" trackpad):
* real fingertip first-frame WIDTH_MAJOR caps at ~2900
* typing palms first-frame WIDTH_MAJOR: 1900-3750 (mean ~2900-3000)
* 3000 catches a large share of palms with negligible finger false
* positives on the sample set.
*/
#define MAGICMOUSE_TP_PALM_WIDTH 3000

/*
* Width (as a percentage of total trackpad X range) of the left/right
* edge palm zones. A NEW contact landing in these zones is classified
* as a palm on its first frame. Contacts that start in the center and
* drag INTO the edge zones are unaffected — the classification only
* runs on freshly-active slots.
*
* Measurement: on a typing session with hands at rest, palm landing X
* clusters in the outermost ~12% of trackpad width on each side; real
* finger landings cluster centrally (~54-67% of width).
*/
#define MAGICMOUSE_TP_PALM_EDGE_PCT 12

struct magicmouse_input_ops {
int (*raw_event)(struct hid_device *hdev,
struct hid_report *report, u8 *data, int size);
Expand Down Expand Up @@ -174,6 +214,10 @@ struct magicmouse_sc {
bool scroll_y_active;
} touches[MAX_CONTACTS];
int tracking_ids[MAX_CONTACTS];
/* Per-slot palm classification (true = MT_TOOL_PALM, false = MT_TOOL_FINGER) */
bool palm_slot[MAX_CONTACTS];
/* Which slots had an active contact in the previous frame */
bool prev_active[MAX_CONTACTS];

struct hid_device *hdev;
struct delayed_work work;
Expand Down Expand Up @@ -719,10 +763,11 @@ struct tp_mouse_report {

static void report_finger_data(struct input_dev *input, int slot,
const struct input_mt_pos *pos,
const struct tp_finger *f)
const struct tp_finger *f, bool is_palm)
{
input_mt_slot(input, slot);
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
input_mt_report_slot_state(input, is_palm ? MT_TOOL_PALM : MT_TOOL_FINGER,
true);

input_report_abs(input, ABS_MT_TOUCH_MAJOR,
le16_to_int(f->touch_major) << 1);
Expand Down Expand Up @@ -785,12 +830,59 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev,
n++;
}

input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0);
input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n,
MAGICMOUSE_TP_DMAX *
input_abs_get_res(input, ABS_MT_POSITION_X));

{
bool curr_active[MAX_CONTACTS] = { false };
int x_min = input->absinfo[ABS_MT_POSITION_X].minimum;
int x_max = input->absinfo[ABS_MT_POSITION_X].maximum;
int x_edge = (x_max - x_min) * MAGICMOUSE_TP_PALM_EDGE_PCT / 100;
int x_left_zone = x_min + x_edge;
int x_right_zone = x_max - x_edge;

for (i = 0; i < n; i++) {
int slot = msc->tracking_ids[i];
int idx = map_contacs[i];

f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz);

if (slot >= 0 && slot < MAX_CONTACTS) {
/*
* Classify on the first frame a slot becomes
* active. Palms land with large WIDTH_MAJOR
* (approach area) even before TOUCH_MAJOR has
* time to ramp up, and they land at the far
* X edges where users rest their hands. Both
* are first-frame-only checks — a finger
* that drags into an edge zone after starting
* in the center keeps its finger tool type.
*/
if (!msc->prev_active[slot]) {
int wm = le16_to_int(f->tool_major) << 1;
int x = msc->pos[i].x;

msc->palm_slot[slot] =
(wm > MAGICMOUSE_TP_PALM_WIDTH ||
x < x_left_zone ||
x > x_right_zone);
}
curr_active[slot] = true;
report_finger_data(input, slot, &msc->pos[i], f,
msc->palm_slot[slot]);
} else {
report_finger_data(input, slot, &msc->pos[i], f,
false);
}
}

for (i = 0; i < n; i++) {
int idx = map_contacs[i];
f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz);
report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f);
/* Roll active state forward; reset palm flag on lifted slots */
for (i = 0; i < MAX_CONTACTS; i++) {
if (!curr_active[i])
msc->palm_slot[i] = false;
msc->prev_active[i] = curr_active[i];
}
}

input_mt_sync_frame(input);
Expand Down Expand Up @@ -1052,6 +1144,15 @@ static int magicmouse_setup_input_mtp(struct input_dev *input,

input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0);

/*
* input_mt_init_slots() does not register ABS_MT_TOOL_TYPE; we need it
* registered so input_mt_report_slot_state() can flag contacts as
* MT_TOOL_PALM. Per Documentation/input/multi-touch-protocol.rst and
* input_mt_report_slot_state() behavior: "The tool type is only
* reported if the corresponding absbit field is set."
*/
input_set_abs_params(input, ABS_MT_TOOL_TYPE, 0, MT_TOOL_PALM, 0, 0);

/*
* This makes libinput recognize this as a PressurePad and
* stop trying to use pressure for touch size. Pressure unit
Expand Down