;; Copyright (C) 2003-2008 Shawn Betts ;; ;; This file is part of stumpwm. ;; ;; stumpwm is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; stumpwm is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this software; see the file COPYING. If not, see ;; . ;; Commentary: ;; ;; This file handles input stuff ;; ;; Code: (in-package :stumpwm) (export '(*input-history-ignore-duplicates* *input-completion-style* *input-map* *numpad-map* completing-read input-delete-region input-goto-char input-insert-char input-insert-string input-point input-substring input-validate-region read-one-char read-one-line)) ;;; General Utilities (defun take (n list) "Returns a list with the first n elements of the given list." (loop repeat n for x in list collect x)) ;; This could use a much more efficient algorithm. ;; But for our purposes with small lists it's likely ok. (defun longest-common-prefix (seqs &key (test #'eql)) "Returns the length of the longest common prefix of the sequences." (flet ((longest-common-prefix-2 (seq1 seq2) (if-let ((i (mismatch seq1 seq2 :test test))) i (length seq1)))) (apply #'min (map-product #'longest-common-prefix-2 seqs seqs)))) (defstruct input-line string position history history-bk password) ;;; completion styles (defgeneric input-completion-reset (completion-style completions) (:documentation "A completion style should implement this function and reset its state when called.")) (defgeneric input-completion-complete (completion-style input direction) (:documentation "A completion style should implement this function and complete the input by mutating it.")) (defclass input-completion-style-cyclic () ((completions :initform nil :type list) (idx :initform 0 :type fixnum))) (defmethod input-completion-reset ((cs input-completion-style-cyclic) completions) (setf (slot-value cs 'completions) completions (slot-value cs 'idx) 0)) (defmethod input-completion-complete ((cs input-completion-style-cyclic) input direction) (with-slots (completions idx) cs (let ((completion-count (length completions))) (if completions (progn (let ((elt (nth idx completions))) (input-delete-region input 0 (input-point input)) (input-insert-string input (if (listp elt) (first elt) elt)) (input-insert-char input #\Space)) ;; Prepare the next completion (setf idx (mod (+ idx (if (eq direction :forward) 1 -1)) completion-count)) :error))))) (defun make-input-completion-style-cyclic () (make-instance 'input-completion-style-cyclic)) (defclass input-completion-style-unambiguous () ((display-limit :initarg :display-limit :initform 64 :type fixnum) (completions :initform nil :type list))) (defmethod input-completion-reset ((cs input-completion-style-unambiguous) completions) (setf (slot-value cs 'completions) (take (slot-value cs 'display-limit) completions))) (defmethod input-completion-complete ((cs input-completion-style-unambiguous) input direction) (declare (ignore direction)) (with-slots (completions) cs (if (null completions) :error (let ((n (longest-common-prefix completions))) (input-delete-region input 0 (input-point input)) (input-insert-string input (subseq (first completions) 0 n)) (if (null (rest completions)) (unmap-message-window (current-screen)) (echo-string-list (current-screen) completions)))))) (defun make-input-completion-style-unambiguous (&key (display-limit 64)) (make-instance 'input-completion-style-unambiguous :display-limit display-limit)) (defvar *input-map* (let ((map (make-sparse-keymap))) (define-key map (kbd "DEL") 'input-delete-backward-char) (define-key map (kbd "M-DEL") 'input-backward-kill-word) (define-key map (kbd "C-d") 'input-delete-forward-char) (define-key map (kbd "M-d") 'input-forward-kill-word) (define-key map (kbd "Delete") 'input-delete-forward-char) (define-key map (kbd "C-f") 'input-forward-char) (define-key map (kbd "Right") 'input-forward-char) (define-key map (kbd "M-f") 'input-forward-word) (define-key map (kbd "C-b") 'input-backward-char) (define-key map (kbd "Left") 'input-backward-char) (define-key map (kbd "M-b") 'input-backward-word) (define-key map (kbd "C-a") 'input-move-beginning-of-line) (define-key map (kbd "Home") 'input-move-beginning-of-line) (define-key map (kbd "C-e") 'input-move-end-of-line) (define-key map (kbd "End") 'input-move-end-of-line) (define-key map (kbd "C-k") 'input-kill-line) (define-key map (kbd "C-u") 'input-kill-to-beginning) (define-key map (kbd "C-p") 'input-history-back) (define-key map (kbd "Up") 'input-history-back) (define-key map (kbd "C-n") 'input-history-forward) (define-key map (kbd "Down") 'input-history-forward) (define-key map (kbd "RET") 'input-submit) (define-key map (kbd "C-g") 'input-abort) (define-key map (kbd "ESC") 'input-abort) (define-key map (kbd "C-y") 'input-yank-selection) (define-key map (kbd "C-Y") 'input-yank-clipboard) (define-key map (kbd "TAB") 'input-complete-forward) (define-key map (kbd "ISO_Left_Tab") 'input-complete-backward) (define-key map t 'input-self-insert) map) "This is the keymap containing all input editing key bindings.") (defvar *input-history* nil "History for the input line.") (defvar *input-shell-history* nil "History for shell lines.") (defvar *input-last-command* nil "The last input command.") (defvar *input-completions* nil "The list of completions") (defvar *input-completion-style* (make-input-completion-style-cyclic) "The completion style to use. A completion style has to implement input-completion-reset and input-completion-complete. Available completion styles include @table @asis @item make-input-completion-style-cyclic @item make-input-completion-style-unambiguous @end table") (defvar *input-history-ignore-duplicates* nil "Do not add a command to the input history if it's already the first in the list.") (defvar *numpad-map* '((87 10 . 16) (88 11 . 16) (89 12 . 16) (106 61 . 16) (83 13 . 16) (84 14 . 16) (85 15 . 16) (86 21 . 17) (79 16 . 16) (80 17 . 16) (81 18 . 16) (63 17 . 17) (82 20 . 16) (104 36 . 16) (91 60 . 16) (90 19 . 16)) "A keycode to keycode map to re-wire numpads when the numlock key is active") ;;; keysym functions (defun is-modifier (keycode) "Return t if keycode is a modifier" (or (find keycode *all-modifiers* :test 'eql) ;; Treat No Symbol keys as modifiers (and therefore ignorable) (= (xlib:keycode->keysym *display* keycode 0) 0))) (defun keycode->character (code mods) (let ((idx (if (member :shift mods) 1 0))) (xlib:keysym->character *display* (xlib:keycode->keysym *display* code idx) 0))) ;;; line and key reading functions (defun setup-input-window (screen prompt input) "Set the input window up to read input" (let* ((height (+ (font-height (screen-font screen)) (* *message-window-y-padding* 2))) (win (screen-input-window screen))) ;; Window dimensions (xlib:with-state (win) (setf (xlib:window-priority win) :above (xlib:drawable-height win) height)) (xlib:map-window win) (draw-input-bucket screen prompt input))) (defun shutdown-input-window (screen) (xlib:unmap-window (screen-input-window screen))) ;; Hack to avoid clobbering input from numpads with numlock on. (defun input-handle-key-press-event (&rest event-slots &key event-key code state &allow-other-keys) (declare (ignore event-slots)) (let ((numlock-on-p (= 2 (logand 2 (nth-value 4 (xlib:keyboard-control *display*))))) (numpad-key (assoc code *numpad-map*))) (when (and numlock-on-p numpad-key) (setf code (first (rest numpad-key)) state (rest (rest numpad-key)))) (list* event-key code state))) (defun input-handle-selection-event (&key window selection property &allow-other-keys) (declare (ignore selection)) (if property (xlib:get-property window property :type :string :result-type 'string :transform #'xlib:card8->char :delete-p t) "")) (defun input-handle-click-event (&key root-x root-y &allow-other-keys) (list :button-press root-x root-y)) (defun read-key-handle-event (&rest event-slots &key event-key &allow-other-keys) (case event-key ((or :key-release :key-press) (apply 'input-handle-key-press-event event-slots)) (t nil))) (defun read-key-or-selection-handle-event (&rest event-slots &key event-key &allow-other-keys) (case event-key ((or :key-release :key-press) (apply 'input-handle-key-press-event event-slots)) (:selection-notify (apply 'input-handle-selection-event event-slots)) (t nil))) (defun read-key-or-click-handle-event (&rest event-slots &key event-key &allow-other-keys) (case event-key ((or :key-release :key-press) (apply 'input-handle-key-press-event event-slots)) (:button-press (apply 'input-handle-click-event event-slots)) (t nil))) (defun read-key () "Return a dotted pair (code . state) key." (loop for ev = (xlib:process-event *display* :handler #'read-key-handle-event :timeout nil) do (when (and (consp ev) (eq (first ev) :key-press)) (return (rest ev))))) (defun read-key-or-click () (loop for ev = (xlib:process-event *display* :handler #'read-key-or-click-handle-event :timeout nil) do (when (consp ev) (when (eq (first ev) :key-press) (return (values nil (rest ev) nil nil))) (when (eq (first ev) :button-press) (return (values t nil (second ev) (third ev))))))) (defun read-key-no-modifiers () "Like read-key but never returns a modifier key." (loop for k = (read-key) while (is-modifier (car k)) finally (return k))) (defun read-key-no-modifiers-or-click () (loop (multiple-value-bind (has-click k x y) (read-key-or-click) (if has-click (return (values t nil x y)) (unless (is-modifier (car k)) (return (values nil k nil nil))))))) (defun read-key-or-selection () (loop for ev = (xlib:process-event *display* :handler #'read-key-or-selection-handle-event :timeout nil) do (cond ((stringp ev) (return ev)) ((and (consp ev) (eq (first ev) :key-press)) (return (rest ev)))))) (defun make-input-string (initial-input) (make-array (length initial-input) :element-type 'character :initial-contents initial-input :adjustable t :fill-pointer t)) (defun completing-read (screen prompt completions &key (initial-input "") require-match) "Read a line of input through stumpwm and return it with TAB completion. Completions can be a list, an fbound symbol, or a function. If its an fbound symbol or a function then that function is passed the substring to complete on and is expected to return a list of matches. If require-match argument is non-nil then the input must match with an element of the completions." (check-type completions (or list function symbol)) (let ((line (read-one-line screen prompt :completions completions :initial-input initial-input :require-match require-match))) (when line (string-trim " " line)))) (defun read-one-line (screen prompt &key completions (initial-input "") require-match password) "Read a line of input through stumpwm and return it. Returns nil if the user aborted." (let ((*input-last-command* nil) (*input-completions* completions) (input (make-input-line :string (make-input-string initial-input) :position (length initial-input) :history -1 :password password))) (labels ((match-input () (let* ((in (string-trim " " (input-line-string input))) (compls (input-find-completions in completions))) (and (consp compls) (string= in (if (consp (car compls)) (caar compls) (car compls)))))) (key-loop () (loop for key = (read-key-or-selection) do (cond ((stringp key) ;; handle selection (input-insert-string input key) (draw-input-bucket screen prompt input)) ;; skip modifiers ((is-modifier (car key))) ((process-input screen prompt input (car key) (cdr key)) (if (or (not require-match) (match-input)) (return (input-line-string input)) (draw-input-bucket screen prompt input "[No match]" t))))))) (setup-input-window screen prompt input) (catch :abort (unwind-protect (with-focus (screen-input-window screen) (key-loop)) (shutdown-input-window screen)))))) (defun read-one-char (screen) "Read a single character from the user." (with-focus (screen-key-window screen) (let ((k (read-key-no-modifiers))) (keycode->character (car k) (xlib:make-state-keys (cdr k)))))) (defun read-one-char-or-click (group) "Read a single character from the user or a click." (with-focus (screen-key-window (group-screen group)) (multiple-value-bind (has-click k x y) (read-key-no-modifiers-or-click) (if has-click (values t nil x y) (values nil (keycode->character (car k) (xlib:make-state-keys (cdr k))) nil nil))))) (defun prompt-text-y (index font y-padding) "Calculate the y position of text in a prompt." (+ y-padding (* (font-height font) index) (font-ascent font))) (defun draw-input-bucket (screen prompt input &optional (tail "") errorp) "Draw to the screen's input window the contents of input." (let* ((gcontext (screen-message-gc screen)) (win (screen-input-window screen)) (font (screen-font screen)) (prompt-lines (ppcre:split #\Newline prompt)) (prompt-lines-length (length prompt-lines)) (prompt-width (loop :for line :in prompt-lines :maximize (text-line-width font line :translate #'translate-id))) (prompt-offset (text-line-width font (first (last prompt-lines)) :translate #'translate-id)) (line-content (input-line-string input)) (string (if (input-line-password input) (make-string (length line-content) :initial-element #\*) line-content)) (string-width (loop for char across string summing (text-line-width (screen-font screen) (string char) :translate #'translate-id))) (space-width (text-line-width (screen-font screen) " " :translate #'translate-id)) (tail-width (text-line-width (screen-font screen) tail :translate #'translate-id)) (full-string-width (+ string-width space-width)) (pos (input-line-position input)) (width (max prompt-width (+ prompt-offset (max 100 (+ full-string-width space-width tail-width)))))) (when errorp (rotatef (xlib:gcontext-background gcontext) (xlib:gcontext-foreground gcontext))) (xlib:with-state (win) (xlib:with-gcontext (gcontext :foreground (xlib:gcontext-background gcontext)) (xlib:draw-rectangle win gcontext 0 0 (xlib:drawable-width win) (xlib:drawable-height win) t)) (setf (xlib:drawable-width win) (+ width (* *message-window-padding* 2))) (setf (xlib:drawable-height win) (+ (* prompt-lines-length (font-height font)) (* *message-window-y-padding* 2))) (setup-win-gravity screen win *input-window-gravity*)) (xlib:with-state (win) (loop for i from 0 below prompt-lines-length do (draw-image-glyphs win gcontext font *message-window-padding* (prompt-text-y i font *message-window-y-padding*) (nth i prompt-lines) :translate #'translate-id :size 16)) (loop with x = (+ *message-window-padding* prompt-offset) for char across string for i from 0 below (length string) for char-width = (text-line-width (screen-font screen) (string char) :translate #'translate-id) if (= pos i) do (xlib:with-gcontext (gcontext :foreground (xlib:gcontext-background gcontext) :background (xlib:gcontext-foreground gcontext)) (draw-image-glyphs win gcontext (screen-font screen) x (prompt-text-y (1- prompt-lines-length) font *message-window-y-padding*) (string char) :translate #'translate-id :size 16)) else do (draw-image-glyphs win gcontext (screen-font screen) x (prompt-text-y (1- prompt-lines-length) font *message-window-y-padding*) (string char) :translate #'translate-id :size 16) end do (incf x char-width) finally (when (>= pos (length string)) (xlib:with-gcontext (gcontext :foreground (xlib:gcontext-background gcontext) :background (xlib:gcontext-foreground gcontext)) (draw-image-glyphs win gcontext (screen-font screen) x (prompt-text-y (1- prompt-lines-length) font *message-window-y-padding*) " " :translate #'translate-id :size 16)))) (draw-image-glyphs win gcontext (screen-font screen) (+ *message-window-padding* prompt-offset full-string-width space-width) (prompt-text-y (1- prompt-lines-length) font *message-window-y-padding*) tail :translate #'translate-id :size 16)) (when errorp (sleep 0.05) (rotatef (xlib:gcontext-background gcontext) (xlib:gcontext-foreground gcontext)) (draw-input-bucket screen prompt input tail)))) (defun code-state->key (code state) (let* ((mods (xlib:make-state-keys state)) (shift-p (and (find :shift mods) t)) (altgr-p (and (intersection (modifiers-altgr *modifiers*) mods) t)) (base (if altgr-p 2 0)) (sym (xlib:keycode->keysym *display* code base)) (upsym (xlib:keycode->keysym *display* code (+ base 1)))) ;; If a keysym has a shift modifier, then use the uppercase keysym ;; and remove remove the shift modifier. (make-key :keysym (if (and shift-p (not (eql sym upsym))) upsym sym) :control (and (find :control mods) t) :shift (and shift-p (eql sym upsym)) :meta (and (intersection mods (modifiers-meta *modifiers*)) t) :alt (and (intersection mods (modifiers-alt *modifiers*)) t) :hyper (and (intersection mods (modifiers-hyper *modifiers*)) t) :super (and (intersection mods (modifiers-super *modifiers*)) t)))) ;;; input string utility functions (defun input-submit (input key) (declare (ignore input key)) :done) (defun input-abort (input key) (declare (ignore input key)) (throw :abort nil)) (defun input-goto-char (input point) "Move the cursor to the specified point in the string" (setf (input-line-position input) (min (max 0 point) (length (input-line-string input))))) (defun input-insert-string (input string) "Insert @var{string} into the input at the current position. @var{input} must be of type @var{input-line}. Input functions are passed this structure as their first argument." (check-type string string) (loop for c across string do (input-insert-char input c))) (defun input-point (input) "Return the position of the cursor." (check-type input input-line) (input-line-position input)) (defun input-validate-region (input start end) "Return a value pair of numbers where the first number is < the second and neither excedes the bounds of the input string." (values (max 0 (min start end)) (min (length (input-line-string input)) (max start end)))) (defun input-delete-region (input start end) "Delete the region between start and end in the input string" (check-type input input-line) (check-type start fixnum) (check-type end fixnum) (multiple-value-setq (start end) (input-validate-region input start end)) (replace (input-line-string input) (input-line-string input) :start2 end :start1 start) (decf (fill-pointer (input-line-string input)) (- end start)) (cond ((< (input-line-position input) start)) ((< (input-line-position input) end) (setf (input-line-position input) start)) (t (decf (input-line-position input) (- end start))))) (defun input-insert-char (input char) "Insert @var{char} into the input at the current position. @var{input} must be of type @var{input-line}. Input functions are passed this structure as their first argument." (vector-push-extend #\_ (input-line-string input)) (replace (input-line-string input) (input-line-string input) :start2 (input-line-position input) :start1 (1+ (input-line-position input))) (setf (char (input-line-string input) (input-line-position input)) char) (incf (input-line-position input))) (defun input-substring (input start end) "Return a the substring in INPUT bounded by START and END." (subseq (input-line-string input) start end)) ;;; "interactive" input functions (defun input-find-completions (str completions) (if (or (functionp completions) (and (symbolp completions) (fboundp completions))) (funcall completions str) (remove-if-not (lambda (elt) (when (listp elt) (setf elt (car elt))) (and (<= (length str) (length elt)) (string= str elt :end1 (length str) :end2 (length str)))) completions))) (defun input-complete (input direction) (unless (find *input-last-command* '(input-complete-forward input-complete-backward)) (input-completion-reset *input-completion-style* (input-find-completions (input-substring input 0 (input-point input)) *input-completions*))) (input-completion-complete *input-completion-style* input direction)) (defun input-complete-forward (input key) (declare (ignore key)) (input-complete input :forward)) (defun input-complete-backward (input key) (declare (ignore key)) (input-complete input :backward)) (defun input-delete-backward-char (input key) (declare (ignore key)) (let ((pos (input-line-position input))) (cond ((or (<= (length (input-line-string input)) 0) (<= pos 0)) :error) (t (replace (input-line-string input) (input-line-string input) :start2 pos :start1 (1- pos)) (decf (fill-pointer (input-line-string input))) (decf (input-line-position input)))))) (defun input-delete-forward-char (input key) (declare (ignore key)) (let ((pos (input-line-position input))) (cond ((>= pos (length (input-line-string input))) :error) (t (replace (input-line-string input) (input-line-string input) :start1 pos :start2 (1+ pos)) (decf (fill-pointer (input-line-string input))))))) (defun input-forward-kill-word (input key) (declare (ignore key)) (let* ((p1 (position-if 'alphanumericp (input-line-string input) :start (input-line-position input))) (p2 (and p1 (position-if-not 'alphanumericp (input-line-string input) :start p1)))) (input-delete-region input (input-point input) (or p2 (length (input-line-string input)))))) (defun input-backward-kill-word (input key) (declare (ignore key)) (let* ((p1 (position-if 'alphanumericp (input-line-string input) :end (input-line-position input) :from-end t)) (p2 (and p1 (position-if-not 'alphanumericp (input-line-string input) :end p1 :from-end t)))) (input-delete-region input (input-point input) (or (and p2 (1+ p2)) 0)))) (defun input-forward-word (input key) (declare (ignore key)) (let* ((p1 (position-if 'alphanumericp (input-line-string input) :start (input-line-position input))) (p2 (and p1 (position-if-not 'alphanumericp (input-line-string input) :start p1)))) (setf (input-line-position input) (or p2 (length (input-line-string input)))))) (defun input-backward-word (input key) (declare (ignore key)) (let* ((p1 (position-if 'alphanumericp (input-line-string input) :end (input-line-position input) :from-end t)) (p2 (and p1 (position-if-not 'alphanumericp (input-line-string input) :end p1 :from-end t)))) (setf (input-line-position input) (or (and p2 (1+ p2)) 0)))) (defun input-forward-char (input key) (declare (ignore key)) (incf (input-line-position input)) (when (> (input-line-position input) (length (input-line-string input))) (setf (input-line-position input) (length (input-line-string input))))) (defun input-backward-char (input key) (declare (ignore key)) (decf (input-line-position input)) (when (< (input-line-position input) 0) (setf (input-line-position input) 0))) (defun input-move-beginning-of-line (input key) (declare (ignore key)) (setf (input-line-position input) 0)) (defun input-move-end-of-line (input key) (declare (ignore key)) (setf (input-line-position input) (length (input-line-string input)))) (defun input-kill-line (input key) (declare (ignore key)) (unless (= (input-line-position input) (length (input-line-string input))) (set-x-selection (subseq (input-line-string input) (input-line-position input)))) (setf (fill-pointer (input-line-string input)) (input-line-position input))) (defun input-kill-to-beginning (input key) (declare (ignore key)) (unless (= (input-line-position input) 0) (set-x-selection (subseq (input-line-string input) 0 (input-line-position input)))) (replace (input-line-string input) (input-line-string input) :start2 (input-line-position input) :start1 0) (decf (fill-pointer (input-line-string input)) (input-line-position input)) (setf (input-line-position input) 0)) (defun input-history-back (input key) (declare (ignore key)) (when (= (input-line-history input) -1) (setf (input-line-history-bk input) (input-line-string input))) (incf (input-line-history input)) (if (>= (input-line-history input) (length *input-history*)) (progn (decf (input-line-history input)) :error) (setf (input-line-string input) (make-input-string (elt *input-history* (input-line-history input))) (input-line-position input) (length (input-line-string input))))) (defun input-history-forward (input key) (declare (ignore key)) (decf (input-line-history input)) (cond ((< (input-line-history input) -1) (incf (input-line-history input)) :error) ((= (input-line-history input) -1) (setf (input-line-string input) (input-line-history-bk input) (input-line-position input) (length (input-line-string input)))) (t (setf (input-line-string input) (make-input-string (elt *input-history* (input-line-history input))) (input-line-position input) (length (input-line-string input)))))) (defun input-self-insert (input key) (let ((char (xlib:keysym->character *display* (key-keysym key)))) (if (or (key-mods-p key) (null char) (not (characterp char))) :error (input-insert-char input char)))) (defun input-yank-selection (input key) (declare (ignore key)) ;; if we own the selection then just insert it. (if (getf *x-selection* :primary) (input-insert-string input (getf *x-selection* :primary)) (xlib:convert-selection :primary :string (screen-input-window (current-screen)) :stumpwm-selection))) (defun input-yank-clipboard (input key) (declare (ignore key)) (if (getf *x-selection* :clipboard) (input-insert-string input (getf *x-selection* :clipboard)) (xlib:convert-selection :clipboard :string (screen-input-window (current-screen)) :stumpwm-selection))) ;;; Misc functions (defun process-input (screen prompt input code state) "Process the key (code and state), given the current input buffer. Returns a new modified input buffer." (labels ((process-key (code state) "Call the appropriate function based on the key pressed. Return 'done when the use has signalled the finish of his input (pressing Return), nil otherwise." (let* ((key (code-state->key code state)) (command (and key (lookup-key *input-map* key t)))) (if command (prog1 (funcall command input key) (setf *input-last-command* command)) :error)))) (case (process-key code state) (:done (unless (or (input-line-password input) (and *input-history-ignore-duplicates* (string= (input-line-string input) (first *input-history*)))) (push (input-line-string input) *input-history*)) :done) (:abort (throw :abort t)) (:error ;; FIXME draw inverted text (draw-input-bucket screen prompt input "" t) nil) (t (draw-input-bucket screen prompt input) nil)))) (defun all-modifier-codes () "Return all the keycodes that are associated with a modifier." (flatten (multiple-value-list (xlib:modifier-mapping *display*)))) (defun get-modifier-map () (labels ((find-mod (mod codes) (let* ((keysym (keysym-name->keysym mod)) (keycodes (multiple-value-list (xlib:keysym->keycodes *display* keysym)))) (intersection keycodes codes)))) (let ((modifiers (make-modifiers))) (multiple-value-bind (shift-codes lock-codes control-codes mod1-codes mod2-codes mod3-codes mod4-codes mod5-codes) (xlib:modifier-mapping *display*) (declare (ignore shift-codes lock-codes control-codes)) (loop for mod in '(:mod-1 :mod-2 :mod-3 :mod-4 :mod-5) for codes in (list mod1-codes mod2-codes mod3-codes mod4-codes mod5-codes) do (cond ((or (find-mod "Meta_L" codes) (find-mod "Meta_R" codes)) (push mod (modifiers-meta modifiers))) ((or (find-mod "Alt_L" codes) (find-mod "Alt_R" codes)) (push mod (modifiers-alt modifiers))) ((or (find-mod "Super_L" codes) (find-mod "Super_R" codes)) (push mod (modifiers-super modifiers))) ((or (find-mod "Hyper_L" codes) (find-mod "Hyper_R" codes)) (push mod (modifiers-hyper modifiers))) ((find-mod "Num_Lock" codes) (push mod (modifiers-numlock modifiers))) ((find-mod "ISO_Level3_Shift" codes) (push mod (modifiers-altgr modifiers))))) ;; If alt is defined but meta isn't set meta to alt and clear alt (when (and (modifiers-alt modifiers) (null (modifiers-meta modifiers))) (setf (modifiers-meta modifiers) (modifiers-alt modifiers) (modifiers-alt modifiers) nil)) modifiers)))) (defun update-modifier-map () (setf *modifiers* (get-modifier-map) *all-modifiers* (all-modifier-codes))) ;; (defun x11mod->stumpmod (screen state) ;; (let ((mod nil)) ;; (when (member state (modifiers-alt (screen-modifiers screen))) ;; (push :alt mod)) ;; (when (member state (modifiers-meta (screen-modifiers screen))) ;; (push :meta mod)) ;; (when (member state (modifiers-hyper (screen-modifiers screen))) ;; (push :hyper mod)) ;; (when (member state (modifiers-super (screen-modifiers screen))) ;; (push :super mod)) ;; (when (member state :control) ;; (push :control mod)) ;; mod)) (defun mod->string (state) "Convert a stump modifier list to a string" (let ((alist '((:alt . "A-") (:meta . "M-") (:hyper . "H-") (:super . "S-")))) (apply #'concatenate 'string (mapcar (lambda (x) (cdr (assoc x alist))) state)))) ;; (defun keycode->string (code state) ;; (concatenate 'string (mod->string state) ;; (string (keysym->character *display* ;; (xlib:keycode->keysym *display* code 0) ;; state)))) ;; (defun cook-keycode (code state) ;; (values (xlib:keycode->keysym *display* code 0) (x11mod->stumpmod state))) (defun y-or-n-p (message) "ask a \"y or n\" question on the current screen and return T if the user presses 'y'" (message "~a(y or n) " message) (char= (read-one-char (current-screen)) #\y)) (defun yes-or-no-p (message) "ask a \"yes or no\" question on the current screen and return T if the user presses 'yes'" (loop for line = (string-trim '(#\Space) (read-one-line (current-screen) (format nil "~a(yes or no) " message) :completions '("yes" "no"))) until (find line '("yes" "no") :test 'string-equal) do (message "Please answer yes or no") (sleep 1) finally (return (string-equal line "yes"))))