Ubuntu16.10にstackでxmonadをインストールして設定する

  • 5
    いいね
  • 0
    コメント

はじめに

aptで入るxmonad-0.12はstackに対応してないので色々と面倒なことが起こる。stackで入る新しめのxmonad(2016年12月14日のコミット以降のもの)からはrecompileするためのビルドスクリプトを読むようになったので捗る。しかし、aptでインストールするとディスプレイマネージャの設定をよしなにやってくれるので嬉しい。そういうことで、ここではまずaptでxmonadを入れ、その後stackで入れたxmonadを使うという流れで作業を行っていく。

aptでxmonad をインストールする

$ sudo apt install xmonad

これでログイン時にXMonadセッションを選択できるようになり、ログインすると以下のスクリプトが実行されるように設定される。

/usr/bin/xmonad-session
#!/bin/bash

if [ -r ".xmonad/xmonad-session-rc" ]
then
  . .xmonad/xmonad-session-rc
fi

exec xmonad "$@"

よって、xmonad-session-rcにxmonadの起動時に実行して欲しいコマンドがあれば以下のように書く。

$HOME/.xmonad/xmonad-session-rc
compton --config ~/.compton.conf -b
setxkbmap -option ctrl:nocaps
synclient TouchpadOff=1
udiskie -t &
nm-applet &
slack &

stackでxmonadをインストールする

ここでは確実に変更が入っているlts-8.5をresolverに指定している。ついでにxmobarも入れる。

$HOME/.stack/global-project/stack.yaml
flags: {}
extra-package-dbs: []
packages: []
extra-deps:
- alsa-core-0.5.0.1
- alsa-mixer-0.2.0.3
- dbus-0.10.12
- libxml-sax-0.7.5
resolver: lts-8.5
$ stack install xmonad xmonad-contrib xmobar --flag xmobar:all_extensions

stackで入れたxmonadが起動するように変更する。

/usr/bin/xmonad-session
#!/bin/bash

if [ -r ".xmonad/xmonad-session-rc" ]
then
  . .xmonad/xmonad-session-rc
fi

exec $HOME/.local/bin/xmonad "$@"

xmonadの設定をする

xmonad、xmobarの設定ファイルを書く。ubuntuはディスプレイマネージャにlightdmを使っているのでdm-toolが画面のロックに使える。モニタの解像度や数に変更が合った場合、M-ESCでやっつけ対応してくれる。

$HOME/.xmonad/xmonad.hs
import           Codec.Binary.UTF8.String         (decodeString)
import           Control.Monad
import qualified Data.Map                         as M
import           Graphics.X11.ExtraTypes.XF86     (xF86XK_AudioLowerVolume,
                                                   xF86XK_AudioMute,
                                                   xF86XK_AudioRaiseVolume)
import           System.Directory                 (getTemporaryDirectory,
                                                   removeFile)
import           System.Exit                      (ExitCode (ExitSuccess),
                                                   exitWith)
import           System.IO                        (FilePath, hClose,
                                                   openTempFile)
import           System.Posix.Files               (createNamedPipe,
                                                   ownerReadMode,
                                                   ownerWriteMode,
                                                   unionFileModes)
import           XMonad
import           XMonad.Actions.SpawnOn           (manageSpawn, spawnHere)
import           XMonad.Actions.UpdatePointer     (updatePointer)
import           XMonad.Actions.WindowGo          (doShift)
import           XMonad.Actions.WithAll           (sinkAll)
import           XMonad.Hooks.DynamicLog          (PP, dynamicLogWithPP,
                                                   ppCurrent, ppHiddenNoWindows,
                                                   ppOrder, ppOutput, ppTitle,
                                                   ppVisible, shorten, wrap,
                                                   xmobarColor)
import           XMonad.Hooks.EwmhDesktops        (ewmh, fullscreenEventHook)
import           XMonad.Hooks.ManageDocks         (avoidStruts, docks)
import           XMonad.Hooks.ManageHelpers       (composeOne, doCenterFloat,
                                                   doFullFloat, isDialog,
                                                   isFullscreen, transience,
                                                   (-?>))
