* bug#74423: Low level key events [not found] ` <s1r8qtzsvbe.fsf@yahoo.com> @ 2024-11-18 20:35 ` Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors ` (2 more replies) 0 siblings, 3 replies; 8+ messages in thread From: Cecilio Pardo @ 2024-11-18 20:35 UTC (permalink / raw) To: 74423 [-- Attachment #1: Type: text/plain, Size: 1798 bytes --] Here is a still incomplete patch for this, renamed from "physical keyboard events", following up to https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00085.html It provides events for press/release of keys, independently of the normal keyboard events. These events are bound int the special-event-map. Some lisp is included to implement detection of multiple tapping on keys, and running commands or simulating modifiers. Currently, only modifier keys are available, because other keys would send their own normal event. This could change when an use case for other keys appears. To provide events for 'logical' modifiers Ctrl, Meta, etc., now there is a new event low-level-modifier, in addition to low-level-key. If you press Shift_L, two events will be generated, one for 'shift as low-level-modifier, and one for 'lshift as low-level-key. The lisp part handles those events separately. I added a variable that enables/disables everything, enable-low-level-key-events. This are some of Po Lu's notes on the previous patch: > [...] Emacs must not depend on XKB for such a basic feature. Fixed. > Please find some means of generating such events by saving them into the > keyboard event buffer defined in handle_ome_xevent. As we may generate two events, that is not possible. > x_filter_events is not the proper location for this call. It is only > invoked to filter an event through the active GTK or X input method > context, and it is not invoked if Emacs is configured not to open input > method contexts, or they are unavailable by happenstance. Done. If XINPUT is enabled, the XI_KeyPress event used, over the gtk one. If not, the gtk event is used unless there is no input method. In this case KeyPress event is used. The pgtk version is not updated. [-- Attachment #2: 0001-Add-events-for-key-press-and-key-release-on-gui-syst.patch --] [-- Type: text/plain, Size: 24075 bytes --] From d2403d9fa14b498263abb242324c21441a4246aa Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 28 Oct 2024 23:57:35 +0100 Subject: [PATCH] Add events for key press and key release on gui systems. And detection of double/triple taps on modifier keys. * lisp/low-level-key.el (llk-tap-timeout): (llk-tap-count): (llk-tap-keys): (llk-bindings): (llk-modifier-bindings): (llk-init): (llk-bind): (llk-modifier-bind): (llk-events): (llk-modifier-events): (llm-handle): * src/gtkutil.c (xg_create_frame_widgets): (xg_maybe_send_low_level_key_event): (xg_widget_key_press_event_cb): (xg_widget_key_release_event_cb): * src/xterm.c (x_maybe_send_physical_key_event): (x_filter_event): (handle_one_xevent): (syms_of_xterm): --- lisp/low-level-key.el | 141 +++++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 133 ++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 38 +++++++++++ src/pgtkterm.c | 37 +++++++++++ src/termhooks.h | 2 + src/w32fns.c | 11 ++++ src/w32term.c | 69 ++++++++++++++++++++ src/w32term.h | 3 +- src/xterm.c | 144 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..639c082da15 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,141 @@ +;;; -*- lexical-binding: t -*- +(require 'cl-lib) + +;; User options +(defvar llk-tap-timeout 1000) +(defvar llk-tap-count 2) +(defvar llk-tap-keys + '(lshift rshift lctrl rctrl lalt ralt shift ctrl alt)) +(defvar llk-bindings nil) +(defvar llm-bindings nil) + +(defun llk-init () + (interactive) + (define-key special-event-map [low-level-key] 'llk-handle) + (define-key special-event-map [low-level-modifier] 'llm-handle) + + (setq llk-bindings nil) + (setq llm-bindings nil) + + ;; (llm-bind 'tap 'shift 'delete-other-windows) + ;; (llk-bind 'tap 'lctrl 'hyper) + (setq enable-low-level-key-events t)) + +;; For example: +;; (llk-add-binding 'tap 'lshift 'delete-other-windows) +;; Can bind to a command, a function or the symbol 'hyper. +(defun llk-bind (action key function) + (push (list action key function) llk-bindings)) + +(defun llm-bind (action key function) + (push (list action key function) llm-bindings)) + +;; We store the last events here to test for multitap. +(defvar llk-events nil) +(defvar llm-events nil) + +;; If positive, return key ('lshift, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + ;; The physical-key event is like this: + ;; (physical-key t lshift 90196265 #<frame>) + ;; The second element is t for a key press, nil for a key release + ;; The fourth element is the time in milliseconds + ;; The fifth is the frame, we don't use it yet. + + (let ((key (cl-third last-input-event))) + (if (not (member key llk-tap-keys)) + ;; 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 (cl-third (car llk-events)) key)) + (setq llk-events nil)) + (push last-input-event 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-fourth (cl-first llk-events)) + (cl-fourth (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + + +;; this function is a copy of llk-detect-n-tap, but for llm-events +(defun llm-detect-n-tap (n timeout) + (let ((key (cl-third last-input-event))) + (if (not (member key llk-tap-keys)) + (setq llm-events nil) + (and llm-events + (not (equal (cl-third (car llm-events)) key)) + (setq llm-events nil)) + (push last-input-event llm-events) + (ntake (* 2 n) llm-events) + (and (eq (* 2 n) (length llm-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 llm-events)) + (< (- (cl-fourth (cl-first llm-events)) + (cl-fourth (car (last llm-events)))) + timeout) + (progn + (setq llm-events nil) + key))))) + +(defun llk-handle () + (interactive) + + (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)) + ((eq 'hyper func) + (message "H-...") + (let ((r (read-event))) + (setq unread-command-events + (list (event-apply-modifier + r 'hyper 24 "H-")))))))))) + +(defun llm-handle() + (interactive) + + (let ((tap-key (llm-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))) + llm-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)) + ((eq 'hyper func) + (message "H-...") + (let ((r (read-event))) + (setq unread-command-events + (list (event-apply-modifier + r 'hyper 24 "H-")))))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..6ca4e313929 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,105 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + int keysym; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (!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; + } + + keysym = xkey.keyval; + + switch (keysym) + { + case GDK_KEY_Shift_L: key = Qlshift; break; + case GDK_KEY_Shift_R: key = Qrshift; break; + case GDK_KEY_Control_L: key = Qlctrl; break; + case GDK_KEY_Control_R: key = Qrctrl; break; + case GDK_KEY_Alt_L: key = Qlalt; break; + case GDK_KEY_Alt_R: key = Qralt; break; + default: + key = Qnil; + } + + switch (keysym) + { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + modifier = Qshift; + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + modifier = Vx_ctrl_keysym; + if (NILP (modifier)) + modifier = Qctrl; + break; + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qalt; + break; + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qmeta; + break; + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + modifier = Vx_hyper_keysym; + if (NILP (modifier)) + modifier = Qhyper; + break; + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + modifier = Vx_super_keysym; + if (NILP (modifier)) + modifier = Qsuper; + break; + default: + modifier = Qnil; + } + + if (!NILP (key)) + { + 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 = list2 (is_press ? Qt : Qnil, key); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); + } + + if (!NILP (modifier)) + { + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list2 (is_press ? Qt : Qnil, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); + } +} + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6510,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 +6667,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..532a1d5dbfe 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,8 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: + case LOW_LEVEL_MODIFIER_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,6 +7120,22 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (5, + Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + make_fixnum (event->timestamp), + event->frame_or_window); + + case LOW_LEVEL_MODIFIER_KEY_EVENT: + return listn (5, + Qlow_level_modifier, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); @@ -12931,6 +12949,20 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* Enabled the recepcion of low level key events. +This includes 'low-level-key' and 'low-level-modifier' events. */); + Venable_low_level_key_events = false; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qlow_level_modifier, "low-level-modifier"); + DEFSYM (Qlshift, "lshift"); + DEFSYM (Qrshift, "rshift"); + DEFSYM (Qlctrl, "lctrl"); + DEFSYM (Qrctrl, "rctrl"); + DEFSYM (Qlalt, "lalt"); + DEFSYM (Qralt, "ralt"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14050,12 @@ 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"); + initial_define_lispy_key (Vspecial_event_map, "low-level-modifier", + "ignore"); + + } /* Mark the pointers in the kboard objects. diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..7091e9ef4c3 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,39 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (GdkEvent *event) +{ + if (!Venable_low_level_key_events) + return; + + Lisp_Object key; + switch (event->key.keyval) + { + case GDK_KEY_Shift_L: key = Qlshift; break; + case GDK_KEY_Shift_R: key = Qrshift; break; + case GDK_KEY_Control_L: key = Qlctrl; break; + case GDK_KEY_Control_R: key = Qrctrl; break; + case GDK_KEY_Alt_L: key = Qlalt; break; + case GDK_KEY_Alt_R: key = Qralt; break; + default: + return; + } + bool keypress = event->key.type == GDK_KEY_PRESS; + struct frame *f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + union buffered_input_event inev; + + 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 = list2 (keypress ? Qt : Qnil, key); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5210,6 +5243,8 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct frame *f; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event(event); + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); @@ -5454,6 +5489,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event(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..006b74809a3 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,8 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT + , LOW_LEVEL_MODIFIER_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..f1850a5f2f3 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 (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 (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..9fae9701daf 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,75 @@ 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 = Qnil; + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: key = Qlshift; break; + case VK_RSHIFT: key = Qrshift; break; + case VK_LCONTROL: key = Qlctrl; break; + case VK_RCONTROL: key = Qrctrl; break; + case VK_LMENU: key = Qlalt; break; + case VK_RMENU: key = Qralt; break; + } + + 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 (!NILP (key)) + { + 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 = list2 (is_wm_keyup ? Qnil : Qt, key); + kbd_buffer_store_event_hold (&inev, hold_quit); + + } + + if (!NILP (modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list2 (is_wm_keyup ? Qnil : Qt, 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..97028c32336 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,143 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (!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; + } + + struct frame *f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + switch (keysym) + { + case XK_Shift_L: key = Qlshift; break; + case XK_Shift_R: key = Qrshift; break; + case XK_Control_L: key = Qlctrl; break; + case XK_Control_R: key = Qrctrl; break; + case XK_Alt_L: key = Qlalt; break; + case XK_Alt_R: key = Qralt; break; + default: + key = Qnil; + } + + switch (keysym) + { + case XK_Shift_L: + case XK_Shift_R: + modifier = Qshift; + break; + case XK_Control_L: + case XK_Control_R: + modifier = Vx_ctrl_keysym; + if (NILP (modifier)) + modifier = Qctrl; + break; + case XK_Alt_L: + case XK_Alt_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qalt; + break; + case XK_Meta_L: + case XK_Meta_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qmeta; + break; + case XK_Hyper_L: + case XK_Hyper_R: + modifier = Vx_hyper_keysym; + if (NILP (modifier)) + modifier = Qhyper; + break; + case XK_Super_L: + case XK_Super_R: + modifier = Vx_super_keysym; + if (NILP (modifier)) + modifier = Qsuper; + break; + default: + modifier = Qnil; + } + + if (!NILP (key)) + { + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list2 (is_press ? Qt : Qnil, key); + kbd_buffer_store_event (&ie); + } + + if (!NILP (modifier)) + { + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list2 (is_press ? Qt : Qnil, key); + 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 +20343,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20853,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24109,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); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24727,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32805,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)); -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo @ 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-23 12:08 ` Cecilio Pardo 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2 siblings, 1 reply; 8+ messages in thread From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-18 23:49 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 Cecilio Pardo <cpardo@imayhem.com> writes: > + switch (keysym) > + { > + case GDK_KEY_Shift_L: > + case GDK_KEY_Shift_R: > + modifier = Qshift; > + break; > + case GDK_KEY_Control_L: > + case GDK_KEY_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case GDK_KEY_Alt_L: > + case GDK_KEY_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case GDK_KEY_Meta_L: > + case GDK_KEY_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case GDK_KEY_Hyper_L: > + case GDK_KEY_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case GDK_KEY_Super_L: > + case GDK_KEY_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } This is not the proper means of establishing the modifier bound to a key symbol in GTK, but I cannot immediately tell you what the alternative is. > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } Stylistic issues. > +static void > +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, > + const XEvent *xev) > +{ > + XKeyEvent xkey; > + bool is_press; > + KeySym keysym; > + Lisp_Object key, modifier; > + struct input_event ie; > + > + if (!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; > + } > + > + struct frame *f = x_any_window_to_frame (dpyinfo, xkey.window); > + if (!f) > + return; > + > + XLookupString (&xkey, NULL, 0, &keysym, NULL); > + > + switch (keysym) > + { > + case XK_Shift_L: key = Qlshift; break; > + case XK_Shift_R: key = Qrshift; break; > + case XK_Control_L: key = Qlctrl; break; > + case XK_Control_R: key = Qrctrl; break; > + case XK_Alt_L: key = Qlalt; break; > + case XK_Alt_R: key = Qralt; break; > + default: > + key = Qnil; > + } This doesn't generate `ralt' events on my system, as that key produces the keysym ISO_Level3_Shift/ > + switch (keysym) > + { > + case XK_Shift_L: > + case XK_Shift_R: > + modifier = Qshift; > + break; > + case XK_Control_L: > + case XK_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case XK_Alt_L: > + case XK_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case XK_Meta_L: > + case XK_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case XK_Hyper_L: > + case XK_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case XK_Super_L: > + case XK_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } You are computing the modifier event to be generated incorrectly. You should search the modifier map (dpyinfo->modmap) for columns containing received keysyms, and compare modifier bits derived from the rows where they appear against meta_mod_mask, shift_lock_mask, alt_mod_mask, super_mod_mask, and hyper_mod_mask in the display structure, or CtrlMask and ShiftMask. > case KeyPress: > + x_maybe_send_low_level_key_event (dpyinfo, event); > x_display_set_last_user_time (dpyinfo, event->xkey.time, > event->xkey.send_event, > true); > @@ -20715,6 +20853,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, > #endif > > case KeyRelease: > + x_maybe_send_low_level_key_event (dpyinfo, event); > #ifdef HAVE_X_I18N > /* Don't dispatch this event since XtDispatchEvent calls > XFilterEvent, and two calls in a row may freeze the > @@ -23970,6 +24109,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); > + > coding = Qlatin_1; > > /* The code under this label is quite desultory. There > @@ -24586,6 +24727,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, > #endif > > case XI_KeyRelease: > + x_maybe_send_low_level_key_event (dpyinfo, event); Would you rearrange the locations of calls to x_any_window_to_frame in handle_one_xevent so that you need not redundantly call the same function in x_maybe_send_low_level_key_event? Thanks for your interest in Emacs. ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-23 12:08 ` Cecilio Pardo 0 siblings, 0 replies; 8+ messages in thread From: Cecilio Pardo @ 2024-11-23 12:08 UTC (permalink / raw) To: Po Lu; +Cc: 74423 On 19/11/2024 0:49, Po Lu wrote: > You are computing the modifier event to be generated incorrectly. You > should search the modifier map (dpyinfo->modmap) for columns containing > received keysyms, and compare modifier bits derived from the rows where > they appear against meta_mod_mask, shift_lock_mask, alt_mod_mask, > super_mod_mask, and hyper_mod_mask in the display structure, or CtrlMask > and ShiftMask. Thanks for the guidance. Does this look ok? 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; } ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2 siblings, 0 replies; 8+ messages in thread From: Eli Zaretskii @ 2024-11-19 15:29 UTC (permalink / raw) To: Cecilio Pardo, Stefan Monnier; +Cc: 74423 > Date: Mon, 18 Nov 2024 21:35:40 +0100 > From: Cecilio Pardo <cpardo@imayhem.com> > > Here is a still incomplete patch for this, renamed from "physical > keyboard events", following up to > https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00085.html > > It provides events for press/release of keys, independently of the > normal keyboard events. These events are bound int the > special-event-map. Some lisp is included to implement detection of > multiple tapping on keys, and running commands or simulating modifiers. Thanks, I have some minor comments, and also added Stefan to the discussion, in case he will also have comments. > + if (!Venable_low_level_key_events) You treat this variable as a C boolean, i.e. assume it was defined with DEFVAR_BOOL, but in that case our convention is to call the C variables without the leading 'V'. OTOH, if you envision that this variable could be something other than nil or t in Lisp, then you need to use NILP here. > + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, > + doc: /* Enabled the recepcion of low level key events. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "If non-nil, reception of low-level key events is enabled." > +static void > +pgtk_maybe_send_low_level_key_event (GdkEvent *event) > +{ > + if (!Venable_low_level_key_events) > + return; Same problem with the test here. > --- 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 (Venable_low_level_key_events) > + { > + signal_user_input (); > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } And here (and in several other places in the patch). ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 15:29 ` Eli Zaretskii @ 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 20:05 ` Cecilio Pardo 2 siblings, 1 reply; 8+ messages in thread From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 16:43 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 > It provides events for press/release of keys, independently of the normal > keyboard events. These events are bound int the special-event-map. Some lisp > is included to implement detection of multiple tapping on keys, and running > commands or simulating modifiers. I hadn't followed the discussion over at emacs-devel, but this is cool. I used to have a local patch which generated extra events for all key presses/releases. I never wrote any useful Lisp-level code for it (all I had were bindings to ignore those events so Emacs was still usable 🙂), but I really think it would make for fun new hacks. I haven't had time to look at your whole patch, but here are some comments. > + (let ((key (cl-third last-input-event))) Please try and use `event-*` functions. If none serve (as will often be the case), define local "replacements" until they can be promoted to `subr.el`. > + ((functionp func) (funcall func)) > + ((eq 'hyper func) > + (message "H-...") > + (let ((r (read-event))) > + (setq unread-command-events > + (list (event-apply-modifier > + r 'hyper 24 "H-")))))))))) Move that code to a function, so you can get rid of this `hyper` special case. BTW, any reason why you couldn't use `event-apply-hyper-modifier`? [ BTW, this becomes more ... interesting ... when you want to be able to cumulative that for several modifiers, in which case your `read-event` might return an event which is not the one to which you want to add `H-`. ] > + switch (keysym) > + { > + case GDK_KEY_Shift_L: key = Qlshift; break; > + case GDK_KEY_Shift_R: key = Qrshift; break; > + case GDK_KEY_Control_L: key = Qlctrl; break; > + case GDK_KEY_Control_R: key = Qrctrl; break; > + case GDK_KEY_Alt_L: key = Qlalt; break; > + case GDK_KEY_Alt_R: key = Qralt; break; > + default: > + key = Qnil; > + } > + > + switch (keysym) > + { > + case GDK_KEY_Shift_L: > + case GDK_KEY_Shift_R: > + modifier = Qshift; > + break; > + case GDK_KEY_Control_L: > + case GDK_KEY_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case GDK_KEY_Alt_L: > + case GDK_KEY_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case GDK_KEY_Meta_L: > + case GDK_KEY_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case GDK_KEY_Hyper_L: > + case GDK_KEY_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case GDK_KEY_Super_L: > + case GDK_KEY_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } I think the list of low-level keys handled here should not be hard-coded. IOW, maybe `enable-low-level-keys` should not be a boolean but a list/map/table indicating which keys to handle. > + if (!NILP (key)) > + { > + 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 = list2 (is_press ? Qt : Qnil, key); > + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); > + } > + > + if (!NILP (modifier)) > + { > + EVENT_INIT (inev.ie); > + XSETFRAME (inev.ie.frame_or_window, f); > + inev.ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; > + inev.ie.timestamp = xkey.time; > + inev.ie.arg = list2 (is_press ? Qt : Qnil, modifier); > + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); > + } > +} So, IIUC you might generate 2 low-level events for a single key press? Why? Other note: in the distant past (back around Emacs-21) I seem to remember Gerd making changes to the event structure so as to avoid allocating Lisp objects for this code. I think it was related to problems due to running this low-level event-handler code from within a C signal handler, which is a practice was have since stopped, luckily, but maybe there are still good reasons to try and avoid involving allocating objects into the Lisp heap in this low-level code. Stefan ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 20:05 ` Cecilio Pardo 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 0 siblings, 1 reply; 8+ messages in thread From: Cecilio Pardo @ 2024-11-19 20:05 UTC (permalink / raw) To: Stefan Monnier; +Cc: 74423 On 19/11/2024 17:43, Stefan Monnier wrote: >> + ((functionp func) (funcall func)) >> + ((eq 'hyper func) >> + (message "H-...") >> + (let ((r (read-event))) >> + (setq unread-command-events >> + (list (event-apply-modifier >> + r 'hyper 24 "H-")))))))))) > > Move that code to a function, so you can get rid of this `hyper` > special case. BTW, any reason why you couldn't use > `event-apply-hyper-modifier`? No, no reason. I will change that. > I think the list of low-level keys handled here should not be hard-coded. > IOW, maybe `enable-low-level-keys` should not be a boolean but > a list/map/table indicating which keys to handle. I was sending the keys for which I have an immediate use case. Using a list looks got, adding some special symbols to choose groups, such as "all modifier keys". > So, IIUC you might generate 2 low-level events for a single key press? > Why? We want to allow to detect modifier keys, regardless of the key that is used. For example, when you press Shift_L we generate an event for 'lshift and other (of different type) for the modifier 'shift. > Other note: in the distant past (back around Emacs-21) I seem to > remember Gerd making changes to the event structure so as to avoid > allocating Lisp objects for this code. I think it was related to > problems due to running this low-level event-handler code from within > a C signal handler, which is a practice was have since stopped, luckily, > but maybe there are still good reasons to try and avoid involving > allocating objects into the Lisp heap in this low-level code. I will look into this. Thanks for your comments! ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-19 20:05 ` Cecilio Pardo @ 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-02 16:54 ` Cecilio Pardo 0 siblings, 1 reply; 8+ messages in thread From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-20 4:21 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 >> Move that code to a function, so you can get rid of this `hyper` >> special case. BTW, any reason why you couldn't use >> `event-apply-hyper-modifier`? > No, no reason. I will change that. To be clear: I don't know that it works here. >> I think the list of low-level keys handled here should not be hard-coded. >> IOW, maybe `enable-low-level-keys` should not be a boolean but >> a list/map/table indicating which keys to handle. > I was sending the keys for which I have an immediate use case. Using a list > looks got, adding some special symbols to choose groups, such as "all > modifier keys". I was also thinking that maybe the list/table could be used to avoid having to define in the C code what is a "modifier key" and things like that. >> So, IIUC you might generate 2 low-level events for a single key press? >> Why? > We want to allow to detect modifier keys, regardless of the key that is > used. For example, when you press Shift_L we generate an event for 'lshift > and other (of different type) for the modifier 'shift. But my question is about the fact that you generate two events, not about the two pieces of information. IOW you could also generate a single event with both pieces of information (and if use the "normal" keymaps instead of `special-event-map` then you might even have a `function-key-map` rule which converts a `Shift_L` down to a `shift` if there was no binding for the `Shift_L` event). Stefan ^ permalink raw reply [flat|nested] 8+ messages in thread
* bug#74423: Low level key events 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-02 16:54 ` Cecilio Pardo 0 siblings, 0 replies; 8+ messages in thread From: Cecilio Pardo @ 2024-12-02 16:54 UTC (permalink / raw) To: Stefan Monnier, Eli Zaretskii, Po Lu; +Cc: 74423 [-- Attachment #1: Type: text/plain, Size: 1643 bytes --] Here is a new version of the patch. Sorry it took so long. It addresses most of the notes I got: Notes by Po Lu: - I think now the associated modifier for a key is computed properly, with or without XKB for plain X, and for gtk. - 'ralt' not generated for ISO_Level3_Shift. This will have to stay like this. What we know about the keys if what the system lets us. - Don't call x_any_window_to_frame if already called for the event. Notes by Eli: - Change testing for Venable_low_level_key_events. This variable now is more complex. Notes by Stefan: - Created and used event-* functions to access event. - Only one event with both pieces of information. - Selection of keys. Now the variable enable-low-level-key-events can be used to select exactly which keys we want to process. - About creating lisp object in handle_one_xevent, it is done in other events too. The lisp interface is as follows: - A list of variables with keysym values is initialized (xk-*), so the user can select keys, not just modifiers. - Users can bind globally commands or functions with llk-bind to taps. - To use a tapped key as a new modifier, this works: (llk-bind 'tap 'xk-shift-r (lambda () (message "H-...") (setq unread-command-events (append (event-apply-hyper-modifier nil) nil)))) - Added the function describe-low-level-key to see what keysym/modifier it triggers. This is implemented for X, pgtkw and MS-Windows. The pgtj implementation detects that a key is a modifier to filter it, but can't decide which one it its. [-- Attachment #2: 0001-Send-event-for-key-presses-and-key-releases.patch --] [-- Type: text/plain, Size: 36568 bytes --] From 863068f4e1e187ba33382c0debb8caee5bbe5cbe Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 2 Dec 2024 17:30:42 +0100 Subject: [PATCH] Send event for key presses and key releases. Detect double/tripe taps. * 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 decice 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 (WM_EMACS_IME_STATUS): 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 definde 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. --- lisp/low-level-key.el | 335 ++++++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 81 ++++++++++ src/keyboard.c | 61 ++++++++ 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 + 11 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..85cdaaaf0f4 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,335 @@ +;;; -*- lexical-binding: t -*- + +;; The physical-key event is like this: +;; (physical-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. +;; 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. + +(require 'cl-lib) + +;; User options + +(defvar llk-bindings nil + "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 in milliseconds 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 modifiry 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 a 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 the next key press and describe the low level key event it +generates." + (interactive) + (setq llk-describe-next-press t)) + +(defun llk-show-event-description () + "Shoe 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..442bf7cee34 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,29 @@ 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. This numbers are platform dependent. + +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 +14077,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 ^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2024-12-02 16:54 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- [not found] <fb50ec11-7aec-481e-8a3a-ecdcf22eb7c0@imayhem.com> [not found] ` <31bdc55d-8c13-4de0-9cef-bd6cc4fb033f@imayhem.com> [not found] ` <s1r8qtzsvbe.fsf@yahoo.com> 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-23 12:08 ` Cecilio Pardo 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 20:05 ` Cecilio Pardo 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-02 16:54 ` Cecilio Pardo
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).