I'm trying to set up different Xmonad key mappings depending on the number of connected monitors. The reason is that I use the same Xmonad config file on multiple systems (desktops, a laptop with different monitor configurations including 3 displays). Displays are listed in a different order on different systems, that's why I need to hardcode display indices when using a 3 monitor setup.
My current best try is something like that (everything that is not relevant has been removed):
import qualified Graphics.X11.Xlib as X11
import qualified Graphics.X11.Xinerama as X11
screenKeysFor2Monitors conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
[((m .|. mod4Mask, key), screenWorkspace sc >>= flip whenJust (windows . f)) -- Replace 'mod1Mask' with your mod key of choice.
| (key, sc) <- zip [xK_w, xK_e] [0, 1] -- Usual screen order
, (f, m) <- [(W.view, 0), (W.shift, shiftMask), (W.greedyView, mod1Mask)]]
screenKeysFor3Monitors conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
[((m .|. mod4Mask, key), screenWorkspace sc >>= flip whenJust (windows . f)) -- Replace 'mod1Mask' with your mod key of choice.
| (key, sc) <- zip [xK_w, xK_e, xK_q] [0, 2, 1] -- hardcoded according to laptop driver
, (f, m) <- [(W.view, 0), (W.shift, shiftMask), (W.greedyView, mod1Mask)]]
screenKeys x = do
numberOfScreens <- getScreens
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
return keyConfig
-- | Get number of screens
getScreens = do
screens <- do
dpy <- X11.openDisplay ""
rects <- X11.getScreenInfo dpy
X11.closeDisplay dpy
return rects
pure $ length screens
xmonadConfig = ewmh xfceConfig{
modMask = mod4Mask
keys = MyKeys.screenKeys
}
I get this error
Error detected while loading xmonad configuration file: /home/me/.xmonad/xmonad.hs
lib/MyXMonad/Keys.hs:51:64: error:
* Couldn't match expected type `M.Map (KeyMask, KeySym) (X ())`
with actual type `IO (X ())`
* In the expression: (screenKeys x)
In the second argument of `($)`, namely
`[(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]`
In the expression:
M.unions $ [(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]
|
51 | keysToAdd x = M.unions $ [(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]
| ^^^^^^^^^^^^
lib/MyXMonad/Keys.hs:242:30: error:
* Couldn't match type `M.Map (KeyMask, KeySym)` with `IO`
Expected type: IO (X ())
Actual type: M.Map (KeyMask, KeySym) (X ())
* In the expression: screenKeysFor3Monitors x
In a case alternative: 3 -> screenKeysFor3Monitors x
In a stmt of a 'do' block:
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
|
242 | 3 -> screenKeysFor3Monitors x
| ^^^^^^^^^^^^^^^^^^^^^^^^
lib/MyXMonad/Keys.hs:243:30: error:
* Couldn't match type `M.Map (KeyMask, KeySym)` with `IO`
Expected type: IO (X ())
Actual type: M.Map (KeyMask, KeySym) (X ())
* In the expression: screenKeysFor2Monitors x
In a case alternative: _ -> screenKeysFor2Monitors x
In a stmt of a 'do' block:
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
|
243 | _ -> screenKeysFor2Monitors x
| ^^^^^^^^^^^^^^^^^^^^^^^^
Please check the file for errors.
If I understand correctly, the problem here is that my code depends on side effects (working with monitor configuration uses IO monad) and becomes non-pure. I can convert IO
monad to X
monad using liftIO
. But the X
monad is accessible only inside key binding handlers. The code that creates key bindings for Xmonad configuration has to be pure, and X
monad is not expected here.
In other words, if I get the situation right, it's not possible to define key bindings using non-pure functions (e.g. by looking on connected monitors). Maybe there is some workaround? I lack a decent understanding of Haskell and maybe I'm missing something obvious for regular Haskell programmers.
not too familiar with Xmonad but you can easily do the following I guess. create a pure function mkConfig
which takes the number of screens and returns the desired key mapping. Then, in your main
pass it to xmonad
function. I haven't tried to compile any of this but probably you can modify it easily
mkConfig numberOfScreens = -- Notice that this is a pure function
case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
main :: IO ()
main = do
numberOfScreens <- getScreens -- Retrive the number of screens from the system
let keyConfig = mkConfig numberOfScreens -- Makes a key mapping out of this
xmonadConfig = ewmh xfceConfig{ modMask = mod4Mask, keys = keyConfig } -- Creates a Xmonad configuration
xmonad xmonadConfig -- Launch Xmonad.