创建 Xmonad 配置时使用 IO键盘映射取决于连接的监视器数量

问题描述

我正在尝试根据连接的显示数量设置不同的 Xmonad 键映射。原因是我在多个系统(台式机、具有不同显示器配置的笔记本电脑,包括 3 个显示器)上使用相同的 Xmonad 配置文件显示在不同系统上以不同顺序列出,这就是为什么我需要在使用 3 显示器设置时对显示索引进行硬编码。

我目前最好的尝试是这样的(所有不相关的都被删除了):

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,xK_e,xK_q] [0,2,1] -- hardcoded according to laptop driver,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
}

我收到此错误

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),(screenKeys x)]
   |
51 | keysToAdd x = M.unions $ [(myKeysToAdd 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) (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.

如果我理解正确,这里的问题是我的代码依赖于副作用(使用监视器配置使用 IO monad)并且变得不纯。我可以使用 IOX monad 转换为 liftIO monad。但是 X monad 只能在键绑定处理程序中访问。为 Xmonad 配置创建键绑定的代码必须是纯代码,这里不需要 X monad。

换句话说,如果我的情况正确,则不可能使用非纯函数(例如,通过查看连接的显示器)来定义键绑定。也许有一些解决方法?我对 Haskell 缺乏适当的理解,也许我遗漏了一些对普通 Haskell 程序员来说显而易见的东西。

解决方法

对 Xmonad 不太熟悉,但我猜您可以轻松地执行以下操作。创建一个纯函数 mkConfig,它获取屏幕数量并返回所需的键映射。然后,在您的 main 中将其传递给 xmonad 函数。我还没有尝试编译其中的任何内容,但您可能可以轻松修改它

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.