Created
September 5, 2011 23:12
-
-
Save anonymous/1196136 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{- # 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 = " – " | |
, 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 = ">" ++ acc | |
sanitize '<' acc = "<" ++ acc | |
sanitize '\"' acc = """ ++ acc | |
sanitize '&' acc = "&" ++ 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