diff --git a/exwm-config.el b/exwm-config.el index a434fe8..5742cac 100644 --- a/exwm-config.el +++ b/exwm-config.el @@ -52,17 +52,17 @@ (interactive (list (read-shell-command "$ "))) (start-process-shell-command command nil command))) ;; Line-editing shortcuts - (exwm-input-set-simulation-keys - '(([?\C-b] . left) - ([?\C-f] . right) - ([?\C-p] . up) - ([?\C-n] . down) - ([?\C-a] . home) - ([?\C-e] . end) - ([?\M-v] . prior) - ([?\C-v] . next) - ([?\C-d] . delete) - ([?\C-k] . (S-end delete)))) + (setq exwm-input-simulation-keys + '(([?\C-b] . [left]) + ([?\C-f] . [right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\M-v] . [prior]) + ([?\C-v] . [next]) + ([?\C-d] . [delete]) + ([?\C-k] . [S-end delete]))) ;; Enable EXWM (exwm-enable) ;; Configure Ido diff --git a/exwm-core.el b/exwm-core.el index 71498c9..41c3b57 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -45,7 +45,7 @@ (defvar exwm--root nil "Root window.") (defvar exwm-input--global-prefix-keys) -(defvar exwm-input--simulation-prefix-keys) +(defvar exwm-input--simulation-keys) (defvar exwm-input-line-mode-passthrough) (defvar exwm-input-prefix-keys) (declare-function exwm-input--fake-key "exwm-input.el" (event)) @@ -189,7 +189,7 @@ least SECS seconds later." (active-minibuffer-window) (memq last-input-event exwm-input--global-prefix-keys) (memq last-input-event exwm-input-prefix-keys) - (memq last-input-event exwm-input--simulation-prefix-keys)) + (gethash last-input-event exwm-input--simulation-keys)) (set-transient-map (make-composed-keymap (list exwm-mode-map global-map))) (push last-input-event unread-command-events)) @@ -229,19 +229,20 @@ least SECS seconds later." ;; This is merely a reference. ("Send simulation key" :filter (lambda (&rest _args) - (mapcar (lambda (i) - (let ((keys (cdr i))) - (if (vectorp keys) - (setq keys (append keys)) - (unless (sequencep keys) - (setq keys (list keys)))) - (vector (key-description keys) - `(lambda () - (interactive) - (dolist (key ',keys) - (exwm-input--fake-key key))) - :keys (key-description (car i))))) - exwm-input--simulation-keys))) + (let (result) + (maphash + (lambda (key value) + (when (sequencep key) + (setq result (append result + `([ + ,(key-description value) + (lambda () + (interactive) + (dolist (i ',value) + (exwm-input--fake-key i))) + :keys ,(key-description key)]))))) + exwm-input--simulation-keys) + result))) ["Define global binding" exwm-input-set-key] diff --git a/exwm-input.el b/exwm-input.el index e536a14..73a0dba 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -113,9 +113,6 @@ (defvar exwm-input--simulation-keys nil "Simulation keys in line-mode.") -(defvar exwm-input--simulation-prefix-keys nil - "List of prefix keys of simulation keys in line-mode.") - (defvar exwm-input--temp-line-mode nil "Non-nil indicates it's in temporary line-mode for char-mode.") @@ -537,7 +534,7 @@ called interactively. Only invoke it non-interactively in configuration." ;; (memq event exwm-input--global-prefix-keys) (memq event exwm-input-prefix-keys) - (memq event exwm-input--simulation-prefix-keys))) + (gethash event exwm-input--simulation-keys))) (setq mode xcb:Allow:AsyncKeyboard) (exwm-input--cache-event event)) (unless mode @@ -733,50 +730,111 @@ multiple keys." ;; (unless (listp last-input-event) ;not a key event ;; (exwm-input--fake-key last-input-event))) -(defun exwm-input--update-simulation-prefix-keys () - "Update the list of prefix keys of simulation keys." - (setq exwm-input--simulation-prefix-keys nil) - (dolist (i exwm-input--simulation-keys) - (if exwm-input--local-simulation-keys - (local-set-key (car i) #'exwm-input-send-simulation-key) - (define-key exwm-mode-map (car i) #'exwm-input-send-simulation-key)) - (cl-pushnew (elt (car i) 0) exwm-input--simulation-prefix-keys))) - (defun exwm-input-set-simulation-keys (simulation-keys) "Set simulation keys. -SIMULATION-KEYS is an alist of the form (original-key . simulated-key)." - (setq exwm-input--simulation-keys nil) +SIMULATION-KEYS is an alist of the form (original-key . simulated-key), +where both original-key and simulated-key are key sequences. + +Simulation keys set this way take effect in real time. For configuration +it's recommended to customize or set `exwm-input-simulation-keys' instead." + ;; Clear keymaps and the hash table. + (when (hash-table-p exwm-input--simulation-keys) + (maphash (lambda (key _value) + (when (sequencep key) + (if exwm-input--local-simulation-keys + (local-unset-key key) + (define-key exwm-mode-map key nil)))) + exwm-input--simulation-keys) + (clrhash exwm-input--simulation-keys)) + ;; Update the hash table. + (setq exwm-input--simulation-keys (make-hash-table :test #'equal)) (dolist (i simulation-keys) - (cl-pushnew `(,(vconcat (car i)) . ,(cdr i)) exwm-input--simulation-keys)) - (exwm-input--update-simulation-prefix-keys)) + (let ((original (vconcat (car i))) + (simulated (cdr i))) + (setq simulated (if (sequencep simulated) + (append simulated nil) + (list simulated))) + ;; The key stored is a key sequence (vector). + ;; The value stored is a list of key events. + (puthash original simulated exwm-input--simulation-keys) + ;; Also mark the prefix key as used. + (puthash (aref original 0) t exwm-input--simulation-keys))) + ;; Update keymaps. + (maphash (lambda (key _value) + (when (sequencep key) + (if exwm-input--local-simulation-keys + (local-set-key key #'exwm-input-send-simulation-key) + (define-key exwm-mode-map key + #'exwm-input-send-simulation-key)))) + exwm-input--simulation-keys)) + +(defcustom exwm-input-simulation-keys nil + "Simulation keys. + +It is an alist of the form (original-key . simulated-key), where both +original-key and simulated-key are key sequences. Original-key is what you +type to an X window in line-mode which then gets translated to simulated-key +by EXWM and forwarded to the X window. + +Notes: +* Setting the value directly (rather than customizing it) after EXWM + finishes initialization has no effect. +* Original-keys consist of multiple key events are only supported in Emacs + 27 and later. +* A minority of applications do not accept simulated keys by default. It's + required to customize them to accept events sent by SendEvent. +* The predefined examples in the Customize interface are not guaranteed to + work for all applications. This can be tweaked on a per application basis + with `exwm-input-set-local-simulation-keys'." + :type '(alist :key-type (choice (key-sequence :tag "Original")) + :value-type (choice (key-sequence :tag "Move left" [left]) + (key-sequence :tag "Move right" [right]) + (key-sequence :tag "Move up" [up]) + (key-sequence :tag "Move down" [down]) + (key-sequence :tag "Move to BOL" [home]) + (key-sequence :tag "Move to EOL" [end]) + (key-sequence :tag "Page up" [prior]) + (key-sequence :tag "Page down" [next]) + (key-sequence :tag "Copy" [C-c]) + (key-sequence :tag "Paste" [C-v]) + (key-sequence :tag "Delete" [delete]) + (key-sequence :tag "Delete to EOL" + [S-end delete]) + (key-sequence :tag "User-defined"))) + :set (lambda (symbol value) + (set symbol value) + (exwm-input-set-simulation-keys value))) + +(defun exwm-input--unset-simulation-keys () + "Clear simulation keys and key bindings defined." + (when (hash-table-p exwm-input--simulation-keys) + (maphash (lambda (key _value) + (when (sequencep key) + (define-key exwm-mode-map key nil))) + exwm-input--simulation-keys) + (clrhash exwm-input--simulation-keys))) (defun exwm-input-set-local-simulation-keys (simulation-keys) "Set buffer-local simulation keys. Its usage is the same with `exwm-input-set-simulation-keys'." (make-local-variable 'exwm-input--simulation-keys) - (make-local-variable 'exwm-input--simulation-prefix-keys) (use-local-map (copy-keymap exwm-mode-map)) (let ((exwm-input--local-simulation-keys t)) (exwm-input-set-simulation-keys simulation-keys))) ;;;###autoload (cl-defun exwm-input-send-simulation-key (times) - "Fake a key event according to the last input key sequence. - -Sending multiple fake keys at once is only supported by Emacs 27 and later." + "Fake a key event according to the last input key sequence." (interactive "p") (unless (derived-mode-p 'exwm-mode) (cl-return-from 'exwm-input-send-simulation-key)) - (let ((pair (assoc (this-single-command-keys) exwm-input--simulation-keys))) - (when pair - (setq pair (cdr pair)) - (unless (listp pair) - (setq pair (list pair))) - (dotimes (_ times) - (dolist (j pair) - (exwm-input--fake-key j)))))) + (let ((keys (gethash (this-single-command-keys) + exwm-input--simulation-keys))) + (dotimes (_ times) + (dolist (key keys) + (exwm-input--fake-key key))))) (defun exwm-input--on-pre-command () "Run in `pre-command-hook'." @@ -814,6 +872,9 @@ Sending multiple fake keys at once is only supported by Emacs 27 and later." :name-len (length atom) :name atom)) 'atom))) + ;; Initialize simulation keys. + (when exwm-input-simulation-keys + (exwm-input-set-simulation-keys exwm-input-simulation-keys)) ;; Attach event listeners (xcb:+event exwm--connection 'xcb:PropertyNotify #'exwm-input--on-PropertyNotify) @@ -841,6 +902,7 @@ Sending multiple fake keys at once is only supported by Emacs 27 and later." (defun exwm-input--exit () "Exit the input module." + (exwm-input--unset-simulation-keys) (remove-hook 'pre-command-hook #'exwm-input--on-pre-command) (remove-hook 'post-command-hook #'exwm-input--on-post-command) (remove-hook 'buffer-list-update-hook #'exwm-input--on-buffer-list-update)