* 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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; 21+ 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] 21+ 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
2024-12-04 20:01 ` Eli Zaretskii
0 siblings, 1 reply; 21+ 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] 21+ messages in thread
* bug#74423: Low level key events
2024-12-02 16:54 ` Cecilio Pardo
@ 2024-12-04 20:01 ` Eli Zaretskii
2024-12-04 21:25 ` Cecilio Pardo
0 siblings, 1 reply; 21+ messages in thread
From: Eli Zaretskii @ 2024-12-04 20:01 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: luangruo, 74423, monnier
> Date: Mon, 2 Dec 2024 17:54:03 +0100
> Cc: 74423@debbugs.gnu.org
> From: Cecilio Pardo <cpardo@imayhem.com>
>
> Here is a new version of the patch. Sorry it took so long. It
> addresses most of the notes I got:
Thanks. There are several minor issues with this (first line of doc
strings not a single complete sentence, one space between sentences,
etc.), but I guess this is too early for such details.
Other than that, I have only one comment:
> + 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.
This seems to say that it is impossible to make the value do the same
on all platforms? If so, I think it's less useful than it could be,
because Emacs generally tries to abstract platform-specific issues as
much as possible, to facilitate platform-independent Lisp programs
that work the same on all supported systems.
Also, there's no information here where to find the list of these xk-
numbers.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-04 20:01 ` Eli Zaretskii
@ 2024-12-04 21:25 ` Cecilio Pardo
2024-12-05 5:41 ` Eli Zaretskii
0 siblings, 1 reply; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-04 21:25 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 74423, monnier
On 04/12/2024 21:01, Eli Zaretskii wrote:
>> + 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.
>
> This seems to say that it is impossible to make the value do the same
> on all platforms? If so, I think it's less useful than it could be,
> because Emacs generally tries to abstract platform-specific issues as
> much as possible, to facilitate platform-independent Lisp programs
> that work the same on all supported systems.
>
> Also, there's no information here where to find the list of these xk-
> numbers.
Those are variables, like xk-shift-l, xk-a, xk-f1. There is also
llk-keysyms, that has a list of all xk-* symbols and their numeric values.
llk-init initializes all of these variables, using numbers that are
platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for
windows. GDK keys match X keys.
Numbers are different, but users should use the variables, not the
numbers, for binding and comparing to event values.
I can translate the Windows numbers to the X equivalents, so that they
are equal on both platforms. In any case, I will document this better,
with a manual change proposal.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-04 21:25 ` Cecilio Pardo
@ 2024-12-05 5:41 ` Eli Zaretskii
2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-13 22:55 ` Cecilio Pardo
0 siblings, 2 replies; 21+ messages in thread
From: Eli Zaretskii @ 2024-12-05 5:41 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: luangruo, 74423, monnier
> Date: Wed, 4 Dec 2024 22:25:32 +0100
> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, 74423@debbugs.gnu.org
> From: Cecilio Pardo <cpardo@imayhem.com>
>
> On 04/12/2024 21:01, Eli Zaretskii wrote:
> >> + 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.
> >
> > This seems to say that it is impossible to make the value do the same
> > on all platforms? If so, I think it's less useful than it could be,
> > because Emacs generally tries to abstract platform-specific issues as
> > much as possible, to facilitate platform-independent Lisp programs
> > that work the same on all supported systems.
> >
> > Also, there's no information here where to find the list of these xk-
> > numbers.
>
> Those are variables, like xk-shift-l, xk-a, xk-f1. There is also
> llk-keysyms, that has a list of all xk-* symbols and their numeric values.
>
> llk-init initializes all of these variables, using numbers that are
> platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for
> windows. GDK keys match X keys.
>
> Numbers are different, but users should use the variables, not the
> numbers, for binding and comparing to event values.
This should be explicitly told in the doc string. Also, if the xk-*
variables are platform-independent, the rule to map their names to
keyboard keys should be explained somewhere, perhaps in the manual, so
that Lisp programmers could easily know how to identify the keys which
they stand for.
> I can translate the Windows numbers to the X equivalents, so that they
> are equal on both platforms.
I'm not sure this is needed, if the xk-* names are the same.
> In any case, I will document this better, with a manual change
> proposal.
Thanks. This facility definitely needs to be in the ELisp manual
before we install it.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-05 5:41 ` Eli Zaretskii
@ 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-07 21:52 ` Cecilio Pardo
2024-12-13 22:55 ` Cecilio Pardo
1 sibling, 1 reply; 21+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-06 1:01 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: 74423, Cecilio Pardo, monnier
Eli Zaretskii <eliz@gnu.org> writes:
>> Date: Wed, 4 Dec 2024 22:25:32 +0100
>> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, 74423@debbugs.gnu.org
>> From: Cecilio Pardo <cpardo@imayhem.com>
>>
>> On 04/12/2024 21:01, Eli Zaretskii wrote:
>> >> + 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.
>> >
>> > This seems to say that it is impossible to make the value do the same
>> > on all platforms? If so, I think it's less useful than it could be,
>> > because Emacs generally tries to abstract platform-specific issues as
>> > much as possible, to facilitate platform-independent Lisp programs
>> > that work the same on all supported systems.
>> >
>> > Also, there's no information here where to find the list of these xk-
>> > numbers.
>>
>> Those are variables, like xk-shift-l, xk-a, xk-f1. There is also
>> llk-keysyms, that has a list of all xk-* symbols and their numeric values.
>>
>> llk-init initializes all of these variables, using numbers that are
>> platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for
>> windows. GDK keys match X keys.
>>
>> Numbers are different, but users should use the variables, not the
>> numbers, for binding and comparing to event values.
>
> This should be explicitly told in the doc string. Also, if the xk-*
> variables are platform-independent, the rule to map their names to
> keyboard keys should be explained somewhere, perhaps in the manual, so
> that Lisp programmers could easily know how to identify the keys which
> they stand for.
>
>> I can translate the Windows numbers to the X equivalents, so that they
>> are equal on both platforms.
>
> I'm not sure this is needed, if the xk-* names are the same.
>
>> In any case, I will document this better, with a manual change
>> proposal.
>
> Thanks. This facility definitely needs to be in the ELisp manual
> before we install it.
I suggest calling XKeysymToString rather than interning the numerical
value of keysyms received. This gives users some indication of the
identity of these keysyms without tediously referring to (at times
vendor-specific) keysymdef.h files.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-12-07 21:52 ` Cecilio Pardo
0 siblings, 0 replies; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-07 21:52 UTC (permalink / raw)
To: 74423
On 06/12/2024 2:01, Po Lu:
> I suggest calling XKeysymToString rather than interning the numerical
> value of keysyms received. This gives users some indication of the
> identity of these keysyms without tediously referring to (at times
> vendor-specific) keysymdef.h files.
Do you mean replacing variable names like xk-backspace, xk-tab with the
strings returned by XKeysymToString?
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-05 5:41 ` Eli Zaretskii
2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-12-13 22:55 ` Cecilio Pardo
2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-14 11:14 ` Eli Zaretskii
1 sibling, 2 replies; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-13 22:55 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 74423, monnier
[-- Attachment #1: Type: text/plain, Size: 84 bytes --]
This new version includes a manual entry, and corrected doc strings and
comments.
[-- Attachment #2: 0001-Send-events-for-key-presses-and-key-releases.patch --]
[-- Type: text/plain, Size: 39113 bytes --]
From f2381ff38157387c4b3e8ca35009675976fd9717 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 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 | 29 ++++
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, 780 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..8a1f17689c9 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -2658,6 +2658,35 @@ 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.
+
+@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..cf13e5e7e63 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
^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-13 22:55 ` Cecilio Pardo
@ 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-14 9:26 ` Cecilio Pardo
2024-12-14 11:14 ` Eli Zaretskii
1 sibling, 1 reply; 21+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-14 1:16 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: 74423, Eli Zaretskii, monnier
Cecilio Pardo <cpardo@imayhem.com> writes:
> +#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
Why is this !HAVE_XINPUT2? GTK input method support exists
independently of XInput 2.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-12-14 9:26 ` Cecilio Pardo
0 siblings, 0 replies; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-14 9:26 UTC (permalink / raw)
To: Po Lu; +Cc: 74423, Eli Zaretskii, monnier
On 14/12/2024 2:16, Po Lu wrote:
> Cecilio Pardo <cpardo@imayhem.com> writes:
>
>> +#ifndef HAVE_XINPUT2
>> +static void
>> +xg_maybe_send_low_level_key_event (struct frame *f,
>> + GdkEvent *xev)
>> +{
>> + GdkEventKey xkey = xev->key;
> Why is this !HAVE_XINPUT2? GTK input method support exists
> independently of XInput 2.
When XInput 2 is active, we get the event through it (XI_KeyPress)
first. That gives the information we need. The GTK code is used when
there is no XInput and use_native_input is active.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-13 22:55 ` Cecilio Pardo
2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-12-14 11:14 ` Eli Zaretskii
2024-12-18 10:59 ` Cecilio Pardo
1 sibling, 1 reply; 21+ messages in thread
From: Eli Zaretskii @ 2024-12-14 11:14 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: luangruo, 74423, monnier
> Date: Fri, 13 Dec 2024 23:55:18 +0100
> Cc: luangruo@yahoo.com, 74423@debbugs.gnu.org, monnier@iro.umontreal.ca
> From: Cecilio Pardo <cpardo@imayhem.com>
>
> This new version includes a manual entry, and corrected doc strings and
> comments.
Thanks, a few minor comments.
> +@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.
Aren't these events sent only if enable-low-level-key-events is
non-nil? If so, this should be documented, and the variable itself
should be documented.
> +@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
^^
Two spaces between sentences.
> + 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
^^^^^^^^^^
In doc strings, we quote symbols `like this'.
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-14 11:14 ` Eli Zaretskii
@ 2024-12-18 10:59 ` Cecilio Pardo
2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 1 reply; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-18 10:59 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: luangruo, 74423, monnier
[-- Attachment #1: Type: text/plain, Size: 240 bytes --]
On 14/12/2024 12:14, Eli Zaretskii wrote:
> Aren't these events sent only if enable-low-level-key-events is
> non-nil? If so, this should be documented, and the variable itself
> should be documented.
Fixed this and the style mistakes.
[-- Attachment #2: 0001-Send-events-for-key-presses-and-key-releases.patch --]
[-- Type: text/plain, Size: 39324 bytes --]
From 9e7d219543b2dd424727125910ec711f1aa6cb55 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 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
^ permalink raw reply related [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-18 10:59 ` Cecilio Pardo
@ 2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-26 10:16 ` Cecilio Pardo
0 siblings, 1 reply; 21+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-22 4:31 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: luangruo, 74423, Eli Zaretskii
Hi, this is shaping up quite nicely, AFAICT.
I'm not expert in the C level part of that code, so I provide
feedback below only for the Lisp-facing part.
Stefan
> --- 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.
Rather than saying it's sent "only on GUI systems", I'd say sent "only
on systems where it is supported": I wouldn't be surprised to hear that
some terminal emulators offer ways to support it.
> +@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.
Please clarify the origin of the event's time (or if possible make it
a valid/normal Emacs time object).
> +@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.
I get the impression that those variables/constants may be
replaced/complemented in the future with alternatives, such as functions
that convert the key number to a symbol, or functions that test whether
the key number is within some set of keys described as symbols, ...
So the documentation of the meaning of those key numbers via associated
variables/constants should probably be placed elsewhere.
> +These variables are initialized by calling the function @code{llk-init}.
> +This function also binds a handler to this event, which allows to detect
[ "Allow" is transitive, so you need to say "allows us" or "allows users"
or something like (or use some other formulation altogether). ]
> --- /dev/null
> +++ b/lisp/low-level-key.el
> @@ -0,0 +1,342 @@
> +;;; -*- lexical-binding: t -*-
Please follow our convention for the first line of ELisp files, and then
include our usual copyright+license blurb.
> +;; 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.
I suggest you replace that text with a `cl-defstruct` (where you can
include the same information but in a way that's available via
`cl-describe-type`). And then you can stop using `cl-third` and use
proper accessors instead.
> +(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.")
AFAICT this is a list of elements of the form `(tab KEY FUNCTION)`.
Why not drop the `tap` and make it a "normal" keymap?
> +(defvar llk-tap-count 2
> + "Number or key press/releases to consider a tap.")
Shouldn't `llk-tap-count` be specified individually for each binding?
> +(defvar llk-tap-timeout 1000
> + "Time (ms) between consecutive key presses/releases to consider a tap.")
Default it to `mouse-double-click-time` (and maybe double it for triple
taps)?
> +(defvar llk-tap-keys
> + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta)
> + "Keys that can generate taps.")
This seems redundant with `llk-bindings`.
> +(defvar llk-describe-next-press nil
> + "Internal variable to mark that next key press should be described.")
Use a "--" in the var's name for internal variables.
> +(defmacro define-xk (name x-keysym w32-keysym)
> + "Internal macro to define keysyms."
Same thing for macros.
> + `(let ((ksym (pcase (window-system)
> + ('pgtk ,x-keysym)
> + ('x ,x-keysym)
> + ('w32 ,w32-keysym))))
You can combine the first two branches into:
((or 'pgtk 'x) ,x-keysym)
> + ;; 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
These tables should be somehow auto-generated, IMO.
> +(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."
Does the user need to know about those things it does?
Would there be any harm to call it automatically when the package is
loaded, or the first time `llk-bind` is called? IOW, please try and
make it unnecessary for the caller to know and call this function (and
then make it internal with a "--" in its name).
> + (interactive)
Why interactive?
> +(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)))
I think the `cl-defstruct` will define those functions for you (with
a different name, but I think it's OK because these aren't applicable
to other events anyway).
> +;; 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."
"--"
I think it should not take `n` as an argument and instead it should
automatically discover how many times the key has been tapped and
generate events of the form `double-xk-shift-l`, `triple-xk-shift-l`,
`tap-xk-shift-l`.
> + (cl-every 'eq
Please #' to quote function names. Here and elsewhere.
> +(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))
Can we get rid of `llk-describe-next-press` and instead temporarily
(define-key special-event-map [low-level-key] 'llk--describe)
or something like that?
> +(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))
Maybe instead of focusing on running a command, `llk-handle` and
`llk-bind` should concentrate on generating "normal" events that are
then looked up in the normal keymaps.
So the user would use them only to specify which events should be
recognized and which should be ignored. Then the user can use the
standard keymaps to declare what to do in response to `double-xk-shift-l`.
WDYT?
> + ((functionp func) (funcall func))))))))
Why?
Stefan
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-12-26 10:16 ` Cecilio Pardo
2024-12-26 14:54 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 1 reply; 21+ messages in thread
From: Cecilio Pardo @ 2024-12-26 10:16 UTC (permalink / raw)
To: Stefan Monnier; +Cc: luangruo, 74423, Eli Zaretskii
On 22/12/2024 5:31, Stefan Monnier wrote:
> I'm not expert in the C level part of that code, so I provide
> feedback below only for the Lisp-facing part.
Thanks a lot for the review. I'm working on it.
> Maybe instead of focusing on running a command, `llk-handle` and
> `llk-bind` should concentrate on generating "normal" events that are
> then looked up in the normal keymaps.
>
> So the user would use them only to specify which events should be
> recognized and which should be ignored. Then the user can use the
> standard keymaps to declare what to do in response to `double-xk-shift-l`.
> WDYT?
Yes, I think that's better. I did it the other way to keep it as
separate as possible from normal events, as some people thought this was
a bad idea.
How can we make sure that only the user configures this, and external
packages do not?
^ permalink raw reply [flat|nested] 21+ messages in thread
* bug#74423: Low level key events
2024-12-26 10:16 ` Cecilio Pardo
@ 2024-12-26 14:54 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
0 siblings, 0 replies; 21+ messages in thread
From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-26 14:54 UTC (permalink / raw)
To: Cecilio Pardo; +Cc: luangruo, 74423, Eli Zaretskii
> How can we make sure that only the user configures this, and external
> packages do not?
I can't think of a good reason why we would actively want to prevent
external packages from touching this. At least not via technical means.
As a matter of fact, I think we'd welcome packages dedicated to
configuring this: your code is currently layered as a C part which
provides the low-level events and an ELisp part which constructs useful
semantics out of it, but that ELisp part can be thought of as a separate
package, so we already have an example of useful "external" package
configuring this. Another package could provide an alternative way to
use those low-level events.
As for "normal" packages (say major modes), I suspect they will tend to
naturally refrain from touching it because it's not
available everywhere.
We may document guidelines about those low-level events alongside our
conventions for key bindings, OTOH.
Stefan
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2024-12-26 14:54 UTC | newest]
Thread overview: 21+ 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
2024-12-04 20:01 ` Eli Zaretskii
2024-12-04 21:25 ` Cecilio Pardo
2024-12-05 5:41 ` Eli Zaretskii
2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-07 21:52 ` Cecilio Pardo
2024-12-13 22:55 ` Cecilio Pardo
2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-14 9:26 ` Cecilio Pardo
2024-12-14 11:14 ` Eli Zaretskii
2024-12-18 10:59 ` Cecilio Pardo
2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-12-26 10:16 ` Cecilio Pardo
2024-12-26 14:54 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
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).