xkb.lisp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. ;;; xkb.lisp --- Wrapper for clx-xkeyboard library
  2. ;; Copyright © 2013–2016, 2019 Alex Kost <alezost@gmail.com>
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;;
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; This file uses xkeyboard extension
  17. ;; <https://github.com/filonenko-mikhail/clx-xkeyboard> (I installed it
  18. ;; with quicklisp to make it available in my config). A big part of the
  19. ;; following code came from the stumpwm example of that extension.
  20. ;; This file provides some functions and commands for working with
  21. ;; layouts. I use it for:
  22. ;;
  23. ;; - different key bindings for different layouts,
  24. ;; - different layouts for different windows,
  25. ;; - setting internal input method in emacs if it is the current window
  26. ;; (by sending a specified key sequence to it) instead of the global
  27. ;; layout switching.
  28. ;; Also I use clx-xkeyboard to control CapsLock, NumLock (to get their
  29. ;; values for the mode line and to change these values).
  30. ;;; Code:
  31. (in-package :stumpwm)
  32. ;;; Keyboard layouts
  33. (defun al/layout-string (group)
  34. "Convert xkb group (number) to a string suitable for the mode line."
  35. ;; Layouts ("dvorak"/"йцукен"/"qwerty") are set by my Xorg config:
  36. ;; <https://github.com/alezost/config/blob/master/X/xorg.conf/10-input.conf>.
  37. (case group
  38. (0 "dv")
  39. (1 "ru")
  40. (2 "qw")))
  41. (defun al/current-layout (&optional (display *display*))
  42. "Return current keyboard layout."
  43. (xlib:device-state-locked-group (xlib:get-state display)))
  44. (defun al/window-layout (window)
  45. "Return keyboard layout of a specified WINDOW."
  46. (getf (xlib:window-plist (window-xwin window))
  47. :keyboard-layout))
  48. (defun al/set-display-layout (group &optional (display *display*))
  49. "Set keyboard layout to a specified GROUP."
  50. (xlib:lock-group display :group group)
  51. (xlib:display-finish-output display))
  52. (defun al/update-window-layout (window previous-window)
  53. "Update keyboard layout when switching from PREVIOUS-WINDOW to WINDOW."
  54. (let ((current-layout (al/current-layout)))
  55. (when previous-window
  56. (setf (getf (xlib:window-plist (window-xwin previous-window))
  57. :keyboard-layout)
  58. current-layout)
  59. (when window
  60. (let ((window-layout (al/window-layout window)))
  61. (when (and window-layout
  62. (not (equal current-layout window-layout)))
  63. (al/set-display-layout window-layout)))))))
  64. (defun al/update-group-layout (group previous-group)
  65. "Update keyboard layout when switching from PREVIOUS-GROUP to GROUP."
  66. (al/update-window-layout (group-current-window group)
  67. (group-current-window previous-group)))
  68. (defcommand al/enable-per-window-layout () ()
  69. "Enable changing keyboard layouts per window."
  70. (add-hook *focus-window-hook* 'al/update-window-layout)
  71. (add-hook *focus-group-hook* 'al/update-group-layout))
  72. (defcommand al/disable-per-window-layout () ()
  73. "Disable changing keyboard layouts per window."
  74. (remove-hook *focus-window-hook* 'al/update-window-layout)
  75. (remove-hook *focus-group-hook* 'al/update-group-layout))
  76. (defcommand al/set-layout (group &optional key)
  77. ((:number "Layout number: ") :key)
  78. "Set keyboard layout to a specified xkb GROUP.
  79. If current window is emacs, send a key sequence KEY to it (if specified)."
  80. (when (and key (al/emacs-window-p))
  81. (setq group 0)
  82. (al/send-key key))
  83. (al/set-display-layout group))
  84. ;;; Mod locks (CapsLock, NumLock, etc.)
  85. ;; These constants were found experimentally (I didn't bother to find
  86. ;; the meaning of the higher bits). I didn't find any mention of the
  87. ;; possible values of "ModLocks" in the XKeyboard Protocol Specification
  88. ;; <https://www.x.org/releases/current/doc/kbproto/xkbproto.html>.
  89. ;; So what is the source of these values (where are they hard-coded)?
  90. (defconstant +shift-lock+ #b1)
  91. (defconstant +caps-lock+ #b10)
  92. (defconstant +ctrl-lock+ #b100)
  93. (defconstant +alt-lock+ #b1000)
  94. (defconstant +num-lock+ #b10000)
  95. (defconstant +mod3-lock+ #b100000) ; Hyper
  96. (defconstant +mod4-lock+ #b1000000) ; Super
  97. (defun al/mod-lock-state (mod mods)
  98. "Return t if MOD lock is enabled in MODS bits.
  99. Return nil otherwise."
  100. (not (zerop (logand mod mods))))
  101. (defun al/set-mod-locks (mod-locks &optional affect-mod-locks)
  102. "Set key mod locks according to MOD-LOCKS bits.
  103. If AFFECT-MOD-LOCKS is nil, use the value of MOD-LOCKS."
  104. (xlib:latch-lock-state
  105. *display*
  106. :mod-locks mod-locks
  107. :affect-mod-locks (or affect-mod-locks mod-locks)
  108. :lock-group nil
  109. :group-lock 0
  110. :mod-latches 0
  111. :affect-mod-latches 0
  112. :latch-group nil
  113. :group-latch 0)
  114. (xlib:display-finish-output *display*))
  115. (defun al/toggle-mod-lock (mod-lock)
  116. "Toggle MOD-LOCK key."
  117. (if (al/mod-lock-state mod-lock
  118. (xlib:device-state-locked-mods
  119. (xlib:get-state *display*)))
  120. (al/set-mod-locks 0 mod-lock)
  121. (al/set-mod-locks mod-lock)))
  122. (defcommand al/toggle-caps-lock () ()
  123. "Toggle CapsLock key."
  124. (al/toggle-mod-lock +caps-lock+))
  125. ;;; xkb.lisp ends here