>From 9295fbb214b32555b55b0c1c56d0831bf6395c1c Mon Sep 17 00:00:00 2001 From: Alan Third Date: Fri, 8 Sep 2017 19:26:47 +0100 Subject: [PATCH] Provide native touchpad scrolling on macOS * etc/NEWS: Describe changes. * lisp/term/ns-win.el (mouse-wheel-scroll-amount, mouse-wheel-progressive-speed): Set to smarter values for macOS touchpads. * src/nsterm.m (emacsView::mouseDown): Use precise scrolling deltas to calculate scrolling for touchpads and mouse wheels. (syms_of_nsterm): Add variables 'ns-use-system-mwheel-acceleration', 'ns-touchpad-scroll-line-height' and 'ns-touchpad-use-momentum'. * src/keyboard.c (make_lispy_event): Pass on .arg when relevant. * src/termhooks.h (event_kind): Update comments re. WHEEL_EVENT. * lisp/mwheel.el (mwheel-scroll): Use line count. * lisp/subr.el (event-line-count): New function. --- etc/NEWS | 6 ++ lisp/mwheel.el | 1 + lisp/subr.el | 5 ++ lisp/term/ns-win.el | 7 +++ src/keyboard.c | 5 +- src/nsterm.m | 158 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/termhooks.h | 4 +- 7 files changed, 172 insertions(+), 14 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 371cdf686c..6111738b0f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -49,6 +49,12 @@ When you add a new item, use the appropriate mark if you are sure it applies, * Changes in Emacs 27.1 on Non-Free Operating Systems +--- +** Mousewheel and trackpad scrolling on macOS 10.7+ now behaves more +like the macOS default. The new variables +'ns-use-system-mwheel-acceleration', 'ns-touchpad-scroll-line-height' +and 'ns-touchpad-use-momentum' can be used to customise the behavior. + ---------------------------------------------------------------------- This file is part of GNU Emacs. diff --git a/lisp/mwheel.el b/lisp/mwheel.el index 2956ba5516..0c0dcb3beb 100644 --- a/lisp/mwheel.el +++ b/lisp/mwheel.el @@ -232,6 +232,7 @@ mwheel-scroll ;; When the double-mouse-N comes in, a mouse-N has been executed already, ;; So by adding things up we get a squaring up (1, 3, 6, 10, 15, ...). (setq amt (* amt (event-click-count event)))) + (when (numberp amt) (setq amt (* amt (event-line-count event)))) (unwind-protect (let ((button (mwheel-event-button event))) (cond ((eq button mouse-wheel-down-event) diff --git a/lisp/subr.el b/lisp/subr.el index 79ae1f4830..f0d0b24462 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -1270,6 +1270,11 @@ event-click-count "Return the multi-click count of EVENT, a click or drag event. The return value is a positive integer." (if (and (consp event) (integerp (nth 2 event))) (nth 2 event) 1)) + +(defsubst event-line-count (event) + "Return the line count of EVENT, a mousewheel event. +The return value is a positive integer." + (if (and (consp event) (integerp (nth 3 event))) (nth 3 event) 1)) ;;;; Extracting fields of the positions in an event. diff --git a/lisp/term/ns-win.el b/lisp/term/ns-win.el index 68b659bf75..6aef27362f 100644 --- a/lisp/term/ns-win.el +++ b/lisp/term/ns-win.el @@ -736,6 +736,13 @@ ns-paste-secondary (global-unset-key [horizontal-scroll-bar drag-mouse-1]) +;;;; macOS-like defaults for trackpad and mouse wheel scrolling. + +(when (featurep 'cocoa) + (setq mouse-wheel-scroll-amount '(1 ((shift) . 5) ((control)))) + (setq mouse-wheel-progressive-speed nil)) + + ;;;; Color support. ;; Functions for color panel + drag diff --git a/src/keyboard.c b/src/keyboard.c index 4db50be855..e8701b8870 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -5925,7 +5925,10 @@ make_lispy_event (struct input_event *event) ASIZE (wheel_syms)); } - if (event->modifiers & (double_modifier | triple_modifier)) + if (NUMBERP (event->arg)) + return list4 (head, position, make_number (double_click_count), + event->arg); + else if (event->modifiers & (double_modifier | triple_modifier)) return list3 (head, position, make_number (double_click_count)); else return list2 (head, position); diff --git a/src/nsterm.m b/src/nsterm.m index 2751533533..776635980e 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -6498,24 +6498,139 @@ - (void)mouseDown: (NSEvent *)theEvent if ([theEvent type] == NSEventTypeScrollWheel) { - CGFloat delta = [theEvent deltaY]; - /* Mac notebooks send wheel events w/delta =0 when trackpad scrolling */ - if (delta == 0) +#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 + if ([theEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) { - delta = [theEvent deltaX]; - if (delta == 0) +#endif + /* If the input device is a touchpad or similar, use precise + * scrolling deltas. These are measured in pixels, so we + * have to add them up until they exceed one line height, + * then we can send a scroll wheel event. + * + * If the device only has coarse scrolling deltas, like a + * real mousewheel, the deltas represent a ratio of whole + * lines, so round up the number of lines. This means we + * always send one scroll event per click, but can still + * scroll more than one line if the OS tells us to. + */ + bool horizontal; + int lines = 0; + int scrollUp = NO; + + /* FIXME: At the top or bottom of the buffer we should + * ignore momentum-phase events. */ + if (! ns_touchpad_use_momentum + && [theEvent momentumPhase] != NSEventPhaseNone) + return; + + if ([theEvent hasPreciseScrollingDeltas]) { - NSTRACE_MSG ("deltaIsZero"); - return; + static int totalDeltaX, totalDeltaY; + int lineHeight; + + if (NUMBERP (ns_touchpad_scroll_line_height)) + lineHeight = XINT (ns_touchpad_scroll_line_height); + else + { + /* FIXME: Use actual line height instead of the default. */ + lineHeight = default_line_pixel_height + (XWINDOW (FRAME_SELECTED_WINDOW (emacsframe))); + } + + if ([theEvent phase] == NSEventPhaseBegan) + { + totalDeltaX = 0; + totalDeltaY = 0; + } + + totalDeltaX += [theEvent scrollingDeltaX]; + totalDeltaY += [theEvent scrollingDeltaY]; + + /* Calculate the number of lines, if any, to scroll, and + * reset the total delta for the direction we're NOT + * scrolling so that small movements don't add up. */ + if (abs (totalDeltaX) > abs (totalDeltaY) + && abs (totalDeltaX) > lineHeight) + { + horizontal = YES; + scrollUp = totalDeltaX > 0; + + lines = abs (totalDeltaX / lineHeight); + totalDeltaX = totalDeltaX % lineHeight; + totalDeltaY = 0; + } + else if (abs (totalDeltaY) >= abs (totalDeltaX) + && abs (totalDeltaY) > lineHeight) + { + horizontal = NO; + scrollUp = totalDeltaY > 0; + + lines = abs (totalDeltaY / lineHeight); + totalDeltaY = totalDeltaY % lineHeight; + totalDeltaX = 0; + } + + if (lines > 1 && ! ns_use_system_mwheel_acceleration) + lines = 1; } - emacs_event->kind = HORIZ_WHEEL_EVENT; + else + { + CGFloat delta; + + if ([theEvent scrollingDeltaY] == 0) + { + horizontal = YES; + delta = [theEvent scrollingDeltaX]; + } + else + { + horizontal = NO; + delta = [theEvent scrollingDeltaY]; + } + + lines = (ns_use_system_mwheel_acceleration) + ? ceil (fabs (delta)) : 1; + + scrollUp = delta > 0; + } + + if (lines == 0) + return; + + emacs_event->kind = horizontal ? HORIZ_WHEEL_EVENT : WHEEL_EVENT; + emacs_event->arg = (make_number (lines)); + + emacs_event->code = 0; + emacs_event->modifiers = EV_MODIFIERS (theEvent) | + (scrollUp ? up_modifier : down_modifier); +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 } else - emacs_event->kind = WHEEL_EVENT; +#endif +#endif /* defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */ +#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 1070 + { + CGFloat delta = [theEvent deltaY]; + /* Mac notebooks send wheel events w/delta =0 when trackpad scrolling */ + if (delta == 0) + { + delta = [theEvent deltaX]; + if (delta == 0) + { + NSTRACE_MSG ("deltaIsZero"); + return; + } + emacs_event->kind = HORIZ_WHEEL_EVENT; + } + else + emacs_event->kind = WHEEL_EVENT; - emacs_event->code = 0; - emacs_event->modifiers = EV_MODIFIERS (theEvent) | - ((delta > 0) ? up_modifier : down_modifier); + emacs_event->code = 0; + emacs_event->modifiers = EV_MODIFIERS (theEvent) | + ((delta > 0) ? up_modifier : down_modifier); + } +#endif } else { @@ -6524,9 +6639,11 @@ - (void)mouseDown: (NSEvent *)theEvent emacs_event->modifiers = EV_MODIFIERS (theEvent) | EV_UDMODIFIERS (theEvent); } + XSETINT (emacs_event->x, lrint (p.x)); XSETINT (emacs_event->y, lrint (p.y)); EV_TRAILER (theEvent); + return; } @@ -9166,6 +9283,23 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with This variable is ignored on Mac OS X < 10.7 and GNUstep. */); ns_use_srgb_colorspace = YES; + DEFVAR_BOOL ("ns-use-system-mwheel-acceleration", + ns_use_system_mwheel_acceleration, + doc: /*Non-nil means use macOS's standard mouse wheel acceleration. +This variable is ignored on macOS < 10.7 and GNUstep. Default is t. */); + ns_use_system_mwheel_acceleration = YES; + + DEFVAR_LISP ("ns-touchpad-scroll-line-height", ns_touchpad_scroll_line_height, + doc: /*The number of pixels touchpad scrolling considers a line. +Nil or a non-number means use the default frame line height. +This variable is ignored on macOS < 10.7 and GNUstep. Default is nil. */); + ns_touchpad_scroll_line_height = Qnil; + + DEFVAR_BOOL ("ns-touchpad-use-momentum", ns_touchpad_use_momentum, + doc: /*Non-nil means touchpad scrolling uses momentum. +This variable is ignored on macOS < 10.7 and GNUstep. Default is t. */); + ns_touchpad_use_momentum = YES; + /* TODO: move to common code */ DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars, doc: /* Which toolkit scroll bars Emacs uses, if any. diff --git a/src/termhooks.h b/src/termhooks.h index 97c128ba4e..b5171bf122 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -116,7 +116,9 @@ enum event_kind .frame_or_window gives the frame the wheel event occurred in. .timestamp gives a timestamp (in - milliseconds) for the event. */ + milliseconds) for the event. + .arg may contain the number of + lines to scroll. */ HORIZ_WHEEL_EVENT, /* A wheel event generated by a second horizontal wheel that is present on some mice. See WHEEL_EVENT. */ -- 2.14.1