]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve touch-screen support
authorPo Lu <luangruo@yahoo.com>
Sun, 16 Jul 2023 07:30:01 +0000 (15:30 +0800)
committerPo Lu <luangruo@yahoo.com>
Sun, 16 Jul 2023 07:30:01 +0000 (15:30 +0800)
* doc/emacs/emacs.texi (Top):
* doc/emacs/input.texi (Other Input Devices): Correctly
capitalize subsection name.
(Touchscreens): Document additional translation.
* doc/lispref/commands.texi (Touchscreen Events): Document that
`touchscreen-end' events now have prefix keys.  Also, describe
mouse emulation and `touchscreen-scroll' events.
* doc/lispref/keymaps.texi (Translation Keymaps): Document
`current-key-remap-sequence'.
* lisp/touch-screen.el (touch-screen-translate-prompt): New
function.
(touch-screen-scroll): New command.  Bind to
`touchscreen-scroll'.
(touch-screen-handle-point-update, touch-screen-handle-point-up)
(touch-screen-handle-touch): Refactor to actually translate
touch screen event sequences, as opposed to looking up commands
and executing them.
(touch-screen-translate-touch): New function.  Bind in
function-key-map to all touch screen events.
(touch-screen-drag-mode-line-1, touch-screen-drag-mode-line)
(touch-screen-tap-header-line): Remove special commands for
dragging the mode line and clicking on the header line.
* lisp/wid-edit.el (widget-button-click): Adjust accordingly.
* src/keyboard.c (access_keymap_keyremap): Bind
`current-key-remap-sequence' to the key sequence being remapped.
(keyremap_step): Give fkey->start and fkey->end to
access_keymap_keyremap.
(head_table): Add imaginary prefix to touchscreen-end events as
well.
(syms_of_keyboard): New variable Vcurrent_key_remap_sequence.

doc/emacs/emacs.texi
doc/emacs/input.texi
doc/lispref/commands.texi
doc/lispref/keymaps.texi
lisp/touch-screen.el
lisp/wid-edit.el
src/keyboard.c

index 92be9f9b9a9102ac8b5ea90bd7f67aef3d73b93e..b255e679d5f8ff3e6283864780fdb9152dc0235d 100644 (file)
@@ -1273,7 +1273,7 @@ Emacs and Android
 * Android Troubleshooting::     Dealing with problems.
 * Android Software::            Getting extra software.
 
-Emacs and unconventional input devices
+Emacs and Unconventional Input Devices
 
 * Touchscreens::        Using Emacs on touchscreens.
 * On-Screen Keyboards:: Using Emacs with virtual keyboards.
index 0df3162ce97fc3c2f676cfd5a37b6b9375757081..66554653def01b4373d39481b80907f252b5a35a 100644 (file)
@@ -2,7 +2,7 @@
 @c Copyright (C) 2023 Free Software Foundation, Inc.
 @c See file emacs.texi for copying conditions.
 @node Other Input Devices
-@appendix Emacs and unconventional input devices
+@appendix Emacs and Unconventional Input Devices
 @cindex other input devices
 
   Emacs was originally developed with the assumption that its users
@@ -21,7 +21,7 @@ which is detailed here.
 
 @node Touchscreens
 @section Using Emacs on touchscreens
-@cindex touchscreens
+@cindex touchscreen input
 
   Touchscreen input works by pressing and moving tools (which include
 fingers and some pointing devices--styluses, for example) onto a frame
@@ -40,6 +40,9 @@ executing any command bound to @code{mouse-1} at that location in the
 window.  If the tap happened on top of a link (@pxref{Mouse
 References}), then Emacs will follow the link instead.
 
+  If a command bound to @code{down-mouse-1} is bound to the location
+where the tap took place, Emacs will execute that command as well.
+
 @item
 @cindex scrolling, touchscreens
   ``Scrolling'', meaning to place a tool on the display and move it up
index 7a076406bed35a9636a0e8c92b3e4917412a4100..725ca900165883283d8772003428fe61db1b1cf3 100644 (file)
@@ -2013,10 +2013,7 @@ finger against the touchscreen.
 
 These events also have imaginary prefixes keys added by
 @code{read-key-sequence} when they originate on top of a special part
-of a frame or window.  @xref{Key Sequence Input}.  The reason the
-other touch screen events do not undergo this treatment is that they
-are rarely useful without being used in tandem from their
-corresponding @code{touchscreen-begin} events.
+of a frame or window.  @xref{Key Sequence Input}.
 
 @cindex @code{touchscreen-update} event
 @item (touchscreen-update @var{points})
@@ -2029,12 +2026,73 @@ up-to-date positions of each touch point currently on the touchscreen.
 This event is sent when @var{point} is no longer present on the
 display, because another program took the grab, or because the user
 raised the finger from the touchscreen.
+
+These events also have imaginary prefixes keys added by
+@code{read-key-sequence} when they originate on top of a special part
+of a frame or window.
 @end table
 
 If a touchpoint is pressed against the menu bar, then Emacs will not
 generate any corresponding @code{touchscreen-begin} or
 @code{touchscreen-end} events; instead, the menu bar may be displayed
-when @code{touchscreen-end} should have been delivered.
+after @code{touchscreen-end} would have been delivered under other
+circumstances.
+
+@cindex mouse emulation from touch screen events
+When no command is bound to @code{touchscreen-begin},
+@code{touchscreen-end} or @code{touchscreen-update}, Emacs calls a
+``key translation function'' (@pxref{Translation Keymaps}) to
+translate key sequences containing touch screen events into ordinary
+mouse events (@pxref{Mouse Events}.)  Since Emacs doesn't support
+distinguishing events originating from separate mouse devices, it
+assumes that only one touchpoint is active while translation takes
+place; breaking this assumption may lead to unexpected behavior.
+
+Emacs applies two different strategies for translating touch events
+into mouse events, contingent on factors such as the commands bound to
+keymaps that are active at the location of the
+@code{touchscreen-begin} event.  If a command is bound to
+@code{down-mouse-1} at that location, the initial translation consists
+of a single @code{down-mouse-1} event, with subsequent
+@code{touchscreen-update} events translated to mouse motion events
+(@pxref{Motion Events}), and a final @code{touchscreen-end} event
+translated to a @code{mouse-1} or @code{drag-mouse-1} event.  This is
+referred to ``simple translation'', and produces a simple
+correspondence between touchpoint motion and mouse motion.
+
+@cindex @code{ignored-mouse-command}, a symbol property
+However, some commands bound to
+@code{down-mouse-1}--@code{mouse-drag-region}, for example--either
+conflict with defined touch screen gestures (such as ``long-press to
+drag''), or with user expectations for touch input, and shouldn't
+subject the touch sequence to simple translation.  If a command whose
+name contains the property @code{ignored-mouse-command} is encountered
+or there is no command bound to @code{down-mouse-1}, a more irregular
+form of translation takes place: here, Emacs processes touch screen
+gestures (@pxref{Touchscreens,,, emacs, The GNU Emacs Manual}) first,
+and finally attempts to translate touch screen events into mouse
+events if no gesture was detected prior to a closing
+@code{touchscreen-end} event and a command is bound to @code{mouse-1}
+at the location of that event.  Before generating the @code{mouse-1}
+event, point is also set to the location of the @code{touchscreen-end}
+event, and the window containing the position of that event is
+selected, as a compromise for packages which assume
+@code{mouse-drag-region} has already set point to the location of any
+mouse click and selected the window where it took place.
+
+@cindex @code{touchscreen-scroll} event
+If a ``scrolling'' gesture is detected during the translation process,
+each subsequent @code{touchscreen-update} event is translated to a
+@code{touchscreen-scroll} event of the form:
+
+@example
+@w{@code{(touchscreen-scroll @var{window} @var{dx} @var{dy})}}
+@end example
+
+where @var{dx} and @var{dy} specify, in pixels, the relative motion of
+the tool from the position of the @code{touchscreen-begin} event that
+started the sequence or the last @code{touchscreen-scroll} event,
+whichever came later.
 
 @cindex handling touch screen events
 @cindex tap and drag, touch screen gestures
index 05dc17eb03f0e47f28f7e2405fb7321a9b7e79f2..e41dbf9def82f9f13b0cef84c9af9fd348b4574a 100644 (file)
@@ -2044,6 +2044,15 @@ to turn the character that follows into a Hyper character:
 @end group
 @end example
 
+@cindex accessing events within a key translation function
+@vindex current-key-remap-sequence
+A key translation function might want to adjust its behavior based on
+parameters to events within a key sequence containing non-key events
+(@pxref{Input Events}.)  This information is available from the
+variable @code{current-key-remap-sequence}, which is bound to the key
+sub-sequence being translated around calls to key translation
+functions.
+
 @subsection Interaction with normal keymaps
 
 The end of a key sequence is detected when that key sequence either is bound
index 242ea4fcd9bde3ae81ae0bf9fe6712b59bcb8d72..0f5842699318ca3267ec6fe6ded8a5d0fce0cfa8 100644 (file)
@@ -49,6 +49,11 @@ keyboard after a mouse command is executed in response to a
   "Timer used to track long-presses.
 This is always cleared upon any significant state change.")
 
+(defvar touch-screen-translate-prompt nil
+  "Prompt given to the touch screen translation function.
+If non-nil, the touch screen key event translation machinery
+is being called from `read-sequence' or some similar function.")
+
 (defcustom touch-screen-display-keyboard nil
   "If non-nil, always display the on screen keyboard.
 A buffer local value means to always display the on screen
@@ -70,6 +75,14 @@ See `pixel-scroll-precision-mode' for more details."
   :group 'mouse
   :version "30.1")
 
+\f
+
+;; Touch screen event translation.  The code here translates raw touch
+;; screen events into `touchscreen-scroll' events and mouse events in
+;; a ``DWIM'' fashion, consulting the keymaps at the position of the
+;; mouse event to determine the best course of action, while also
+;; recognizing drag-to-select and other gestures.
+
 (defun touch-screen-relative-xy (posn window)
   "Return the coordinates of POSN, a mouse position list.
 However, return the coordinates relative to WINDOW.
@@ -201,6 +214,26 @@ horizontal scrolling according to the movement in DX."
                  (setcar (nthcdr 8 touch-screen-current-tool) lines-hscrolled)
                  nil)))))
 
