2016年2月14日日曜日

xmonad いじり

3 年ぶりくらいに xmonad をいじって,なかなか快適になったので忘れないようにメモしておく.3 年前の記事が dr's tech memo: Haskell に興味がある人向け xmonad 設定ガイド にあるため,その後どういった変更を行ったかについてまとめる.現在の動作環境は以下.
  • もちろん Gentoo
  • ghc-7.8.4
  • xmonad-0.11-r1
  • xmonad-contrib-0.11.2

1. startupHook

startupHook を利用して,従来 .xinitrc に書いていたものを少し xmonad のほうに持ってきた.とはいえ .xinitrc との棲み分けをちゃんとできているわけではなく,適当.
import XMonad.Util.Cursor               -- setDefaultCursor
import XMonad.Util.Run                  -- unsafeSpawn, spawnPipe, hPutStrLn

main = do
    myStatusBar <- spawnPipe "xmobar"
    xmonad $ ewmh
           $ defaultConfig {
                   layoutHook      = myLayoutHook
                 , manageHook      = myManageHook
                 , handleEventHook = myHandleEventHook
                 , modMask         = myModMask
                 , logHook         = myLogHook myStatusBar
                 , startupHook     = myStartupHook
                 }
                 `additionalKeysP` myAdditionalKeysP

myStartupHook = do
    setDefaultCursor xC_left_ptr
    unsafeSpawn "thunderbird"
    unsafeSpawn "pidgin"
startupHookX () の型として定義されている.
ghci> :m +XMonad
ghci> :i startupHook
data XConfig (l :: * -> *)
  = XConfig {..., startupHook :: !X (), ...}
        -- Defined in ‘XMonad.Core’
xmonad における 「アクション」 はこれを満たす関数 (モナド) になっているので,スタートアップ時に実行したいアクションを (do でつなげて) 列挙してやれば良い.上記の setDefaultCursor はその名のとおりポインタを設定するもの.従来 xsetroot コマンドで行っていたけれど,たまたま見つけたので使ってみたというレベルのもの.なお参考までに,.xinitrc は以下のようになっている.
# ~/.xinitrc
xrdb -merge $HOME/.Xresources

xmodmap $HOME/.Xmodmap

autocutsel &
autocutsel -selection PRIMARY &
dispad

export GTK_IM_MODULE="uim"
export QT_IM_MODULE="uim"
uim-xim &
export XMODIFIERS="@im=uim"

exec xmonad
他のものも xmonad のほうに移せるだろうけれど,とりあえず残している.特に明確な基準はない.
それから,startupHook を使用せずとも,xmonad 関数を呼び出す前の xmobar を spawn しているところにアクションを列挙していけば同様のことができると想像している (試していないけれど).ただ,せっかく startupHook という機構を用意してくれているので,戻り値が必要な特殊なケースを除けば,別のところにまとめておいたほうが見通しが良くなるのだと思う.

2. additionalKeysP

前回はキーバインドの追加に additionalKeys を使用していたが,同様に XMonad.Util.EZConfig に用意されている additionalKeysP を使用することにした.従来は xbindkeys で割り当てていたものを xmonad に持ってこようとしたときに,additionalKeysP のほうが手軽に利用できるキーが多いようだったため.
import XMonad.Util.Run                  -- unsafeSpawn
import XMonad.Util.EZConfig             -- additionalKeysP

myAdditionalKeysP = [
    --snip --
    
    -- volume control
    , ("<XF86AudioRaiseVolume>", unsafeSpawn "amixer set Master playback 10+")
    , ("<XF86AudioLowerVolume>", unsafeSpawn "amixer set Master playback 10-")
    , ("<XF86AudioMute>",        unsafeSpawn "amixer set Master toggle")
    ]
利用できるキーは XMonad.Util.EZConfig で参照可能.

3. xmonad と相性の悪いアプリケーションへの対応

3.1. mpv (MPlayer)

