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

Add stripModMask to customize cleanMask/extraModifiers #374

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

liskin
Copy link
Member

@liskin liskin commented Feb 10, 2022

Description

Added stripModMask to allow customizing which modifiers are irrelevant for key bindings. Useful for binding numpad keys only when Num Lock is off, or to make Mod5 irrelevant in addition to the default Num/Caps Lock.

Fixes: #172
Relates: xmonad/xmonad-contrib#290

Checklist

  • I've read CONTRIBUTING.md

  • I've confirmed these changes don't belong in xmonad-contrib instead

  • I've considered how to best test these changes (property, unit, manually, ...) and concluded: I'm hoping that someone who requested this in Key bindings ignore numlock state #172 will test it manually :-)

  • I updated the CHANGES.md file

liskin added a commit to liskin/xmonad-contrib that referenced this pull request Feb 10, 2022
(Unfortunately MIN_VERSION_xmonad has only 3 parameters and adding a
cabal flag seems too heavyweight.)

Relates: xmonad/xmonad#374
Relates: xmonad/xmonad#172
Relates: xmonad#290
Copy link
Member

@slotThe slotThe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinging @kurogetsusai and @ibbem for testing

src/XMonad/Config.hs Outdated Show resolved Hide resolved
Comment on lines 506 to 510
smm <- join $ asks $ stripModMask . config
return $ map (foldl' (.|.) 0) (subsequences (bits smm))
where
bits 0 = []
bits n = let b = countTrailingZeros n in bit b : bits (n `clearBit` b)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be much more understandable if we just specified a list in stripModMask, right?1 Is there any reason why we don't do that and instead or everything together there already?

Footnotes

  1. AFAICT we could then just use subsequences alone.

Copy link
Member Author

@liskin liskin Feb 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Understandable" refers to the implementation or the user interface? I wanted to abstract away the implementation detail that we need to grab all bit combinations and just expose a mask that's directly used in cleanMask and processed into combinations in here.

You could argue, however, that abstracting the implementation detail is harmful in here, because people who want to not strip numlock will need to manually grab some keys with and without numlock, so abstracting it away will make it harder for them to get the right mental models. Hm, not sure what to do. :-/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that the fact that key masks are or'd together is an implementation detail in itself, which is not necessarily known to the user (many people don't know what the funnly looking .|. is doing in their configs :). Intuitively, I would expect an option along the lines of "filter out these modifiers" to take a list of key masks instead of a single one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that makes sense: 67304e9

-- but only when Num Lock is off (or on). Another use case is adding
-- 'mod5Mask' to the list of stripped/irrelevant modifiers.
defaultStripModMask :: X [KeyMask]
defaultStripModMask = gets numberlockMask <&> \numLockMask -> [lockMask, numLockMask]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is gets ((:[lockMask]) . numberlockMask) better? I don't know. :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think one is more obscure than the other, so feel free to pick the one you like best :)

-- but only when Num Lock is off (or on). Another use case is adding
-- 'mod5Mask' to the list of stripped/irrelevant modifiers.
defaultStripModMask :: X [KeyMask]
defaultStripModMask = gets numberlockMask <&> \numLockMask -> [lockMask, numLockMask]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think one is more obscure than the other, so feel free to pick the one you like best :)

@ibbem
Copy link

ibbem commented Feb 13, 2022

I currently try to test this change, but while updating my configuration I'm stuck with trying to figure out how to bind a key while numlock is active. The problem is, that the numlock mask is determined during the startup of xmonad, so it is not available at configuration time. (I also tried to run the numlock extraction code before the configuration code, but for that I would have to open a X connection.)
This is how my minimal attempt looks like:

import XMonad
import XMonad.Util.CustomKeys

main :: IO ()
main =
  xmonad
    def
      { stripModMask = lockMask,
        keys =
          customKeys (const []) $
            \XConfig {modMask = modMask} ->
              [ ((modMask, xK_y), xmessage "without numlock"),
                ((modMask .|. undefined {- how do I obtain the numlock mask? -}, xK_y), xmessage "with numlock")
              ]
      }

Is it possible to use a static numlock mask? When does the numlock mask actually change (I saw that it is updated when a MappingNotifyEvent is handled)?

When I use different mappings for the caps lock state it works as expected with this configuration:

import XMonad
import XMonad.Util.CustomKeys

main :: IO ()
main =
  xmonad
    def
      { stripModMask = gets numberlockMask,
        keys =
          customKeys (const []) $
            \XConfig {modMask = modMask} ->
              [ ((modMask, xK_y), xmessage "without caps lock"),
                ((modMask .|. lockMask, xK_y), xmessage "with caps lock")
              ]
      }

So the actual logic of this change seems to work.


For the interface it would probably be a good idea to offer a function (maybe in xmonad-contrib) to ignore a modifier for all other mappings. I'm not exactly sure about the interface for such a function, some quick ideas include:

keysWithoutIgnoringModifiers1 :: KeyMask -> [((KeyMask, KeySym), X ())] -> XConfig l -> XConfig l

This would automatically remove the modifiers from the stripModMask, duplicate the key mappings from the given config, with and without the modifier, and add the given key mapping unmodified.

keysWithoutIgnoringModifiers2 :: KeyMask -> (XConfig Layout -> [((KeyMask, KeySym), X ())]) -> XConfig l -> XConfig l

Variation on the above that use a function like the one used for keys or customKeys.

ignoreModifiers :: KeyMask -> [((KeyMask, KeySym), X ())] -> [((KeyMask, KeySym), X ())]

This would only duplicate the key mappings with and without the modifier. Note that it is unnecessary to adapt this one to use a function like keys or customKeys do, as its intended use case already allows such a function to be used (see example below).

Usage of these would look like:

import XMonad
import XMonad.Util.CustomKeys

main :: IO ()
main =
  xmonad
    $ keysWithoutIgnoringModifiers2 lockMask (\XConfig {modMask = modMask} ->
       [ ((modMask, xK_y), xmessage "without caps lock"),
         ((modMask .|. lockMask, xK_y), xmessage "with caps lock")
       ])
    $ def
      { keys =
          customKeys (const []) $
            \XConfig {modMask = modMask} ->
              [ {- some custom keys -} ]
      }

or

import XMonad
import XMonad.Util.CustomKeys

main :: IO ()
main =
  xmonad
    def
      { stripModMask = gets numberlockMask,
        keys =
          customKeys (const []) $
            \XConfig {modMask = modMask, workspaces = workspaces} ->
              ignoreModifiers lockMask
                [ {- some other key bindings -} ]
              ++
                [ ((modMask, xK_y), xmessage "without caps lock"),
                  ((modMask .|. lockMask, xK_y), xmessage "with caps lock")
                ]
      }

I think I prefer keysWithoutIgnoringModifiers2, although there is probably a better name for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Key bindings ignore numlock state
3 participants