+(defun touch-screen-scroll (event)
+  "Scroll the window within EVENT, a `touchscreen-scroll' event.
+If `touch-screen-precision-scroll', scroll the window vertically
+by the number of pixels specified within that event.  Else,
+scroll the window by one line for every
+`window-default-line-height' pixels worth of movement.
+
+If EVENT also specifies horizontal motion and no significant
+amount of vertical scrolling has taken place, also scroll the
+window horizontally in conjunction with the number of pixels in
+the event."
+  (interactive "e")
+  (let ((window (nth 1 event))
+        (dx (nth 2 event))
+        (dy (nth 3 event)))
+    (with-selected-window window
+      (touch-screen-handle-scroll dx dy))))
+
+(global-set-key [touchscreen-scroll] #'touch-screen-scroll)
+
 (defun touch-screen-handle-timeout (arg)
   "Start the touch screen timeout or handle it depending on ARG.
 When ARG is nil, start the `touch-screen-current-timer' to go off
@@ -236,19 +269,30 @@ known position of the tool."
 
 (defun touch-screen-handle-point-update (point)
   "Notice that the touch point POINT has changed position.
+Perform the editing operations or throw to the input translation
+function with an input event tied to any gesture that is
+recognized.
+
 POINT must be the touch point currently being tracked as
 `touch-screen-current-tool'.
 
 If the fourth element of `touch-screen-current-tool' is nil, then
 the touch has just begun.  Determine how much POINT has moved.
 If POINT has moved upwards or downwards by a significant amount,
-then set the fourth element to `scroll'.  Then, call
-`touch-screen-handle-scroll' to scroll the display by that
-amount.
+then set the fourth element to `scroll'.  Then, generate a
+`touchscreen-scroll' event with the window that POINT was
+initially placed upon, and pixel deltas describing how much point
+has moved relative to its previous position in the X and Y axes.
+
+If the fourth element of `touchscreen-current-tool' is `scroll',
+then generate a `touchscreen-scroll' event with the window that
+qPOINT was initially placed upon, and pixel deltas describing how
+much point has moved relative to its previous position in the X
+and Y axes.
 
-If the fourth element of `touch-screen-current-tool' is `scroll',
-then scroll the display by how much POINT has moved in the Y
-axis.
+If the fourth element of `touch-screen-current-tool' is
+`mouse-drag' and `track-mouse' is non-nil, then generate a
+`mouse-movement' event with the position of POINT.
 
 If the fourth element of `touch-screen-current-tool' is `held',
 then the touch has been held down for some time.  If motion
@@ -275,8 +319,11 @@ then move point to the position of POINT."
                        'scroll)
                (setcar (nthcdr 2 touch-screen-current-tool)
                        relative-xy)
-               (with-selected-window window
-                 (touch-screen-handle-scroll diff-x diff-y))
+               ;; Generate a `touchscreen-scroll' event with `diff-x'
+               ;; and `diff-y'.
+               (throw 'input-event
+                      (list 'touchscreen-scroll
+                            window diff-x diff-y))
                ;; Cancel the touch screen long-press timer, if it is
                ;; still there by any chance.
                (when touch-screen-current-timer
@@ -301,8 +348,18 @@ then move point to the position of POINT."
              (setcar (nthcdr 2 touch-screen-current-tool)
                      relative-xy)
              (unless (and (zerop diff-x) (zerop diff-y))
-               (with-selected-window window
-                 (touch-screen-handle-scroll diff-x diff-y)))))
+               (throw 'input-event
+                      ;; Generate a `touchscreen-scroll' event with
+                      ;; `diff-x' and `diff-y'.
+                      (list 'touchscreen-scroll
+                            window diff-x diff-y)))))
+          ((eq what 'mouse-drag)
+           ;; There was a `down-mouse-1' event bound at the starting
+           ;; point of the event.  Generate a mouse-motion event if
+           ;; mouse movement is being tracked.
+           (when track-mouse
+             (throw 'input-event (list 'mouse-movement
+                                       (cdr point)))))
           ((eq what 'held)
            (let* ((posn (cdr point))
                   (relative-xy
@@ -319,7 +376,6 @@ then move point to the position of POINT."
                ;; Activate the mark.  It should have been set by the
                ;; time `touch-screen-timeout' was called.
                (activate-mark)
-
                ;; Figure out what character to go to.  If this posn is
                ;; in the window, go to (posn-point posn).  If not,
                ;; then go to the line before either window start or
@@ -385,127 +441,357 @@ in response to the minibuffer being closed."
         (cancel-timer minibuffer-on-screen-keyboard-timer)
         (setq minibuffer-on-screen-keyboard-timer nil)))))
 
