From owner-svn-src-all@FreeBSD.ORG Tue Oct 14 17:48:36 2008 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id AEED61065688; Tue, 14 Oct 2008 17:48:36 +0000 (UTC) (envelope-from dumbbell@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 9B22B8FC18; Tue, 14 Oct 2008 17:48:36 +0000 (UTC) (envelope-from dumbbell@FreeBSD.org) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.3/8.14.3) with ESMTP id m9EHmao8012355; Tue, 14 Oct 2008 17:48:36 GMT (envelope-from dumbbell@svn.freebsd.org) Received: (from dumbbell@localhost) by svn.freebsd.org (8.14.3/8.14.3/Submit) id m9EHma9d012354; Tue, 14 Oct 2008 17:48:36 GMT (envelope-from dumbbell@svn.freebsd.org) Message-Id: <200810141748.m9EHma9d012354@svn.freebsd.org> From: Jean-Sebastien Pedron Date: Tue, 14 Oct 2008 17:48:36 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r183888 - head/sys/dev/atkbdc X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 14 Oct 2008 17:48:36 -0000 Author: dumbbell Date: Tue Oct 14 17:48:36 2008 New Revision: 183888 URL: http://svn.freebsd.org/changeset/base/183888 Log: Rewrite Synaptics touchpads support with the following goals in mind: o better quality of the movement smoothing o more features such as tap-hold and virtual scrolling Support must still be enabled with this line in your /boot/loader.conf: hw.psm.synaptics_support="1" The following sysctls were removed: hw.psm.synaptics.low_speed_threshold hw.psm.synaptics.min_movement hw.psm.synaptics.squelch_level An overview of this new driver and a short documentation about the added sysctls is available on the wiki: http://wiki.freebsd.org/SynapticsTouchpad Modified: head/sys/dev/atkbdc/psm.c Modified: head/sys/dev/atkbdc/psm.c ============================================================================== --- head/sys/dev/atkbdc/psm.c Tue Oct 14 17:47:29 2008 (r183887) +++ head/sys/dev/atkbdc/psm.c Tue Oct 14 17:48:36 2008 (r183888) @@ -163,15 +163,101 @@ typedef struct packetbuf { #define PSM_PACKETQUEUE 128 #endif +enum { + SYNAPTICS_SYSCTL_MIN_PRESSURE, + SYNAPTICS_SYSCTL_MAX_PRESSURE, + SYNAPTICS_SYSCTL_MAX_WIDTH, + SYNAPTICS_SYSCTL_MARGIN_TOP, + SYNAPTICS_SYSCTL_MARGIN_RIGHT, + SYNAPTICS_SYSCTL_MARGIN_BOTTOM, + SYNAPTICS_SYSCTL_MARGIN_LEFT, + SYNAPTICS_SYSCTL_NA_TOP, + SYNAPTICS_SYSCTL_NA_RIGHT, + SYNAPTICS_SYSCTL_NA_BOTTOM, + SYNAPTICS_SYSCTL_NA_LEFT, + SYNAPTICS_SYSCTL_WINDOW_MIN, + SYNAPTICS_SYSCTL_WINDOW_MAX, + SYNAPTICS_SYSCTL_MULTIPLICATOR, + SYNAPTICS_SYSCTL_WEIGHT_CURRENT, + SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS, + SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA, + SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED, + SYNAPTICS_SYSCTL_DIV_MIN, + SYNAPTICS_SYSCTL_DIV_MAX, + SYNAPTICS_SYSCTL_DIV_MAX_NA, + SYNAPTICS_SYSCTL_DIV_LEN, + SYNAPTICS_SYSCTL_TAP_MAX_DELTA, + SYNAPTICS_SYSCTL_TAP_MIN_QUEUE, + SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT, + SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA, + SYNAPTICS_SYSCTL_VSCROLL_VER_AREA, + SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA, + SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN, + SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX +}; + typedef struct synapticsinfo { - struct sysctl_ctx_list sysctl_ctx; + struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; - int directional_scrolls; - int low_speed_threshold; - int min_movement; - int squelch_level; + int directional_scrolls; + int min_pressure; + int max_pressure; + int max_width; + int margin_top; + int margin_right; + int margin_bottom; + int margin_left; + int na_top; + int na_right; + int na_bottom; + int na_left; + int window_min; + int window_max; + int multiplicator; + int weight_current; + int weight_previous; + int weight_previous_na; + int weight_len_squared; + int div_min; + int div_max; + int div_max_na; + int div_len; + int tap_max_delta; + int tap_min_queue; + int taphold_timeout; + int vscroll_ver_area; + int vscroll_hor_area; + int vscroll_min_delta; + int vscroll_div_min; + int vscroll_div_max; } synapticsinfo_t; +typedef struct synapticspacket { + int x; + int y; +} synapticspacket_t; + +#define SYNAPTICS_PACKETQUEUE 10 +#define SYNAPTICS_QUEUE_CURSOR(x) \ + (x + SYNAPTICS_PACKETQUEUE) % SYNAPTICS_PACKETQUEUE + +typedef struct synapticsaction { + synapticspacket_t queue[SYNAPTICS_PACKETQUEUE]; + int queue_len; + int queue_cursor; + int window_min; + int start_x; + int start_y; + int avg_dx; + int avg_dy; + int squelch_x; + int squelch_y; + int fingers_nb; + int tap_button; + int in_taphold; + int in_vscroll; +} synapticsaction_t; + /* driver control block */ struct psm_softc { /* Driver status information */ int unit; @@ -185,6 +271,7 @@ struct psm_softc { /* Driver status inf mousehw_t hw; /* hardware information */ synapticshw_t synhw; /* Synaptics hardware information */ synapticsinfo_t syninfo; /* Synaptics configuration */ + synapticsaction_t synaction; /* Synaptics action context */ mousemode_t mode; /* operation mode */ mousemode_t dflt_mode; /* default operation mode */ mousestatus_t status; /* accumulated mouse movement */ @@ -781,6 +868,35 @@ doopen(struct psm_softc *sc, int command { int stat[3]; + /* + * FIXME: Synaptics TouchPad seems to go back to Relative Mode with + * no obvious reason. Thus we check the current mode and restore the + * Absolute Mode if it was cleared. + * + * The previous hack at the end of psmprobe() wasn't efficient when + * moused(8) was restarted. + * + * A Reset (FF) or Set Defaults (F6) command would clear the + * Absolute Mode bit. But a verbose boot or debug.psm.loglevel=5 + * doesn't show any evidence of such a command. + */ + if (sc->hw.model == MOUSE_MODEL_SYNAPTICS) { + mouse_ext_command(sc->kbdc, 1); + get_mouse_status(sc->kbdc, stat, 0, 3); + if (stat[1] == 0x47 && stat[2] == 0x40) { + /* Set the mode byte -- request wmode where + * available */ + if (sc->synhw.capExtended) + mouse_ext_command(sc->kbdc, 0xc1); + else + mouse_ext_command(sc->kbdc, 0xc0); + set_mouse_sampling_rate(sc->kbdc, 20); + VLOG(5, (LOG_DEBUG, "psm%d: Synaptis Absolute Mode " + "hopefully restored\n", + sc->unit)); + } + } + /* enable the mouse device */ if (!enable_aux_dev(sc->kbdc)) { /* MOUSE ERROR: failed to enable the mouse because: @@ -1272,15 +1388,6 @@ psmprobe(device_t dev) endprobe(ENXIO); } - /* - * Synaptics TouchPad seems to go back to Relative Mode after - * the previous set_controller_command_byte() call; by issueing - * a Read Mode Byte command, the touchpad is in Absolute Mode - * again. - */ - if (sc->hw.model == MOUSE_MODEL_SYNAPTICS) - mouse_ext_command(sc->kbdc, 1); - /* done */ kbdc_set_device_mask(sc->kbdc, mask | KBD_AUX_CONTROL_BITS); kbdc_lock(sc->kbdc, FALSE); @@ -2398,7 +2505,7 @@ proc_synaptics(struct psm_softc *sc, pac { static int touchpad_buttons; static int guest_buttons; - int w, x0, y0, xavg, yavg, xsensitivity, ysensitivity, sensitivity = 0; + int w, x0, y0; /* TouchPad PS/2 absolute mode message format * @@ -2418,7 +2525,7 @@ proc_synaptics(struct psm_softc *sc, pac * U: up button * W: "wrist" value * X: x position - * Y: x position + * Y: y position * Z: pressure * * Absolute reportable limits: 0 - 6143. @@ -2438,22 +2545,44 @@ proc_synaptics(struct psm_softc *sc, pac (pb->ipacket[3] & 0xc8) != 0xc0) return (-1); - *x = *y = x0 = y0 = 0; + *x = *y = 0; - /* Pressure value. */ + /* + * Pressure value. + * Interpretation: + * z = 0 No finger contact + * z = 10 Finger hovering near the pad + * z = 30 Very light finger contact + * z = 80 Normal finger contact + * z = 110 Very heavy finger contact + * z = 200 Finger lying flat on pad surface + * z = 255 Maximum reportable Z + */ *z = pb->ipacket[2]; - /* Finger width value */ + /* + * Finger width value + * Interpretation: + * w = 0 Two finger on the pad (capMultiFinger needed) + * w = 1 Three or more fingers (capMultiFinger needed) + * w = 2 Pen (instead of finger) (capPen needed) + * w = 3 Reserved (passthrough?) + * w = 4-7 Finger of normal width (capPalmDetect needed) + * w = 8-14 Very wide finger or palm (capPalmDetect needed) + * w = 15 Maximum reportable width (capPalmDetect needed) + */ + /* XXX Is checking capExtended enough? */ if (sc->synhw.capExtended) w = ((pb->ipacket[0] & 0x30) >> 2) | ((pb->ipacket[0] & 0x04) >> 1) | ((pb->ipacket[3] & 0x04) >> 2); else { - /* Assume a finger of regular width */ + /* Assume a finger of regular width. */ w = 4; } /* Handle packets from the guest device */ + /* XXX Documentation? */ if (w == 3 && sc->synhw.capPassthrough) { *x = ((pb->ipacket[1] & 0x10) ? pb->ipacket[4] - 256 : pb->ipacket[4]); @@ -2470,7 +2599,7 @@ proc_synaptics(struct psm_softc *sc, pac guest_buttons |= MOUSE_BUTTON3DOWN; ms->button = touchpad_buttons | guest_buttons; - return (0); + goto SYNAPTICS_END; } /* Button presses */ @@ -2492,6 +2621,7 @@ proc_synaptics(struct psm_softc *sc, pac * the packet indicates that we have an extended * button press. */ + /* XXX Documentation? */ if (pb->ipacket[3] & 0x02) { /* * if directional_scrolls is not 1, we treat any of @@ -2507,151 +2637,495 @@ proc_synaptics(struct psm_softc *sc, pac if (pb->ipacket[5] & 0x02) touchpad_buttons |= MOUSE_BUTTON7DOWN; } else { - if ((pb->ipacket[4] & 0x0F) || (pb->ipacket[5] & 0x0F)) + if ((pb->ipacket[4] & 0x0F) || + (pb->ipacket[5] & 0x0F)) touchpad_buttons |= MOUSE_BUTTON2DOWN; } } ms->button = touchpad_buttons | guest_buttons; - /* There is a finger on the pad. */ - if ((w >= 4 && w <= 7) && (*z >= 16 && *z < 200)) { + /* Check pressure to detect a real wanted action on the + * touchpad. */ + if (*z >= sc->syninfo.min_pressure) { + synapticsaction_t *synaction; + int cursor, peer, window; + int dx, dy, dxp, dyp; + int max_width, max_pressure; + int margin_top, margin_right, margin_bottom, margin_left; + int na_top, na_right, na_bottom, na_left; + int window_min, window_max; + int multiplicator; + int weight_current, weight_previous, weight_len_squared; + int div_min, div_max, div_len; + int vscroll_hor_area, vscroll_ver_area; + + int len, weight_prev_x, weight_prev_y; + int div_max_x, div_max_y, div_x, div_y; + + /* Read sysctl. */ + /* XXX Verify values? */ + max_width = sc->syninfo.max_width; + max_pressure = sc->syninfo.max_pressure; + margin_top = sc->syninfo.margin_top; + margin_right = sc->syninfo.margin_right; + margin_bottom = sc->syninfo.margin_bottom; + margin_left = sc->syninfo.margin_left; + na_top = sc->syninfo.na_top; + na_right = sc->syninfo.na_right; + na_bottom = sc->syninfo.na_bottom; + na_left = sc->syninfo.na_left; + window_min = sc->syninfo.window_min; + window_max = sc->syninfo.window_max; + multiplicator = sc->syninfo.multiplicator; + weight_current = sc->syninfo.weight_current; + weight_previous = sc->syninfo.weight_previous; + weight_len_squared = sc->syninfo.weight_len_squared; + div_min = sc->syninfo.div_min; + div_max = sc->syninfo.div_max; + div_len = sc->syninfo.div_len; + vscroll_hor_area = sc->syninfo.vscroll_hor_area; + vscroll_ver_area = sc->syninfo.vscroll_ver_area; + + /* Palm detection. */ + if (!( + (sc->synhw.capMultiFinger && (w == 0 || w == 1)) || + (sc->synhw.capPalmDetect && w >= 4 && w <= max_width) || + (!sc->synhw.capPalmDetect && *z <= max_pressure) || + (sc->synhw.capPen && w == 2))) { + /* + * We consider the packet irrelevant for the current + * action when: + * - the width isn't comprised in: + * [4; max_width] + * - the pressure isn't comprised in: + * [min_pressure; max_pressure] + * - pen aren't supported but w is 2 + * + * Note that this doesn't terminate the current action. + */ + VLOG(2, (LOG_DEBUG, + "synaptics: palm detected! (%d)\n", w)); + goto SYNAPTICS_END; + } + + /* Read current absolute position. */ x0 = ((pb->ipacket[3] & 0x10) << 8) | - ((pb->ipacket[1] & 0x0f) << 8) | pb->ipacket[4]; + ((pb->ipacket[1] & 0x0f) << 8) | + pb->ipacket[4]; y0 = ((pb->ipacket[3] & 0x20) << 7) | - ((pb->ipacket[1] & 0xf0) << 4) | pb->ipacket[5]; + ((pb->ipacket[1] & 0xf0) << 4) | + pb->ipacket[5]; - if (sc->flags & PSM_FLAGS_FINGERDOWN) { - *x = x0 - sc->xold; - *y = y0 - sc->yold; + synaction = &(sc->synaction); - /* - * we compute averages of x and y - * movement - */ - if (sc->xaverage == 0) - sc->xaverage = *x; + /* + * If the action is just beginning, init the structure and + * compute tap timeout. + */ + if (!(sc->flags & PSM_FLAGS_FINGERDOWN)) { + VLOG(3, (LOG_DEBUG, "synaptics: ----\n")); - if (sc->yaverage == 0) - sc->yaverage = *y; + /* Store the first point of this action. */ + synaction->start_x = x0; + synaction->start_y = y0; + dx = dy = 0; + + /* Initialize queue. */ + synaction->queue_cursor = SYNAPTICS_PACKETQUEUE; + synaction->queue_len = 0; + synaction->window_min = window_min; + + /* Reset average. */ + synaction->avg_dx = 0; + synaction->avg_dy = 0; + + /* Reset squelch. */ + synaction->squelch_x = 0; + synaction->squelch_y = 0; + + /* Reset pressure peak. */ + sc->zmax = 0; + + /* Reset fingers count. */ + synaction->fingers_nb = 0; + + /* Reset virtual scrolling state. */ + synaction->in_vscroll = 0; + + /* Compute tap timeout. */ + sc->taptimeout.tv_sec = tap_timeout / 1000000; + sc->taptimeout.tv_usec = tap_timeout % 1000000; + timevaladd(&sc->taptimeout, &sc->lastsoftintr); - xavg = sc->xaverage; - yavg = sc->yaverage; + sc->flags |= PSM_FLAGS_FINGERDOWN; + } else { + /* Calculate the current delta. */ + cursor = synaction->queue_cursor; + dx = x0 - synaction->queue[cursor].x; + dy = y0 - synaction->queue[cursor].y; + } - sc->xaverage = (xavg + *x) >> 1; - sc->yaverage = (yavg + *y) >> 1; + /* If in tap-hold, add the recorded button. */ + if (synaction->in_taphold) + ms->button |= synaction->tap_button; - /* - * then use the averages to compute - * a sensitivity level in each dimension - */ - xsensitivity = (sc->xaverage - xavg); - if (xsensitivity < 0) - xsensitivity = -xsensitivity; - - ysensitivity = (sc->yaverage - yavg); - if (ysensitivity < 0) - ysensitivity = -ysensitivity; + /* + * From now on, we can use the SYNAPTICS_END label to skip + * the current packet. + */ + + /* + * Limit the coordinates to the specified margins because + * this area isn't very reliable. + */ + if (x0 <= margin_left) + x0 = margin_left; + else if (x0 >= 6143 - margin_right) + x0 = 6143 - margin_right; + if (y0 <= margin_bottom) + y0 = margin_bottom; + else if (y0 >= 6143 - margin_top) + y0 = 6143 - margin_top; + + VLOG(3, (LOG_DEBUG, "synaptics: ipacket: [%d, %d], %d, %d\n", + x0, y0, *z, w)); + + /* Queue this new packet. */ + cursor = SYNAPTICS_QUEUE_CURSOR(synaction->queue_cursor - 1); + synaction->queue[cursor].x = x0; + synaction->queue[cursor].y = y0; + synaction->queue_cursor = cursor; + if (synaction->queue_len < SYNAPTICS_PACKETQUEUE) + synaction->queue_len++; + VLOG(5, (LOG_DEBUG, + "synaptics: cursor[%d]: x=%d, y=%d, dx=%d, dy=%d\n", + cursor, x0, y0, dx, dy)); + /* + * For tap, we keep the maximum number of fingers and the + * pressure peak. Also with multiple fingers, we increase + * the minimum window. + */ + switch (w) { + case 1: /* Three or more fingers. */ + synaction->fingers_nb = imax(3, synaction->fingers_nb); + synaction->window_min = window_max; + break; + case 0: /* Two fingers. */ + synaction->fingers_nb = imax(2, synaction->fingers_nb); + synaction->window_min = window_max; + break; + default: /* One finger or undetectable. */ + synaction->fingers_nb = imax(1, synaction->fingers_nb); + } + sc->zmax = imax(*z, sc->zmax); + + /* Do we have enough packets to consider this a movement? */ + if (synaction->queue_len < synaction->window_min) + goto SYNAPTICS_END; + + /* Is a scrolling action occuring? */ + if (!synaction->in_taphold && !synaction->in_vscroll) { /* - * The sensitivity level is higher the faster - * the finger is moving. It also tends to be - * higher in the middle of a touchpad motion - * than on either end - * Note - sensitivity gets to 0 when moving slowly - - * so we add 1 to it to give it a meaningful value - * in that case. + * A scrolling action must not conflict with a tap + * action. Here are the conditions to consider a + * scrolling action: + * - the action in a configurable area + * - one of the following: + * . the distance between the last packet and the + * first should be above a configurable minimum + * . tap timed out */ - sensitivity = (xsensitivity & ysensitivity) + 1; + dxp = abs(synaction->queue[synaction->queue_cursor].x - + synaction->start_x); + dyp = abs(synaction->queue[synaction->queue_cursor].y - + synaction->start_y); + + if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, >) || + dxp >= sc->syninfo.vscroll_min_delta || + dyp >= sc->syninfo.vscroll_min_delta) { + /* Check for horizontal scrolling. */ + if ((vscroll_hor_area > 0 && + synaction->start_y <= vscroll_hor_area) || + (vscroll_hor_area < 0 && + synaction->start_y >= + 6143 + vscroll_hor_area)) + synaction->in_vscroll += 2; + + /* Check for vertical scrolling. */ + if ((vscroll_ver_area > 0 && + synaction->start_x <= vscroll_ver_area) || + (vscroll_ver_area < 0 && + synaction->start_x >= + 6143 + vscroll_ver_area)) + synaction->in_vscroll += 1; + + /* Avoid conflicts if area overlaps. */ + if (synaction->in_vscroll == 3) + synaction->in_vscroll = + (dxp > dyp) ? 2 : 1; + } + VLOG(5, (LOG_DEBUG, + "synaptics: virtual scrolling: %s " + "(direction=%d, dxp=%d, dyp=%d)\n", + synaction->in_vscroll ? "YES" : "NO", + synaction->in_vscroll, dxp, dyp)); + } + weight_prev_x = weight_prev_y = weight_previous; + div_max_x = div_max_y = div_max; + + if (synaction->in_vscroll) { + /* Dividers are different with virtual scrolling. */ + div_min = sc->syninfo.vscroll_div_min; + div_max_x = div_max_y = sc->syninfo.vscroll_div_max; + } else { /* - * If either our x or y change is greater than - * our hi/low speed threshold - we do the high-speed - * absolute to relative calculation otherwise - * we do the low-speed calculation. + * There's a lot of noise in coordinates when + * the finger is on the touchpad's borders. When + * using this area, we apply a special weight and + * div. */ - if ((*x > sc->syninfo.low_speed_threshold || - *x < -sc->syninfo.low_speed_threshold) || - (*y > sc->syninfo.low_speed_threshold || - *y < -sc->syninfo.low_speed_threshold)) { - x0 = (x0 + sc->xold * 3) / 4; - y0 = (y0 + sc->yold * 3) / 4; - *x = (x0 - sc->xold) * 10 / 85; - *y = (y0 - sc->yold) * 10 / 85; - } else { - /* - * This is the low speed calculation. - * We simply check to see if our movement is - * more than our minimum movement threshold - * and if it is - set the movement to 1 - * in the correct direction. - * NOTE - Normally this would result - * in pointer movement that was WAY too fast. - * This works due to the movement squelch - * we do later. - */ - if (*x < -sc->syninfo.min_movement) - *x = -1; - else if (*x > sc->syninfo.min_movement) - *x = 1; - else - *x = 0; - if (*y < -sc->syninfo.min_movement) - *y = -1; - else if (*y > sc->syninfo.min_movement) - *y = 1; - else - *y = 0; + if (x0 <= na_left || x0 >= 6143 - na_right) { + weight_prev_x = sc->syninfo.weight_previous_na; + div_max_x = sc->syninfo.div_max_na; + } + if (y0 <= na_bottom || y0 >= 6143 - na_top) { + weight_prev_y = sc->syninfo.weight_previous_na; + div_max_y = sc->syninfo.div_max_na; } - } else - sc->flags |= PSM_FLAGS_FINGERDOWN; + } /* - * The squelch process. Take our sensitivity value and - * add it to the current squelch value - if squelch is - * less than our squelch threshold we kill the movement, - * otherwise we reset squelch and pass the movement through. - * Since squelch is cumulative - when mouse movement is slow - * (around sensitivity 1) the net result is that only 1 - * out of every squelch_level packets is delivered, - * effectively slowing down the movement. - */ - sc->squelch += sensitivity; - if (sc->squelch < sc->syninfo.squelch_level) { - *x = 0; - *y = 0; - } else - sc->squelch = 0; + * Calculate weights for the average operands and + * the divisor. Both depend on the distance between + * the current packet and a previous one (based on the + * window width). + */ + window = imin(synaction->queue_len, window_max); + peer = SYNAPTICS_QUEUE_CURSOR(cursor + window - 1); + dxp = abs(x0 - synaction->queue[peer].x) + 1; + dyp = abs(y0 - synaction->queue[peer].y) + 1; + len = (dxp * dxp) + (dyp * dyp); + weight_prev_x = imin(weight_prev_x, + weight_len_squared * weight_prev_x / len); + weight_prev_y = imin(weight_prev_y, + weight_len_squared * weight_prev_y / len); + + len = (dxp + dyp) / 2; + div_x = div_len * div_max_x / len; + div_x = imin(div_max_x, div_x); + div_x = imax(div_min, div_x); + div_y = div_len * div_max_y / len; + div_y = imin(div_max_y, div_y); + div_y = imax(div_min, div_y); + + VLOG(3, (LOG_DEBUG, + "synaptics: peer=%d, len=%d, weight=%d/%d, div=%d/%d\n", + peer, len, weight_prev_x, weight_prev_y, div_x, div_y)); + + /* Compute averages. */ + synaction->avg_dx = + (weight_current * dx * multiplicator + + weight_prev_x * synaction->avg_dx) / + (weight_current + weight_prev_x); + + synaction->avg_dy = + (weight_current * dy * multiplicator + + weight_prev_y * synaction->avg_dy) / + (weight_current + weight_prev_y); + + VLOG(5, (LOG_DEBUG, + "synaptics: avg_dx~=%d, avg_dy~=%d\n", + synaction->avg_dx / multiplicator, + synaction->avg_dy / multiplicator)); + + /* Use these averages to calculate x & y. */ + synaction->squelch_x += synaction->avg_dx; + *x = synaction->squelch_x / (div_x * multiplicator); + synaction->squelch_x = synaction->squelch_x % + (div_x * multiplicator); + + synaction->squelch_y += synaction->avg_dy; + *y = synaction->squelch_y / (div_y * multiplicator); + synaction->squelch_y = synaction->squelch_y % + (div_y * multiplicator); + + if (synaction->in_vscroll) { + switch(synaction->in_vscroll) { + case 1: /* Vertical scrolling. */ + if (*y != 0) + ms->button |= (*y > 0) ? + MOUSE_BUTTON4DOWN : + MOUSE_BUTTON5DOWN; + break; + case 2: /* Horizontal scrolling. */ + if (*x != 0) + ms->button |= (*x > 0) ? + MOUSE_BUTTON7DOWN : + MOUSE_BUTTON6DOWN; + break; + } + + /* The pointer is not moved. */ + *x = *y = 0; + } else { + VLOG(3, (LOG_DEBUG, "synaptics: [%d, %d] -> [%d, %d]\n", + dx, dy, *x, *y)); + } + } else if (sc->flags & PSM_FLAGS_FINGERDOWN) { + /* + * An action is currently taking place but the pressure + * dropped under the minimum, putting an end to it. + */ + synapticsaction_t *synaction; + int taphold_timeout, dx, dy, tap_max_delta; + + synaction = &(sc->synaction); + dx = abs(synaction->queue[synaction->queue_cursor].x - + synaction->start_x); + dy = abs(synaction->queue[synaction->queue_cursor].y - + synaction->start_y); + + /* Max delta is disabled for multi-fingers tap. */ + if (synaction->fingers_nb > 1) + tap_max_delta = imax(dx, dy); + else + tap_max_delta = sc->syninfo.tap_max_delta; - sc->xold = x0; - sc->yold = y0; - sc->zmax = imax(*z, sc->zmax); - } else { sc->flags &= ~PSM_FLAGS_FINGERDOWN; - if (sc->zmax > tap_threshold && - timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) { - if (w == 0) - ms->button |= MOUSE_BUTTON3DOWN; - else if (w == 1) - ms->button |= MOUSE_BUTTON2DOWN; - else - ms->button |= MOUSE_BUTTON1DOWN; - } + /* Check for tap. */ + VLOG(3, (LOG_DEBUG, + "synaptics: zmax=%d, dx=%d, dy=%d, " + "delta=%d, fingers=%d, queue=%d\n", + sc->zmax, dx, dy, tap_max_delta, synaction->fingers_nb, + synaction->queue_len)); + if (!synaction->in_vscroll && sc->zmax >= tap_threshold && + timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=) && + dx <= tap_max_delta && dy <= tap_max_delta && + synaction->queue_len >= sc->syninfo.tap_min_queue) { + /* + * We have a tap if: + * - the maximum pressure went over tap_threshold + * - the action ended before tap_timeout + * + * To handle tap-hold, we must delay any button push to + * the next action. + */ + if (synaction->in_taphold) { + /* + * This is the second and last tap of a + * double tap action, not a tap-hold. + */ + synaction->in_taphold = 0; - sc->zmax = 0; - sc->taptimeout.tv_sec = tap_timeout / 1000000; - sc->taptimeout.tv_usec = tap_timeout % 1000000; - timevaladd(&sc->taptimeout, &sc->lastsoftintr); + /* + * For double-tap to work: + * - no button press is emitted (to + * simulate a button release) + * - PSM_FLAGS_FINGERDOWN is set to + * force the next packet to emit a + * button press) + */ + VLOG(2, (LOG_DEBUG, + "synaptics: button RELEASE: %d\n", + synaction->tap_button)); + sc->flags |= PSM_FLAGS_FINGERDOWN; + } else { + /* + * This is the first tap: we set the + * tap-hold state and notify the button + * down event. + */ + synaction->in_taphold = 1; + taphold_timeout = sc->syninfo.taphold_timeout; + sc->taptimeout.tv_sec = taphold_timeout / + 1000000; + sc->taptimeout.tv_usec = taphold_timeout % + 1000000; + timevaladd(&sc->taptimeout, &sc->lastsoftintr); + + switch (synaction->fingers_nb) { + case 3: + synaction->tap_button = + MOUSE_BUTTON2DOWN; + break; + case 2: + synaction->tap_button = + MOUSE_BUTTON3DOWN; + break; + default: + synaction->tap_button = + MOUSE_BUTTON1DOWN; + } + VLOG(2, (LOG_DEBUG, + "synaptics: button PRESS: %d\n", + synaction->tap_button)); + ms->button |= synaction->tap_button; + } + } else { + /* + * Not enough pressure or timeout: reset + * tap-hold state. + */ + if (synaction->in_taphold) { + VLOG(2, (LOG_DEBUG, + "synaptics: button RELEASE: %d\n", + synaction->tap_button)); + synaction->in_taphold = 0; + } else { + VLOG(2, (LOG_DEBUG, + "synaptics: not a tap-hold\n")); + } + } + } else if (!(sc->flags & PSM_FLAGS_FINGERDOWN) && + sc->synaction.in_taphold) { + /* + * For a tap-hold to work, the button must remain down at + * least until timeout (where the in_taphold flags will be + * cleared) or during the next action. + */ + if (timevalcmp(&sc->lastsoftintr, &sc->taptimeout, <=)) { + ms->button |= sc->synaction.tap_button; + } else { + VLOG(2, (LOG_DEBUG, + "synaptics: button RELEASE: %d\n", + sc->synaction.tap_button)); + sc->synaction.in_taphold = 0; + } } - /* Use the extra buttons as a scrollwheel */ - if (ms->button & MOUSE_BUTTON4DOWN) +SYNAPTICS_END: + /* + * Use the extra buttons as a scrollwheel + * + * XXX X.Org uses the Z axis for vertical wheel only, + * whereas moused(8) understands special values to differ + * vertical and horizontal wheels. + * + * xf86-input-mouse needs therefore a small patch to + * understand these special values. Without it, the + * horizontal wheel acts as a vertical wheel in X.Org. + * + * That's why the horizontal wheel is disabled by + * default for now. + */ + if (ms->button & MOUSE_BUTTON4DOWN) { *z = -1; - else if (ms->button & MOUSE_BUTTON5DOWN) + ms->button &= ~MOUSE_BUTTON4DOWN; + } else if (ms->button & MOUSE_BUTTON5DOWN) { *z = 1; - else + ms->button &= ~MOUSE_BUTTON5DOWN; + } else if (ms->button & MOUSE_BUTTON6DOWN) { + *z = -2; + ms->button &= ~MOUSE_BUTTON6DOWN; + } else if (ms->button & MOUSE_BUTTON7DOWN) { + *z = 2; + ms->button &= ~MOUSE_BUTTON7DOWN; + } else *z = 0; return (0); @@ -3400,13 +3874,87 @@ enable_4dplus(struct psm_softc *sc) /* Synaptics Touchpad */ static int -enable_synaptics(struct psm_softc *sc) +synaptics_sysctl(SYSCTL_HANDLER_ARGS) { - int status[3]; - KBDC kbdc; + int error, arg; - if (!synaptics_support) - return (FALSE); + /* Read the current value. */ + arg = *(int *)oidp->oid_arg1; + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* Sanity check. */ + if (error || !req->newptr) + return (error); + + /* + * Check that the new value is in the concerned node's range + * of values. + */ + switch (oidp->oid_arg2) { + case SYNAPTICS_SYSCTL_MIN_PRESSURE: + case SYNAPTICS_SYSCTL_MAX_PRESSURE: + if (arg < 0 || arg > 255) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_MAX_WIDTH: + if (arg < 4 || arg > 15) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_MARGIN_TOP: + case SYNAPTICS_SYSCTL_MARGIN_RIGHT: + case SYNAPTICS_SYSCTL_MARGIN_BOTTOM: + case SYNAPTICS_SYSCTL_MARGIN_LEFT: + case SYNAPTICS_SYSCTL_NA_TOP: + case SYNAPTICS_SYSCTL_NA_RIGHT: + case SYNAPTICS_SYSCTL_NA_BOTTOM: + case SYNAPTICS_SYSCTL_NA_LEFT: + if (arg < 0 || arg > 6143) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_WINDOW_MIN: + case SYNAPTICS_SYSCTL_WINDOW_MAX: + case SYNAPTICS_SYSCTL_TAP_MIN_QUEUE: + if (arg < 1 || arg > SYNAPTICS_PACKETQUEUE) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_MULTIPLICATOR: + case SYNAPTICS_SYSCTL_WEIGHT_CURRENT: + case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS: + case SYNAPTICS_SYSCTL_WEIGHT_PREVIOUS_NA: + case SYNAPTICS_SYSCTL_WEIGHT_LEN_SQUARED: + case SYNAPTICS_SYSCTL_DIV_MIN: + case SYNAPTICS_SYSCTL_DIV_MAX: + case SYNAPTICS_SYSCTL_DIV_MAX_NA: + case SYNAPTICS_SYSCTL_DIV_LEN: + case SYNAPTICS_SYSCTL_VSCROLL_DIV_MIN: + case SYNAPTICS_SYSCTL_VSCROLL_DIV_MAX: + if (arg < 1) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_TAP_MAX_DELTA: + case SYNAPTICS_SYSCTL_TAPHOLD_TIMEOUT: + case SYNAPTICS_SYSCTL_VSCROLL_MIN_DELTA: + if (arg < 0) + return (EINVAL); + break; + case SYNAPTICS_SYSCTL_VSCROLL_HOR_AREA: + case SYNAPTICS_SYSCTL_VSCROLL_VER_AREA: + if (arg < -6143 || arg > 6143) + return (EINVAL); + break; + default: + return (EINVAL); + } + + /* Update. */ + *(int *)oidp->oid_arg1 = arg; + + return (error); +} + +static void +synaptics_sysctl_create_tree(struct psm_softc *sc) +{ /* Attach extra synaptics sysctl nodes under hw.psm.synaptics */ sysctl_ctx_init(&sc->syninfo.sysctl_ctx); @@ -3414,54 +3962,307 @@ enable_synaptics(struct psm_softc *sc) SYSCTL_STATIC_CHILDREN(_hw_psm), OID_AUTO, "synaptics", CTLFLAG_RD, 0, "Synaptics TouchPad"); - /* - * synaptics_directional_scrolls - if non-zero, the directional - * pad scrolls, otherwise it registers as a middle-click. - */ + /* hw.psm.synaptics.directional_scrolls. */ sc->syninfo.directional_scrolls = 1; SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx, SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO, - "directional_scrolls", CTLFLAG_RW, + "directional_scrolls", CTLFLAG_RW|CTLFLAG_ANYBODY, &sc->syninfo.directional_scrolls, 0, - "directional pad scrolls (1=yes 0=3rd button)"); + "Enable hardware scrolling pad (if non-zero) or register it as " + "a middle-click (if 0)"); - /* - * Synaptics_low_speed_threshold - the number of touchpad units - * below-which we go into low-speed tracking mode. - */ - sc->syninfo.low_speed_threshold = 20; - SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx, + /* hw.psm.synaptics.min_pressure. */ + sc->syninfo.min_pressure = 16; + SYSCTL_ADD_PROC(&sc->syninfo.sysctl_ctx, SYSCTL_CHILDREN(sc->syninfo.sysctl_tree), OID_AUTO, - "low_speed_threshold", CTLFLAG_RW, - &sc->syninfo.low_speed_threshold, 0, - "threshold between low and hi speed positioning"); - - /* - * Synaptics_min_movement - the number of touchpad units below - * which we ignore altogether. - */ - sc->syninfo.min_movement = 2; - SYSCTL_ADD_INT(&sc->syninfo.sysctl_ctx, + "min_pressure", CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, + &sc->syninfo.min_pressure, SYNAPTICS_SYSCTL_MIN_PRESSURE, + synaptics_sysctl, "I", + "Minimum pressure required to start an action"); + + /* hw.psm.synaptics.max_pressure. */ *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***