Skip to content

Instantly share code, notes, and snippets.

Created September 5, 2011 23:12
Show Gist options
  • Save anonymous/1196136 to your computer and use it in GitHub Desktop.
Save anonymous/1196136 to your computer and use it in GitHub Desktop.
{- # OPTIONS_GHC -fglasgow-exts #-} -- For deriving Data/Typeable
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-}
import IO
import Control.Monad
import Control.OldException(catchDyn,try)
import XMonad.Util.Run
import Control.Concurrent
import DBus
import DBus.Connection
import DBus.Message
import System.Cmd
import XMonad hiding ((|||))
import XMonad.Operations
import XMonad.Config.Kde
import qualified XMonad.StackSet as W
import XMonad.Util.EZConfig
import XMonad.Actions.FindEmptyWorkspace
import XMonad.Config.Desktop
import XMonad.Layout hiding ((|||))
import XMonad.Layout.Tabbed
import XMonad.Layout.ThreeColumns
import XMonad.Actions.CycleWS
import XMonad.Layout.NoBorders
import XMonad.Layout.Combo
import XMonad.Layout.Grid
import XMonad.Layout.TwoPane
import XMonad.Layout.WindowNavigation
import XMonad.Layout.IM
import XMonad.Layout.ToggleLayouts
import XMonad.Actions.WindowBringer
import Data.Int
import Data.List
import Data.List.Utils (split)
import Data.Ratio
import Data.Maybe
import Data.Monoid
import qualified Data.HashTable as H
import XMonad.Actions.GridSelect
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.DynamicLog
import XMonad.Util.WorkspaceCompare
import XMonad.Layout.Named
import XMonad.Actions.Plane
import XMonad.Layout.LayoutCombinators ((|||))
import XMonad.Actions.CycleSelectedLayouts
import XMonad.Actions.Warp
import XMonad.Actions.Promote
import XMonad.Hooks.UrgencyHook
import XMonad.Util.XUtils (fi)
import XMonad.Util.WindowProperties (getProp32, getProp32s)
import qualified XMonad.Util.ExtensibleState as XS
import Char
myWorkspaces = ["q", "w", "e", "a", "s", "d", "y", "x", "c"]
myLayout = (desktopLayoutModifiers $ tiledN ||| tiled ||| mtiled ||| tab ||| chat) ||| ffull
where
tiledN = named "tl" $ TallNoMax 1 (3/100) (0.51)
tiled = named "TL" $ Tall 1 (3/100) (0.51)
mtiled = named "hz" $ Mirror $ Tall 1 (3/100) (0.51)
tab = named "tab" $ simpleTabbed
chat = named "IM" $ withIM (0.5) (ClassName "Konversation") twotabs
twotabs = windowNavigation $
combineTwo (Mirror $ TwoPane 0.03 0.5) simpleTabbed (TwoPane 0.03 0.5)
ffull = named "fu" $ noBorders $ Full
hashWin :: Window -> Int32
hashWin = H.hashInt . fromIntegral
main = withConnection Session $ \ dbus -> do
getWellKnownName dbus
pidhash <- H.new (==) H.hashInt
winhash <- H.new (==) hashWin
(xmonad
$ withUrgencyHook NoUrgencyHook
$ kde4Config {
modMask = mod4Mask -- use the Windows button as mod
, manageHook = pidManageHook pidhash winhash <+> manageHook kde4Config <+> myManageHook <+> doF W.shiftMaster
, handleEventHook = handleEventHook kde4Config <+> attentionEventHook <+> pidEventHook pidhash winhash
, workspaces = myWorkspaces
, layoutHook = myLayout
, logHook = logHook kde4Config >> dynamicLogWithPP (myPrettyPrinter dbus)
, borderWidth = 2
}
`removeKeysP` [ "M-"++n | n <- map show [3..9 :: Int] ]
`additionalKeys` [((mod4Mask, xK_section), swapNextScreen)]
`additionalKeysP` myKeys
`additionalMouseBindings`
[ ((mod4Mask, button1), (\w -> focus w >> windows W.swapMaster))
, ((mod4Mask, button2), (\w -> focus w >> mouseMoveWindow w >> windows W.shiftMaster))
, ((mod4Mask, button3), (\w -> focus w >> mouseResizeWindow w))
, ((mod4Mask, button4), (\_ -> windows W.focusUp ))
, ((mod4Mask, button5), (\_ -> windows W.focusDown))
])
where
myManageHook = composeAll . concat $
[ [ className =? c --> doFloat | c <- myFloats]
, [ title =? t --> doFloat | t <- myOtherFloats]
, [ className =? c --> doF (W.shift "w") | c <- mailApps]
, [ className =? c --> doF (W.shift "c") | c <- ircApps]
, [ (fmap ("kmail-composer" `isPrefixOf`) role) --> doF (W.shift "q") ]
, [ liftX (gets (W.currentTag . windowset)) =? "w" <&&> className =? c --> doShift "e" | c <- webApps ]
]
myFloats = ["MPlayer", "Gimp", "krunner", "Nvidia-settings", "Plasma-desktop"]
myOtherFloats = ["alsamixer", "Password – KDE Dæmon"]
mailApps = ["Kontact"]
webApps = ["Firefox", "Conkeror"] -- open on desktop 3
ircApps = ["Konversation", "Skype", "Kopete"] -- open on desktop 9
role = stringProperty "WM_WINDOW_ROLE"
myKeys = [
("M-<Return>", promote)
, ("M-v", kill)
, ("M-'", spawn "xmonad --recompile && xmonad --restart")
, ("M-1", screenWorkspace 0 >>= flip whenJust (windows . W.view))
, ("M-2", screenWorkspace 1 >>= flip whenJust (windows . W.view))
, ("M-o", nextScreen)
, ("M-S-1", screenWorkspace 0 >>= flip whenJust (windows . W.shift))
, ("M-S-2", screenWorkspace 1 >>= flip whenJust (windows . W.shift))
, ("M-r M-f", spawn "conkeror")
, ("M-r M-g", spawn "conkeror")
, ("M-r M-r", spawn "konsole")
, ("M-r M-<Space>", spawn "qdbus org.kde.krunner /App org.kde.krunner.App.display")
, ("M-<Space>", spawn "qdbus org.kde.krunner /App org.kde.krunner.App.display")
, ("M-r M-d", spawn "digikam")
, ("M-r M-l", spawn "dolphin")
, ("M-f M-f", withFocused float )
, ("M-f M-t", withFocused $ windows . W.sink )
, ("M-0", refresh)
, ("M-<U>", sendMessage $ Move U)
, ("M-<D>", sendMessage $ Move D)
, ("M-<R>", sendMessage $ Move R)
, ("M-<L>", sendMessage $ Move L)
, ("M-n", viewEmptyWorkspace)
, ("S-M-n", tagToEmptyWorkspace)
, ("M-g", gotoMenu)
, ("M-b", bringMenu)
, ("M-S-b", sendMessage ToggleStruts)
, ("M-<F1>", cycleThroughLayouts ["tl"])
, ("M-<F2>", cycleThroughLayouts ["TL"])
, ("M-<F3>", cycleThroughLayouts ["hz"])
, ("M-<F4>", cycleThroughLayouts ["tab"])
, ("M-<F5>", cycleThroughLayouts ["IM"])
, ("M-<F6>", cycleThroughLayouts ["fu"])
, ("M-t", warpToWindow (1%2) (1%2))
, ("M-u", focusUrgent)
] ++ [ ("M-" ++ w, windows $ W.greedyView w) | w <- myWorkspaces ]
++ [ ("S-M-" ++ w, windows $ W.shift w) | w <- myWorkspaces ]
-- | The builtin tiling mode of xmonad. Supports 'Shrink', 'Expand' and
-- 'IncMasterN'.
data TallNoMax a = TallNoMax { tallNMaster :: !Int -- ^ The default number of windows in the master pane (default: 1)
, tallRatioIncrement :: !Rational -- ^ Percent of screen to increment by when resizing panes (default: 3/100)
, tallRatio :: !Rational } -- ^ Default proportion of screen occupied by master pane (default: 1/2)
deriving (Show, Read)
-- a nice pure layout, lots of properties for the layout, and its messages, in Properties.hs
instance LayoutClass TallNoMax a where
pureLayout (TallNoMax nmaster _ frac) r s = zip ws rs
where ws = W.integrate s
rs = tileNoMax frac r nmaster (length ws)
pureMessage (TallNoMax nmaster delta frac) m =
msum [fmap resize (fromMessage m)
,fmap incmastern (fromMessage m)]
where resize Shrink = TallNoMax nmaster delta (max 0 $ frac-delta)
resize Expand = TallNoMax nmaster delta (min 1 $ frac+delta)
incmastern (IncMasterN d) = TallNoMax (max 0 (nmaster+d)) delta frac
description _ = "TallNoMax"
-- | Compute the positions for windows using the default two-pane tiling
-- algorithm.
--
-- The screen is divided into two panes. All clients are
-- then partioned between these two panes. One pane, the master, by
-- convention has the least number of windows in it.
tileNoMax
:: Rational -- ^ @frac@, what proportion of the screen to devote to the master area
-> Rectangle -- ^ @r@, the rectangle representing the screen
-> Int -- ^ @nmaster@, the number of windows in the master pane
-> Int -- ^ @n@, the total number of windows to tileNoMax
-> [Rectangle]
tileNoMax f r nmaster n =
splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns
where (r1,r2) = splitHorizontallyBy f r
myPrettyPrinter :: Connection -> PP
myPrettyPrinter dbus = defaultPP {
ppOutput = outputThroughDBus dbus
, ppTitle = \x -> x
, ppCurrent = wrap "[" "]" . bold . pangoSanitize
, ppVisible = bold
, ppHidden = pangoSanitize
, ppHiddenNoWindows = \x -> ""
, ppUrgent = wrap "" "!" . bold . map toUpper
, ppOrder = \(ws:layout:_:_) -> [ws,layout]
, ppSep = " &ndash; "
, ppSort = mkWsSort getXineramaWsCompare
}
where
bold = wrap "<span style=\"font-weight: bold;\">" "</span>"
debugPrint :: String -> IO ()
--debugPrint = appendFile "/tmp/xmonad-debug"
debugPrint _ = return ()
knownPid :: H.HashTable Int Window -> Int -> IO (Maybe Window)
knownPid pidhash pid = do
found <- H.lookup pidhash pid
case found of
Just w -> do
debugPrint $ "knownPid true in first case " ++ (show pid) ++ "\n"
return $ Just w
_ -> do ppid <- getppid pid
debugPrint $ "knownPid ppid is " ++ (show ppid) ++ "\n"
if ppid == pid || ppid <= 1
then return Nothing
else knownPid pidhash ppid
getppid :: Int -> IO Int
getppid pid = catch (do stat <- readFile $ "/proc/" ++ (show pid) ++ "/stat"
let (_:_:_:ppidstr:_) = split " " stat
in return $ read ppidstr)
(\e -> return 1)
hasPid :: H.HashTable Int Window -> Query (Maybe Window)
hasPid pidhash = ask >>= \w -> liftX $ do
pid <- getProp32s "_NET_WM_PID" w
io $ debugPrint $ "hasPid " ++ (show pid) ++ "\n"
case pid of
Just [p] -> io $ knownPid pidhash (fromIntegral p)
_ -> return Nothing
pidManageHook :: H.HashTable Int Window -> H.HashTable Window Int -> ManageHook
pidManageHook pidhash winhash = do
interesting <- hasPid pidhash
case interesting of
Just parent -> do
pdesk <- liftX $ gets (W.findTag parent . windowset)
case pdesk of
Just d -> do ask >>= \w -> liftX $ flagUrgent w
doF $ W.shift d
_ -> idHook
_ -> idHook
isPidInteresting :: Query Bool
isPidInteresting = className =? "Konsole"
updatePid :: H.HashTable Int Window -> H.HashTable Window Int -> Window -> X ()
updatePid pidhash winhash w = do
pid <- getProp32s "_NET_WM_PID" w
io $ debugPrint $ "updatePid " ++ (show pid) ++ "\n"
case pid of
Just [p] -> do
_ <- io $ H.update pidhash (fromIntegral p) w
_ <- io $ H.update winhash w (fromIntegral p)
return ()
_ -> return ()
removePid :: H.HashTable Int Window -> H.HashTable Window Int -> Window -> X ()
removePid pidhash winhash w = do
pid <- io $ H.lookup winhash w
io $ debugPrint $ "removePid " ++ (show pid) ++ "\n"
case pid of
Just p -> do
_ <- io $ H.delete winhash w
_ <- io $ H.delete pidhash (fromIntegral p)
return ()
_ -> return ()
pidEventHook :: H.HashTable Int Window -> H.HashTable Window Int -> Event -> X All
pidEventHook pidhash winhash (MapNotifyEvent {ev_window = w}) = do
whenX (runQuery isPidInteresting w) (updatePid pidhash winhash w)
return $ All True
pidEventHook pidhash winhash (DestroyWindowEvent {ev_window = w}) = do
removePid pidhash winhash w
return $ All True
pidEventHook pidhash winhash _ = return $ All True
-- -----------------------------------------------------------------------------
-- This retry is really awkward, but sometimes DBus won't let us get our
-- name unless we retry a couple times.
getWellKnownName :: Connection -> IO ()
getWellKnownName dbus = tryGetName `catchDyn` (\ (DBus.Error _ _) ->
getWellKnownName dbus)
where
tryGetName = do
namereq <- newMethodCall serviceDBus pathDBus interfaceDBus "RequestName"
addArgs namereq [String "org.xmonad.Log", Word32 5]
sendWithReplyAndBlock dbus namereq 0
return ()
outputThroughDBus :: Connection -> String -> IO ()
outputThroughDBus dbus str = do
let str' = "<span style=\"font-size: 12pt\">" ++ str ++ "</span>"
msg <- newSignal "/org/xmonad/Log" "org.xmonad.Log" "Update"
addArgs msg [String str']
send dbus msg 0 `catchDyn` (\ (DBus.Error _ _ ) -> return 0)
return ()
pangoColor :: String -> String -> String
pangoColor fg = wrap left right
where
left = "<span foreground=\"" ++ fg ++ "\">"
right = "</span>"
pangoSanitize :: String -> String
pangoSanitize = foldr sanitize ""
where
sanitize '>' acc = "&gt;" ++ acc
sanitize '<' acc = "&lt;" ++ acc
sanitize '\"' acc = "&quot;" ++ acc
sanitize '&' acc = "&amp;" ++ acc
sanitize x acc = x:acc
flagUrgent :: Window -> X ()
flagUrgent win = adjustUrgents (\ws -> if elem win ws then ws else win : ws)
attentionEventHook :: Event -> X All
attentionEventHook (ClientMessageEvent _ _ _ dpy win typ (action:dats)) = do
state <- getAtom "_NET_WM_STATE"
attention <- getAtom "_NET_WM_STATE_DEMANDS_ATTENTION"
wstate <- fromMaybe [] `fmap` getProp32 state win
let isFull = fromIntegral attention `elem` wstate
-- Constants for the _NET_WM_STATE protocol:
remove = 0
add = 1
toggle = 2
ptype = 4 -- The atom property type for changeProperty
chWstate f = io $ changeProperty32 dpy win state ptype propModeReplace (f wstate)
when (typ == state && fi attention `elem` dats) $ do
when (action == add || (action == toggle && not isFull)) $ do
flagUrgent win
userCodeDef () =<< asks (logHook . config)
when (action == remove || (action == toggle && isFull)) $ do
clearUrgency win
userCodeDef () =<< asks (logHook . config)
return $ All True
attentionEventHook _ = return $ All True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment