Move xinerama current/visible/hidden workspace logic into StackSet directly.

This commit is contained in:
Don Stewart
2007-05-21 05:52:53 +00:00
parent 02073c547b
commit ea80d2a71f
3 changed files with 120 additions and 126 deletions

View File

@@ -65,11 +65,12 @@ shift n = withFocused hide >> windows (W.shift n)
-- | view. Change the current workspace to workspace at offset n (0 indexed).
view :: WorkspaceId -> X ()
view n = withWorkspace $ \w -> when (n /= (W.tag (W.current w))) $ do
view n = withWorkspace $ \old -> when (n /= (W.tag (W.workspace (W.current old)))) $ do
windows $ W.view n -- move in new workspace first, to avoid flicker
-- Hide the old workspace if it is no longer visible
oldWsNotVisible <- (not . M.member (W.tag . W.current $ w) . W.screens) `liftM` gets windowset
when oldWsNotVisible $ mapM_ hide (W.index w)
oldWsNotVisible <- liftM (notElem (W.current old)) (gets (W.visible . windowset))
when oldWsNotVisible $ mapM_ hide (W.index old)
clearEnterEvents -- better clear any events from the old workspace
-- | Kill the currently focused client. If we do kill it, we'll get a
@@ -114,12 +115,13 @@ refresh = do
XConf { xineScreens = xinesc, display = d } <- ask
-- for each workspace, layout the currently visible workspaces
flip mapM_ (M.assocs (W.screens ws)) $ \(n, scn) -> do
let this = W.view n ws
(`mapM_` (W.current ws : W.visible ws)) $ \w -> do
let n = W.tag (W.workspace w)
this = W.view n ws
Just l = fmap fst $ M.lookup n fls
-- now tile the windows on this workspace
rs <- doLayout l (genericIndex xinesc scn) (W.index this)
mapM_ (\(w,rect) -> io (tileWindow d w rect)) rs
rs <- doLayout l (genericIndex xinesc (W.screen w)) (W.index this)
mapM_ (\(win,rect) -> io (tileWindow d win rect)) rs
-- and raise the focused window if there is one.
whenJust (W.peek this) $ io . raiseWindow d
@@ -178,8 +180,8 @@ setFocusX w = withWorkspace $ \ws -> do
XConf { display = dpy , normalBorder = nbc, focusedBorder = fbc } <- ask
-- clear mouse button grab and border on other windows
(`mapM_` (M.keys . W.screens $ ws)) $ \n -> do
(`mapM_` (W.index (W.view n ws))) $ \otherw -> do
(`mapM_` (W.current ws : W.visible ws)) $ \wk -> do
(`mapM_` (W.index (W.view (W.tag (W.workspace wk)) ws))) $ \otherw -> do
setButtonGrab True otherw
io $ setWindowBorder dpy otherw (color_pixel nbc)
@@ -282,7 +284,7 @@ splitVerticallyBy f r = (\(a,b)->(mirrorRect a,mirrorRect b)) $ splitHorizontall
layout :: ((Layout, [Layout]) -> (Layout, [Layout])) -> X ()
layout f = do
modify $ \s ->
let n = W.tag . W.current . windowset $ s
let n = W.tag . W.workspace . W.current . windowset $ s
(Just fl) = M.lookup n $ layouts s
in s { layouts = M.insert n (f fl) (layouts s) }
refresh

View File

