-------------------------------------------------------------------------- {{{ -- | -- Module : App -- Copyright : (c) Mads N Noe 2010 -- Maintainer : mail (@) madsnoe.dk -- License : as-is -- -- Per application configuration. See MyApps for use. -- -------------------------------------------------------------------------- }}} module App ( App (..) , AppType (..) , nullApp , raiseApp , jumpToOrRestore , hideSummonWindows , summonWindow , hideFocused , restoreLast , appManageHook , makeKeys ) where -- Haskell modules import Control.Monad (filterM) import Data.Maybe import Data.List -- XMonad modules import XMonad import XMonad.Actions.WindowGo import XMonad.Core import XMonad.ManageHook (composeAll) import qualified XMonad.StackSet as W -- Custom modules import Config import Utils -- | Holds WM related configuration for a given application. data App = App { cmd :: X () -- ^ Command used to launch the application. , appType :: AppType -- ^ See AppType. , query :: Query Bool -- ^ Used to identify the windows owned by the application. , key :: (ButtonMask, KeySym) -- ^ Key binding to launch the application. (0,0) if no key -- binding is associated. , icon :: String -- ^ Relative path to the XPM icon used by the Pager module. , hook :: Maybe ManageHook -- ^ Application ManageHook. } -- | Used when toggling between applications of type Summon. -- As they are floating, it makes sense to only show one -- at a time. instance Eq App where (==) App { appType = Summon a _ } App { appType = Summon b _ } = a == b _ == _ = False data AppType = OpenNew -- ^ Open a new instance of the application each time. | JumpTo -- ^ Jump to the workspace containing the application. | Summon -- ^ Summon the application to the current workspace. -- They are typically floating, and used for -- "transient" tasks. String -- Identifier. [App] -- Applications to replace when toggling. -- | Default to these settings when entries are omitted. nullApp = App { cmd = return () , appType = OpenNew , query = return False , key = (0, 0) , icon = defaultIcon , hook = Nothing } -- Focus an application. How this happens is specified by the application's AppType. raiseApp App { appType = OpenNew , cmd = c } = c raiseApp App { appType = JumpTo , query = q , cmd = c } = jumpToOrRestore c q raiseApp app@App { appType = Summon _ apps , query = q } = summonWindow (filterSummonedApps apps) app -- | Raise a window as follows. -- If there exists a matching window -- * that is hidden, shift it to the current workspace. -- * on the current workspace, hide it. -- * on another workspace, jump to it. -- Otherwise launch the application. -- TODO: This behavior made it impossible to cycle between two windows, -- as we now hide the current window instead of jumping to the next. -- I'll have to rethink this one eventually, but as I seldomly need -- to cycle between windows of the same app, it is not a big -- problem at the moment. jumpToOrRestore c q = flip (ifWindows q) c $ \ws -> withWindowSet $ \s -> dispatch ws s where dispatch ws s = case hidden of [] -> jumpToOrHide hws -> shiftToCurrent hws where hidden = filter (\w -> fromMaybe "" (W.findTag w s) == hiddenWorkspaceTag) ws shiftToCurrent hws = mapM_ (windows . W.shiftWin (W.currentTag s)) hws cws = maybe [] W.integrate $ W.stack $ W.workspace $ W.current s jumpToOrHide = case cws `intersect` ws of [] -> jumpTo $ W.peek s iws -> mapM_ (windows . W.shiftWin hiddenWorkspaceTag) iws jumpTo (Just w) | w `elem` ws = let (_:y:_) = dropWhile (/=w) $ cycle ws -- cannot fail to match in windows $ W.focusWindow y jumpTo _ = windows . W.focusWindow . head $ ws -- | Hide all windows on the current workspace of the AppType Summon. hideSummonWindows :: [App] -> X () hideSummonWindows apps = withWindowSet $ \s -> do let ws = (maybe [] W.integrate . W.stack . W.workspace . W.current) s sWinsQuery = foldr1 (<||>) $ map query $ filterSummonedApps apps sWins <- filterM (runQuery sWinsQuery) ws mapM_ (windows . W.shiftWin summonWorkspaceTag) sWins -- | Shift the specified app to the current workspace or hide it. summonWindow :: [App] -- ^ Apps of type Summon to replace. -> App -- ^ App to summon. -> X () summonWindow apps app = withWindowSet $ \s -> do let ws = (maybe [] W.integrate . W.stack . W.workspace . W.current) s q = query app o = foldr1 (<||>) $ map query $ filter (app/=) apps matchingWins <- filterM (runQuery q) ws otherWins <- filterM (runQuery o) ws case matchingWins of (x:_) -> do hideSummonWindows apps [] -> do mapM_ (windows . W.shiftWin summonWorkspaceTag) otherWins filterAll <- filterM (runQuery (query app)) (W.allWindows s) case filterAll of (x:_) -> windows $ W.shiftWin (W.currentTag s) x [] -> cmd app -- | Hide the focused window. A hidden window is placed on a workspace that is -- treated specially by all other workspace handling commands used. hideFocused :: WindowSet -> WindowSet hideFocused = W.shift hiddenWorkspaceTag -- | Restore the window that was hidden most recently, like pushing and pulling -- from a stack. restoreLast :: WindowSet -> WindowSet restoreLast s = maybe s (flip (W.shiftWin $ W.currentTag s) s) $ getHidden s where getHidden s = listToMaybe $ maybe [] (W.integrate' . W.stack) $ listToMaybe $ filter (\wsp -> W.tag wsp == hiddenWorkspaceTag) $ W.workspaces s -- | Run all the hooks associated with the applications. appManageHook :: [App] -> ManageHook appManageHook = composeAll . fmap makeQueriedHook . filter hasHook where hasHook app = isJust $ hook app makeQueriedHook app@App { query = q , hook = Just h } = q --> h makeQueriedHook _ = idHook -- never reached -- | Generate the keybinding list from a list of Apps. makeKeys :: [App] -> [((ButtonMask, KeySym), X ())] makeKeys apps = map makeKey $ filter hasKey apps where makeKey app = (key app, raiseApp app) hasKey app = key app /= (0, 0) filterSummonedApps = filter (isSummonedApp . appType) where isSummonedApp (Summon _ _) = True isSummonedApp _ = False