{-# LANGUAGE PatternGuards #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Actions.CopyWindow -- Copyright : (c) David Roundy , Ivan Veselov , Lanny Ripple -- License : BSD3-style (see LICENSE) -- -- Maintainer : ??? -- Stability : unstable -- Portability : unportable -- -- Provides bindings to duplicate a window on multiple workspaces, -- providing dwm-like tagging functionality. -- ----------------------------------------------------------------------------- module XMonad.Actions.CopyWindow ( -- * Usage -- $usage copy, copyToAll, copyWindow, runOrCopy , killAllOtherCopies, kill1 -- * Highlight workspaces containing copies in logHook -- $logHook , wsContainingCopies ) where import XMonad import Control.Arrow ((&&&)) import qualified Data.List as L import XMonad.Actions.WindowGo import qualified XMonad.StackSet as W -- $usage -- -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: -- -- > import XMonad.Actions.CopyWindow -- -- Then add something like this to your keybindings: -- -- > -- mod-[1..9] @@ Switch to workspace N -- > -- mod-shift-[1..9] @@ Move client to workspace N -- > -- mod-control-shift-[1..9] @@ Copy client to workspace N -- > [((m .|. modm, k), windows $ f i) -- > | (i, k) <- zip (workspaces x) [xK_1 ..] -- > , (f, m) <- [(W.view, 0), (W.shift, shiftMask), (copy, shiftMask .|. controlMask)]] -- -- To use the above key bindings you need also to import -- "XMonad.StackSet": -- -- > import qualified XMonad.StackSet as W -- -- You may also wish to redefine the binding to kill a window so it only -- removes it from the current workspace, if it's present elsewhere: -- -- > , ((modm .|. shiftMask, xK_c ), kill1) -- @@ Close the focused window -- -- Instead of copying a window from one workspace to another maybe you don't -- want to have to remember where you placed it. For that consider: -- -- > , ((modm, xK_b ), runOrCopy "firefox" (className =? "Firefox")) -- @@ run or copy firefox -- -- Another possibility which this extension provides is 'making window -- always visible' (i.e. always on current workspace), similar to corresponding -- metacity functionality. This behaviour is emulated through copying given -- window to all the workspaces and then removing it when it's unneeded on -- all workspaces any more. -- -- Here is the example of keybindings which provide these actions: -- -- > , ((modm, xK_v ), windows copyToAll) -- @@ Make focused window always visible -- > , ((modm .|. shiftMask, xK_v ), killAllOtherCopies) -- @@ Toggle window state back -- -- For detailed instructions on editing your key bindings, see -- "XMonad.Doc.Extending#Editing_key_bindings". -- $logHook -- To distinguish workspaces containing copies of the focused window use -- something like: -- -- > sampleLogHook h = do -- > copies <- wsContainingCopies -- > let check ws | ws `elem` copies = pad . xmobarColor "red" "black" $ ws -- > | otherwise = pad ws -- > dynamicLogWithPP myPP {ppHidden = check, ppOutput = hPutStrLn h} -- > -- > main = do -- > h <- spawnPipe "xmobar" -- > xmonad defaultConfig { logHook = sampleLogHook h } -- | Copy the focused window to a workspace. copy :: (Eq s, Eq i, Eq a) => i -> W.StackSet i l a s sd -> W.StackSet i l a s sd copy n s | Just w <- W.peek s = copyWindow w n s | otherwise = s -- | Copy the focused window to all workspaces. copyToAll :: (Eq s, Eq i, Eq a) => W.StackSet i l a s sd -> W.StackSet i l a s sd copyToAll s = foldr copy s $ map W.tag (W.workspaces s) -- | Copy an arbitrary window to a workspace. copyWindow :: (Eq a, Eq i, Eq s) => a -> i -> W.StackSet i l a s sd -> W.StackSet i l a s sd copyWindow w n = copy' where copy' s = if n `W.tagMember` s then W.view (W.currentTag s) $ insertUp' w $ W.view n s else s insertUp' a s = W.modify (Just $ W.Stack a [] []) (\(W.Stack t l r) -> if a `elem` t:l++r then Just $ W.Stack t l r else Just $ W.Stack a (L.delete a l) (L.delete a (t:r))) s -- | runOrCopy will run the provided shell command unless it can -- find a specified window in which case it will copy the window to -- the current workspace. Similar to (i.e., stolen from) "XMonad.Actions.WindowGo". runOrCopy :: String -> Query Bool -> X () runOrCopy = copyMaybe . spawn -- | Copy a window if it exists, run the first argument otherwise. copyMaybe :: X () -> Query Bool -> X () copyMaybe f qry = ifWindow qry copyWin f where copyWin = ask >>= \w -> doF (\ws -> copyWindow w (W.currentTag ws) ws) -- | Remove the focused window from this workspace. If it's present in no -- other workspace, then kill it instead. If we do kill it, we'll get a -- delete notify back from X. -- -- There are two ways to delete a window. Either just kill it, or if it -- supports the delete protocol, send a delete event (e.g. firefox). kill1 :: X () kill1 = do ss <- gets windowset whenJust (W.peek ss) $ \w -> if W.member w $ delete'' w ss then windows $ delete'' w else kill where delete'' w = W.modify Nothing (W.filter (/= w)) -- | Kill all other copies of focused window (if they're present). -- 'All other' means here 'copies which are not on the current workspace'. killAllOtherCopies :: X () killAllOtherCopies = do ss <- gets windowset whenJust (W.peek ss) $ \w -> windows $ W.view (W.currentTag ss) . delFromAllButCurrent w where delFromAllButCurrent w ss = foldr ($) ss $ map (delWinFromWorkspace w . W.tag) $ W.hidden ss ++ map W.workspace (W.visible ss) delWinFromWorkspace w wid = viewing wid $ W.modify Nothing (W.filter (/= w)) viewing wis f ss = W.view (W.currentTag ss) $ f $ W.view wis ss -- | A list of hidden workspaces containing a copy of the focused window. wsContainingCopies :: X [WorkspaceId] wsContainingCopies = do ws <- gets windowset return $ copiesOfOn (W.peek ws) (taggedWindows $ W.hidden ws) -- | Get a list of tuples (tag, [Window]) for each workspace. taggedWindows :: [W.Workspace i l a] -> [(i, [a])] taggedWindows = map $ W.tag &&& W.integrate' . W.stack -- | Get tags with copies of the focused window (if present.) copiesOfOn :: (Eq a) => Maybe a -> [(i, [a])] -> [i] copiesOfOn foc tw = maybe [] hasCopyOf foc where hasCopyOf f = map fst $ filter ((f `elem` ) . snd) tw