@@ -75,13 +75,13 @@
-- 'delete'.
--
module StackSet (
StackSet(..), Workspace(..), Stack(..),
StackSet(..), Workspace(..), Screen(..), Stack(..),
new, view, lookupWorkspace, peek, index, focusLeft, focusRight,
focusWindow, member, findIndex, insertLeft, delete, swap, shift
) where
import qualified Data.Map as M
import Data.Maybe (listToMaybe)
import qualified Data.List as L (delete,find,genericSplitAt)
-- API changes from xmonad 0.1:
@@ -104,23 +104,28 @@ import Data.Maybe (listToMaybe)
--
-- A cursor into a non-empty list of workspaces.
-- We puncture the workspace list, producing a hole in the structure
-- used to track the currently focused workspace. The two other lists
-- that are produced are used to track those workspaces visible as
-- Xinerama screens, and those workspaces not visible anywhere.
--
data StackSet i a screen =
data StackSet i a sid =
StackSet { size :: !i -- number of workspaces
, current :: !(Workspace i a) -- currently focused workspace
, prev :: [Workspace i a] -- workspaces to the left
, next :: [Workspace i a] -- workspaces to the right
, screens :: M.Map i screen -- a map of visible workspaces to their screens
, current :: !(Screen i a sid) -- currently focused workspace
, visible :: [Screen i a sid] -- non-focused workspaces, visible in xinerama
, hidden :: [Workspace i a] -- workspaces not visible anywhere
} deriving (Show, Eq)
-- Visible workspaces, and their Xinerama screens.
data Screen i a sid = Screen { workspace :: !(Workspace i a), screen :: !sid }
deriving (Show, Eq)
--
-- A workspace is just a tag - its index - and a stack
--
data Workspace i a = Workspace { tag :: !i, stack :: Stack a }
deriving (Show, Eq)
-- TODO an unmanaged floating layer would go in here somewhere (a 2nd stack?)
--
-- A stack is a cursor onto a (possibly empty) window list.
-- The data structure tracks focus by construction, and
@@ -149,40 +154,41 @@ data Stack a = Empty
-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0.
--
new :: (Integral i, Integral s) => i -> s -> StackSet i a s
new n m | n > 0 && m > 0 = StackSet n h [] ts xine
new n m | n > 0 && m > 0 = StackSet n cur visi unseen
| otherwise = error "non-positive arguments to StackSet.new"
where (h:ts) = Workspace 0 Empty : [ Workspace i Empty | i <- [1 ..n-1]]
xine = M.fromList [ (fromIntegral s, s) | s <- [0 .. m-1] ]
where (seen,unseen) = L.genericSplitAt m $ Workspace 0 Empty : [ Workspace i Empty | i <- [1 ..n-1]]
(cur:visi) = [ Screen i s | (i,s) <- zip seen [0..] ]
-- now zip up visibles with their screen id
--
-- /O(w)/. Set focus to the workspace with index 'i'.
-- If the index is out of range, return the original StackSet.
--
-- Xinerama: If the workspace is not visible on any Xinerama screen, it
-- is raised on the current screen. If it is already visible, focus is
-- becomes the current screen. If it is in the visible list, it becomes
-- current.
-- is raised to the current screen. If it is already visible, focus is
-- just moved.
--
view :: Integral i => i -> StackSet i a s -> StackSet i a s
view i s@(StackSet sz (Workspace n _) _ _ scrs)
| i >= 0 && i < sz
= setCurrent $ if M.member i scrs
then s -- already visisble. just set current.
else case M.lookup n scrs of -- TODO current should always be valid
Nothing -> error "xmonad:view: No physical screen"
Just sc -> s { screens = M.insert i sc (M.delete n scrs) }
| otherwise = s
view :: (Eq i, Eq a, Eq s, Integral i) => i -> StackSet i a s -> StackSet i a s
view i s
| i < 0 && i >= size s || i == tag (workspace (current s)) = s -- out of bounds or current
-- actually moving focus is easy:
where setCurrent x = foldr traverse x [1..abs (i-n)]
| Just x <- L.find ((i==).tag.workspace) (visible s)
-- if it is visible, it is just raised
= s { current = x, visible = current s : L.delete x (visible s) }
-- work out which direction to move
traverse _ = if signum (i-n) >= 0 then viewRight else viewLeft
| Just x <- L.find ((i==).tag) (hidden s)
-- if it was hidden, it is raised on the xine screen currently used
= s { current = Screen x (screen (current s))
, hidden = workspace (current s) : L.delete x (hidden s) }
-- /O(1)/. Move workspace focus left or right one node, a la Huet.
viewLeft (StackSet m t (l:ls) rs sc) = StackSet m l ls (t:rs) sc
viewLeft t = t
viewRight (StackSet m t ls (r:rs) sc) = StackSet m r (t:ls) rs sc
viewRight t = t
| otherwise = error "Inconsistent StackSet: workspace not found"
-- 'Catch'ing this might be hard. Relies on monotonically increasing
-- workspace tags defined in 'new'
-- ---------------------------------------------------------------------
-- Xinerama operations
@@ -190,7 +196,7 @@ view i s@(StackSet sz (Workspace n _) _ _ scrs)
-- | Find the tag of the workspace visible on Xinerama screen 'sc'.
-- Nothing if screen is out of bounds.
lookupWorkspace :: Eq s => s -> StackSet i a s -> Maybe i
lookupWorkspace sc w = listToMaybe [ i | (i,s) <- M.assocs (screens w), s == sc ]
lookupWorkspace sc w = listToMaybe [ tag i | Screen i s <- current w : visible w, s == sc ]
-- ---------------------------------------------------------------------
-- Operations on the current stack
@@ -202,7 +208,7 @@ lookupWorkspace sc w = listToMaybe [ i | (i,s) <- M.assocs (screens w), s == sc
-- returning the result. It is like 'maybe' for the focused workspace.
--
with :: b -> (Stack a -> b) -> StackSet i a s -> b
with dflt f s = case stack (current s) of Empty -> dflt; v -> f v
with dflt f s = case stack (workspace (current s)) of Empty -> dflt; v -> f v
-- TODO: ndm: a 'catch' proof here that 'f' only gets Node
-- constructors, hence all 'f's are safe below?
@@ -210,7 +216,8 @@ with dflt f s = case stack (current s) of Empty -> dflt; v -> f v
-- Apply a function, and a default value for Empty, to modify the current stack.
--
modify :: Stack a -> (Stack a -> Stack a) -> StackSet i a s -> StackSet i a s
modify d f s = s { current = (current s) { stack = with d f s } }
modify d f s = s { current = (current s)
{ workspace = (workspace (current s)) { stack = with d f s }}}
--
-- /O(1)/. Extract the focused element of the current stack.
@@ -248,10 +255,10 @@ focusRight = modify Empty $ \c -> case c of
Node t ls [] -> Node x [] (xs ++ [t]) where (x:xs) = reverse ls
--
-- | /O(1) on current window, O(n) in general/. Focus the window 'w'. If the
-- workspace 'w' is on is not visible, 'view' that workspace first.
-- | /O(1) on current window, O(n) in general/. Focus the window 'w',
-- and set its workspace as current.
--
focusWindow :: (Integral i, Eq a) => a -> StackSet i a s -> StackSet i a s
focusWindow :: (Integral i, Eq s, Eq a) => a -> StackSet i a s -> StackSet i a s
focusWindow w s | Just w == peek s = s
| otherwise = maybe s id $ do
n <- findIndex w s
@@ -270,7 +277,8 @@ member a s = maybe False (const True) (findIndex a s)
-- Return Just the workspace index of the given window, or Nothing
-- if the window is not in the StackSet.
findIndex :: Eq a => a -> StackSet i a s -> Maybe i
findIndex a s = listToMaybe [ tag w | w <- current s : prev s ++ next s, has a (stack w) ]
findIndex a s = listToMaybe
[ tag w | w <- workspace (current s) : map workspace (visible s) ++ hidden s, has a (stack w) ]
where has _ Empty = False
has x (Node t l r) = x `elem` (t : l ++ r)
@@ -314,9 +322,9 @@ insertLeft a s = if member a s then s else insert
-- * deleting the master window resets it to the newly focused window
-- * otherwise, delete doesn't affect the master.
--
delete :: (Integral i, Eq a) => a -> StackSet i a s -> StackSet i a s
delete :: (Integral i, Eq a, Eq s) => a -> StackSet i a s -> StackSet i a s
delete w s | Just w == peek s = remove s -- common case.
| otherwise = maybe s (removeWindow . tag . current $ s) (findIndex w s)
| otherwise = maybe s (removeWindow.tag.workspace.current $ s) (findIndex w s)
where
-- find and remove window script
removeWindow o n = foldr ($) s [view o,remove ,until ((Just w ==) . peek) focusLeft,view n]
@@ -351,8 +359,9 @@ swap = modify Empty $ \c -> case c of
-- workspace. The actual focused workspace doesn't change. If there is
-- no element on the current stack, the original stackSet is returned.
--
shift :: (Eq a, Integral i) => i -> StackSet i a s -> StackSet i a s
shift n s = if and [n >= 0,n < size s,n /= tag (current s)] then maybe s go (peek s) else s
where go w = foldr ($) s [view (tag (current s)),insertLeft w,view n,delete w]
shift :: (Eq a, Eq s, Integral i) => i -> StackSet i a s -> StackSet i a s
shift n s = if and [n >= 0,n < size s,n /= tag (workspace (current s))]
then maybe s go (peek s) else s
where go w = foldr ($) s [view (tag (workspace (current s))),insertLeft w,view n,delete w]
-- ^^ poor man's state monad :-)

View File

@@ -84,7 +84,7 @@ fromList (o,m,fs,xs) =
type T = StackSet Int Char Int
-- Useful operation, the non-local workspaces
hidden x = [ w | w <- prev x ++ next x ] -- the hidden workspaces
hidden_spaces x = map workspace (visible x) ++ hidden x
-- Basic data invariants of the StackSet
--
@@ -105,27 +105,23 @@ invariant (s :: T) = and
[ noDuplicates
-- all this xinerama stuff says we don't have the right structure
, currentIsVisible
, validScreens
, validWorkspaces
, inBounds
-- , validScreens
-- , validWorkspaces
-- , inBounds
]
where
ws = [ focus t : left t ++ right t
| w <- current s : prev s ++ next s, let t = stack w, t /= Empty ]
| w <- workspace (current s) : map workspace (visible s) ++ hidden s
, let t = stack w, t /= Empty ]
noDuplicates = nub ws == ws
-- xinerama invariants:
-- validScreens = monotonic . sort . M. . (W.current s : W.visible : W$ s
currentIsVisible = M.member (tag (current s)) (screens s)
-- validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
-- where allworkspaces = map tag $ current s : prev s ++ next s
validScreens = monotonic . sort . M.elems . screens $ s
validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
where allworkspaces = map tag $ current s : prev s ++ next s
inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
monotonic [] = True
monotonic (x:[]) = True
@@ -149,7 +145,7 @@ prop_focusRight_I (n :: NonNegative Int) (x :: T) =
prop_focus_I (n :: NonNegative Int) (x :: T) =
case peek x of
Nothing -> True
Just _ -> let w = focus . stack . current $ foldr (const focusLeft) x [1..n]
Just _ -> let w = focus . stack . workspace . current $ foldr (const focusLeft) x [1..n]
in invariant $ focusWindow w x
prop_insertLeft_I n (x :: T) = invariant $ insertLeft n x
@@ -171,13 +167,14 @@ prop_shift_I (n :: NonNegative Int) (x :: T) =
-- empty StackSets have no windows in them
prop_empty (n :: Positive Int)
(m :: Positive Int) =
all (== Empty) [ stack w | w <- current x : prev x ++ next x ]
all (== Empty) [ stack w | w <- workspace (current x)
: map workspace (visible x) ++ hidden x ]
where x = new (fromIntegral n) (fromIntegral m) :: T
-- empty StackSets always have focus on workspace 0
prop_empty_current (n :: Positive Int)
(m :: Positive Int) = tag (current x) == 0
(m :: Positive Int) = tag (workspace $ current x) == 0
where x = new (fromIntegral n) (fromIntegral m) :: T
-- no windows will be a member of an empty workspace
@@ -189,7 +186,7 @@ prop_member_empty i (n :: Positive Int) (m :: Positive Int)
-- view sets the current workspace to 'n'
prop_view_current (x :: T) (n :: NonNegative Int) = i < size x ==>
tag (current (view i x)) == i
tag (workspace $ current (view i x)) == i
where
i = fromIntegral n
@@ -199,14 +196,15 @@ prop_view_local (x :: T) (n :: NonNegative Int) = i < size x ==>
workspaces x == workspaces (view i x)
where
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
current a : prev a ++ next a
workspace (current a)
: map workspace (visible a) ++ hidden a
i = fromIntegral n
-- view should result in a visible xinerama screen
prop_view_xinerama (x :: T) (n :: NonNegative Int) = i < size x ==>
M.member i (screens (view i x))
where
i = fromIntegral n
-- prop_view_xinerama (x :: T) (n :: NonNegative Int) = i < size x ==>
-- M.member i (screens (view i x))
-- where
-- i = fromIntegral n
-- view is idempotent
prop_view_idem (x :: T) r =
@@ -214,21 +212,27 @@ prop_view_idem (x :: T) r =
sz = size x
in view i (view i x) == (view i x)
-- view is reversible
prop_view_reversible r (x :: T) = view n (view i x) == x
where n = tag (current x)
-- view is reversible, though shuffles the order of hidden/visible
prop_view_reversible r (x :: T) = normal (view n (view i x)) == normal x
where n = tag (workspace $ current x)
sz = size x
i = fromIntegral $ r `mod` sz
-- normalise workspace list
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
where
f = \a b -> tag (workspace a) `compare` tag (workspace b)
g = \a b -> tag a `compare` tag b
-- ---------------------------------------------------------------------
-- Xinerama
-- every screen should yield a valid workspace
prop_lookupWorkspace (n :: NonNegative Int) (x :: T) =
s < M.size (screens x) ==>
fromJust (lookupWorkspace s x) `elem` (map tag $ current x : prev x ++ next x)
where
s = fromIntegral n
-- prop_lookupWorkspace (n :: NonNegative Int) (x :: T) =
-- s < M.size (screens x) ==>
-- fromJust (lookupWorkspace s x) `elem` (map tag $ current x : prev x ++ next x)
-- where
-- s = fromIntegral n
-- ---------------------------------------------------------------------
-- peek/index
@@ -249,33 +253,12 @@ prop_index_length (x :: T) =
Empty -> length (index x) == 0
Node {} -> length (index x) == length list
where
it = stack . current $ x
it = stack . workspace . current $ x
list = focus it : left it ++ right it
-- ---------------------------------------------------------------------
-- rotating focus
--
-- Unfortunately, in the presence of wrapping of focus, we don't have a
-- simple identity where focusLeft . focusRight == id, as the focus
-- operations repartition the structure on wrapping.
--
-- Note the issue with equality on Stacks given the wrapping semantics.
--
-- [1,2,3] ++ [4] ++ [5]
--
-- should be equivalent to:
--
-- [] ++ [4] ++ [5,1,2,3]
--
-- However, we can simply normalise the list, taking focus as the head,
-- and the items should be the same.
-- So we normalise the stack on the current workspace.
-- We normalise by moving everything to the 'left' of the focused item,
-- to the right.
-- normal (x :: T) = modify Empty (\c -> case c of
-- Node t ls rs -> Node t [] (rs ++ reverse ls)) x
normal = id
-- master/focus
--
@@ -293,8 +276,8 @@ prop_focusWindow_master (n :: NonNegative Int) (x :: T) =
in index (focusWindow (s !! i) x) == index x
-- shifting focus is trivially reversible
prop_focus_left (x :: T) = normal (focusLeft (focusRight x)) == normal x
prop_focus_right (x :: T) = normal (focusRight (focusLeft x)) == normal x
prop_focus_left (x :: T) = (focusLeft (focusRight x)) == x
prop_focus_right (x :: T) = (focusRight (focusLeft x)) == x
-- focusWindow actually leaves the window focused...
prop_focusWindow_works (n :: NonNegative Int) (x :: T) =
@@ -302,26 +285,26 @@ prop_focusWindow_works (n :: NonNegative Int) (x :: T) =
Nothing -> True
Just _ -> let s = index x
i = fromIntegral n `mod` length s
in (focus . stack . current) (focusWindow (s !! i) x) == (s !! i)
in (focus . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
-- rotation through the height of a stack gets us back to the start
prop_focus_all_l (x :: T) = normal (foldr (const focusLeft) x [1..n]) == normal x
prop_focus_all_l (x :: T) = (foldr (const focusLeft) x [1..n]) == x
where n = length (index x)
prop_focus_all_r (x :: T) = normal (foldr (const focusRight) x [1..n]) == normal x
prop_focus_all_r (x :: T) = (foldr (const focusRight) x [1..n]) == x
where n = length (index x)
-- prop_rotate_all (x :: T) = f (f x) == f x
-- f x' = foldr (\_ y -> rotate GT y) x' [1..n]
-- focus is local to the current workspace
prop_focus_local (x :: T) = hidden (focusRight x) == hidden x
prop_focus_local (x :: T) = hidden_spaces (focusRight x) == hidden_spaces x
prop_focusWindow_local (n :: NonNegative Int) (x::T ) =
case peek x of
Nothing -> True
Just _ -> let s = index x
i = fromIntegral n `mod` length s
in hidden (focusWindow (s !! i) x) == hidden x
in hidden_spaces (focusWindow (s !! i) x) == hidden_spaces x
-- ---------------------------------------------------------------------
-- member/findIndex
@@ -332,7 +315,7 @@ prop_focusWindow_local (n :: NonNegative Int) (x::T ) =
--
prop_findIndex (x :: T) =
and [ tag w == fromJust (findIndex i x)
| w <- current x : prev x ++ next x
| w <- workspace (current x) : map workspace (visible x) ++ hidden x
, let t = stack w
, t /= Empty
, i <- focus (stack w) : left (stack w) ++ right (stack w)
@@ -352,7 +335,7 @@ prop_insert_idem i (x :: T) = insertLeft i x == insertLeft i (insertLeft i x)
prop_insert_duplicate i (x :: T) = member i x ==> insertLeft i x == x
-- push shouldn't change anything but the current workspace
prop_insert_local (x :: T) i = not (member i x) ==> hidden x == hidden (insertLeft i x)
prop_insert_local (x :: T) i = not (member i x) ==> hidden_spaces x == hidden_spaces (insertLeft i x)
-- Inserting a (unique) list of items into an empty stackset should
-- result in the last inserted element having focus.
@@ -402,7 +385,7 @@ prop_delete_insert (x :: T) =
prop_delete_local (x :: T) =
case peek x of
Nothing -> True
Just i -> hidden x == hidden (delete i x)
Just i -> hidden_spaces x == hidden_spaces (delete i x)
-- ---------------------------------------------------------------------
-- swap: setting the master window
@@ -414,10 +397,10 @@ prop_delete_local (x :: T) =
prop_swap_focus (x :: T)
= case peek x of
Nothing -> True
Just f -> focus (stack (current (swap x))) == f
Just f -> focus (stack (workspace $ current (swap x))) == f
-- swap is local
prop_swap_local (x :: T) = hidden x == hidden (swap x)
prop_swap_local (x :: T) = hidden_spaces x == hidden_spaces (swap x)
-- TODO swap is reversible
-- swap is reversible, but involves moving focus back the window with
@@ -441,10 +424,10 @@ prop_swap_idempotent (x :: T) = swap (swap x) == swap x
prop_shift_reversible (r :: Int) (x :: T) =
let i = fromIntegral $ r `mod` sz
sz = size y
n = tag (current y)
n = tag (workspace $ current y)
in case peek y of
Nothing -> True
Just _ -> (view n . shift n . view i . shift i) y == y
Just _ -> normal ((view n . shift n . view i . shift i) y) == normal y
where
y = swap x
@@ -499,11 +482,11 @@ main = do
,("view : invariant" , mytest prop_view_I)
,("view sets current" , mytest prop_view_current)
,("view idempotent" , mytest prop_view_idem)
,("view reviersible" , mytest prop_view_reversible)
,("view / xinerama" , mytest prop_view_xinerama)
,("view reversible" , mytest prop_view_reversible)
-- ,("view / xinerama" , mytest prop_view_xinerama)
,("view is local" , mytest prop_view_local)
,("valid workspace xinerama", mytest prop_lookupWorkspace)
-- ,("valid workspace xinerama", mytest prop_lookupWorkspace)
,("peek/member " , mytest prop_member_peek)