Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the ability to grab modifier keys as individual keys #301

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Advertise your module here, open a PR and include a org-mode link!
- [[./media/stumpwm-mixer/README.md][stumpwm-mixer]] :: Interface to FreeBSD's built-in sound mixer
- [[./media/stumpwm-sndioctl/README.md][stumpwm-sndioctl]] :: Interface to OpenBSD's sndioctl from StumpWM.
** Minor Modes
- [[./minor-mode/grabbable-modifier-keys/README.md][grabbable-modifier-keys]] :: Allow StumpWM users to grab modifier keys and bind them
- [[./minor-mode/mpd/README.org][mpd]] :: Displays information about the music player daemon (MPD).
- [[./minor-mode/notifications/README.org][notifications]] :: A notification library that sends notifications to the modeline via stumpish or from stumpwm itself.
** Modeline
Expand Down
45 changes: 45 additions & 0 deletions minor-mode/grabbable-modifier-keys/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Grabbable Modifier Keys

This module implements grabbable modifier keys, allowing the user to bind
actions to the press and/or release of a modifier key.

## Usage

After loading the module, use `SWM/GMK:SET-MODIFIER-ACTIONS` to set actions for
a modifier key (`:MOD1`, `:LOCK`, `:MOD4`, etc.). Setting an action
automatically registers the modifier key to be grabbed. As an example, to bind
mod4 (super on the authors machine) to toggle the mode line on press/release, we
would place the following in our `.stumpwmrc`

```
(flet ((toggler (c s)
(declare (ignore c s))
(let* ((s (stumpwm:current-screen))
(g (stumpwm:current-group s))
(h (stumpwm:current-head g)))
(stumpwm:toggle-mode-line s h))))
(swm/gmk:set-modifier-actions :mod4 #'toggler #'toggler))
```

## Quirks

Modifier key presses do not follow the normal key binding and command invocation
of StumpWM. Rather they expose the key event parameters `CODE` and `STATE` to
the user, and expect the user to do whatever legwork is neccessary based on
those parameters.

This minor mode *will* clobber any custom key event handler the user has
installed. A way around this is to modify the `:KEY-PRESS` event handler and
change the `UNLESS` form into an `IF`, which calls
`SWM/GMK:MODIFIER-KEY-PRESS-HANDLER` when a modifier code sneaks through.

Additionally, the key press will not have the modifier set in its state mask,
but on key release the modifier will be set in the state mask. E.g. from the
above example if only super is pressed with no other modifiers then the state
will be zero on press and 64 on release (on the authors machine).

Finally, if a key is defined in the top map that uses the modifier, then a press
to that key binding will prevent the firing of the release action. E.g. from our
above example, if `super-p` is bound then pressing super will toggle the mode
line, pressing p will fire whatever command it is bound to, and releasing super
will ***not*** fire the release action.
120 changes: 120 additions & 0 deletions minor-mode/grabbable-modifier-keys/gmk.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

(uiop:define-package #:swm/gmk
(:use :cl)
(:export set-modifier-actions
*modifiers*
modifier-key-release-handler
modifier-key-press-handler))

(in-package #:swm/gmk)

(stumpwm:define-minor-mode grabbable-modifier-keys-mode () ()
(:documentation
"When enabled, modifiers specified by the user are grabbed and functions fired
on their press and release.")
(:scope :unscoped)
(:interactive make-modifiers-grabbable)
(:lighter "GMK")
(:expose-keymaps t))

(defvar *modifiers-to-grab* nil
"Set by the user to control which mods are grabbable")

; grabbable-modifier-keys actions, called on modifier key press and release
; with both the code and the state
(defparameter *gmk-on-mod-press-release* nil
"Internal variable, hold a plist with modifiers as keys and a cons of
press/release functions as the value.")

(defun set-modifier-actions (mod press release)
"Add or replace a modifiers press and release actions. PRESS and RELEASE must be
functions of arity 2 which take the key code and state mask."
(pushnew mod *modifiers-to-grab*)
(setf (getf *gmk-on-mod-press-release* mod) (cons press release))
(when (and (null press) (null release))
(setf *modifiers-to-grab* (remove mod *modifiers-to-grab*))))

(defun invoke-on-press-action (mod code state)
(let ((todo (getf *gmk-on-mod-press-release* mod)))
(when (car todo)
(values (funcall (car todo) code state) t))))

(defun invoke-on-release-action (mod code state)
(let ((todo (getf *gmk-on-mod-press-release* mod)))
(when (cdr todo)
(values (funcall (cdr todo) code state) t))))

; Our modifier map
(defvar *modifiers* nil
"Map our modifiers to their individual keys so we can grab them")

(defun fill-*modifiers* ()
"Fill in our modifier map"
(multiple-value-bind (shift lock control mod1 mod2 mod3 mod4 mod5)
(xlib:modifier-mapping stumpwm:*display*)
(setf *modifiers*
(list :shift shift
:lock lock
:control control
:mod1 mod1
:mod2 mod2
:mod3 mod3
:mod4 mod4
:mod5 mod5))))

(defun grab-modifier-keys (modifier &optional ungrab)
"Grab or ungrab modifier keys for the screen root"
(if ungrab
(loop for key-code in (getf *modifiers* modifier)
do (xlib:ungrab-key (stumpwm:screen-root (stumpwm:current-screen))
key-code))
(loop for key-code in (getf *modifiers* modifier)
do (xlib:grab-key (stumpwm:screen-root (stumpwm:current-screen))
key-code))))

(defun compute-which-mod (code)
"Given CODE, find which modifier it belongs to"
(let ((which-mod
(loop for (mod codes) on *modifiers* by #'cddr
when (member code codes :test #'=)
collect mod)))
(when (cdr which-mod)
(warn "Multiple mods defined for key code ~D" code))
(car which-mod)))

(defun modifier-key-release-handler (code state)
(let ((which-mod (compute-which-mod code)))
(multiple-value-bind (res found)
(invoke-on-release-action which-mod code state)
(when found
(values t res)))))

(defun modifier-key-press-handler (code state)
(let ((which-mod (compute-which-mod code)))
(multiple-value-bind (res found)
(invoke-on-press-action which-mod code state)
(when found
(values t res)))))

(defun on-enter (sym obj)
(declare (ignore sym obj))
(fill-*modifiers*)
(setf stumpwm::*custom-key-event-handler* 'modifier-key-press-handler)
(dolist (mod *modifiers-to-grab*)
(grab-modifier-keys mod)))

(defun on-exit (sym obj)
(declare (ignore sym obj))
(setf stumpwm::*custom-key-event-handler* nil)
(dolist (mod *modifiers-to-grab*)
(grab-modifier-keys mod t)))

(stumpwm:add-hook *grabbable-modifier-keys-mode-enable-hook* 'on-enter)
(stumpwm:add-hook *grabbable-modifier-keys-mode-disable-hook* 'on-exit)

(in-package :stumpwm)

(define-stump-event-handler :key-release (code state)
(declare (ignorable code state))
(dformat 1 "~&>>> Key Release ~A ~A~&" code state)
(swm/gmk::modifier-key-release-handler code state))
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@


(asdf:defsystem #:grabbable-modifier-keys
:description "Allow StumpWM users to grab modifier keys and bind them"
:license "GLPv3"
:depends-on (#:stumpwm)
:serial t
:components ((:file "gmk")))