MPlayer の時からそうだったけれど,mpv でフルスクリーン表示にならないという問題があった.これは manageHook で mpv のウィンドウを float させてやることで解決する.
import XMonad.Hooks.ManageHelpers       -- isFullscreen, doFullFloat

myManageHook =   manageDocks
             <+> composeAll [
                        isFullscreen                      --> doFullFloat
                      , className =? "mpv"                --> doFloat
                      -- snip --
                      ]

3.2. Java Swing アプリケーション

JabRef など,Swing を利用した Java アプリケーションはうまく表示が行えないという問題があった.まさかウィンドウマネージャとの相性の問題だとは…….と思ったら FAQ にあった.

4. その他

4.1. XMonad.Prompt.Shell

dmenu の代わりに XMonad.Prompt.Shell. 外部プログラムへの依存が減る,カスタマイズ性が高いといったところがメリット.
import XMonad.Prompt                    -- XPConfig
import XMonad.Prompt.Shell              -- shellPrompt

myAdditionalKeysP = [
      ("M-p",   shellPrompt myXPConfig)
      -- snip --
    ]

myXPConfig = defaultXPConfig {
                   font              = "xft:sans-serif:size=6"
                 , bgColor           = "black"
                 , fgColor           = "grey"
                 , promptBorderWidth = 0
                 , position          = Top
                 , alwaysHighlight   = True
                 , height            = 30
                 }

4.2. XMonad.Layout.HintedTile/XMonad.Layout.Renamed

Tall の代わりに XMonad.Layout.HintedTile. ターミナルのようなアプリケーションで 1 行以下の 「余り」 が出ないように,ウィンドウサイズのほうを調整 (縮小) する.場合によっては並んだウィンドウの高さや幅ががたがたになるので,Tall とどっちが良いかは好みによりそう.XMonad.Layout.Spacing も併用すると綺麗になることを期待したのだけれど,どうやら HintedTile とは相性が悪そう (ヒンティングしたあとにスペースを入れることになり,結果として全てのウィンドウでヒンティングが合わなくなる).
それから,レイアウトに機能を足していくと名前が長くなってくるので,それをリネームするための XMonad.Layout.Remaned.
import XMonad hiding (Tall)
import XMonad.Layout.HintedTile
import XMonad.Layout.Renamed

myLayoutHook =   renamed [CutWordsLeft 2]
                 $ smartBorders $ avoidStruts $ maximize $ minimize
                 $ (hintedTile Tall ||| hintedTile Wide)
             ||| (avoidStruts $ noBorders Full)
    where
        hintedTile = HintedTile nmaster delta ratio TopLeft
        nmaster    = 1
        ratio      = 1/2
        delta      = 3/100

4.3. XMonad.Layout.LayoutScreens

スクリーンのなかで workspace を切るという機能.最近 31.5 インチのディスプレイを買ったので,便利に使えるかどうかを試してみている.
import XMonad.Layout.LayoutScreens      -- layoutSplitScreen
import XMonad.Layout.TwoPane

myAdditionalKeysP = [
    -- snip --
    -- split screen
    , ("M-M1-<Space>",   layoutSplitScreen 2 (TwoPane (3/100) 0.7))
    , ("M-M1-S-<Space>", rescreen)
    ]

4.4. XMonad.Hooks.InsertPosition

insertPosition を使用して,新しいウィンドウがアクティブなウィンドウの次に来る,すなわち新しいウィンドウにマスタを取られないようにした.
import XMonad.Hooks.InsertPosition

myManageHook =   insertPosition Below Newer
             <+> manageDocks
             <+> composeAll [ -- snip -- ]

5. スクリーンショット

高解像度っぷりのアピールを含め.

6. 現在の xmonad.hs

 -- ~/.xmonad/xmonad.hs
