From 9e7d219543b2dd424727125910ec711f1aa6cb55 Mon Sep 17 00:00:00 2001 From: Cecilio Pardo Date: Mon, 2 Dec 2024 17:30:42 +0100 Subject: [PATCH] Send events for key presses and key releases. Detect double/triple taps. * doc/lispref/commands.texi: Added information about new event. * src/gtkutil.c (xg_create_frame_widgets): Modified to handle key release events. (xg_maybe_send_low_level_key_event): New function that sends key events. (xg_widget_key_press_event_cb): Modified to send low level key events. (xg_widget_key_release_event_cb): New function to send low level key events on key release. * src/keyboard.c (kbd_buffer_get_event): Modified to handle low level keyboard events. (make_lispy_event): Modified to handle low level keyboard events. (kbd_low_level_key_is_enabled): New function to decide if a particular key should generate events, looking at configuration. (syms_of_keyboard): Added symbols and varialbles. (keys_of_keyboard): Modified to map low level key events to 'special-event-map'. * src/keyboard.h: Modified with function prototype. * src/pgtkterm.c (pgtk_maybe_send_low_level_key_event): New function that sends key events. (key_press_event): Modified to handle low level keyboard events. (key_release_event): Modified to handle low level keyboard events. * src/termhooks.h: Modified to define constant. * src/w32fns.c (w32_wnd_proc): Modified to generate low level key events. * src/w32term.c (w32_read_socket): Modified to generate low level key events. * src/w32term.h: Modified to define constants. * src/xterm.c (x_get_modifier_for_keycode): New function to decide which (if any) modifier corresponds to a given key. (x_maybe_send_low_level_key_event): New function that sends key events. (handle_one_xevent): Modified to handle low level key events. (syms_of_xterm): Modified to define symbol. * src/xterm.h: Modified with function prototype * lisp/low-level-key.el (New file). (llk-bindings): User bindings for low level key events. (llk-tap-count): User option. (llk-tap-timeout): User option. (llk-tap-keys): User option. (llk-keysyms): List of available keysyms. (define-xk): Macro for defining keysyms. (llk-define-keysyms): Build llk-keysyms. (llk-init): Function to initialize low level key handling. (event-is-key-press): Get field from event. (event-keysym): Get field from event. (event-modifier): Get field from event. (event-time): Get field from event. (llk-bind): Function to create a binding. (llk-events): Event history for tap detection. (llk-detect-n-tap): Function to detect taps. (describe-low-level-key): Command to get information about a key. (llk-show-event-description): Show help buffer with information about an event. (llk-handle): Handler for key events. --- doc/lispref/commands.texi | 32 ++++ lisp/low-level-key.el | 342 ++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 81 +++++++++ src/keyboard.c | 65 ++++++++ src/keyboard.h | 1 + src/pgtkterm.c | 55 ++++++ src/termhooks.h | 1 + src/w32fns.c | 11 ++ src/w32term.c | 49 ++++++ src/w32term.h | 3 +- src/xterm.c | 142 ++++++++++++++++ src/xterm.h | 2 + 12 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index df8266a7157..88dd6d4baa0 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2658,6 +2658,38 @@ Misc Events respectively. They are usually interpreted as being relative to the size of the object beneath the gesture: image, window, etc. +@cindex @code{low-level-key} event +@item (low-level-key @var{is-key-press} @var{key} @var{modifier} @var{time} @var{frame}) +This event is sent on the physical press or release of keys, only on GUI +systems, currently X, MS-Windows and PGTK, and only if the variable +@code{enable-low-level-key-events} has a non-@code{nil} value. See its +documentation for the values it can take, that allow to filter which +keys should generate this kind of event. + +@var{is-key-press} is @code{t} for a key press, @code{nil} for a key release. +@var{time} is the event's time in milliseconds, @var{frame} is the +frame receiving it. @var{modifier} is @code{nil} if the key is not a +modifier key, @code{t} if it is, but it is unknown which one, or one of +@code{shift}, @code{control}, @code{meta}, @code{alt}, @code{super}, +@code{hyper}. + +@var{key}, an integer, identifies the key pressed or released. This +number is platform dependent, but there are variables for most keys that +can be used in place of the numbers to identify them. For example, the +variable @code{xk-backspace} identifies the @key{backspace} key. + +The names are parallel to those for KeySyms on X, as defined in +@file{xkeysymdef.h}. For example, @code{XK_Shift_L} (the left shift +key), corresponds to @code{xk-shift-l}. The @file{xkeysymdef.h} file +defines different KeySyms for capital and small versions of letters. +For this event, only the capital version is used, with the variables +@code{xk-a}, @code{xk-b}, etc. + +These variables are initialized by calling the function @code{llk-init}. +This function also binds a handler to this event, which allows to detect +double taps on keys (normally modifier keys) and bind commands or +functions to be run. See the function @code{llk-bind}. + @cindex @code{preedit-text} event @item (preedit-text @var{arg}) This event is sent when a system input method tells Emacs to display diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..bc9311f82d0 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,342 @@ +;;; -*- lexical-binding: t -*- + +;; The physical-key event is like this: +;; (low-level-key IS-KEY-PRESS KEY MODIFIER TIME FRAME) +;; IS-KEY-PRESS is t if the key has been pressed, nil if it has been released. +;; KEY is the keysym number. +;; MODIFIER is the modifier associated with this key. It is nil if the key is +;; not a modifier. It can be one of the following symbols: shift, control, meta, +;; super, hyper, alt. It can also be t if the key is a modifier but it can't be +;; identified, as in the PGTK backend. +;; TIME is the timestamp in milliseconds of the event. +;; FRAME is the frame where the event happened. +;; +;; After calling 'llk-init' and setting a non-nil value for +;; 'enable-low-level-key-events', events begin to be handled by 'llk-handler', +;; which tries to detect n-taps and calls the corresponding function. +;; +;; To implement other functionalities, you can replace llk-handler with +;; your own function. + +(require 'cl-lib) + +;; User options + +(defvar llk-bindings nil + "List of bindings for low level key events (press/release/tap). + +Use the `llk-bind' function to add bindings. See its documentation for +a description of the binding information.") + +(defvar llk-tap-count 2 + "Number or key press/releases to consider a tap.") + +(defvar llk-tap-timeout 1000 + "Time (ms) between consecutive key presses/releases to consider a tap.") + +(defvar llk-tap-keys + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta) + "Keys that can generate taps.") + +(defvar llk-keysyms nil + "List of keysym numbers and their corresponding symbols. + +Each element has the form (KEYSYM . SYMBOL). The variable value for +each symbol is the keysym. This list is initialized by `llk-init'.") + +(defvar llk-describe-next-press nil + "Internal variable to mark that next key press should be described.") + +(defmacro define-xk (name x-keysym w32-keysym) + "Internal macro to define keysyms." + `(let ((ksym (pcase (window-system) + ('pgtk ,x-keysym) + ('x ,x-keysym) + ('w32 ,w32-keysym)))) + (defconst ,name ksym "Constant for a keysym value.") + (push (cons ksym ',name) llk-keysyms))) + +(defun llk-define-keysyms () + "Initialize the keysym list, `llk-keysyms'. + +Called from `llk-init'." + (setq llk-keysyms nil) + + ;; tty keys + (define-xk xk-backspace #xff08 #x08) ;; XK_BackSpace VK_BACK + (define-xk xk-tab #xff09 #x09) ;; XK_Tab VK_TAB + (define-xk xk-clear #xff0b #x0C) ;; XK_Clear VK_CLEAR + (define-xk xk-return #xff0d #x0D) ;; XK_Return VK_RETURN + (define-xk xk-pause #xff13 #x13) ;; XK_Pause VK_PAUSE + (define-xk xk-scroll-lock #xff14 #x91) ;; XK_Scroll_Lock VK_SCROLL + (define-xk xk-escape #xff1B #x1B) ;; XK_Escape VK_ESCAPE + (define-xk xk-delete #xffff #x2E) ;; XK_Delete VK_DELETE + + ;; Cursor control and motion + (define-xk xk-home #xff50 #x24) ;; XK_Home VK_HOME + (define-xk xk-left #xff51 #x25) ;; XK_Left VK_LEFT + (define-xk xk-up #xff52 #x26) ;; XK_Up VK_UP + (define-xk xk-right #xff53 #x27) ;; XK_Right VK_RIGHT + (define-xk xk-down #xff54 #x28) ;; XK_Down VK_DOWN + (define-xk xk-page-up #xff55 #x21) ;; XK_Page_Up VK_PRIOR + (define-xk xk-page-down #xff56 #x22) ;; XK_Page_Down VK_NEXT + (define-xk xk-end #xff57 #x23) ;; XK_End VK_END + (define-xk xk-begin #xff58 #x24) ;; XK_Begin VK_HOME + + ;; Special Windows keyboard keys + (define-xk xk-win-l #xFF5B #x5B) ;; XK_Win_L VK_LWIN + (define-xk xk-win-r #xFF5C #x5C) ;; XK_Win_R VK_RWIN + (define-xk xk-app #xFF5D #x5D) ;; XK_App VK_APPS + + ;; Misc functions + (define-xk xk-select #xff60 #x29) ;; XK_Select VK_SELECT + (define-xk xk-print #xff61 #x2A) ;; XK_Print VK_PRINT + (define-xk xk-insert #xff64 #x2D) ;; XK_Insert VK_INSERT + (define-xk xk-num-lock #xff7f #x90) ;; XK_Num_Lock VK_NUMLOCK + + ;; Keypad + ;; TODO: Check values for MS-Windows + (define-xk xk-kp-enter #xff8d nil) ;; XK_KP_Enter ??? + (define-xk xk-kp-multiply #xffaa nil) ;; XK_KP_Multiply ??? + (define-xk xk-kp-add #xffab nil) ;; XK_KP_Add ??? + (define-xk xk-kp-subtract #xffad nil) ;; XK_KP_Subtract ??? + (define-xk xk-kp-decimal #xffae nil) ;; XK_KP_Decimal ??? + (define-xk xk-kp-divide #xffaf nil) ;; XK_KP_Divide ??? + (define-xk xk-kp-0 #xffb0 #x60) ;; XK_KP_0 VK_NUMPAD0 + (define-xk xk-kp-1 #xffb1 #x61) ;; XK_KP_1 VK_NUMPAD1 + (define-xk xk-kp-2 #xffb2 #x62) ;; XK_KP_2 VK_NUMPAD2 + (define-xk xk-kp-3 #xffb3 #x63) ;; XK_KP_3 VK_NUMPAD3 + (define-xk xk-kp-4 #xffb4 #x64) ;; XK_KP_4 VK_NUMPAD4 + (define-xk xk-kp-5 #xffb5 #x65) ;; XK_KP_5 VK_NUMPAD5 + (define-xk xk-kp-6 #xffb6 #x66) ;; XK_KP_6 VK_NUMPAD6 + (define-xk xk-kp-7 #xffb7 #x67) ;; XK_KP_7 VK_NUMPAD7 + (define-xk xk-kp-8 #xffb8 #x68) ;; XK_KP_8 VK_NUMPAD8 + (define-xk xk-kp-9 #xffb9 #x69) ;; XK_KP_9 VK_NUMPAD9 + + ;; Function keys + (define-xk xk-f1 #xffbe #x70) ;; XK_F1 VK_F1 + (define-xk xk-f2 #xffbf #x71) ;; XK_F2 VK_F2 + (define-xk xk-f3 #xffc0 #x72) ;; XK_F3 VK_F3 + (define-xk xk-f4 #xffc1 #x73) ;; XK_F4 VK_F4 + (define-xk xk-f5 #xffc2 #x74) ;; XK_F5 VK_F5 + (define-xk xk-f6 #xffc3 #x75) ;; XK_F6 VK_F6 + (define-xk xk-f7 #xffc4 #x76) ;; XK_F7 VK_F7 + (define-xk xk-f8 #xffc5 #x77) ;; XK_F8 VK_F8 + (define-xk xk-f9 #xffc6 #x78) ;; XK_F9 VK_F9 + (define-xk xk-f10 #xffc7 #x79) ;; XK_F10 VK_F10 + (define-xk xk-f11 #xffc8 #x7A) ;; XK_F11 VK_F11 + (define-xk xk-f12 #xffc9 #x7B) ;; XK_F12 VK_F12 + (define-xk xk-f13 #xffca #x7C) ;; XK_F13 VK_F13 + (define-xk xk-f14 #xffcb #x7D) ;; XK_F14 VK_F14 + (define-xk xk-f15 #xffcc #x7E) ;; XK_F15 VK_F15 + (define-xk xk-f16 #xffcd #x7F) ;; XK_F16 VK_F16 + (define-xk xk-f17 #xffce #x80) ;; XK_F17 VK_F17 + (define-xk xk-f18 #xffcf #x81) ;; XK_F18 VK_F18 + (define-xk xk-f19 #xffd0 #x82) ;; XK_F19 VK_F19 + (define-xk xk-f20 #xffd1 #x83) ;; XK_F20 VK_F20 + (define-xk xk-f21 #xffd2 #x84) ;; XK_F21 VK_F21 + (define-xk xk-f22 #xffd3 #x85) ;; XK_F22 VK_F22 + (define-xk xk-f23 #xffd4 #x86) ;; XK_F23 VK_F23 + (define-xk xk-f24 #xffd5 #x87) ;; XK_F24 VK_F24 + + ;; Modifier keys + (define-xk xk-shift-l #xffe1 #xA0) ;; XK_Shift_L VK_LSHIFT + (define-xk xk-shift-r #xffe2 #xA1) ;; XK_Shift_R VK_RSHIFT + (define-xk xk-control-l #xffe3 #xA2) ;; XK_Control_L VK_LCONTROL + (define-xk xk-control-r #xffe4 #xA3) ;; XK_Control_R VK_RCONTROL + (define-xk xk-caps-lock #xffe5 #x14) ;; XK_Caps_Lock VK_CAPITAL + (define-xk xk-metal-l #xffe7 nil) ;; XK_Meta_L + (define-xk xk-metal-t #xffee nil) ;; XK_Meta_R + (define-xk xk-alt-l #xffe9 #xA4) ;; XK_Alt_L VK_LMENU + (define-xk xk-alt-r #xffea #xA5) ;; XK_Alt_R VK_RMENU + (define-xk xk-super-l #xffeb nil) ;; XK_Super_L + (define-xk xk-super-r #xffec nil) ;; XK_Super_R + (define-xk xk-hyper-l #xffed nil) ;; XK_Hyper_L + (define-xk xk-hyper-r #xffee nil) ;; XK_Hyper_R + + ;; Latin 1 + ;; For numbers and letters, MS-Windows does not define constant names. + ;; X11 defines distinct keysyms for lowercase and uppercase + ;; letters. We use only the uppercase ones. Events with lowercase + ;; letters are converted to uppercase. + (define-xk xk-space #x0020 #x20) ;; XK_space VK_SPACE + (define-xk xk-0 #x0030 #x30) ;; XK_0 + (define-xk xk-1 #x0031 #x31) ;; XK_1 + (define-xk xk-2 #x0032 #x32) ;; XK_2 + (define-xk xk-3 #x0033 #x33) ;; XK_3 + (define-xk xk-4 #x0034 #x34) ;; XK_4 + (define-xk xk-5 #x0035 #x35) ;; XK_5 + (define-xk xk-6 #x0036 #x36) ;; XK_6 + (define-xk xk-7 #x0037 #x37) ;; XK_7 + (define-xk xk-8 #x0038 #x38) ;; XK_8 + (define-xk xk-9 #x0039 #x39) ;; XK_9 + (define-xk xk-a #x0041 #x41) ;; XK_A + (define-xk xk-b #x0042 #x42) ;; XK_B + (define-xk xk-c #x0043 #x43) ;; XK_C + (define-xk xk-d #x0044 #x44) ;; XK_D + (define-xk xk-e #x0045 #x45) ;; XK_E + (define-xk xk-f #x0046 #x46) ;; XK_F + (define-xk xk-g #x0047 #x47) ;; XK_G + (define-xk xk-h #x0048 #x48) ;; XK_H + (define-xk xk-i #x0049 #x49) ;; XK_I + (define-xk xk-j #x004A #x4A) ;; XK_J + (define-xk xk-k #x004B #x4B) ;; XK_K + (define-xk xk-l #x004C #x4C) ;; XK_L + (define-xk xk-m #x004D #x4D) ;; XK_M + (define-xk xk-n #x004E #x4E) ;; XK_N + (define-xk xk-o #x004F #x4F) ;; XK_O + (define-xk xk-p #x0050 #x50) ;; XK_P + (define-xk xk-q #x0051 #x51) ;; XK_Q + (define-xk xk-r #x0052 #x52) ;; XK_R + (define-xk xk-s #x0053 #x53) ;; XK_S + (define-xk xk-t #x0054 #x54) ;; XK_T + (define-xk xk-u #x0055 #x55) ;; XK_U + (define-xk xk-v #x0056 #x56) ;; XK_V + (define-xk xk-w #x0057 #x57) ;; XK_W + (define-xk xk-x #x0058 #x58) ;; XK_X + (define-xk xk-y #x0059 #x59) ;; XK_Y + (define-xk xk-z #x005A #x5A));; XK_Z + +(defun llk-init () + "Initialize low level key events. + +Fills the `llk-keysyms' list, and binds the `low-level-key' event +to the `llk-handle' function. Resets the `llk-bindings' list. +Besides calling this function, you need to set `enable-low-level-key-events' +to a non-nil value." + (interactive) + (llk-define-keysyms) + (define-key special-event-map [low-level-key] 'llk-handle) + (setq llk-bindings nil)) + +(defsubst event-is-key-press (event) + "Return the value of the IS-KEY-PRESS field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 1 event))) + +(defsubst event-keysym (event) + "Return the value of the KEY field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 2 event))) + +(defsubst event-modifier (event) + "Return the value of the MODIFIER field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 3 event))) + +(defsubst event-time (event) + "Return the value of the TIME field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 4 event))) + +;; For example: +;; Bind key tap to command +;; (llk-bind 'tap 'xk-shift-l 'delete-other-windows) +;; Bind modifier tap to command +;; (llk-bind 'tap 'shift 'delete-other-windows) +;; Bind tap to hyper modifier +;; (llk-bind 'tap 'xk-shift-r (lambda () +;; (message "H-...") +;; (setq unread-command-events +;; (append (event-apply-hyper-modifier nil) nil)))) +;; Can bind to a command or function +(defun llk-bind (action key function) + "Bind a command or function to a low level key event. + +The only action supported currently is `tap'. The key can be a keysym +symbol, or a modifier symbol (shift, control, alt, meta, hyper, super). +If there is no keysym symbol for a key, use the keysym number." + (push (list action key function) llk-bindings)) + +;; We store the last events (key/modifier is-press timestamp) here to +;; test for multitap. +(defvar llk-events nil + "Internal variable for detecting taps.") + +;; If positive, return key (xk-shift-l, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + "Internal function to detect n-tap keys." + (let (key + (is-press (event-is-key-press last-input-event)) + ;; convert number to keysym symbol + (keysym (cdr (assoc (event-keysym last-input-event) llk-keysyms))) + (timestamp (event-time last-input-event)) + (modifier (event-modifier last-input-event))) + + ;; if ehte is no symbol for this key, use its keysym number + (unless keysym (setq keysym (event-keysym last-input-event))) + + ;; look in llk-tap-keys for the key, then the modifier + (if (member keysym llk-tap-keys) + (setq key keysym) + (if (member modifier llk-tap-keys) + (setq key modifier))) + + (if (not key) + ;; Key not in tap list, clear history + (setq llk-events nil) + ;; Clear it also if the first element is from a different key + (and llk-events + (not (equal (car (car llk-events)) key)) + (setq llk-events nil)) + (push (list key is-press timestamp) llk-events) + ;; Only care about last 2xN events + (ntake (* 2 n) llk-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length llk-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llk-events)) + (< (- (cl-third (cl-first llk-events)) + (cl-third (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + +(defun describe-low-level-key () + "Wait for key press and describe the low level key event it generates." + (interactive) + (setq llk-describe-next-press t)) + +(defun llk-show-event-description () + "Show information about the last low level key event." + (setq llk-describe-next-press nil) + (with-help-window (help-buffer) + (insert "\n") + (let* ((xk (event-keysym last-input-event)) + (sym (assoc xk llk-keysyms))) + (insert (format "Keysym number: %d (#x%X),\n" xk xk)) + (if sym + (insert (format "which corresponds to named key %s.\n\n" (cdr sym))) + (insert "which does not correspond to any known named key.\n\n")) + (if (event-modifier last-input-event) + (insert (format "This key corresponds to the %s modifier.\n\n" + (event-modifier last-input-event))) + (insert "This key does not correspond to a modifier.\n\n")) + (insert "See the value of the `llk-keysyms' variable for a list of known keys.\n")))) + +(defun llk-handle () + "Internal function to handle low level key events." + (interactive) + (if (and (event-is-key-press last-input-event) + llk-describe-next-press) + (llk-show-event-description) + (let ((tap-key (llk-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llk-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..86d4321cf35 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -98,6 +98,7 @@ G_DEFINE_TYPE (EmacsMenuBar, emacs_menu_bar, GTK_TYPE_MENU_BAR) static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); static void xg_im_context_preedit_end (GtkIMContext *, gpointer); static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); +static bool xg_widget_key_release_event_cb (GtkWidget *, GdkEvent *, gpointer); #endif #if GTK_CHECK_VERSION (3, 10, 0) @@ -1749,6 +1750,12 @@ xg_create_frame_widgets (struct frame *f) g_signal_connect (G_OBJECT (wfixed), "key-press-event", G_CALLBACK (xg_widget_key_press_event_cb), NULL); + + g_signal_connect (G_OBJECT (wfixed), "key-release-event", + G_CALLBACK (xg_widget_key_release_event_cb), + NULL); + + #endif { @@ -6376,6 +6383,53 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +#ifndef HAVE_XINPUT2 +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + modifier = x_get_modifier_for_keycode (FRAME_OUTPUT_DATA (f)->display_info, + xev->key.hardware_keycode); + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); +} +#endif + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6458,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifndef HAVE_XINPUT2 + xg_maybe_send_low_level_key_event (f, event); +#endif + if (popup_activated ()) return true; @@ -6557,6 +6615,29 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, return true; } +static bool +xg_widget_key_release_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ +#ifndef HAVE_XINPUT2 + Lisp_Object tail, tem; + struct frame *f = NULL; + + FOR_EACH_FRAME (tail, tem) + { + if (FRAME_X_P (XFRAME (tem)) + && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) + { + f = XFRAME (tem); + break; + } + } + if (f) + xg_maybe_send_low_level_key_event (f, event); +#endif + return true; +} + bool xg_filter_key (struct frame *frame, XEvent *xkey) { diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..eaa84f50327 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,7 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,12 +7119,47 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (6, Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + XCAR (XCDR (XCDR (event->arg))), /* The modifier. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); } } +bool +kbd_low_level_key_is_enabled (int keysym, Lisp_Object modifier) +{ + if (Venable_low_level_key_events == Qt) + return true; + + if (Venable_low_level_key_events == Qnil) + return false; + + if (FIXNUMP (Venable_low_level_key_events)) + return keysym == XFIXNUM (Venable_low_level_key_events); + + if (Venable_low_level_key_events == Qmodifiers) + return modifier != Qnil; + + for (Lisp_Object e = Venable_low_level_key_events; CONSP (e); e = XCDR (e)) + { + Lisp_Object c = XCAR (e); + if (FIXNUMP (c) && XFIXNUM (c) == keysym) + return true; + if (c == Qmodifiers && modifier != Qnil) + return true; + } + + return false; +} + static Lisp_Object make_lispy_movement (struct frame *frame, Lisp_Object bar_window, enum scroll_bar_part part, Lisp_Object x, Lisp_Object y, Time t) @@ -12931,6 +12967,33 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + + DEFSYM (Qmodifiers, "modifiers"); + + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* If non-nil, reception of low-level key events is enabled. + +The value configures the set of keys that are handled: + +If t, send events for all keys. + +If a number, send events for the corresponding keysym. When calling +`llk-init', a set of variables with the xk- prefix is initialized with +the numeric values for keysyms. Because this number are platform +dependent, only the variables should be used to refer to a key. For +example, the 'xk-backspace' variable refer to the backspace key, with +the numeric value 0xff08 on X, and the 0x08 on MS-Windows. You can see +all defined variables on the variable `llk-keysyms'. + +If a symbol, a predefined set of keys is selected. The only currently +valid symbol is 'modifiers. + +If a list of numbers and/or symbols, the corresponding keysyms and sets +are selected. */); + Venable_low_level_key_events = Qnil; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14081,8 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "low-level-key", + "ignore"); } /* Mark the pointers in the kboard objects. diff --git a/src/keyboard.h b/src/keyboard.h index 387501c9f88..83f9a0f141a 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -511,6 +511,7 @@ kbd_buffer_store_event_hold (struct input_event *event, extern Lisp_Object menu_item_eval_property (Lisp_Object); extern bool kbd_buffer_events_waiting (void); extern void add_user_signal (int, const char *); +extern bool kbd_low_level_key_is_enabled (int, Lisp_Object); extern int tty_read_avail_input (struct terminal *, struct input_event *); extern struct timespec timer_check (void); diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..fba81f5ec0e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,56 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (struct frame *f, GdkEvent *event) +{ + GdkEventKey xkey = event->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (event->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + /* We don't support modifier identification on PGTK. We only can tell + if the key corresponds to a modifier or not, which is used for + filtering enabled keys with kbd_low_level_key_is_enabled. */ + modifier = event->key.is_modifier ? Qt : Qnil; + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + if (!f) + f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5211,6 +5261,9 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct pgtk_display_info *dpyinfo; f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + pgtk_maybe_send_low_level_key_event (f, event); + EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); nbytes = 0; @@ -5454,6 +5507,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event (NULL, event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..966a6492f69 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,7 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index e2455b9271e..1d18bf408e1 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } goto dflt; case WM_KEYDOWN: @@ -4695,6 +4700,12 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index e18f39dd2a8..a2e0a4b0fa0 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,55 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_LOW_LEVEL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = make_fixnum (vk); + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: + case VK_RSHIFT: + modifier = Qshift; + break; + case VK_LCONTROL: + case VK_RCONTROL: + modifier = Qctrl; + break; + case VK_LMENU: + case VK_RMENU: + modifier = Qmeta; + break; + } + + if (kbd_low_level_key_is_enabled (vk, modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list3 (is_wm_keyup ? Qnil : Qt, key, modifier); + kbd_buffer_store_event_hold (&inev, hold_quit); + } + + inev.kind = NO_EVENT; + + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..88f7dfeef8b 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_LOW_LEVEL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..72715e0ed73 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,141 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +Lisp_Object +x_get_modifier_for_keycode (struct x_display_info *dpyinfo, + int keycode) +{ +#ifdef HAVE_XKB + if (dpyinfo->xkb_desc) + for (int mod = 0; mod < XkbNumModifiers; mod++) + { + int mask = (1 << mod); + if (dpyinfo->xkb_desc->map->modmap[keycode] & mask) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } +#endif + XModifierKeymap *map = dpyinfo->modmap; + if (map) + for (int mod = 0; mod < 8; mod++) + { + int mask = (1 << mod); + for (int key = 0; key < map->max_keypermod; key++) + if (map->modifiermap[mod * map->max_keypermod + key] == keycode) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } + return Qnil; +} + +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev, struct frame *f) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case KeyPress: + is_press = true; + xkey = xev->xkey; + break; + case KeyRelease: + is_press = false; + xkey = xev->xkey; + break; +#ifdef HAVE_XINPUT2 + case GenericEvent: + XIDeviceEvent *xiev = xev->xcookie.data; + switch (xev->xgeneric.evtype) + { + case XI_KeyPress: + is_press = true; + break; + case XI_KeyRelease: + is_press = false; + break; + default: + return; + } + + xkey.serial = xiev->serial; + xkey.send_event = xiev->send_event; + xkey.display = xiev->display; + xkey.window = xiev->event; + xkey.root = xiev->root; + xkey.subwindow = xiev->child; + xkey.time = xiev->time; + xkey.x = xiev->event_x; + xkey.y = xiev->event_y; + xkey.x_root = xiev->root_x; + xkey.y_root = xiev->root_y; + xkey.state = xiev->mods.effective; + xkey.keycode = xiev->detail; + xkey.same_screen = 1; + break; +#endif + default: + return; + } + + if (!f) + f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + modifier = x_get_modifier_for_keycode (dpyinfo, xkey.keycode); + + /* Convert lowercase latin letter to uppercase. */ + if (keysym >= XK_a && keysym <= XK_z) + keysym -= XK_a - XK_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_event (&ie); +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -20206,6 +20341,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event, any); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20851,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24107,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, struct xi_device_t *device, *source; XKeyPressedEvent xkey; + x_maybe_send_low_level_key_event (dpyinfo, event, any); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24725,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32803,7 @@ syms_of_xterm (void) Vx_toolkit_scroll_bars = Qnil; #endif + DEFSYM (Qshift, "shift"); DEFSYM (Qmodifier_value, "modifier-value"); DEFSYM (Qctrl, "ctrl"); Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier)); diff --git a/src/xterm.h b/src/xterm.h index 8d5c9917749..66e052e7acd 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1906,6 +1906,8 @@ x_mutable_colormap (XVisualInfo *visual) extern void tear_down_x_back_buffer (struct frame *f); extern void initial_set_up_x_back_buffer (struct frame *f); +extern Lisp_Object x_get_modifier_for_keycode (struct x_display_info *, int); + /* Defined in xfns.c. */ extern void x_real_positions (struct frame *, int *, int *); extern void x_change_tab_bar_height (struct frame *, int); -- 2.35.1.windows.2