-(defun touch-screen-handle-point-up (point)
+(defun touch-screen-handle-point-up (point prefix)
   "Notice that POINT has been removed from the screen.
 POINT should be the point currently tracked as
 `touch-screen-current-tool'.
+PREFIX should be a virtual function key used to look up key
+bindings.
+
+If the fourth element of `touch-screen-current-tool' is nil, move
+point to the position of POINT, selecting the window under POINT
+as well, and deactivate the mark; if there is a button or link at
+POINT, call the command bound to `mouse-2' there.  Otherwise,
+call the command bound to `mouse-1'.
 
-If the fourth argument of `touch-screen-current-tool' is nil,
-move point to the position of POINT, selecting the window under
-POINT as well, and deactivate the mark; if there is a button or
-link at POINT, call the command bound to `mouse-2' there.
-Otherwise, call the command bound to `mouse-1'.
+If the fourth element of `touch-screen-current-tool' is
+`mouse-drag', then generate either a `mouse-1' or a
+`drag-mouse-1' event depending on how far the position of POINT
+is from the starting point of the touch.
 
 If the command being executed is listed in
 `touch-screen-set-point-commands' also display the on-screen
 keyboard if the current buffer and the character at the new point
 is not read-only."
-  (let ((what (nth 3 touch-screen-current-tool)))
+  (let ((what (nth 3 touch-screen-current-tool))
+        (posn (cdr point)) window point)
     (cond ((null what)
-           (when (windowp (posn-window (cdr point)))
+           (when (windowp (posn-window posn))
+             (setq point (posn-point point)
+                   window (posn-window posn))
              ;; Select the window that was tapped.
-             (select-window (posn-window (cdr point)))
+             (select-window window)
              ;; Now simulate a mouse click there.  If there is a link
              ;; or a button, use mouse-2 to push it.
-             (let ((event (list (if (or (mouse-on-link-p (cdr point))
-                                        (button-at (posn-point (cdr point))))
-                                    'mouse-2
-                                  'mouse-1)
-                                (cdr point)))
-                   ;; Look for an extra keymap to look in.
-                   (keymap (and (posn-object (cdr point))
-                                (stringp
-                                 (posn-object (cdr point)))
-                                (get-text-property
-                                 0 'keymap
-                                 (posn-object (cdr point)))))
-                   command)
-               (save-excursion
-                 (when (posn-point (cdr point))
-                   (goto-char (posn-point (cdr point))))
-                 (if keymap
-                     (setq keymap (cons keymap (current-active-maps t)))
-                   (setq keymap (current-active-maps t)))
-                 (setq command (lookup-key keymap (vector (car event)))))
+             (let* ((event (list (if (or (mouse-on-link-p posn)
+                                         (and point (button-at point)))
+                                     'mouse-2
+                                   'mouse-1)
+                                 posn))
+                    ;; Look for the command bound to this event.
+                    (command (key-binding (if prefix
+                                                 (vector prefix
+                                                         (car event))
+                                            (vector (car event)))
+                                          t nil posn)))
                (deactivate-mark)
-               ;; This is necessary for following links.
-               (goto-char (posn-point (cdr point)))
+               (when point
+                 ;; This is necessary for following links.
+                 (goto-char point))
                ;; Figure out if the on screen keyboard needs to be
                ;; displayed.
                (when command
-                 (call-interactively command nil
-                                     (vector event))
-                 (when (memq command touch-screen-set-point-commands)
-                   (if (and (or (not buffer-read-only)
-                                touch-screen-display-keyboard)
-                            ;; Detect the splash screen and avoid
-                            ;; displaying the on screen keyboard
-                            ;; there.
-                            (not (equal (buffer-name) "*GNU Emacs*")))
-                       ;; Once the on-screen keyboard has been opened,
-                       ;; add `touch-screen-window-selection-changed'
-                       ;; as a window selection change function This
-                       ;; allows the on screen keyboard to be hidden
-                       ;; if the selected window's point becomes read
-                       ;; only at some point in the future.
-                       (progn
-                         (add-hook 'window-selection-change-functions
-                                   #'touch-screen-window-selection-changed)
-                         (frame-toggle-on-screen-keyboard (selected-frame) nil))
-                     ;; Otherwise, hide the on screen keyboard now.
-                     (frame-toggle-on-screen-keyboard (selected-frame) t))))))))))
-
-(defun touch-screen-handle-touch (event)
+                 (if (memq command touch-screen-set-point-commands)
+                     (if touch-screen-translate-prompt
+                         ;; When a `mouse-set-point' command is
+                         ;; encountered and
+                         ;; `touch-screen-handle-touch' is being
+                         ;; called from the keyboard command loop,
+                         ;; call it immediately so that point is set
+                         ;; prior to the on screen keyboard being
+                         ;; displayed.
+                         (call-interactively command nil
+                                             (vector event))
+                       (if (and (or (not buffer-read-only)
+                                    touch-screen-display-keyboard)
+                                ;; Detect the splash screen and avoid
+                                ;; displaying the on screen keyboard
+                                ;; there.
+                                (not (equal (buffer-name) "*GNU Emacs*")))
+                           ;; Once the on-screen keyboard has been
+                           ;; opened, add
+                           ;; `touch-screen-window-selection-changed'
+                           ;; as a window selection change function
+                           ;; This allows the on screen keyboard to be
+                           ;; hidden if the selected window's point
+                           ;; becomes read only at some point in the
+                           ;; future.
+                           (progn
+                             (add-hook 'window-selection-change-functions
+                                       #'touch-screen-window-selection-changed)
+                             (frame-toggle-on-screen-keyboard (selected-frame)
+                                                              nil))
+                         ;; Otherwise, hide the on screen keyboard
+                         ;; now.
+                         (frame-toggle-on-screen-keyboard (selected-frame) t))
+                       ;; But if it's being called from `describe-key'
+                       ;; or some such, return it as a key sequence.
+                       (throw 'input-event event)))
+                 ;; If not, return the event.
+                 (throw 'input-event event)))))
+          ((eq what 'mouse-drag)
+           ;; Generate a corresponding `mouse-1' event.
+           (let* ((new-window (posn-window posn))
+                  (new-point (posn-point posn))
+                  (old-posn (nth 4 touch-screen-current-tool))
+                  (old-window (posn-window posn))
+                  (old-point (posn-point posn)))
+             (throw 'input-event
+                    ;; If the position of the touch point hasn't
+                    ;; changed, or it doesn't start or end on a
+                    ;; window...
+                    (if (and (eq new-window old-window)
+                             (eq new-point old-point)
+                             (windowp new-window)
+                             (windowp old-window))
+                        ;; ... generate a mouse-1 event...
+                        (list 'mouse-1 posn)
+                      ;; ... otherwise, generate a drag-mouse-1 event.
+                      (list 'drag-mouse-1 (cons old-window
+                                                old-posn)
+                            (cons new-window posn)))))))))
+
+(defun touch-screen-handle-touch (event prefix &optional interactive)
   "Handle a single touch EVENT, and perform associated actions.
-EVENT can either be a touchscreen-begin, touchscreen-update or
-touchscreen-end event."
-  (interactive "e")
-  (cond
-   ((eq (car event) 'touchscreen-begin)
-    ;; A tool was just pressed against the screen.  Figure out the
-    ;; window where it is and make it the tool being tracked on the
-    ;; window.
-    (let ((touchpoint (caadr event))
-          (position (cdadr event)))
-      ;; Cancel the touch screen timer, if it is still there by any
-      ;; chance.
-      (when touch-screen-current-timer
-        (cancel-timer touch-screen-current-timer)
-        (setq touch-screen-current-timer nil))
-      ;; Replace any previously ongoing gesture.  If POSITION has no
-      ;; window or position, make it nil instead.
-      (setq touch-screen-current-tool (and (windowp (posn-window position))
-                                           (posn-point position)
-                                           (list touchpoint
-                                                 (posn-window position)
-                                                 (posn-x-y position)
-                                                 nil position nil nil
-                                                 nil nil)))
-      ;; Start the long-press timer.
-      (touch-screen-handle-timeout nil)))
-   ((eq (car event) 'touchscreen-update)
-    ;; The positions of tools currently pressed against the screen
-    ;; have changed.  If there is a tool being tracked as part of a
-    ;; gesture, look it up in the list of tools.
-    (let ((new-point (assq (car touch-screen-current-tool)
-                           (cadr event))))
-      (when new-point
-        (touch-screen-handle-point-update new-point))))
-   ((eq (car event) 'touchscreen-end)
-    ;; A tool has been removed from the screen.  If it is the tool
-    ;; currently being tracked, clear `touch-screen-current-tool'.
-    (when (eq (caadr event) (car touch-screen-current-tool))
-      ;; Cancel the touch screen long-press timer, if it is still there
-      ;; by any chance.
-      (when touch-screen-current-timer
-        (cancel-timer touch-screen-current-timer)
-        (setq touch-screen-current-timer nil))
-      (touch-screen-handle-point-up (cadr event))
-      (setq touch-screen-current-tool nil)))))
-
-(define-key global-map [touchscreen-begin] #'touch-screen-handle-touch)
-(define-key global-map [touchscreen-update] #'touch-screen-handle-touch)
-(define-key global-map [touchscreen-end] #'touch-screen-handle-touch)
+EVENT can either be a `touchscreen-begin', `touchscreen-update' or
+`touchscreen-end' event.
+PREFIX is either nil, or a symbol specifying a virtual function
+key to apply to EVENT.
+
+If INTERACTIVE, execute the command associated with any event
+generated instead of throwing `input-event'.  Otherwise, throw
+`input-event' with a single input event if that event should take
+the place of EVENT within the key sequence being translated, or
+`nil' if all tools have been released."
+  (interactive "e\ni\np")
+  (if interactive
+      ;; Called interactively (probably from wid-edit.el.)
+      ;; Add any event generated to `unread-command-events'.
+      (let ((event (catch 'input-event
+                     (touch-screen-handle-touch event prefix) nil)))
+        (when event
+          (setq unread-command-events
+                (nconc unread-command-events
+                       (list event)))))
+    (cond
+     ((eq (car event) 'touchscreen-begin)
+      ;; A tool was just pressed against the screen.  Figure out the
+      ;; window where it is and make it the tool being tracked on the
+      ;; window.
+      (let* ((touchpoint (caadr event))
+             (position (cdadr event))
+             (window (posn-window position))
+             (point (posn-point position)))
+        ;; Cancel the touch screen timer, if it is still there by any
+        ;; chance.
+        (when touch-screen-current-timer
+          (cancel-timer touch-screen-current-timer)
+          (setq touch-screen-current-timer nil))
+        ;; Replace any previously ongoing gesture.  If POSITION has no
+        ;; window or position, make it nil instead.
+        (setq touch-screen-current-tool (and (windowp window)
+                                             (list touchpoint window
+                                                   (posn-x-y position)
+                                                   nil position
+                                                   nil nil nil nil)))
+        ;; Determine if there is a command bound to `down-mouse-1' at
+        ;; the position of the tap and that command is not a command
+        ;; whose functionality is replaced by the long-press mechanism.
+        ;; If so, set the fourth element of `touch-screen-current-tool'
+        ;; to `mouse-drag' and generate an emulated `mouse-1' event.
+        (if (and touch-screen-current-tool
+                 (with-selected-window window
+                   (let ((binding (key-binding (if prefix
+                                                   (vector prefix
+                                                           'down-mouse-1)
+                                                 [down-mouse-1])
+                                               t nil position)))
+                     (and binding
+                          (not (and (symbolp binding)
+                                    (get binding 'ignored-mouse-command)))))))
+            (progn (setcar (nthcdr 3 touch-screen-current-tool)
+                           'mouse-drag)
+                   (throw 'input-event (list 'down-mouse-1 position)))
+          (and point
+               ;; Start the long-press timer.
+               (touch-screen-handle-timeout nil)))))
+     ((eq (car event) 'touchscreen-update)
+      ;; The positions of tools currently pressed against the screen
+      ;; have changed.  If there is a tool being tracked as part of a
+      ;; gesture, look it up in the list of tools.
+      (let ((new-point (assq (car touch-screen-current-tool)
+                             (cadr event))))
+        (when new-point
+          (touch-screen-handle-point-update new-point))))
+     ((eq (car event) 'touchscreen-end)
+      ;; A tool has been removed from the screen.  If it is the tool
+      ;; currently being tracked, clear `touch-screen-current-tool'.
+      (when (eq (caadr event) (car touch-screen-current-tool))
+        ;; Cancel the touch screen long-press timer, if it is still there
+        ;; by any chance.
+        (when touch-screen-current-timer
+          (cancel-timer touch-screen-current-timer)
+          (setq touch-screen-current-timer nil))
+        (unwind-protect
+            (touch-screen-handle-point-up (cadr event) prefix)
+          ;; Make sure the tool list is cleared even if
+          ;; `touch-screen-handle-point-up' throws.
+          (setq touch-screen-current-tool nil)))
+      ;; Throw to the key translation function.
+      (throw 'input-event nil)))))
+
+;; Mark `mouse-drag-region' as ignored for the purposes of mouse click
+;; emulation.
+
+(put 'mouse-drag-region 'ignored-mouse-command t)
+
+(defun touch-screen-translate-touch (prompt)
+  "Translate touch screen events into a sequence of mouse events.
+PROMPT is the prompt string given to `read-key-sequence', or nil
+if this function is being called from the keyboard command loop.
+Value is a new key sequence.
+
+Read the touch screen event within `current-key-remap-sequence'
+and give it to `touch-screen-handle-touch'.  Return any key
+sequence signaled.
+
+If `touch-screen-handle-touch' does not signal for an event to be
+returned after the last element of the key sequence is read,
+continue reading touch screen events until
+`touch-screen-handle-touch' signals.  Return a sequence
+consisting of the first event encountered that is not a touch
+screen event.
+
+In addition to non-touchscreen events read, key sequences
+returned may contain any one of the following events:
+
+  (touchscreen-scroll WINDOW DX DY)
+
+where WINDOW specifies a window to scroll, and DX and DY are
+integers describing how many pixels to be scrolled horizontally
+and vertically.
+
+  (down-mouse-1 POSN)
+  (drag-mouse-1 POSN)
+
+where POSN is the position of the mouse button press or click.
+
+  (mouse-1 POSN)
+  (mouse-2 POSN)
+
+where POSN is the position of the mouse click, either `mouse-2'
+if POSN is on a link or a button, or `mouse-1' otherwise."
+  (if (> (length current-key-remap-sequence) 0)
+      ;; Save the virtual function key if this is a mode line event.
+      (let* ((prefix (and (> (length current-key-remap-sequence) 1)
+                          (aref current-key-remap-sequence 0)))
+             (touch-screen-translate-prompt prompt)
+             (event (catch 'input-event
+                      ;; First, process the one event already within
+                      ;; `current-key-remap-sequence'.
+                      (touch-screen-handle-touch
+                       (aref current-key-remap-sequence
+                             (if prefix 1 0))
+                       prefix)
+                      ;; Next, continue reading input events.
+                      (while t
+                        (let ((event1 (read-event)))
+                          ;; If event1 is a virtual function key, make
+                          ;; it the new prefix.
+                          (if (memq event1 '(mode-line tab-line
+                                             header-line tool-bar tab-bar
+                                             left-fringe right-fringe
+                                             left-margin right-margin
+                                             right-divider bottom-divider))
+                              (setq prefix event1)
+                            ;; If event1 is not a touch screen event, return
+                            ;; it.
+                            (if (not (memq (car-safe event1)
+                                           '(touchscreen-begin
+                                             touchscreen-end
+                                             touchscreen-update)))
+                                (throw 'input-event event1)
+                              ;; Process this event as well.
+                              (touch-screen-handle-touch event1 prefix))))))))
+        ;; Return a key sequence consisting of event
+        ;; or an empty vector if it is nil, meaning that
+        ;; no key events have been translated.
+        (if event (or (and prefix (consp event)
+                           ;; If this is a mode line event, then generate
+                           ;; the appropriate function key.
+                           (vector prefix event))
+                      (vector event))
+          ""))))
+
+(define-key function-key-map [touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [mode-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [mode-line touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [mode-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [header-line touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [header-line touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [header-line touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [bottom-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [bottom-divider touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [bottom-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-divider touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-divider touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [left-fringe touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-fringe touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-fringe touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-fringe touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-fringe touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-fringe touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [left-margin touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-margin touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [left-margin touchscreen-end]
+            #'touch-screen-translate-touch)
+
+(define-key function-key-map [right-margin touchscreen-begin]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-margin touchscreen-update]
+            #'touch-screen-translate-touch)
+(define-key function-key-map [right-margin touchscreen-end]
+            #'touch-screen-translate-touch)
 
 \f
 ;; Exports.  These functions are intended for use externally.
@@ -582,149 +868,6 @@ touch point in EVENT did not move significantly, and t otherwise."
 
 \f
 
-;; Modeline dragging.
-
-(defun touch-screen-drag-mode-line-1 (event)
-  "Internal helper for `touch-screen-drag-mode-line'.
-This is called when that function determines that no drag really
-happened.  EVENT is the same as in `touch-screen-drag-mode-line'."
-  ;; If there is an object at EVENT, then look either a keymap bound
-  ;; to [down-mouse-1] or a command bound to [mouse-1].  Then, if a
-  ;; keymap was found, pop it up as a menu.  Otherwise, wait for a tap
-  ;; to complete and run the command found.
-  ;; Also, select the window in EVENT.
-  (select-window (posn-window (cdadr event)))
-  (let* ((object (posn-object (cdadr event)))
-         (object-keymap (and (consp object)
-                             (stringp (car object))
-                             (or (get-text-property (cdr object)
-                                                    'keymap
-                                                    (car object))
-                                 (get-text-property (cdr object)
-                                                    'local-map
-                                                    (car object)))))
-         (keymap (lookup-key object-keymap [mode-line down-mouse-1]))
-         (command (or (lookup-key object-keymap [mode-line mouse-1])
-                      keymap)))
-    (when (or (keymapp keymap) command)
-      (if (keymapp keymap)
-          (when-let* ((command (x-popup-menu event keymap))
-                      (tem (lookup-key keymap
-                                       (if (consp command)
-                                           (apply #'vector command)
-                                         (vector command))
-                                       t)))
-            (call-interactively tem))
-        (when (commandp command)
-          (call-interactively command nil
-                              (vector (list 'mouse-1 (cdadr event)))))))))
-
-(defun touch-screen-drag-mode-line (event)
-  "Begin dragging the mode line in response to a touch EVENT.
-Change the height of the window based on where the touch point in
-EVENT moves.
-
-If it does not actually move anywhere and the touch point is
-removed, and EVENT lies on top of text with a mouse command
-bound, run that command instead."
-  (interactive "e")
-  ;; Find the window that should be dragged and the starting position.
-  (let* ((window (posn-window (cdadr event)))
-         (relative-xy (touch-screen-relative-xy (cdadr event)
-                                                'frame))
-         (last-position (cdr relative-xy)))
-    (when (window-resizable window 0)
-      (when (eq
-             (touch-screen-track-drag
-              event (lambda (new-event &optional _data)
-                      ;; Find the position of the touchpoint in
-                      ;; NEW-EVENT.
-                      (let* ((touchpoint (assq (caadr event)
-                                               (cadr new-event)))
-                             (new-relative-xy
-                              (touch-screen-relative-xy (cdr touchpoint) 'frame))
-                             (position (cdr new-relative-xy))
-                             (window-resize-pixelwise t)
-                             growth)
-                        ;; Now set the new height of the window.  If
-                        ;; new-relative-y is above relative-xy, then
-                        ;; make the window that much shorter.
-                        ;; Otherwise, make it bigger.
-                        (unless (or (zerop (setq growth
-                                                 (- position last-position)))
-                                    (and (> growth 0)
-                                         (< position
-                                            (+ (window-pixel-top window)
-                                               (window-pixel-height window))))
-                                    (and (< growth 0)
-                                         (> position
-                                            (+ (window-pixel-top window)
-                                               (window-pixel-height window)))))
-                          (when (ignore-errors
-                                  (adjust-window-trailing-edge window growth nil t) t)
-                            (setq last-position position))))))
-             'no-drag)
-        ;; Dragging did not actually happen, so try to run any command
-        ;; necessary.
-        (touch-screen-drag-mode-line-1 event)))))
-
-(global-set-key [mode-line touchscreen-begin]
-                #'touch-screen-drag-mode-line)
-(global-set-key [bottom-divider touchscreen-begin]
-                #'touch-screen-drag-mode-line)
-
-\f
-
-;; Header line tapping.
-
-(defun touch-screen-tap-header-line (event)
-  "Handle a `touchscreen-begin' EVENT on the header line.
-Wait for the tap to complete, then run any command bound to
-`mouse-1' at the position of EVENT.
-
-If another keymap is bound to `down-mouse-1', then display a menu
-with its contents instead, and run the selected command."
-  (interactive "e")
-  (let* ((posn (cdadr event))
-         (object (posn-object posn))
-         ;; Look for the keymap defined by the object itself.
-         (object-keymap (and (consp object)
-                             (stringp (car object))
-                             (or (get-text-property (cdr object)
-                                                    'keymap
-                                                    (car object))
-                                 (get-text-property (cdr object)
-                                                    'local-map
-                                                    (car object)))))
-         command keymap)
-    ;; Now look for either a command bound to `mouse-1' or a keymap
-    ;; bound to `down-mouse-1'.
-    (with-selected-window (posn-window posn)
-      (setq command (lookup-key object-keymap
-                               [header-line mouse-1] t)
-            keymap (lookup-key object-keymap
-                              [header-line down-mouse-1] t))
-      (unless (keymapp keymap)
-       (setq keymap nil)))
-    ;; Wait for the tap to complete.
-    (when (touch-screen-track-tap event)
-      ;; Select the window whose header line was clicked.
-      (with-selected-window (posn-window posn)
-        (if keymap
-            (when-let* ((command (x-popup-menu event keymap))
-                        (tem (lookup-key keymap
-                                         (if (consp command)
-                                             (apply #'vector command)
-                                           (vector command))
-                                         t)))
-              (call-interactively tem))
-          (when (commandp command)
-            (call-interactively command nil
-                                (vector (list 'mouse-1 (cdadr event))))))))))
-
-(global-set-key [header-line touchscreen-begin]
-                #'touch-screen-tap-header-line)
-
 (provide 'touch-screen)
 
 ;;; touch-screen ends here
index 4df1fb7ab08c028c7e32953252de29c61622b229..fa801cab51b9a00285c2403a95f670789b7fe82a 100644 (file)
@@ -1193,8 +1193,7 @@ If nothing was called, return non-nil."
            ;; up event.
             (cond
              ((eq (car event) 'touchscreen-begin)
-              (setq command (lookup-key widget-global-map
-                                        [touchscreen-begin])))
+              (setq command 'touch-screen-handle-touch))
              (mouse-1 (cond ((setq command     ;down event
                                    (lookup-key widget-global-map [down-mouse-1]))
                              (setq up nil))
@@ -1213,6 +1212,11 @@ If nothing was called, return non-nil."
              (call-interactively command)))))
     (message "You clicked somewhere weird.")))
 
+;; Make sure `touch-screen-handle-touch' abstains from emulating
+;; down-mouse-1 events for `widget-button-click'.
+
+(put 'widget-button-click 'ignored-mouse-command t)
+
 (defun widget-button-press (pos &optional event)
   "Invoke button at POS."
   (interactive "@d")
index ea07c538aa220396d4bccbfd09f04cb59bff2edd..e10128def13ff9f9e03a6af906bb2d8e4757d7e3 100644 (file)
@@ -9994,13 +9994,18 @@ typedef struct keyremap
    If the mapping is a function and DO_FUNCALL is true,
    the function is called with PROMPT as parameter and its return
    value is used as the return value of this function (after checking
-   that it is indeed a vector).  */
+   that it is indeed a vector).
+
+   START and END are the indices of the first and last key of the
+   sequence being remapped within the keyboard buffer KEYBUF.  */
 
 static Lisp_Object
 access_keymap_keyremap (Lisp_Object map, Lisp_Object key, Lisp_Object prompt,
-                       bool do_funcall)
+                       bool do_funcall, ptrdiff_t start, ptrdiff_t end,
+                       Lisp_Object *keybuf)
 {
   Lisp_Object next;
+  specpdl_ref count;
 
   next = access_keymap (map, key, 1, 0, 1);
 
@@ -10016,10 +10021,18 @@ access_keymap_keyremap (Lisp_Object map, Lisp_Object key, Lisp_Object prompt,
      its value instead.  */
   if (do_funcall && FUNCTIONP (next))
     {
-      Lisp_Object tem;
+      Lisp_Object tem, remap;
       tem = next;
 
-      next = call1 (next, prompt);
+      /* Build Vcurrent_key_remap_sequence.  */
+      remap = Fvector (end - start + 1, keybuf + start);
+
+      /* Bind `current-key-remap-sequence' to the key sequence being
+        remapped.  */
+      count = SPECPDL_INDEX ();
+      specbind (Qcurrent_key_remap_sequence, remap);
+      next = unbind_to (count, call1 (next, prompt));
+
       /* If the function returned something invalid,
         barf--don't ignore it.  */
       if (! (NILP (next) || VECTORP (next) || STRINGP (next)))
@@ -10044,11 +10057,17 @@ keyremap_step (Lisp_Object *keybuf, volatile keyremap *fkey,
               int input, bool doit, int *diff, Lisp_Object prompt)
 {
   Lisp_Object next, key;
+  ptrdiff_t buf_start, buf_end;
+
+  /* Save the key sequence being translated.  */
+  buf_start = fkey->start;
+  buf_end = fkey->end;
 
   key = keybuf[fkey->end++];
 
   if (KEYMAPP (fkey->parent))
-    next = access_keymap_keyremap (fkey->map, key, prompt, doit);
+    next = access_keymap_keyremap (fkey->map, key, prompt, doit,
+                                  buf_start, buf_end, keybuf);
   else
     next = Qnil;
 
@@ -12479,6 +12498,7 @@ static const struct event_head head_table[] = {
   {SYMBOL_INDEX (Qselect_window),       SYMBOL_INDEX (Qswitch_frame)},
   /* Touchscreen events should be prefixed by the posn.  */
   {SYMBOL_INDEX (Qtouchscreen_begin),  SYMBOL_INDEX (Qtouchscreen)},
+  {SYMBOL_INDEX (Qtouchscreen_end),    SYMBOL_INDEX (Qtouchscreen)},
 };
 
 static Lisp_Object
@@ -13575,6 +13595,15 @@ If non-nil, text conversion will continue to happen after a prefix
 key has been read inside `read-key-sequence'.  */);
   disable_inhibit_text_conversion = false;
 
+  DEFVAR_LISP ("current-key-remap-sequence",
+              Vcurrent_key_remap_sequence,
+    doc: /* The key sequence currently being remap, or nil.
+Bound to a vector containing the sub-sequence matching a binding
+within `input-decode-map' or `local-function-key-map' when its bound
+function is called to remap that sequence.  */);
+  Vcurrent_key_remap_sequence = Qnil;
+  DEFSYM (Qcurrent_key_remap_sequence, "current-key-remap-sequence");
+
   pdumper_do_now_and_after_load (syms_of_keyboard_for_pdumper);
 }