import           XMonad.Hooks.SetWMName           (setWMName)
import           XMonad.Layout.IndependentScreens (countScreens, marshallPP,
                                                   onCurrentScreen,
                                                   whenCurrentOn, withScreens,
                                                   workspaces')
import           XMonad.Layout.ResizableTile      (MirrorResize (MirrorExpand, MirrorShrink),
                                                   ResizableTall (ResizableTall))
import qualified XMonad.StackSet                  as W
import           XMonad.Util.Run                  (unsafeSpawn)
import           XMonad.Util.SpawnOnce            (spawnOnce)


type FocusPipe      = FilePath
type WorkspacesPipe = FilePath
data XMobarOption   = XMobarOption ScreenId FocusPipe WorkspacesPipe


main :: IO ()
main = do
  unsafeSpawn "~/.xmonad/autodetect-monitor"
  unsafeSpawn "feh --bg-scale ~/Pictures/Wallpapers/wp_1.jpg"
  unsafeSpawn "\
               \trayer --edge top --align right --SetDockType true --SetPartialStrut true\
               \       --expand true --widthtype percent --width 5 --height 20\
               \       --transparent true --tint 0x000000 --monitor primary\
               \"
  nScreens   <- countScreens
  barOptions <- forM [0 .. nScreens - 1] $ \n -> do
                  [pipe1, pipe2] <- replicateM 2 (getTempFifo "xmobar-")
                  return $ XMobarOption n pipe1 pipe2
  mapM_ (unsafeSpawn . xmobarCommand) barOptions

  xmonad $ ewmh $ docks $ def {
      borderWidth        = 3
    , workspaces         = withScreens nScreens $ map show [1 .. 9]
    , layoutHook         = avoidStruts $
                           ResizableTall 1 (3/100) (1/2) [] ||| Full
    , logHook            = do updatePointer (0.5, 0.5) (1, 1)
                              forM_ barOptions $ \(XMobarOption n pipe1 pipe2) -> do
                                let pp1 = ppFocus      pipe1 n
                                    pp2 = ppWorkspaces pipe2 n
                                dynamicLogWithPP pp1
                                dynamicLogWithPP pp2
    , terminal           = "gnome-terminal"
    , modMask            = mod4Mask
    , keys               = myKeys
    , startupHook        = refresh >> rescreen >> setWMName "LG3D"
    , manageHook         = composeOne [ className =? "trayer"-?> doIgnore
                                      , isFullscreen         -?> doFullFloat
                                      , isDialog             -?> doCenterFloat
                                      , transience
                                      ]
                           <+> manageSpawn
    , handleEventHook    = handleEventHook def <+> fullscreenEventHook
  }


myKeys :: XConfig Layout -> M.Map (KeyMask, KeySym) (X ())
myKeys conf@(XConfig { XMonad.modMask = modMask }) = M.fromList $
  [ ((modMask .|. shiftMask,   xK_Return), spawnHere $ terminal conf)
  , ((modMask,                 xK_semicolon), spawnHere $ "rofi -combi-modi window,drun,run -show combi -modi combi")
  , ((modMask .|. shiftMask,   xK_c), kill)
  , ((modMask,                 xK_space), sendMessage NextLayout)
  , ((modMask .|. shiftMask,   xK_space), setLayout $ layoutHook conf)
  , ((modMask,                 xK_r), refresh)
  , ((modMask,                 xK_j), windows W.focusDown)
  , ((modMask,                 xK_k), windows W.focusUp)
  , ((modMask,                 xK_m), windows W.focusMaster)
  , ((modMask,                 xK_Return), windows W.swapMaster)
  , ((modMask .|. shiftMask,   xK_j), windows W.swapDown)
  , ((modMask .|. shiftMask,   xK_k), windows W.swapUp)
  , ((modMask,                 xK_h), sendMessage Shrink)
  , ((modMask,                 xK_l), sendMessage Expand)
  , ((modMask,                 xK_n), sendMessage MirrorShrink)
  , ((modMask,                 xK_p), sendMessage MirrorExpand)
  , ((modMask,                 xK_t), withFocused $ windows . W.sink)
  , ((modMask,                 xK_comma), sendMessage (IncMasterN 1))
  , ((modMask,                 xK_period), sendMessage (IncMasterN (-1)))
  , ((0,                       xF86XK_AudioLowerVolume), unsafeSpawn "amixer set Master 2dB-")
  , ((0,                       xF86XK_AudioRaiseVolume), unsafeSpawn "amixer set Master 2dB+")
  , ((0,                       xF86XK_AudioMute), unsafeSpawn "amixer -D pulse set Master toggle")
  , ((0,                       xK_Print), unsafeSpawn "gnome-screenshot -i")
  , ((modMask .|. controlMask, xK_l), unsafeSpawn "dm-tool lock")
  , ((modMask,                 xK_q), unsafeSpawn "pkill trayer" >> unsafeSpawn "pkill xmobar" >> restart "xmonad" True)
  , ((modMask,                 xK_Escape), do ss <- gets windowset
                                              let ws = W.allWindows ss
                                              forM_ ws $ \w -> do
                                                screenWorkspace 0 >>= flip whenJust (windows . flip W.shiftWin w)
                                              screenWorkspace 0 >>= flip whenJust (windows . W.view)
                                              sinkAll
                                              unsafeSpawn "pkill trayer"
                                              unsafeSpawn "pkill xmobar"
                                              unsafeSpawn "~/.xmonad/autodetect-monitor"
                                              rescreen
                                              restart "xmonad" True)
  , ((modMask .|. shiftMask,   xK_q), io (exitWith ExitSuccess))
  , ((modMask .|. shiftMask,   xK_slash), unsafeSpawn $ "echo \"" ++ help ++ "\" | xmessage -file -")
  , ((modMask,                 xK_a), screenWorkspace 0 >>= flip whenJust (windows . W.view))
  , ((modMask,                 xK_s), screenWorkspace 1 >>= flip whenJust (windows . W.view))
  , ((modMask,                 xK_d), screenWorkspace 2 >>= flip whenJust (windows . W.view))
  -- ...
  , ((modMask .|. shiftMask,   xK_a), screenWorkspace 0 >>= flip whenJust (windows . (\i -> W.view i . W.shift i)))
  , ((modMask .|. shiftMask,   xK_s), screenWorkspace 1 >>= flip whenJust (windows . (\i -> W.view i . W.shift i)))
  , ((modMask .|. shiftMask,   xK_d), screenWorkspace 2 >>= flip whenJust (windows . (\i -> W.view i . W.shift i)))
  -- ...
  ]
  ++
  [((m .|. modMask, k), windows $ onCurrentScreen f i)
  | (i, k) <- zip (workspaces' conf) [xK_1 .. xK_9]
  , (f, m) <- [(W.greedyView, 0), (W.shift , shiftMask)]
  ]


ppFocus :: FilePath -> ScreenId -> PP
ppFocus pipe (S s) = whenCurrentOn (S s) def {
    ppTitle  = shorten 80
  , ppOrder  = \(_:_:title:_) -> [title]
  , ppOutput = appendFile pipe . decodeString . (++ "\n")
  }


ppWorkspaces :: FilePath -> ScreenId -> PP
ppWorkspaces pipe (S s) = marshallPP (S s) def {
    ppCurrent = xmobarColor "orange" ""
  , ppVisible = xmobarColor "white" ""
  , ppHiddenNoWindows = xmobarColor dark ""
  , ppOrder   = \(ws:_:_:_) -> [ws]
  , ppOutput  = appendFile pipe . decodeString . (++ "\n")
  }


getTempFifo :: String -> IO FilePath
getTempFifo prefix = do
  tmpDir <- getTemporaryDirectory
  (tmpFile, h) <- openTempFile tmpDir prefix
  hClose h
  removeFile tmpFile
  createNamedPipe tmpFile $ unionFileModes ownerReadMode ownerWriteMode
  return tmpFile


xmobarCommand :: XMobarOption -> String
xmobarCommand (XMobarOption (S n) pipe1 pipe2) =
  "\
   \xmobar\
   \ -x " ++ show n ++ "\
   \ -C '[Run PipeReader \"" ++ pipe1 ++ "\" \"focus\", \
         \Run PipeReader \"" ++ pipe2 ++ "\" \"workspaces\"]'"


dark :: String
dark = "#13294e"


orange :: String
orange = "#ee9a00"


help :: String
help = unlines ["The default modifier key is 'alt'. Default keybindings:",
                "",
                "-- launching and killing programs",
                "mod-Shift-Return  Launch terminal",
                "mod-;             Launch rofi",
                "mod-Shift-c       Close/kill the focused window",
                "mod-Space         Rotate through the available layout algorithms",
                "mod-Shift-Space   Reset the layouts on the current workSpace to default",
                "mod-r             Resize/refresh viewed windows to the correct size",
                "",
                "-- move focus up or down the window stack",
                "mod-j          Move focus to the next window",
                "mod-k          Move focus to the previous window",
                "mod-m          Move focus to the master window",
                "",
                "-- modifying the window order",
                "mod-Return   Swap the focused window and the master window",
                "mod-Shift-j  Swap the focused window with the next window",
                "mod-Shift-k  Swap the focused window with the previous window",
                "",
                "-- resizing the master/slave ratio",
                "mod-h  Shrink the master area",
                "mod-l  Expand the master area",
                "",
                "-- floating layer support",
                "mod-t  Push window back into tiling; unfloat and re-tile it",
                "",
                "-- increase or decrease number of windows in the master area",
                "mod-comma  (mod-,)   Increment the number of windows in the master area",
                "mod-period (mod-.)   Deincrement the number of windows in the master area",
                "",
                "-- restart xmonad, Logout",
                "mod-q        Restart xmonad",
                "mod-Shift-q  Logout",
                "",
                "-- screeen lock",
                "mod-Ctrl-l   Lock screen",
                "",
                "-- Workspaces & screens & display",
                "mod-[a,s,d]        swith to display a=0, s=1, d=2",
                "mod-Shift-[a,s,d]  Move client to display a=0, s=1, d=2",
                "mod-[1..9]         Switch to workSpace N",
                "mod-Shift-[1..9]   Move client to workspace N",
                ""
               ]

stack経由でxmonadをコンパイルする

$HOME/.xmonad/build
#!/bin/sh
# Recompile XMonad using Stack
stack ghc -- $(dirname $0)/xmonad.hs -o $1
$ chmod +x $HOME/.xmonad/build
$ xmonad --recompile

再起動してXMonadセッションでログインする

Screenshot from 2017-04-04 19-31-00.png

おわりに

xmonadは一度ハマると色々カスタマイズしたくなると思います。ここではマルチモニタで使えるシンプルな設定(XMonad.Layout.IndependentScreensモジュールを使った複数のディスプレイにそれぞれ独立したワークスペースのセットを割り当てる設定)を紹介しました。ここから自分に合ったウィンドウレイアウトを探したりとタイル型ウィンドウマネージャの世界にどっぷり浸かると良いと思います。(一周廻って私はシンプルな設定に落ち着きましたが…
現在の私のxmonadの設定