525 lines
19 KiB
Haskell
525 lines
19 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
module Main where
|
|
|
|
|
|
import qualified Data.Map as M
|
|
import Data.Monoid (All, Endo)
|
|
import Data.Ratio ((%))
|
|
import FloatKeys (keysResizeWindow)
|
|
import NixCommands
|
|
import qualified SolarizedLight as Solarized
|
|
import System.Exit
|
|
import XMonad
|
|
import XMonad.Actions.CopyWindow (copy, copyToAll, kill1,
|
|
killAllOtherCopies,
|
|
wsContainingCopies)
|
|
import XMonad.Actions.CycleWS (toggleWS')
|
|
import XMonad.Actions.DynamicProjects (Project (..),
|
|
dynamicProjects)
|
|
import XMonad.Actions.DynamicWorkspaces (addHiddenWorkspace, removeEmptyWorkspaceAfterExcept,
|
|
renameWorkspace,
|
|
withWorkspace)
|
|
import XMonad.Actions.UpdatePointer (updatePointer)
|
|
import XMonad.Actions.Warp (warpToScreen)
|
|
import XMonad.Hooks.DynamicLog (PP (..), dynamicLog, shorten,
|
|
statusBar, wrap)
|
|
import XMonad.Hooks.ManageHelpers (doCenterFloat)
|
|
import XMonad.Hooks.SetWMName (setWMName)
|
|
import XMonad.Hooks.UrgencyHook (BorderUrgencyHook (..),
|
|
SpawnUrgencyHook (..),
|
|
withUrgencyHook)
|
|
import XMonad.Layout.BoringWindows (boringWindows, clearBoring,
|
|
focusDown, focusUp,
|
|
markBoring)
|
|
import XMonad.Layout.Mosaic (Aspect (Reset), mosaic)
|
|
import XMonad.Layout.NoBorders (noBorders, smartBorders)
|
|
import XMonad.Layout.ResizableTile (MirrorResize (MirrorExpand, MirrorShrink),
|
|
ResizableTall (..))
|
|
import XMonad.Layout.Simplest (Simplest (..))
|
|
import XMonad.Layout.SubLayouts (GroupMsg (..), onGroup,
|
|
pullGroup, subLayout,
|
|
subTabbed)
|
|
import XMonad.Layout.Tabbed (TabbedDecoration, addTabs,
|
|
addTabsAlways, shrinkText)
|
|
import XMonad.Layout.WindowNavigation (configurableNavigation,
|
|
noNavigateBorders)
|
|
import XMonad.Prompt (XPConfig (..))
|
|
import qualified XMonad.StackSet as W
|
|
import XMonad.Util.EZConfig (additionalKeysP)
|
|
import XMonad.Util.Scratchpad (scratchpadManageHook,
|
|
scratchpadSpawnAction)
|
|
import XMonad.Util.SpawnOnce (spawnOnce)
|
|
import XMonad.Util.Types (Direction2D (D, L, R, U))
|
|
|
|
import TabbedFix (historyLayout, runAllPending)
|
|
import XMonad.Layout.StateFull (focusTracking)
|
|
|
|
-- only needed to retrigger a build
|
|
import XMonad.Layout.DwmStyle
|
|
|
|
------------------------------------------------------------------------
|
|
--
|
|
-- Layouts
|
|
--
|
|
------------------------------------------------------------------------
|
|
|
|
selectionColor :: String
|
|
selectionColor = Solarized.violet
|
|
|
|
nonSelectionColor :: String
|
|
nonSelectionColor = Solarized.base02
|
|
|
|
|
|
-- http://hackage.haskell.org/package/xmonad-contrib-0.15/docs/XMonad-Layout-Tabbed.html#t:Theme
|
|
|
|
-- todo : when there is only one window there should not be a decoration bar, when there are 2 there should be a decoration bar
|
|
-- ResizableTall is same as Tall but has resizable rightside window
|
|
myLayout = (windowConfiguration $ myTabbed $ mySubLayout $ boringWindows resizeableTall) ||| noBorders Full
|
|
-- needed for rebuild sometimes
|
|
-- myLayout = dwmStyle shrinkText def ( layoutHook def ) ||| noBorders Full
|
|
where
|
|
resizeableTall = ResizableTall nmaster delta ratio []
|
|
-- The default number of windows in the master pane
|
|
nmaster = 1
|
|
-- Default proportion of screen occupied by master pane
|
|
ratio = 12 / 20
|
|
-- Percent of screen to increment by when resizing panes
|
|
delta = 3 / 100
|
|
|
|
windowConfiguration = configurableNavigation noNavigateBorders
|
|
|
|
myTabbed x = smartBorders $ addTabsAlways shrinkText tabDecoration x
|
|
|
|
mySubLayout x = subLayout [] Simplest x
|
|
|
|
tabDecoration = def { activeColor = selectionColor
|
|
, activeBorderColor = selectionColor
|
|
, activeTextColor = Solarized.base03
|
|
, inactiveColor = nonSelectionColor
|
|
, inactiveBorderColor = nonSelectionColor
|
|
, inactiveTextColor = Solarized.base0
|
|
, fontName = "-*-terminus-medium-*-*-*-10101010-*-*-*-*-*-*-*"
|
|
, decoHeight = 11
|
|
}
|
|
|
|
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- predefined workspaces
|
|
--
|
|
-- ------------------------------------------------------------
|
|
-- default workspaces they will always be there.
|
|
-- And they are protected against renaming
|
|
myWorkspaces :: [String]
|
|
myWorkspaces = ["1", "2", "3", "4"]
|
|
|
|
-- workspaces names to be used only by one program, partly spawning on startup.
|
|
autoSpawnWorkspaces = [ "-copyq" ]
|
|
|
|
-- theses workspaces should not be removed by the workspace
|
|
-- switch commands
|
|
nonRemovableWorkspaces = myWorkspaces ++ autoSpawnWorkspaces
|
|
|
|
-- projects
|
|
-- named workspaces with predefined behavior
|
|
projects :: [Project]
|
|
projects =
|
|
[ Project { projectName = "chat"
|
|
, projectDirectory = "~/"
|
|
, projectStartHook = Just $ do spawn nixStartIrc
|
|
}
|
|
, Project { projectName = "audio"
|
|
, projectDirectory = "~/music-library"
|
|
, projectStartHook = Just $ do spawn nixStartAudacious
|
|
}
|
|
, Project { projectName = "nixos"
|
|
, projectDirectory = "~/dev/krops"
|
|
, projectStartHook = Nothing
|
|
}
|
|
]
|
|
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- key definitions
|
|
--
|
|
-- ------------------------------------------------------------
|
|
myKeys :: XConfig Layout -> M.Map (ButtonMask, KeySym) (X ())
|
|
myKeys XConfig {modMask = modm} =
|
|
M.fromList $
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- predefined workspaces
|
|
--
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- mod-[1..9], Switch to workspace N
|
|
[ ( (m .|. modm, k)
|
|
, removeEmptyWorkspaceAfterExcept nonRemovableWorkspaces $ windows $ f i)
|
|
| (i, k) <- zip myWorkspaces [xK_1 .. xK_9]
|
|
, (f, m) <- [(W.greedyView, 0)]
|
|
] ++
|
|
-- mod-<shift>-[1..9] move window to workspace N
|
|
-- mod-<control>-[1..9] copy window to workspace N
|
|
[ ((m .|. modm, k), windows $ f i)
|
|
| (i, k) <- zip myWorkspaces [xK_1 .. xK_9]
|
|
, (f, m) <- [(W.shift, shiftMask), (copy, controlMask)]
|
|
]
|
|
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- select next Screen/Monitor.
|
|
-- (works for 2 and 1 monitor, but might also work for more)
|
|
--
|
|
-- ------------------------------------------------------------
|
|
selectNextScreen :: X ()
|
|
selectNextScreen = do
|
|
W.StackSet {W.current = current, W.visible = visible} <- gets windowset
|
|
warpToScreen (nextScreen current visible) (1 % 2) (1 % 2)
|
|
where
|
|
nextScreen current [] = W.screen current
|
|
nextScreen _ (x:_) = W.screen x
|
|
|
|
isFloat :: Window -> X Bool
|
|
isFloat w = gets windowset >>= \ws -> return (M.member w $ W.floating ws)
|
|
|
|
-- | add different shortcuts for different type
|
|
-- of situation. Floating or Tiling
|
|
floatTileCommand :: X () -> X () -> Window -> X ()
|
|
floatTileCommand forFloating forTileing window = do
|
|
floating <- isFloat window
|
|
if floating
|
|
then forFloating
|
|
else forTileing
|
|
|
|
toggleFloating :: W.RationalRect -> Window -> X ()
|
|
toggleFloating position =
|
|
floatTileCommand
|
|
(withFocused (windows . W.sink))
|
|
(withFocused (windows . (`W.float` position)))
|
|
|
|
multiKeys [] = []
|
|
multiKeys ((key, command):xs) = (createMultiKey key command) ++ multiKeys xs
|
|
where
|
|
createMultiKey keyString command =
|
|
[("M4-" ++ keyString, command), ("M4-z " ++ keyString, command)]
|
|
|
|
myAdditionaKeys :: [(String, X ())]
|
|
myAdditionaKeys
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- dynamic workspaces
|
|
--
|
|
-- ------------------------------------------------------------
|
|
-- switch to workspace
|
|
=
|
|
(multiKeys
|
|
[ ( "`"
|
|
, removeEmptyWorkspaceAfterExcept nonRemovableWorkspaces $
|
|
withWorkspace autoXPConfig (windows . W.greedyView))
|
|
-- move focused window to workspace
|
|
, ("S-<Space>", withWorkspace myXPConfig (windows . W.shift))
|
|
-- copy focused window to workspace
|
|
, ("C-<Space>", withWorkspace myXPConfig (windows . copy))
|
|
-- make windows "sticky" by copy and remove them to and from all other windows
|
|
, ( "s"
|
|
, do copies <- wsContainingCopies
|
|
if not (null copies)
|
|
then do killAllOtherCopies
|
|
clearBoring
|
|
else do windows copyToAll
|
|
markBoring)
|
|
-- rename workspace but make sure myWorkspaces still exist
|
|
, ( "r"
|
|
, do renameWorkspace myXPConfig
|
|
sequence_ [addHiddenWorkspace ws | ws <- myWorkspaces])
|
|
, ("<Esc>", toggleWS' ["NSP"])
|
|
]) ++
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- launch applications
|
|
--
|
|
-- ------------------------------------------------------------
|
|
(multiKeys
|
|
-- launch a terminal
|
|
[ ("<Return>", spawn $ XMonad.terminal defaults)
|
|
, ("q", kill1)
|
|
-- open scratchpad
|
|
, ("-", scratchpadSpawnAction defaults)
|
|
]) ++
|
|
[ ( "<Print>"
|
|
-- create screenshot
|
|
, spawn nixStartFlameshot)
|
|
-- invert color for bright or dark days
|
|
, ("<Pause>", spawn nixInvertColors)
|
|
] ++
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- Window and Layout
|
|
--
|
|
-- ------------------------------------------------------------
|
|
(multiKeys
|
|
[
|
|
-- remove window from tabbed group
|
|
("i", withFocused (sendMessage . UnMerge))
|
|
-- merge with left window
|
|
, ("<Left>", sendMessage $ pullGroup L)
|
|
, ("S-<Left>", sendMessage $ pullGroup L)
|
|
-- merge with right window
|
|
, ("<Right>", sendMessage $ pullGroup R)
|
|
, ("S-<Right>", sendMessage $ pullGroup R)
|
|
-- merge with upper window
|
|
, ("<Up>", sendMessage $ pullGroup U)
|
|
, ("S-<Up>", sendMessage $ pullGroup U)
|
|
-- merge with lower window
|
|
, ("<Down>", sendMessage $ pullGroup D)
|
|
, ("S-<Down>", sendMessage $ pullGroup D)
|
|
-- Change the selection in the tabbed Subgroup
|
|
, ("o", onGroup W.focusDown')
|
|
|
|
-- Move focus to the next window
|
|
, ("j", do sendMessage FirstLayout
|
|
focusDown)
|
|
-- Move focus to the previous window
|
|
, ("k", do sendMessage FirstLayout
|
|
focusUp)
|
|
-- Swap the focused window and the master window
|
|
, ("<Tab>", windows W.swapMaster)
|
|
-- Swap the focused window with the next window
|
|
, ("S-j", do sendMessage FirstLayout
|
|
windows W.swapDown)
|
|
-- Swap the focused window with the previous window
|
|
, ("S-k", do sendMessage FirstLayout
|
|
windows W.swapUp)
|
|
-- Rotate through the available layout algorithms
|
|
, ("f", sendMessage NextLayout)
|
|
-- Shrink the current area
|
|
-- Shrink the master area
|
|
, ( "h"
|
|
, withFocused $
|
|
floatTileCommand
|
|
(withFocused (keysResizeWindow (10, 0) (1, 1 % 2)))
|
|
(do sendMessage Shrink
|
|
sendMessage Reset))
|
|
-- Expand the master area
|
|
, ( "l"
|
|
, withFocused $
|
|
floatTileCommand
|
|
(withFocused (keysResizeWindow (-10, 0) (1, 1 % 2)))
|
|
(do sendMessage Expand
|
|
sendMessage Reset))
|
|
-- Expand the current area
|
|
, ( "S-l"
|
|
, withFocused $
|
|
floatTileCommand
|
|
(withFocused (keysResizeWindow (0, -10) (1 % 2, 1)))
|
|
(do sendMessage MirrorExpand
|
|
sendMessage Reset))
|
|
, ( "S-h"
|
|
, withFocused $
|
|
floatTileCommand
|
|
(withFocused (keysResizeWindow (0, 10) (1 % 2, 1)))
|
|
(do sendMessage MirrorShrink
|
|
sendMessage Reset))
|
|
-- Toggle window tiling/floating
|
|
, ("t", withFocused $ toggleFloating (W.RationalRect 0.65 0.65 0.35 0.35))
|
|
-- Increment the number of windows in the master area
|
|
, (",", sendMessage (IncMasterN 1))
|
|
-- Deincrement the number of windows in the master area
|
|
, (".", sendMessage (IncMasterN (-1)))
|
|
]) ++
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- Xmonad Commands
|
|
--
|
|
-- ------------------------------------------------------------
|
|
-- Quit xmonad
|
|
(multiKeys
|
|
[ ("S-q", io exitSuccess)
|
|
-- restart xmonad
|
|
, ("S-r", spawn "xmonad --recompile; xmonad --restart")
|
|
-- select next screen/monitor
|
|
, ("<Backspace>", selectNextScreen)
|
|
-- move window next screen/monitor
|
|
-- , ("M4-S-<Backspace>", moveWindowToNextScreen)
|
|
]) ++
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- Volume Control
|
|
--
|
|
-- ------------------------------------------------------------
|
|
[ ("<XF86AudioRaiseVolume>", spawn "amixer set Master 5%+")
|
|
, ("<XF86AudioLowerVolume>", spawn "amixer set Master 5%-")
|
|
, ("<XF86AudioMute>", spawn "amixer set Master toggle")
|
|
] ++
|
|
-- ------------------------------------------------------------
|
|
--
|
|
-- Redshift
|
|
--
|
|
-- ------------------------------------------------------------
|
|
[ ("M4-<F9>", spawn nixStartRedshift)
|
|
, ("M4-<F10>", spawn nixResetRedshift)
|
|
|
|
]
|
|
|
|
------------------------------------------------------------------------
|
|
-- Mouse bindings: default actions bound to mouse events
|
|
--
|
|
mouse :: XConfig t -> M.Map (KeyMask, Button) (Window -> X ())
|
|
-- mouse _ = M.empty
|
|
mouse XConfig {XMonad.modMask = modm} =
|
|
M.fromList
|
|
-- mod-button1, Set the window to floating mode and move by dragging
|
|
[ ( (modm, button1)
|
|
, \w -> do
|
|
focus w
|
|
mouseMoveWindow w
|
|
windows W.shiftMaster)
|
|
-- mod-button2, Raise the window to the top of the stack
|
|
, ( (modm, button2)
|
|
, \w -> do
|
|
focus w
|
|
windows W.shiftMaster)
|
|
-- mod-button3, Set the window to floating mode and resize by dragging
|
|
, ( (modm, button3)
|
|
, \w -> do
|
|
focus w
|
|
mouseResizeWindow w
|
|
windows W.shiftMaster)
|
|
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
|
]
|
|
|
|
|
|
|
|
------------------------------------------------------------------------
|
|
-- 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 :: Query (Endo WindowSet)
|
|
myManageHook =
|
|
composeAll
|
|
[ className =? "Gimp" --> doFloat
|
|
, title =? "fzfmenu" --> doCenterFloat
|
|
, resource =? "copyq" --> doShift "-copyq"
|
|
, scratchpadManageHook
|
|
(W.RationalRect
|
|
-- | percentage distance from left
|
|
0.2
|
|
-- | percentage distance from top
|
|
0.2
|
|
-- | width
|
|
0.6
|
|
-- | height
|
|
0.6)
|
|
]
|
|
|
|
------------------------------------------------------------------------
|
|
-- Event handling
|
|
-- * EwmhDesktops users should change this to ewmhDesktopsEventHook
|
|
--
|
|
-- Defines a custom handler function for X Events. The function should
|
|
-- return (All True) if the default handler is to be run afterwards. To
|
|
-- combine event hooks use mappend or mconcat from Data.Monoid.
|
|
--
|
|
myEventHook :: Event -> X All
|
|
myEventHook = mempty
|
|
|
|
------------------------------------------------------------------------
|
|
-- Status bars and logging
|
|
-- Perform an arbitrary action on each internal state change or X event.
|
|
-- See the 'XMonad.Hooks.DynamicLog' extension for examples.
|
|
--
|
|
myLogHook :: X ()
|
|
myLogHook = do
|
|
dynamicLog
|
|
-- make sure the pointer always follows the focused window, when we use shortcuts
|
|
updatePointer (0.5, 0.5) (0, 0)
|
|
|
|
------------------------------------------------------------------------
|
|
-- Startup hook
|
|
-- Perform an arbitrary action each time xmonad starts or is restarted
|
|
-- with mod-q. Used by, e.g., XMonad.Layout.PerWorkspace to initialize
|
|
-- per-workspace layout choices.
|
|
--
|
|
-- By default, do nothing.
|
|
startUp :: X ()
|
|
startUp
|
|
-- java fix
|
|
= do
|
|
setWMName "LG3D"
|
|
spawn nixSetCursorImage
|
|
spawn nixSetBackground
|
|
spawn nixStartAlbert
|
|
spawnOnce nixStartCopyq
|
|
|
|
|
|
------------------------------------------------------------------------
|
|
-- Now run xmonad with all the defaults we set up.
|
|
-- Run xmonad with the settings you specify. No need to modify this.
|
|
--
|
|
main :: IO ()
|
|
main = do
|
|
xmonad $ withUrgencyHook (SpawnUrgencyHook "echo emit Urgency ") $ dynamicProjects projects $ defaults
|
|
|
|
myTerm :: FilePath
|
|
myTerm = nixStartTerminal
|
|
|
|
-- 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
|
|
--
|
|
-- No need to modify this.
|
|
--
|
|
defaults =
|
|
def
|
|
{ terminal = myTerm
|
|
|
|
-- Whether focus follows the mouse pointer.
|
|
, focusFollowsMouse = True
|
|
-- Whether clicking on a window to focus also passes the click to the window
|
|
, clickJustFocuses = False
|
|
|
|
-- color configuration
|
|
, normalBorderColor = nonSelectionColor
|
|
, focusedBorderColor = selectionColor
|
|
, borderWidth = 1
|
|
|
|
-- modMask lets you specify which modkey you want to use.
|
|
-- mod1Mask ("left alt").
|
|
-- mod3Mask ("right alt")
|
|
-- mod4Mask ("windows key")
|
|
, modMask = mod4Mask
|
|
, workspaces = nonRemovableWorkspaces
|
|
-- key bindings
|
|
, keys = myKeys
|
|
, mouseBindings = mouse
|
|
-- hooks, layouts
|
|
, layoutHook = focusTracking $ historyLayout myLayout
|
|
, manageHook = myManageHook
|
|
, handleEventHook = myEventHook
|
|
, logHook = myLogHook <> runAllPending
|
|
, startupHook = startUp
|
|
} `additionalKeysP`
|
|
myAdditionaKeys
|
|
|
|
autoXPConfig :: XPConfig
|
|
autoXPConfig = myXPConfig {autoComplete = Just 5000}
|
|
|
|
myXPConfig :: XPConfig
|
|
myXPConfig =
|
|
def
|
|
{ bgColor = Solarized.base03
|
|
, fgColor = Solarized.base0
|
|
, promptBorderWidth = 0
|
|
, font = "xft:inconsolata:pixelsize=18:antialias=true:hinting=true"
|
|
}
|