{-| XMonad configuration file

Author: Timothy Beyer <beyert AT_SYMBOL fastmail DOT_SYMBOL net>

Copyright: (2008 - 2014) XMonad License (3-clause BSD), unless otherwise noted.
Some portions based on, or are influenced by code from the XMonad sources (also
3-clause BSD), but the code is otherwise original.

This configuration file closely emulates the window division behaviors of Wmii
/ Orion / I3 / Ion, and other non-minimal tiling window managers (eg. not DWM,
or other window managers of the strongly "minimalist" flavor).

It focuses heavily on the ability to manipulate multiple independent groups of
windows one or more monitors, with a high degree of granularity.  The current
way that this is abstracted is through the use of LayoutScreens to pretend that
the different "panes" of a Rectangle are in fact different screens.  The user
may work at a higher level of abstraction, not worrying about master-slave
window relationships, nor excessively rigid "dynamic" window behavior.

This is particularly useful in a number of circumstances:

  1.  When using a widescreen monitor, and more efficient screen utilization of
  the screen space is desired

  2.  When using multiple monitors, and Xinerama or TWinView are unavailable

  3.  When using systems with limited or restrictive X servers, that are
  incapable of using XRender (xrdp sessions with the span option are a case in
  point for this functionality)

Another method which achieves this effect, albeit in a less orthogonal way, is
through XMonad.Layout.Groups.Wmii, although it was so difficult for me to grasp
its abstractions in the context of multiple columns (functionality which I
think was never finished) that I had to write code to detect the side of the
screen to act appropriately, as noted in XMonad.Layout.Groups.WmiiTwoPane.  Due
to layoutScreens, this approach is no longer necessary, nor is it recommended,
but the functionality still exists if anyone is interested in continuing the
approach.

It is unfortunate that many users are completely unware of LayoutScreens, as
well as how to define precise layouts using Rectangles (GridVariants is a good
alternative, although it doesn't address non-ratio sizes).  These abstractions
addresses the fundamental shortcoming that XMonad adopted from "modern" dynamic
tiling window managers, yet as this configuration file shows, the problem is
easily solved in XMonad, even for non-programmers, who would simply need to see
a good example of how to achieve this workflow.

Included are some other useful behaviors that are not commonly implemented:

  *  Handles process spawning on FreeBSD correctly

  *  Allows variable-width layouts properly in conjunction with layoutScreens
     (via a custom layout, as defined in this file) with an arbitrary number of
     subdivisions (as noted above)

  *  Easy dynamic switching between variable width subdivisions of the
     workspace

  *  Adapted version of XMonad.Layout.Groups.Wmii
     (XMonad.Layout.Groups.WmiiTwoPane)

  *  Integrated with transparent versions of xmobar and dmenu (compatible with
     XMonad transparent window borders as well)

  *  Both Next/previous tab and next/previous screen are fully working in all
     possible layouts.  In both cases, moving the focus and/or a window are
     fully supported, and are constrained on the edges/corners of the screen
     (no cycles / non-wrapping).

  *  Handles desktop windows correctly: ensures that rdesktop windows are both
     non-floating (safely sinks the window) and shifted to a target workspace,
     with no ghosting effect.

There are some limitations:

  *  Due to the design of layoutScreens and of my custom layout, Shrink and
     Expand messages do not work, so at the moment, there is no efficient way
     to interactively adjust window size (although it is trivial to toggle
     between different sizes of screen sizes on the fly, so it hasn't been an
     issue for me at least).  I might resolve this issue at some point.

  *  The modified Accordion layout (Accordion2) is a mediocre emulation of the
     Wmii stacked layout, (lower compatibility with fussy programs, and uglier
     appearance due to improper spacing) but this is a non-issue, because the
     tabbed layout is almost identical in usage, and is very well supported
     with this configuration (most notably, the next and previous actions are
     compatible with tabbed in this configuration).  Tabbed supports more
     predictable sizing for programs like tmux, due to the nature of the way
     the stacked layout works, in any implementation.

  *  The status bar sometimes draws on fullscreen windows (generally just
     terminals).

-}

import XMonad hiding ((|||))
import qualified XMonad.StackSet as W

import XMonad.Layout.LayoutScreens
import XMonad.Layout.Groups.WmiiTwoPane
import XMonad.Layout.Decoration
import XMonad.Layout.LayoutHints
import XMonad.Layout.PerWorkspace
import XMonad.Layout.LayoutCombinators
import XMonad.Layout.Minimize

import XMonad.Actions.Navigation2D
import XMonad.Actions.CopyWindow (copy, copyWindow)
import XMonad.Actions.GridSelect
import XMonad.Actions.Submap
import XMonad.Actions.WorkspaceCursors (noWrapUp, noWrapDown)
import XMonad.Actions.WindowBringer (gotoMenuArgs')
-- import XMonad.Hooks.ManageDocks (manageDocks, avoidStruts, docksEventHook)
import XMonad.Hooks.ManageDocks (manageDocks, docksEventHook)
import XMonad.Hooks.SetWMName
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.FadeInactive

import System.Exit

import qualified Data.Map as M
import Data.List as L
import Data.IORef (atomicModifyIORef, IORef, newIORef, readIORef)

import Text.Regex (splitRegex, mkRegex)

import Codec.Binary.UTF8.String (encodeString)
import System.Posix.Process (executeFile)
import System.Posix.Types (ProcessID)
import System.Posix.IO (createPipe, closeFd, stdInput, dupTo, setFdOption,
                        fdToHandle, FdOption(CloseOnExec))
import System.IO (BufferMode(LineBuffering), hSetBuffering, Handle, hPutStrLn)
import System.IO.Unsafe (unsafePerformIO)
import XMonad.Util.Run (runProcessWithInput, runProcessWithInputAndWait,
                        seconds)

-- The preferred terminal program, which is used in a binding below and by
-- certain contrib modules.
myTerminal = "metaterm"

-- Background image to be used for wallpaper
myBackgroundImage = "/usr/local/lib/X11/xdm/pixmaps/bg.png"

-- Width of the window border in pixels.
myBorderWidth = 1

-- modMask lets you specify which modkey you want to use. The default is
-- mod1Mask ("left alt").  You may also consider using mod3Mask ("right alt"),
-- which does not conflict with emacs keybindings. The "windows key" is usually
-- mod4Mask.
myModMask = mod4Mask

-- The mask for the numlock key. Numlock status is "masked" from the current
-- modifier status, so the keybindings will work with numlock on or off. You
-- may need to change this on some systems.
--
-- You can find the numlock modifier by running "xmodmap" and looking for a
-- modifier with Num_Lock bound to it:
--
-- > $ xmodmap | grep Num
-- > mod2        Num_Lock (0x4d)
--
-- Set numlockMask = 0 if you don't have a numlock key, or want to treat
-- numlock status separately.
myNumlockMask = mod2Mask

-- The default number of workspaces (virtual screens) and their names.  By
-- default, we use numeric strings, but any string may be used as a workspace
-- name. The number of workspaces is determined by the length of this list.
--
-- A tagging example:
--
-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9]
workspaceList' = map show $ [1 .. 9 :: Int] ++ [0 :: Int]
fullScreenWorkspaceList = map show $ [5 .. 9 :: Int]

xmobarEscape = concatMap doubleLts
  where doubleLts '<' = "<<"; doubleLts x = [x]

clickWorkspaces wss = [ "<action=" ++ "xdotool key Hyper_L+" ++ show (n) ++ ">"
                        ++ wss' ++ "</action>"
                        | (i, wss') <- zip workspaceList' wss, let n = i ]
clickWorkspace ws = head $ clickWorkspaces [ws]

myWorkspaces :: [String]
myWorkspaces = clickWorkspaces . (map xmobarEscape) $ workspaceList'

-- Border colors for unfocused and focused windows, respectively.
myNormalBorderColor = "#4A4D52"
myFocusedBorderColor = "#343F47"

-- /bin/tcsh version of process spawn code
spawnPIDTCSH :: MonadIO m => String -> m ProcessID
spawnPIDTCSH x = xfork $ executeFile "/bin/tcsh" False ["-c", encodeString x]
                 Nothing
spawn' :: MonadIO m => String -> m ()
spawn' x = spawnPIDTCSH x >> return ()

-- | Launch an external application through the system shell and return a
--   @Handle@ to its standard input.
spawnPipe' :: MonadIO m => String -> m Handle
spawnPipe' x = io $ do
    (rd, wr) <- createPipe
    setFdOption wr CloseOnExec True
    h <- fdToHandle wr
    hSetBuffering h LineBuffering
    _ <- xfork $ do
          _ <- dupTo rd stdInput
          executeFile "/bin/tcsh" False ["-c", encodeString x] Nothing
    closeFd rd
    return h

stringSearchAndReplace s search replace =
  foldr (\x y -> case y of [] -> x; y -> x ++ replace ++ y) []
          $ (splitRegex $ mkRegex search) s

fontNameLg t = stringSearchAndReplace (fontName t) "12" "13"

dmenuRun t@(Theme{ inactiveColor = ibc, inactiveTextColor = ifc,
                   activeColor = abc, activeTextColor = afc }) =
           "dmenu2_run -nb " ++ sq "#414F59" ++ " -nf " ++ sq ifc ++
           " -sb " ++ sq "#636D7B" ++ " -sf " ++ sq "#CEE7EF" ++
           " -fn " ++ sq (fontNameLg t) ++ " -p " ++ sq "Run Command:" ++
           " -l 50" ++ " -o 0.85"
  where sq v = "'" ++ v ++ "'"

singleColumnPercentages :: [Int]
singleColumnPercentages = [100]
dualColumnPercentages :: IORef [Int]
dualColumnPercentages = unsafePerformIO $ newIORef [60, 40]
tripleColumnPercentages :: IORef [Int]
tripleColumnPercentages = unsafePerformIO $ newIORef [32, 34, 34]
quadColumnPercentages :: IORef [Int]
quadColumnPercentages = unsafePerformIO $ newIORef [25, 25, 25, 25]

singleColumnFavor, dualColumnFavor, tripleColumnFavor, quadColumnFavor :: Int
singleColumnFavor = 1
dualColumnFavor = 2
tripleColumnFavor = 1
quadColumnFavor = 1

------------------------------------------------------------------------
-- Key bindings. Add, modify or remove key bindings here.
myKeys cfg@(XConfig {XMonad.modMask = modMask}) = M.fromList $

    -- Launch external applications
    [ ((modMask, xK_Return), spawn' $ termTmux' "x11")
    , ((modMask .|. shiftMask, xK_Return), spawn' $ term "")
    , ((mod1Mask .|. controlMask, xK_Insert), spawn' $ term "")
    , ((mod1Mask .|. controlMask, xK_Return), spawn' $ term "")
    , ((modMask, xK_x), spawn' $ dmenuRun myTheme)
    , ((mod1Mask, xK_F2), spawn' $ dmenuRun myTheme)
    , ((modMask, xK_p), spawn' "dmenu-user_menu")
    , ((mod1Mask, xK_F10), spawn' "dmenu-user_menu")
    , ((modMask .|. shiftMask, xK_x), spawn' $ xemacs "tty")
    , ((modMask .|. controlMask, xK_x), spawn' $ xemacs "tty2")
    , ((modMask, xK_u), spawn' $ termTmux "top")
    , ((modMask .|. shiftMask, xK_u), spawn' . termTitle "" $
                      termName ++ ": tmux (admin-top)")
    , ((modMask .|. shiftMask, xK_g), spawn' $ xemacs "gui")
    , ((modMask, xK_space), spawn' "lock-screen")
    -- screenshot related commands
    , ((modMask .|. controlMask, xK_p), spawn' "get-screenshot auto")
    , ((modMask .|. shiftMask, xK_p), spawn' "get-window-screenshot auto")
    -- XEmacs audio related commands
    , ((modMask .|. controlMask, xK_b), spawn' "audio-focus-buffer")
    , ((modMask .|. controlMask, xK_o), spawn' "audio-seek-absolute")
    , ((modMask .|. controlMask, xK_1), spawn' "audio-backward-10")
    , ((modMask .|. controlMask, xK_3), spawn' "audio-pause")
    , ((modMask .|. controlMask, xK_5), spawn' "audio-forward-10")
    -- XEmacs IM related commands
    , ((modMask .|. controlMask, xK_t), spawn' "im-accept")
    , ((modMask .|. mod1Mask, xK_t), spawn' "im-show-buddies")

    -- Set layout algorithm

    -- j_oin windows as tabbed layout
    , ((modMask, xK_o), groupToTabbedLayout')
    -- join windows as _tabbed layout
    , ((modMask, xK_t), groupToTabbedLayout')
    -- j_Oin windows as accordion layout
    , ((modMask .|. shiftMask, xK_o), groupToAccordionLayout')
    -- joi_n windows as accordion layout
    , ((modMask, xK_n), groupToAccordionLayout')
    -- _split windows as column layout
    , ((modMask, xK_s), groupToColumnLayout')

    -- Minimization related
    , ((modMask .|. shiftMask, xK_i), sendMessage RestoreNextMinimizedWin)
    , ((modMask, xK_i), withFocused minimizeWindow)

    -- Maximization related
    -- maximize windows as _full layout
    , ((modMask, xK_f), groupToFullLayout')

    -- Workspace / screen / column related

    -- View specific workspaces individually without changing the number or
    -- layout of screens, nor the contents of other screens..
    , ((modMask, xK_g), submap . M.fromList $
                        [ ((0, xK_1), view' 1)
                        , ((0, xK_2), view' 2)
                        , ((0, xK_3), view' 3)
                        , ((0, xK_4), view' 4)
                        , ((0, xK_5), view' 5)
                        , ((0, xK_6), view' 6)
                        , ((0, xK_7), view' 7)
                        , ((0, xK_8), view' 8)
                        , ((0, xK_9), view' 9)
                        , ((0, xK_0), view' 10) ])

    -- Special workspace / screen switching actions that integrate with
    -- layoutScreens
    , ((modMask, xK_1), viewDual' 1 2 1)
    , ((modMask, xK_2), viewDual' 1 2 2)
    , ((modMask, xK_3), viewDual' 3 4 3)
    , ((modMask, xK_4), viewDual' 3 4 4)
    , ((modMask, xK_5), viewMaximize 5)
    , ((modMask, xK_6), viewMaximize 6)
    , ((modMask, xK_7), viewMaximize 7)
    , ((modMask, xK_8), viewMaximize 8)
    , ((modMask, xK_9), viewMaximize 9)
    , ((modMask, xK_0), viewTriple' 1 2 10 10)

    -- Directly set the number of screens / columns, with various presets
    -- , ((modMask, xK_m), viewSingle' 1)
    , ((modMask, xK_z), viewDual' 1 2 1)
    , ((modMask, xK_w), viewTriple' 1 2 3 1)
    , ((modMask, xK_e), viewQuad' 1 2 3 4 1)
    , ((modMask .|. shiftMask, xK_m), viewSingle' 2)
    , ((modMask .|. shiftMask, xK_z), viewDual' 1 2 2)
    , ((modMask .|. shiftMask, xK_w), viewTriple' 1 2 3 2)
    , ((modMask .|. shiftMask, xK_e), viewQuad' 1 2 3 4 2)

    --  Reset the layouts on the current workspace to default
    , ((modMask .|. controlMask .|. shiftMask, xK_t),
       setLayout $ XMonad.layoutHook cfg)

    -- Resize viewed windows to the correct size
    , ((modMask, xK_r), refresh)

    -- Swap Window based on direction
    -- xK_Left combinations now use screen related movement commands...
    , ((modMask .|. shiftMask, xK_Up), windows . W.modify' $ swapUpNoWrap')
    , ((modMask .|. shiftMask, xK_Down), windows . W.modify' $ swapDownNoWrap')
    -- xK_Right combinations now use screen related movement commands...

    -- xK_j combinations now use screen related movement commands...
    , ((modMask .|. shiftMask, xK_j), windows . W.modify' $ swapUpNoWrap')
    , ((modMask .|. shiftMask, xK_k), windows . W.modify' $ swapDownNoWrap')
    -- xK_k combinations now use screen related movement commands...

    -- movement related keys
    -- xK_Left combinations now use screen related movement commands...
    , ((modMask, xK_Up), windows . W.modify' $ focusUpNoWrap')
    , ((modMask, xK_Down), windows . W.modify' $ focusDownNoWrap')
    -- xK_Right combinations now use screen related movement commands...

    -- xK_h combinations now use screen related movement commands...
    , ((modMask, xK_j), windows . W.modify' $ focusUpNoWrap')
    , ((modMask, xK_k), windows . W.modify' $ focusDownNoWrap')
    -- xK_l combinations now use screen related movement commands...

    , ((modMask, xK_m), goToSelected myGSConfig)
    -- switch windows much like Orion or *Emacs
    , ((modMask, xK_b), gotoMenuArgs' "dmenu2" dmenuSwitchMenuArgs)
    , ((modMask .|. controlMask, xK_m), goToSelected myGSConfig)
    , ((modMask, xK_semicolon), goToSelected myGSConfig)

    -- close focused window 
    , ((modMask, xK_c), kill)

    -- Push window back into tiling
    , ((modMask .|. shiftMask, xK_t), withFocused $ windows . W.sink)

    -- Make current window floating
    , ((modMask .|. shiftMask, xK_f), withFocused $ float)

    -- Shrink / Expand the column (doesn't work, to fix...)
    -- , ((modMask .|. controlMask, xK_h), sendMessage Shrink)
    -- , ((modMask .|. controlMask, xK_l), sendMessage Expand)
    , ((modMask .|. controlMask, xK_h), spawn' $ "focus-cursor left")
    , ((modMask .|. controlMask, xK_j), spawn' $ "focus-cursor up")
    , ((modMask .|. controlMask, xK_k), spawn' $ "focus-cursor down")
    , ((modMask .|. controlMask, xK_l), spawn' $ "focus-cursor right")
    , ((modMask .|. controlMask .|. shiftMask, xK_h), spawn' $ "focus-cursor left")
    , ((modMask .|. controlMask .|. shiftMask, xK_j), spawn' $ "focus-cursor up")
    , ((modMask .|. controlMask .|. shiftMask, xK_k), spawn' $ "focus-cursor down")
    , ((modMask .|. controlMask .|. shiftMask, xK_l), spawn' $ "focus-cursor right")
    , ((modMask .|. controlMask, xK_s), spawn' $ "focus-cursor home")
    , ((modMask .|. controlMask, xK_f), spawn' $ "focus-cursor end")
    , ((modMask .|. controlMask, xK_x), spawn' $ "focus-cursor prior")
    , ((modMask .|. controlMask, xK_v), spawn' $ "focus-cursor next")
    --, ((modMask .|. controlMask .|. shiftMask, xK_x), spawn' $ "focus-cursor cHome")
    --, ((modMask .|. controlMask .|. shiftMask, xK_v), spawn' $ "focus-cursor cEnd")
    --, ((modMask .|. controlMask .|. shiftMask, xK_s), spawn' $ "focus-cursor cPrior")
    --, ((modMask .|. controlMask .|. shiftMask, xK_f), spawn' $ "focus-cursor cNext")
    , ((modMask .|. controlMask .|. shiftMask, xK_s), spawn' $ "focus-cursor home")
    , ((modMask .|. controlMask .|. shiftMask, xK_f), spawn' $ "focus-cursor end")
    , ((modMask .|. controlMask .|. shiftMask, xK_x), spawn' $ "focus-cursor prior")
    , ((modMask .|. controlMask .|. shiftMask, xK_v), spawn' $ "focus-cursor next")
    , ((modMask .|. mod1Mask .|. controlMask, xK_h), spawn' $ "chromium-focus-tab prior")
    , ((modMask .|. mod1Mask .|. controlMask, xK_l), spawn' $ "chromium-focus-tab next")
    , ((modMask .|. controlMask .|. shiftMask, xK_b), spawn' $ "chromium-manage-url backward")
    , ((modMask .|. controlMask .|. shiftMask, xK_o), spawn' $ "chromium-manage-url forward")
    , ((modMask .|. mod1Mask .|. controlMask, xK_b), spawn' $ "chromium-manage-url downward")
    , ((modMask .|. mod1Mask .|. controlMask, xK_o), spawn' $ "chromium-manage-url upward")
    , ((modMask .|. controlMask, xK_d), spawn' $ "chromium-manage-tab close")
    , ((modMask .|. controlMask, xK_u), spawn' $ "chromium-restore-tab restore")

    -- Move between screens (virtual or otherwise, wmii/orion/ion-style, thanks
    -- to LayoutScreens)
    , ((modMask, xK_h), screenGo L False)
    , ((modMask, xK_l), screenGo R False)
    , ((modMask .|. shiftMask, xK_h), windowToScreen' L False)
    , ((modMask .|. shiftMask, xK_l), windowToScreen' R False)

    , ((modMask, xK_Left), screenGo L False)
    , ((modMask, xK_Right), screenGo R False)
    , ((modMask .|. shiftMask, xK_Left), windowToScreen' L False)
    , ((modMask .|. shiftMask, xK_Right), windowToScreen' R False)

    -- Restart XMonad
    , ((modMask, xK_q),
       broadcastMessage ReleaseResources >> restart "xmonad" True)

    -- Quit XMonad
    , ((modMask .|. mod1Mask .|. shiftMask, xK_q), io (exitWith ExitSuccess))
    ] ++

    -- mod-[0..9], Switch to workspace N (moved elsewhere due to customization)
    -- mod-shift-[0..9], Move client to workspace N
    -- mod-mod1-[0..9], Copy client to workspace N
    [((m .|. modMask, k), windows $ f i)
        | (i, k) <- zip (XMonad.workspaces cfg) $ [xK_1 .. xK_9] ++ [xK_0]
        , (f, m) <- [-- (W.view, 0),
                     (W.shift, shiftMask), (copy, mod1Mask)]]

    where termName = XMonad.terminal cfg
          term "" = termName
          term s  = termName ++ " " ++ s
          termTitle c t = termName ++ " -T " ++ "'" ++ t ++ "' " ++ c
          termTmux c = termTmuxTitle c $ termName ++ ": tmux (" ++ c ++ ")"
          termTmux' c = termTmuxTitle c $ termName ++ ": tmux"
          termTmuxTitle c t = termTitle ("-e tmux attach -t " ++ c) t
          term' c t = termName ++ " -T " ++ "'" ++ t ++ "'" ++ " -e " ++ c
          xemacs "gui" = "gnuclient"
          xemacs "tty" = term' "gnuclient -nw" (termName ++ ": XEmacs")
          xemacs "tty2" = term' "gnuclient -nw"
                          $ termName ++ ": XEmacs (secondary)"
          xemacs "tty3" = term' "gnuclient -nw"
                          $ termName ++ ": XEmacs (tertiary)"

          dmenuTransArgs = ["-l", "50", "-nb", "#414F59", "-nf",
                            inactiveTextColor t, "-sb", "#636D7B", "-sf",
                            "#CEE7EF", "-fn", fontNameLg t, "-o", "0.85"]
            where t = myTheme
          dmenuSwitchMenuArgs = dmenuTransArgs ++ ["-p", "Switch to Window:"]

          windowToScreen' direction wrap = do windowToScreen direction wrap
                                              screenGo direction wrap
          viewShift i = do W.view i . W.shift i
          view' n = do windows . W.greedyView $ myWorkspaces !! (n - 1)

          fi :: (Integral a, Num b) => a -> b
          fi = fromIntegral

          horizontalSizes :: Dimension -> [Int] -> Int -> Int
                          -> [(Position, Dimension)]
          horizontalSizes dim' [a] 100 _ | a == 100 = [(0, dim')]
          horizontalSizes dim' [a, b] 100 favored
            | a + b == 100 =
              case a' + b' of
                dim' -> [(0, fi a'), (fi a', fi b')]
                n -> let n' = dim' - n in
                     case favored of
                       1 -> [(0, fi a' + n'), (fi a' + fi n', fi b')]
                       2 -> [(0, fi a'), (fi a', b' + n')]
            where dim'' = fi . read . show $ dim'
                  a' = floor $ dim'' * fi a * 0.01
                  b' = floor $ dim'' * fi b * 0.01
          horizontalSizes dim' [a, b, c] 100 favored
            | a + b + c == 100 =
              case a' + b' + c' of
                dim' -> [(0, fi a'), (a', fi b'), (a' + b', fi c')]
                n -> let n' = fi dim' - fi n in
                     case favored of
                       1 -> [(0, fi $ a' + n'), (a' + n', fi b'),
                             (a' + n' + b', fi c')]
                       2 -> [(0, fi a'), (a', fi $ b' + n'),
                             (a' + b' + n', fi c')]
                       3 -> [(0, fi a'), (a', fi b'), (a' + b', fi $ c' + n')]
            where dim'' = fi $ read . show $ dim'
                  a' = floor $ dim'' * fi a * 0.01
                  b' = floor $ dim'' * fi b * 0.01
                  c' = floor $ dim'' * fi c * 0.01
          horizontalSizes dim' [a, b, c, d] 100 favored
            | a + b + c + d == 100 =
              case a' + b' + c' + d' of
                dim' -> [(0, fi a'), (a', fi b'), (a' + b', fi c'),
                         (a' + b' + c', fi d')]
                n -> let n' = fi dim' - fi n in
                     case favored of
                       1 -> [(0, fi $ a' + n'), (a' + n', fi b'),
                             (a' + n' + b', fi c'), (a' + n' + b' + c', fi d')]
                       2 -> [(0, fi a'), (a', fi $ b' + n'),
                             (a' + b' + n', fi c'), (a' + b' + n' + c', fi d')]
                       3 -> [(0, fi a'), (a', fi b'),
                             (a' + b', fi $ c' + n'),
                             (a' + b' + c' + n', fi d')]
                       4 -> [(0, fi a'), (a', fi b'),
                             (a' + b', fi c'), (a' + b' + c', fi $ d' + n')]
            where dim'' = fi . read . show $ dim'
                  a' = floor $ dim'' * fi a * 0.01
                  b' = floor $ dim'' * fi b * 0.01
                  c' = floor $ dim'' * fi c * 0.01
                  d' = floor $ dim'' * fi d * 0.01

          viewSingle :: [Char] -> X ()
          viewSingle m =
            do withDisplay $ \dpy ->
                 -- note that this is *not* an XMonad screen, it is an X11
                 -- screen; it has nothing to do with virtual screens...
                 --
                 -- equivalent in meaning to the code shown above
                 -- let s = defaultScreenOfDisplay dpy
                 --     w = read . show $ widthOfScreen s in
                 let s = defaultScreen dpy
                     w = read . show $ displayWidth dpy s
                     h = read . show $ displayHeight dpy s
                     p = singleColumnPercentages
                     [(p1, d1)] = horizontalSizes w p 100 1 in
                 layoutScreens 1 $ fixedLayout [Rectangle p1 0 d1 (fi h)]
               screenWorkspace 0 >>= flip whenJust (windows . W.view)
               windows $ W.greedyView m
               return ()
          viewDual :: [Char] -> [Char] -> [Char] -> X ()
          viewDual m n o =
            do withDisplay $ \dpy ->
                 let s = defaultScreen dpy
                     w = read . show $ displayWidth dpy s
                     h = read . show $ displayHeight dpy s
                     p = unsafePerformIO $ readIORef dualColumnPercentages
                     [(p1, d1), (p2, d2)] = horizontalSizes w p 100 2 in
                 do layoutScreens 2 $ fixedLayout [Rectangle p1 0 d1 (fi h),
                                                   Rectangle p2 0 d2 (fi h)]
                    screenWorkspace 0 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView m
                    screenWorkspace 1 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView n
                    windows $ W.view o
                    return ()
          viewTriple :: [Char] -> [Char] -> [Char] -> [Char] -> X ()
          viewTriple m n o p =
            do withDisplay $ \dpy ->
                 let s = defaultScreen dpy
                     w = read . show $ displayWidth dpy s
                     h = read . show $ displayHeight dpy s
                     cols = unsafePerformIO $ readIORef tripleColumnPercentages
                     [(p1, d1), (p2, d2), (p3, d3)] =
                       horizontalSizes w cols 100 1 in
                 do layoutScreens 3 $ fixedLayout [Rectangle p1 0 d1 (fi h),
                                                   Rectangle p2 0 d2 (fi h),
                                                   Rectangle p3 0 d3 (fi h)]
                    screenWorkspace 0 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView m
                    screenWorkspace 1 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView n
                    screenWorkspace 2 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView o
                    windows $ W.view p
                    return ()
          viewQuad :: [Char] -> [Char] -> [Char] -> [Char] -> [Char] -> X ()
          viewQuad m n o p q =
            do withDisplay $ \dpy ->
                 let s = defaultScreen dpy
                     w = read . show $ displayWidth dpy s
                     h = read . show $ displayHeight dpy s
                     cols = unsafePerformIO $ readIORef quadColumnPercentages
                     [(p1, d1), (p2, d2), (p3, d3), (p4, d4)] =
                       horizontalSizes w cols 100 1 in
                 do layoutScreens 4 $ fixedLayout [Rectangle p1 0 d1 h,
                                                   Rectangle p2 0 d2 h,
                                                   Rectangle p3 0 d3 h,
                                                   Rectangle p4 0 d4 h]
                    screenWorkspace 0 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView m
                    screenWorkspace 1 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView n
                    screenWorkspace 2 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView o
                    screenWorkspace 3 >>= flip whenJust (windows . W.view)
                    windows $ W.greedyView p
                    windows $ W.view q
                    return ()

          viewSingle' :: Int -> X ()
          viewSingle' m = do viewSingle $ myWorkspaces !! (m - 1)
          viewDual' :: Int -> Int -> Int -> X ()
          viewDual' m n o = do viewDual m' n' o'
            where m' = myWorkspaces !! (m - 1)
                  n' = myWorkspaces !! (n - 1)
                  o' = myWorkspaces !! (o - 1)
          viewTriple' :: Int -> Int -> Int -> Int -> X ()
          viewTriple' m n o p = viewTriple m' n' o' p'
            where m' = myWorkspaces !! (m - 1)
                  n' = myWorkspaces !! (n - 1)
                  o' = myWorkspaces !! (o - 1)
                  p' = myWorkspaces !! (p - 1)
          viewQuad' :: Int -> Int -> Int -> Int -> Int -> X ()
          viewQuad' m n o p q = viewQuad m' n' o' p' q'
            where m' = myWorkspaces !! (m - 1)
                  n' = myWorkspaces !! (n - 1)
                  o' = myWorkspaces !! (o - 1)
                  p' = myWorkspaces !! (p - 1)
                  q' = myWorkspaces !! (q - 1)

          viewMaximize n = do windows . W.view $ myWorkspaces !! (n - 1)
                              rescreen

          focusUpNoWrap' :: W.Stack t -> W.Stack t
          focusUpNoWrap' = noWrapUp

          focusDownNoWrap' :: W.Stack t -> W.Stack t
          focusDownNoWrap' = noWrapDown

          -- directly borrowed from StackSet.hs
          -- | reverse a stack: up becomes down and down becomes up.
          reverseStack :: W.Stack a -> W.Stack a
          reverseStack (W.Stack t ls rs) = W.Stack t rs ls

          -- | non-wrapping version of 'W.swapUp''
          -- loosely based on code from WorkspaceCursors.hs and StackSet.hs
          swapUpNoWrap' ::  W.Stack t -> W.Stack t
          swapUpNoWrap' x@(W.Stack _ [] _) = x
          swapUpNoWrap' (W.Stack t (l:ls) rs) = W.Stack t ls (l:rs)
          swapUpNoWrap' (W.Stack t [] rs) = W.Stack t (reverse rs) []

          -- | non-wrapping version of 'W.swapDown''
          -- loosely based on code from StackSet.hs
          swapDownNoWrap' :: W.Stack t -> W.Stack t
          swapDownNoWrap' = reverseStack . swapUpNoWrap' . reverseStack

------------------------------------------------------------------------
-- Mouse bindings: default actions bound to mouse events
--
-- you may also bind events to the mouse scroll wheel (button4 and button5)
myMouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList $
    -- mod-button1, Set the window to floating mode and move by dragging
    [ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w))
    -- mod-button2, Raise the window to the top of the stack
    , ((modMask, button2), (\w -> focus w >> windows W.swapMaster))
    -- in emergencies, we might need to access the dmenu menu via the mouse...
    , ((0, button2), (\_ -> spawn' $ "dmenu-user_menu") :: Window -> X ())
    -- mod-button3, Set the window to floating mode and resize by dragging
    , ((modMask, button3), (\w -> focus w >> mouseResizeWindow w)) ]

myTheme :: Theme
myTheme = defaultTheme { decoHeight = 17
                       , activeTextColor = "#9ED5F7"
                       , inactiveTextColor = "#FFFFFF"
                       , activeColor = "#121212"
                       , inactiveColor = "#000000"
                       , activeBorderColor = "#82B0CC"
                       , inactiveBorderColor = "#3A5568"
                       , fontName = "xft:DejaVu Sans:pixelsize=12:" ++
                                    "antialias=true:autohint=false" }

------------------------------------------------------------------------
-- Layouts:

-- You can specify and transform your layouts by modifying these values.  If
-- you change layout bindings be sure to use 'mod-shift-space' after restarting
-- (with 'mod-q') to reset your layout state to the new defaults, as XMonad
-- preserves your old layout settings by default.

-- The available layouts.  Note that each layout is separated by |||, which
-- denotes layout choice.
myLayout theme' = layoutHints . onWorkspaces wss mainFull
                  . minimize $ mhs ||| mainFull
  where
    wss = map (\ws -> myWorkspaces !! ((read ws) - 1)) fullScreenWorkspaceList
    -- tile and tab layout defaults
    mhs = wmiiR shrinkText theme'
    mainFull = wmii shrinkText theme'

------------------------------------------------------------------------
-- Window rules:

-- Execute arbitrary actions and WindowSet manipulations when managing a new
-- window. You can use this to, for example, always float a particular program,
-- or have a client always appear on a particular workspace.
--
-- To find the property name associated with a program, use
-- > xprop | grep WM_CLASS
-- and click on the client you're interested in.
--
-- To match on the WM_NAME, you can use 'title' in the same way that
-- 'className' and 'resource' are used below.
myManageHook = manageDocks <+> composeAll
               [ className =? "MPlayer" --> doFloat
               , className =? "Chrome" --> doShifts "1" ["5"]
               , fmap (L.isSuffixOf "Chromium") title --> doShifts "1" ["5"]
               , className =? "Xombrero" --> doShifts "1" ["5"]
               , title =? (myTerminal ++ ": XEmacs") --> doShifts "2" ["6"]
               , title =? (myTerminal ++ ": XEmacs (secondary)")
               --> doShift' "10"
               , className =? "Emacs" --> doShifts "2" ["6"]
               , className =? "Emacs (secondary)" --> doShift' "10"
               , fmap (L.isSuffixOf "(admin-top)") title --> doShift' "3"
               , fmap (L.isSuffixOf "(top)") title --> doShift' "4"
               , className =? "rdesktop" --> doHideSinkShift' "7"
               , className =? "xfreerdp" --> doShift' "7"
               , className =? "Vncviewer" --> doShift' "7"
               , className =? "winxp_vm" --> doShift' "7"
               , title =? "Wine desktop" --> doShift' "7"
               , className =? "feh" --> doShift' "8"
               , className =? "Xpdf" --> doShift' "8"
               , className =? "Zathura" --> doShift' "8"
               , className =? "Acroread" --> doShift' "8"
               , className =? "GV" --> doShift' "8"
               , title =? "OpenOffice.org" --> doShift' "8"
               , title =? "OpenMPT" --> doShift' "9"
               , title =? "MadTracker" --> doShift' "9"
               , className =? "Gimp" --> doShift' "9"
               , className =? "qgo" --> doShift' "9"
               , className =? "qGo" --> doShift' "9"
               , resource  =? "desktop_window" --> doIgnore
               ]
  where -- The portions of doShifts in the let binding are my own work, and the
        -- rest of that function is adapted from parts of copyMaybe and
        -- killAllOtherCopies from XMonad.Actions.CopyWindow
        doShifts main wss =
          let main' = myWorkspaces !! ((read main) - 1)
              wss' = map (\ws -> myWorkspaces !! ((read ws) - 1)) wss in
          (ask >>= doF . \w -> (\ws -> foldr ($) ws (map (copyWindow w) wss'))
                               . W.shift main') :: ManageHook
        doShift' main = doShift $ myWorkspaces !! ((read main) - 1)
        doHideSink = ask >>= \w -> liftX (hide w) >> doF (W.sink w)
        doHideSinkShift w = doHideSink <+> doShift w :: ManageHook
        doHideSinkShift' w = doHideSinkShift $ myWorkspaces !! ((read w) - 1)

-- Whether focus follows the mouse pointer.
myFocusFollowsMouse :: Bool
myFocusFollowsMouse = False

------------------------------------------------------------------------
-- Status bars and logging
 
-- Perform an arbitrary action on each internal state change or X event.
-- See the 'DynamicLog' extension for examples.
myLogHook :: Theme -> Handle -> X ()
myLogHook theme@(Theme{ activeColor = abc, activeTextColor = afc,
                        inactiveTextColor = ifc }) xmproc =
  do fadeInactiveCurrentWSLogHook 0.92; dynamicLogWithPP $ xmobar'
  where xmobar' = xmobarPP {
                    ppOutput = hPutStrLn xmproc
                  , ppTitle = xmobarColor afc "" . wrap " " " "
                  , ppCurrent = xmobarColor afc "" . wrap "[ " " ]"
                  , ppVisible = xmobarColor ifc "" . wrap "( " " )"
                  , ppOrder = \(ws : layout : title : _) -> [ws, title, layout]
                  , ppSep = "  |  " }

-- Navigation settings used by GSConfig
myNavigation :: TwoD a (Maybe a)
myNavigation = makeXEventhandler $ shadowWithKeymap navKeyMap navDefaultHandler
  where navKeyMap = M.fromList [
                     ((0, xK_Escape),      cancel)
                    ,((0, xK_period),      cancel)
                    ,((controlMask, xK_g), cancel)
                    ,((0, xK_Return),      select)
                    ,((0, xK_slash),       substringSearch myNavigation)
                    ,((0, xK_Left),        move (-1,0)  >> myNavigation)
                    ,((0, xK_h),           move (-1,0)  >> myNavigation)
                    ,((0, xK_Right),       move (1,0)   >> myNavigation)
                    ,((0, xK_l),           move (1,0)   >> myNavigation)
                    ,((0, xK_Down),        move (0,1)   >> myNavigation)
                    ,((0, xK_k),           move (0,1)   >> myNavigation)
                    ,((0, xK_Up),          move (0,-1)  >> myNavigation)
                    ,((0, xK_j),           move (0,-1)  >> myNavigation)
                    ,((0, xK_y),           move (-1,-1) >> myNavigation)
                    ,((0, xK_i),           move (1,-1)  >> myNavigation)
                    ,((0, xK_n),           move (-1,1)  >> myNavigation)
                    ,((0, xK_m),           move (1,-1)  >> myNavigation)
                    ,((0, xK_space),       setPos (0,0) >> myNavigation) ]
        -- The navigation handler ignores unknown key symbols
        navDefaultHandler = const myNavigation

-- More Navigation settings used by GSConfig
myGSConfig = defaultGSConfig { gs_cellheight = 30
                             , gs_cellwidth = 100
                             , gs_navigate = myNavigation }

------------------------------------------------------------------------

-- Run XMonad with the settings you specify.
main = do
  runProcessWithInput "/usr/bin/killall" ["-9", "compton"] ""
  spawn' $ "feh --bg-tile " ++ myBackgroundImage
  runProcessWithInputAndWait "/usr/bin/killall" ["-9", "xmobar-trans"] ""
       (seconds 1)
  spawn' $ "xmodmap ~/.Xmodmap-ergodox"
  -- spawn' $ "xmodmap ~/.Xmodmap-ms_natural"
  spawn' $ "xrdb -merge ~/.Xresources"
  spawn' $ "compton --config ~/.compton.conf -e 0.5"

  -- xmproc <- spawnPipe' "/usr/local/bin/xmobar-trans --alpha=100"
  -- regular xmobar supports transparency now out of the box :)
  xmproc <- spawnPipe' "/usr/local/bin/xmobar --alpha=100"
  xmonad $ defaults xmproc

-- A structure containing your configuration settings, overriding
-- fields in the default config. Any you don't override, will 
-- use the defaults defined in xmonad/XMonad/Config.hs
defaults xmproc = ewmh defaultConfig {
      -- simple stuff
        terminal           = myTerminal,
        focusFollowsMouse  = myFocusFollowsMouse,
        workspaces         = myWorkspaces,

      -- colors, appearance
        normalBorderColor  = myNormalBorderColor,
        focusedBorderColor = myFocusedBorderColor,
        borderWidth        = myBorderWidth,

      -- key bindings
        keys               = myKeys,
        mouseBindings      = myMouseBindings,
        modMask            = myModMask,
        -- numlockMask        = myNumlockMask,

        startupHook = setWMName "LG3D",


      -- hooks, layouts
        -- avoidStruts not required?
        -- layoutHook         = avoidStruts $ myLayout myTheme,
        layoutHook         = myLayout myTheme,
        manageHook         = manageDocks <+> myManageHook,
        logHook            = myLogHook myTheme xmproc,
        handleEventHook    = docksEventHook <+>
                             fullscreenEventHook }