import XMonad hiding (Tall)
import XMonad.Hooks.DynamicLog          -- logHook-related
import XMonad.Hooks.EwmhDesktops        -- ewmh, fullscreenEventHook
import XMonad.Hooks.InsertPosition
import XMonad.Hooks.ManageDocks         -- manageDocks, avoidStruts, docksEventHook
import XMonad.Hooks.ManageHelpers       -- isFullscreen, doFullFloat
import XMonad.Layout.HintedTile
import XMonad.Layout.LayoutScreens      -- layoutSplitScreen
import XMonad.Layout.Maximize
import XMonad.Layout.Minimize
import XMonad.Layout.NoBorders          -- smartBorders, noBorders
import XMonad.Layout.Renamed
import XMonad.Layout.TwoPane
import XMonad.Prompt                    -- XPConfig
import XMonad.Prompt.Shell              -- shellPrompt
import XMonad.Util.Cursor               -- setDefaultCursor
import XMonad.Util.EZConfig             -- additionalKeysP
import XMonad.Util.Run                  -- unsafeSpawn, spawnPipe, hPutStrLn
import XMonad.Util.WorkspaceCompare     -- getSortByXineramaRule

main = do
    myStatusBar <- spawnPipe "xmobar"
    xmonad $ ewmh
           $ defaultConfig {
                   layoutHook      = myLayoutHook
                 , manageHook      = myManageHook
                 , handleEventHook = myHandleEventHook
                 , modMask         = myModMask
                 , logHook         = myLogHook myStatusBar
                 , startupHook     = myStartupHook
                 }
                 `additionalKeysP` myAdditionalKeysP

myModMask = mod4Mask

myLayoutHook =   renamed [CutWordsLeft 2]
                 $ smartBorders $ avoidStruts $ maximize $ minimize
                 $ (hintedTile Tall ||| hintedTile Wide)
             ||| (avoidStruts $ noBorders Full)
    where
        hintedTile = HintedTile nmaster delta ratio TopLeft
        nmaster    = 1
        ratio      = 1/2
        delta      = 3/100

myManageHook =   insertPosition Below Newer
             <+> manageDocks
             <+> composeAll [
                        isFullscreen                      --> doFullFloat
                      , className =? "mpv"                --> doFloat
                      , className =? "Gimp"               --> doFloat
                      , className =? "Pidgin"             --> doShift "9"
                      , className =? "Thunderbird"        --> doShift "9"
                      ]

myHandleEventHook =   docksEventHook
                  <+> fullscreenEventHook

myLogHook h = dynamicLogWithPP xmobarPP {
                    ppSep    = " | "
                  , ppTitle  = xmobarColor "green" "" . shorten 80
                  , ppOutput = hPutStrLn h
                  , ppSort   = getSortByXineramaRule
                  }

myStartupHook = do
    setDefaultCursor xC_left_ptr
    unsafeSpawn "thunderbird"
    unsafeSpawn "pidgin"

myAdditionalKeysP = [
      ("M-p",   shellPrompt myXPConfig)
    , ("M-m",   withFocused (sendMessage . maximizeRestore))
    , ("M-n",   withFocused minimizeWindow)
    , ("M-S-n", sendMessage RestoreNextMinimizedWin)

    -- toggle dock visibility
    , ("M-b", sendMessage ToggleStruts)

    -- split screen
    , ("M-M1-<Space>",   layoutSplitScreen 2 (TwoPane (3/100) 0.7))
    , ("M-M1-S-<Space>", rescreen)

    -- volume control
    , ("<XF86AudioRaiseVolume>", unsafeSpawn "amixer set Master playback 10+")
    , ("<XF86AudioLowerVolume>", unsafeSpawn "amixer set Master playback 10-")
    , ("<XF86AudioMute>",        unsafeSpawn "amixer set Master toggle")

    -- other commands
    , ("M-S-l", unsafeSpawn "alock -auth pam -bg blank")
    , ("M-c",   unsafeSpawn "chromium --incognito --force-device-scale-factor=1.6")
    ]

myXPConfig = defaultXPConfig {
                   font              = "xft:sans-serif:size=6"
                 , bgColor           = "black"
                 , fgColor           = "grey"
                 , promptBorderWidth = 0
                 , position          = Top
                 , alwaysHighlight   = True
                 , height            = 30
                 }