mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-08-20 22:43:48 -07:00
Now that the user configs are on the website, it is time to deprecate them. At the same time deprecated X.C.Monad, which hasn't been updated since 2008 and X.C.Prime, which has confused users quite recently, thinking it to be a better starting place. Fixes: https://github.com/xmonad/xmonad-contrib/issues/677 Fixes: https://github.com/xmonad/xmonad-contrib/issues/595 Related: https://github.com/xmonad/xmonad-web/pull/49
271 lines
12 KiB
Haskell
271 lines
12 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
-----------------------------------------------------------------------------
|
|
-- |
|
|
-- Module : XMonad.Layout.IndependentScreens
|
|
-- Description : Simulate independent sets of workspaces on each screen (dwm-like).
|
|
-- Copyright : (c) 2009 Daniel Wagner
|
|
-- License : BSD3
|
|
--
|
|
-- Maintainer : <daniel@wagner-home.com>
|
|
-- Stability : unstable
|
|
-- Portability : unportable
|
|
--
|
|
-- Utility functions for simulating independent sets of workspaces on
|
|
-- each screen (like dwm's workspace model), using internal tags to
|
|
-- distinguish workspaces associated with each screen.
|
|
-----------------------------------------------------------------------------
|
|
|
|
module XMonad.Layout.IndependentScreens (
|
|
-- * Usage
|
|
-- $usage
|
|
VirtualWorkspace, PhysicalWorkspace,
|
|
VirtualWindowSpace, PhysicalWindowSpace,
|
|
workspaces',
|
|
withScreen, withScreens,
|
|
onCurrentScreen,
|
|
marshallPP,
|
|
whenCurrentOn,
|
|
countScreens,
|
|
workspacesOn,
|
|
workspaceOnScreen, focusWindow', focusScreen, nthWorkspace, withWspOnScreen,
|
|
-- * Converting between virtual and physical workspaces
|
|
-- $converting
|
|
marshall, unmarshall, unmarshallS, unmarshallW,
|
|
marshallWindowSpace, unmarshallWindowSpace, marshallSort,
|
|
) where
|
|
|
|
import Control.Arrow ((***))
|
|
import Graphics.X11.Xinerama
|
|
import XMonad
|
|
import XMonad.Hooks.StatusBar.PP
|
|
import XMonad.Prelude
|
|
import qualified XMonad.StackSet as W
|
|
|
|
-- $usage
|
|
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
|
|
--
|
|
-- > import XMonad.Layout.IndependentScreens
|
|
--
|
|
-- You can define your workspaces by calling @withScreens@:
|
|
--
|
|
-- > myConfig = def { workspaces = withScreens 2 ["web", "email", "irc"] }
|
|
--
|
|
-- This will create \"physical\" workspaces with distinct internal names for
|
|
-- each (screen, virtual workspace) pair.
|
|
--
|
|
-- Then edit any keybindings that use the list of workspaces or refer
|
|
-- to specific workspace names. In the default configuration, only
|
|
-- the keybindings for changing workspace do this:
|
|
--
|
|
-- > keyBindings conf = let modm = modMask conf in fromList $
|
|
-- > {- lots of other keybindings -}
|
|
-- > [((m .|. modm, k), windows $ f i)
|
|
-- > | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
|
|
-- > , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
|
|
--
|
|
-- This should change to
|
|
--
|
|
-- > keyBindings conf = let modm = modMask conf in fromList $
|
|
-- > {- lots of other keybindings -}
|
|
-- > [((m .|. modm, k), windows $ onCurrentScreen f i)
|
|
-- > | (i, k) <- zip (workspaces' conf) [xK_1 .. xK_9]
|
|
-- > , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
|
|
--
|
|
-- In particular, the analogue of @XMonad.workspaces@ is
|
|
-- @workspaces'@, and you can use @onCurrentScreen@ to convert functions
|
|
-- of virtual workspaces to functions of physical workspaces, which work
|
|
-- by marshalling the virtual workspace name and the currently focused
|
|
-- screen into a physical workspace name.
|
|
--
|
|
-- A complete example abusing many of the functions below is available in the
|
|
-- <https://xmonad.org/configurations.html XMonad.Config.Dmwit> configuration.
|
|
|
|
type VirtualWorkspace = WorkspaceId
|
|
type PhysicalWorkspace = WorkspaceId
|
|
|
|
-- | A 'WindowSpace' whose tags are 'PhysicalWorkspace's.
|
|
type PhysicalWindowSpace = WindowSpace
|
|
-- | A 'WindowSpace' whose tags are 'VirtualWorkspace's.
|
|
type VirtualWindowSpace = WindowSpace
|
|
|
|
-- $converting
|
|
-- You shouldn't need to use the functions below very much. They are used
|
|
-- internally. However, in some cases, they may be useful, and so are exported
|
|
-- just in case. In general, the \"marshall\" functions convert the convenient
|
|
-- form (like \"web\") you would like to use in your configuration file to the
|
|
-- inconvenient form (like \"2_web\") that xmonad uses internally. Similarly,
|
|
-- the \"unmarshall\" functions convert in the other direction.
|
|
|
|
marshall :: ScreenId -> VirtualWorkspace -> PhysicalWorkspace
|
|
marshall (S sc) vws = show sc ++ '_':vws
|
|
|
|
unmarshall :: PhysicalWorkspace -> (ScreenId, VirtualWorkspace)
|
|
unmarshallS :: PhysicalWorkspace -> ScreenId
|
|
unmarshallW :: PhysicalWorkspace -> VirtualWorkspace
|
|
|
|
unmarshall = ((S . read) *** drop 1) . break (=='_')
|
|
unmarshallS = fst . unmarshall
|
|
unmarshallW = snd . unmarshall
|
|
|
|
-- | Get a list of all the virtual workspace names.
|
|
workspaces' :: XConfig l -> [VirtualWorkspace]
|
|
workspaces' = nub . map unmarshallW . workspaces
|
|
|
|
-- | Specify workspace names for each screen
|
|
withScreen :: ScreenId -- ^ The screen to make workspaces for
|
|
-> [VirtualWorkspace] -- ^ The desired virtual workspace names
|
|
-> [PhysicalWorkspace] -- ^ A list of all internal physical workspace names
|
|
withScreen n = map (marshall n)
|
|
|
|
-- | Make all workspaces across the monitors bear the same names
|
|
withScreens :: ScreenId -- ^ The number of screens to make workspaces for
|
|
-> [VirtualWorkspace] -- ^ The desired virtual workspace names
|
|
-> [PhysicalWorkspace] -- ^ A list of all internal physical workspace names
|
|
withScreens n vws = concatMap (`withScreen` vws) [0..n-1]
|
|
|
|
-- | Transform a function over physical workspaces into a function over virtual workspaces.
|
|
-- This is useful as it allows you to write code without caring about the current screen, i.e. to say "switch to workspace 3"
|
|
-- rather than saying "switch to workspace 3 on monitor 3".
|
|
onCurrentScreen :: (PhysicalWorkspace -> WindowSet -> a) -> (VirtualWorkspace -> WindowSet -> a)
|
|
onCurrentScreen f vws ws =
|
|
let currentScreenId = W.screen $ W.current ws
|
|
in f (marshall currentScreenId vws) ws
|
|
|
|
-- | Get the workspace currently active on a given screen
|
|
workspaceOnScreen :: ScreenId -> WindowSet -> Maybe PhysicalWorkspace
|
|
workspaceOnScreen screenId ws = W.tag . W.workspace <$> screenOnMonitor screenId ws
|
|
|
|
-- | Generate WindowSet transformation by providing a given function with the workspace active on a given screen.
|
|
-- This may for example be used to shift a window to another screen as follows:
|
|
--
|
|
-- > windows $ withWspOnScreen 1 W.shift
|
|
--
|
|
withWspOnScreen :: ScreenId -- ^ The screen to run on
|
|
-> (PhysicalWorkspace -> WindowSet -> WindowSet) -- ^ The transformation that will be passed the workspace currently active on there
|
|
-> WindowSet -> WindowSet
|
|
withWspOnScreen screenId operation ws = case workspaceOnScreen screenId ws of
|
|
Just wsp -> operation wsp ws
|
|
Nothing -> ws
|
|
|
|
-- | Get the workspace that is active on a given screen.
|
|
screenOnMonitor :: ScreenId -> WindowSet -> Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)
|
|
screenOnMonitor screenId ws = find ((screenId ==) . W.screen) (W.current ws : W.visible ws)
|
|
|
|
-- | Focus a window, switching workspace on the correct Xinerama screen if neccessary.
|
|
focusWindow' :: Window -> WindowSet -> WindowSet
|
|
focusWindow' window ws
|
|
| Just window == W.peek ws = ws
|
|
| otherwise = case W.findTag window ws of
|
|
Just tag -> W.focusWindow window $ focusScreen (unmarshallS tag) ws
|
|
Nothing -> ws
|
|
|
|
-- | Focus a given screen.
|
|
focusScreen :: ScreenId -> WindowSet -> WindowSet
|
|
focusScreen screenId = withWspOnScreen screenId W.view
|
|
|
|
-- | Get the nth virtual workspace
|
|
nthWorkspace :: Int -> X (Maybe VirtualWorkspace)
|
|
nthWorkspace n = (!? n) . workspaces' <$> asks config
|
|
|
|
-- | In case you don't know statically how many screens there will be, you can call this in main before starting xmonad. For example, part of my config reads
|
|
--
|
|
-- > main = do
|
|
-- > nScreens <- countScreens
|
|
-- > xmonad $ def {
|
|
-- > ...
|
|
-- > workspaces = withScreens nScreens (workspaces def),
|
|
-- > ...
|
|
-- > }
|
|
--
|
|
countScreens :: (MonadIO m, Integral i) => m i
|
|
countScreens = fmap genericLength . liftIO $ openDisplay "" >>= liftA2 (<*) getScreenInfo closeDisplay
|
|
|
|
-- | This turns a pretty-printer into one that is aware of the independent screens. The
|
|
-- converted pretty-printer first filters out physical workspaces on other screens, then
|
|
-- converts all the physical workspaces on this screen to their virtual names.
|
|
-- Note that 'ppSort' still operates on physical (marshalled) workspace names,
|
|
-- otherwise functions from "XMonad.Util.WorkspaceCompare" wouldn't work.
|
|
-- If you need to sort on virtual names, see 'marshallSort'.
|
|
--
|
|
-- For example, if you have have two bars on the left and right screens, respectively, and @pp@ is
|
|
-- a pretty-printer, you could apply 'marshallPP' when creating a @StatusBarConfig@ from "XMonad.Hooks.StatusBar".
|
|
--
|
|
-- A sample config looks like this:
|
|
--
|
|
-- > mySBL = statusBarProp "xmobar" $ pure (marshallPP (S 0) pp)
|
|
-- > mySBR = statusBarProp "xmobar" $ pure (marshallPP (S 1) pp)
|
|
-- > main = xmonad $ withEasySB (mySBL <> mySBR) defToggleStrutsKey def
|
|
--
|
|
marshallPP :: ScreenId -> PP -> PP
|
|
marshallPP s pp = pp { ppRename = ppRename pp . unmarshallW
|
|
, ppSort = (. workspacesOn s) <$> ppSort pp }
|
|
|
|
-- | Take a pretty-printer and turn it into one that only runs when the current
|
|
-- workspace is one associated with the given screen. The way this works is a
|
|
-- bit hacky, so beware: the 'ppOutput' field of the input will not be invoked
|
|
-- if either of the following conditions is met:
|
|
--
|
|
-- 1. The 'ppSort' of the input returns an empty list (when not given one).
|
|
--
|
|
-- 2. The 'ppOrder' of the input returns the exact string @\"\\0\"@.
|
|
--
|
|
-- For example, you can use this to create a pipe which tracks the title of the
|
|
-- window currently focused on a given screen (even if the screen is not
|
|
-- current) by doing something like this:
|
|
--
|
|
-- > ppFocus s = whenCurrentOn s def
|
|
-- > { ppOrder = \(_:_:title:_) -> [title]
|
|
-- > , ppOutput = appendFile ("focus" ++ show s) . (++ "\n")
|
|
-- > }
|
|
--
|
|
-- Sequence a few of these pretty-printers to get a log hook that keeps each
|
|
-- screen's title up-to-date.
|
|
whenCurrentOn :: ScreenId -> PP -> PP
|
|
whenCurrentOn s pp = pp
|
|
{ ppSort = do
|
|
sorter <- ppSort pp
|
|
pure $ \case xs@(x:_) | unmarshallS (W.tag x) == s -> sorter xs
|
|
_ -> []
|
|
|
|
, ppOrder = \case ("":_) -> ["\0"] -- we got passed no workspaces; this is the signal from ppSort that this is a boring case
|
|
list -> ppOrder pp list
|
|
|
|
, ppOutput = \case "\0" -> pure () -- we got passed the signal from ppOrder that this is a boring case
|
|
output -> ppOutput pp output
|
|
}
|
|
|
|
-- | Filter workspaces that are on a given screen.
|
|
workspacesOn :: ScreenId -> [PhysicalWindowSpace] -> [PhysicalWindowSpace]
|
|
workspacesOn s = filter (\ws -> unmarshallS (W.tag ws) == s)
|
|
|
|
-- | @vSort@ is a function that sorts 'VirtualWindowSpace's with virtual names.
|
|
-- @marshallSort s vSort@ is a function which sorts 'PhysicalWindowSpace's with virtual names,
|
|
-- but keeps only the 'WindowSpace'\'s on screen @s@.
|
|
--
|
|
-- NOTE: @vSort@ operating on virtual names comes with some caveats, see
|
|
-- <https://github.com/xmonad/xmonad-contrib/issues/420 this issue> for
|
|
-- more information. You can use 'marshallSort' like in the following example:
|
|
--
|
|
-- === __Example__
|
|
--
|
|
-- > pp' :: ScreenId -> PP -> PP
|
|
-- > pp' s pp = (marshallPP s pp) { ppSort = fmap (marshallSort s) (ppSort pp) }
|
|
-- >
|
|
-- > mySBL = statusBarProp "xmobar" $ pure (pp' (S 0) pp)
|
|
-- > mySBR = statusBarProp "xmobar" $ pure (pp' (S 1) pp)
|
|
-- > main = xmonad $ withEasySB (mySBL <> mySBR) defToggleStrutsKey def
|
|
--
|
|
-- In this way, you have a custom virtual names sort on top of 'marshallPP'.
|
|
marshallSort :: ScreenId -> ([VirtualWindowSpace] -> [VirtualWindowSpace]) -> ([PhysicalWindowSpace] -> [PhysicalWindowSpace])
|
|
marshallSort s vSort = pScreens . vSort . vScreens where
|
|
vScreens = map unmarshallWindowSpace . workspacesOn s
|
|
pScreens = map (marshallWindowSpace s)
|
|
|
|
-- | Convert the tag of the 'WindowSpace' from a 'VirtualWorkspace' to a 'PhysicalWorkspace'.
|
|
marshallWindowSpace :: ScreenId -> WindowSpace -> WindowSpace
|
|
-- | Convert the tag of the 'WindowSpace' from a 'PhysicalWorkspace' to a 'VirtualWorkspace'.
|
|
unmarshallWindowSpace :: WindowSpace -> WindowSpace
|
|
|
|
marshallWindowSpace s ws = ws { W.tag = marshall s (W.tag ws) }
|
|
unmarshallWindowSpace ws = ws { W.tag = unmarshallW (W.tag ws) }
|