xmonad: cleaned SubLayout Boring and TabbedFix

The only thing that is not working right now, is
when I float a window (C-t) and make it sticky (C-s)
and use on another window (like a browser) passmenu or albert (C-<space>)
This commit is contained in:
Ingolf Wagner 2019-11-10 18:38:14 +01:00
parent ab165dc2b5
commit ba7825cc30
Signed by: palo
GPG key ID: 76BF5F1928B9618B
3 changed files with 513 additions and 3 deletions

View file

@ -30,6 +30,7 @@ in {
home.file.".xmonad/lib/FloatKeys.hs".source = ./xmonad/FloatKeys.hs;
home.file.".xmonad/lib/TabbedFix.hs".source = ./xmonad/TabbedFix.hs;
home.file.".xmonad/lib/BoringWindows.hs".source = ./xmonad/BoringWindows.hs;
home.file.".xmonad/lib/SubLayouts.hs".source = ./xmonad/SubLayouts.hs;
home.file.".xmonad/xmonad.cabal".source = ./xmonad/palos-xmonad.cabal;
home.file.".xmonad/Main.hs".source = ./xmonad/Main.hs;

View file

@ -2,6 +2,7 @@
module Main where
import BoringWindows (BoringMessage (..),
boringWindows, clearBoring,
focusDown, focusUp,
@ -12,6 +13,9 @@ import Data.Ratio ((%))
import FloatKeys (keysResizeWindow)
import NixCommands
import qualified SolarizedLight as Solarized
import SubLayouts (GroupMsg (..), onGroup,
pullGroup, subLayout,
subTabbed)
import System.Exit
import XMonad
import XMonad.Actions.CopyWindow (copy, copyToAll, kill1,
@ -38,9 +42,6 @@ import XMonad.Layout.NoBorders (noBorders, smartBorders)
import XMonad.Layout.ResizableTile (MirrorResize (MirrorExpand, MirrorShrink),
ResizableTall (..))
import XMonad.Layout.Simplest (Simplest (..))
import XMonad.Layout.SubLayouts (GroupMsg (..), onGroup,
pullGroup, subLayout,
subTabbed)
import XMonad.Layout.Tabbed (TabbedDecoration, addTabs,
addTabsAlways, shrinkText)
import XMonad.Layout.WindowNavigation (configurableNavigation,

View file

@ -0,0 +1,508 @@
{-# LANGUAGE PatternGuards, ParallelListComp, DeriveDataTypeable, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, TypeSynonymInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Layout.SubLayouts
-- Copyright : (c) 2009 Adam Vogt
-- License : BSD-style (see xmonad/LICENSE)
--
-- Maintainer : vogt.adam@gmail.com
-- Stability : unstable
-- Portability : unportable
--
-- A layout combinator that allows layouts to be nested.
--
-- had to copy it because
-- * uses the wrong Boring Windows library
-----------------------------------------------------------------------------
module SubLayouts (
-- * Usage
-- $usage
subLayout,
subTabbed,
pushGroup, pullGroup,
pushWindow, pullWindow,
onGroup, toSubl, mergeDir,
GroupMsg(..),
Broadcast(..),
defaultSublMap,
Sublayout,
-- * Screenshots
-- $screenshots
-- * Todo
-- $todo
)
where
import XMonad.Layout.Circle () -- so haddock can find the link
import XMonad.Layout.Decoration(Decoration, DefaultShrinker)
import XMonad.Layout.LayoutModifier(LayoutModifier(handleMess, modifyLayout,
redoLayout),
ModifiedLayout(..))
import XMonad.Layout.Simplest(Simplest(..))
import XMonad.Layout.Tabbed(shrinkText,
TabbedDecoration, addTabs)
import XMonad.Layout.WindowNavigation(Navigate(Apply))
import XMonad.Util.Invisible(Invisible(..))
import XMonad.Util.Types(Direction2D(..))
import XMonad hiding (def)
import Control.Applicative((<$>),(<*))
import Control.Arrow(Arrow(second, (&&&)))
import Control.Monad(MonadPlus(mplus), foldM, guard, when, join)
import Data.Function(on)
import Data.List(nubBy, (\\), find)
import Data.Maybe(isNothing, fromMaybe, listToMaybe, mapMaybe)
import Data.Traversable(sequenceA)
import qualified BoringWindows as B
import qualified XMonad as X
import qualified XMonad.StackSet as W
import qualified Data.Map as M
import Data.Map(Map)
-- $screenshots
--
-- <<http://haskell.org/sitewiki/images/thumb/8/8b/Xmonad-SubLayouts-xinerama.png/480px-Xmonad-SubLayouts-xinerama.png>>
--
-- Larger version: <http://haskell.org/sitewiki/images/8/8b/Xmonad-SubLayouts-xinerama.png>
-- $todo
-- /Issue 288/
--
-- "XMonad.Layout.ResizableTile" assumes that its environment
-- contains only the windows it is running: sublayouts are currently run with
-- the stack containing only the windows passed to it in its environment, but
-- any changes that the layout makes are not merged back.
--
-- Should the behavior be made optional?
--
-- /Features/
--
-- * suggested managehooks for merging specific windows, or the apropriate
-- layout based hack to find out the number of groups currently showed, but
-- the size of current window groups is not available (outside of this
-- growing module)
--
-- /SimpleTabbed as a SubLayout/
--
-- 'subTabbed' works well, but it would be more uniform to avoid the use of
-- addTabs, with the sublayout being Simplest (but
-- 'XMonad.Layout.Tabbed.simpleTabbed' is this...). The only thing to be
-- gained by fixing this issue is the ability to mix and match decoration
-- styles. Better compatibility with some other layouts of which I am not
-- aware could be another benefit.
--
-- 'simpleTabbed' (and other decorated layouts) fail horribly when used as
-- subLayouts:
--
-- * decorations stick around: layout is run after being told to Hide
--
-- * mouse events do not change focus: the group-ungroup does not respect
-- the focus changes it wants?
--
-- * sending ReleaseResources before running it makes xmonad very slow, and
-- still leaves borders sticking around
--
-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Layout.SubLayouts
-- > import XMonad.Layout.WindowNavigation
--
-- Using "XMonad.Layout.BoringWindows" is optional and it allows you to add a
-- keybinding to skip over the non-visible windows.
--
-- > import XMonad.Layout.BoringWindows
--
-- Then edit your @layoutHook@ by adding the 'subTabbed' layout modifier:
--
-- > myLayout = windowNavigation $ subTabbed $ boringWindows $
-- > Tall 1 (3/100) (1/2) ||| etc..
-- > main = xmonad def { layoutHook = myLayout }
--
-- "XMonad.Layout.WindowNavigation" is used to specify which windows to merge,
-- and it is not integrated into the modifier because it can be configured, and
-- works best as the outer modifier.
--
-- Then to your keybindings add:
--
-- > , ((modm .|. controlMask, xK_h), sendMessage $ pullGroup L)
-- > , ((modm .|. controlMask, xK_l), sendMessage $ pullGroup R)
-- > , ((modm .|. controlMask, xK_k), sendMessage $ pullGroup U)
-- > , ((modm .|. controlMask, xK_j), sendMessage $ pullGroup D)
-- >
-- > , ((modm .|. controlMask, xK_m), withFocused (sendMessage . MergeAll))
-- > , ((modm .|. controlMask, xK_u), withFocused (sendMessage . UnMerge))
-- >
-- > , ((modm .|. controlMask, xK_period), onGroup W.focusUp')
-- > , ((modm .|. controlMask, xK_comma), onGroup W.focusDown')
--
-- These additional keybindings require the optional
-- "XMonad.Layout.BoringWindows" layoutModifier. The focus will skip over the
-- windows that are not focused in each sublayout.
--
-- > , ((modm, xK_j), focusDown)
-- > , ((modm, xK_k), focusUp)
--
-- A 'submap' can be used to make modifying the sublayouts using 'onGroup' and
-- 'toSubl' simpler:
--
-- > ,((modm, xK_s), submap $ defaultSublMap conf)
--
-- /NOTE:/ is there some reason that @asks config >>= submap . defaultSublMap@
-- could not be used in the keybinding instead? It avoids having to explicitly
-- pass the conf.
--
-- For more detailed instructions, see:
--
-- "XMonad.Doc.Extending#Editing_the_layout_hook"
-- "XMonad.Doc.Extending#Adding_key_bindings"
-- | The main layout modifier arguments:
--
-- @subLayout advanceInnerLayouts innerLayout outerLayout@
--
-- [@advanceInnerLayouts@] When a new group at index @n@ in the outer layout
-- is created (even with one element), the @innerLayout@ is used as the
-- layout within that group after being advanced with @advanceInnerLayouts !!
-- n@ 'NextLayout' messages. If there is no corresponding element in the
-- @advanceInnerLayouts@ list, then @innerLayout@ is not given any 'NextLayout'
-- messages.
--
-- [@innerLayout@] The single layout given to be run as a sublayout.
--
-- [@outerLayout@] The layout that determines the rectangles given to each
-- group.
--
-- Ex. The second group is 'Tall', the third is 'Circle', all others are tabbed
-- with:
--
-- > myLayout = addTabs shrinkText def
-- > $ subLayout [0,1,2] (Simplest ||| Tall 1 0.2 0.5 ||| Circle)
-- > $ Tall 1 0.2 0.5 ||| Full
subLayout :: [Int] -> subl a -> l a -> ModifiedLayout (Sublayout subl) l a
subLayout nextLayout sl x = ModifiedLayout (Sublayout (I []) (nextLayout,sl) []) x
-- | @subTabbed@ is a use of 'subLayout' with 'addTabs' to show decorations.
subTabbed :: (Eq a, LayoutModifier (Sublayout Simplest) a, LayoutClass l a) =>
l a -> ModifiedLayout (Decoration TabbedDecoration DefaultShrinker)
(ModifiedLayout (Sublayout Simplest) l) a
subTabbed x = addTabs shrinkText X.def $ subLayout [] Simplest x
-- | @defaultSublMap@ is an attempt to create a set of keybindings like the
-- defaults ones but to be used as a 'submap' for sending messages to the
-- sublayout.
defaultSublMap :: XConfig l -> Map (KeyMask, KeySym) (X ())
defaultSublMap (XConfig { modMask = modm }) = M.fromList
[((modm, xK_space), toSubl NextLayout),
((modm, xK_j), onGroup W.focusDown'),
((modm, xK_k), onGroup W.focusUp'),
((modm, xK_h), toSubl Shrink),
((modm, xK_l), toSubl Expand),
((modm, xK_Tab), onGroup W.focusDown'),
((modm .|. shiftMask, xK_Tab), onGroup W.focusUp'),
((modm, xK_m), onGroup focusMaster'),
((modm, xK_comma), toSubl $ IncMasterN 1),
((modm, xK_period), toSubl $ IncMasterN (-1)),
((modm, xK_Return), onGroup swapMaster')
]
where
-- should these go into XMonad.StackSet?
focusMaster' st = let (f:fs) = W.integrate st
in W.Stack f [] fs
swapMaster' (W.Stack f u d) = W.Stack f [] $ reverse u ++ d
data Sublayout l a = Sublayout
{ delayMess :: Invisible [] (SomeMessage,a)
-- ^ messages are handled when running the layout,
-- not in the handleMessage, I'm not sure that this
-- is necessary
, def :: ([Int], l a) -- ^ how many NextLayout messages to send to newly
-- populated layouts. If there is no corresponding
-- index, then don't send any.
, subls :: [(l a,W.Stack a)]
-- ^ The sublayouts and the stacks they manage
}
deriving (Read,Show)
-- | Groups assumes this invariant:
-- M.keys gs == map W.focus (M.elems gs) (ignoring order)
-- All windows in the workspace are in the Map
--
-- The keys are visible windows, the rest are hidden.
--
-- This representation probably simplifies the internals of the modifier.
type Groups a = Map a (W.Stack a)
-- | GroupMsg take window parameters to determine which group the action should
-- be applied to
data GroupMsg a
= UnMerge a -- ^ free the focused window from its tab stack
| UnMergeAll a
-- ^ separate the focused group into singleton groups
| Merge a a -- ^ merge the first group into the second group
| MergeAll a
-- ^ make one large group, keeping the parameter focused
| Migrate a a
-- ^ used to the window named in the first argument to the
-- second argument's group, this may be replaced by a
-- combination of 'UnMerge' and 'Merge'
| WithGroup (W.Stack a -> X (W.Stack a)) a
| SubMessage SomeMessage a
-- ^ the sublayout with the given window will get the message
deriving (Typeable)
-- | merge the window that would be focused by the function when applied to the
-- W.Stack of all windows, with the current group removed. The given window
-- should be focused by a sublayout. Example usage: @withFocused (sendMessage .
-- mergeDir W.focusDown')@
mergeDir :: (W.Stack Window -> W.Stack Window) -> Window -> GroupMsg Window
mergeDir f w = WithGroup g w
where g cs = do
let onlyOthers = W.filter (`notElem` W.integrate cs)
flip whenJust (sendMessage . Merge (W.focus cs) . W.focus . f)
=<< fmap (onlyOthers =<<) currentStack
return cs
data Broadcast = Broadcast SomeMessage -- ^ send a message to all sublayouts
deriving (Typeable)
instance Message Broadcast
instance Typeable a => Message (GroupMsg a)
-- | @pullGroup@, @pushGroup@ allow you to merge windows or groups inheriting
-- the position of the current window (pull) or the other window (push).
--
-- @pushWindow@ and @pullWindow@ move individual windows between groups. They
-- are less effective at preserving window positions.
pullGroup,pushGroup,pullWindow,pushWindow :: Direction2D -> Navigate
pullGroup = mergeNav (\o c -> sendMessage $ Merge o c)
pushGroup = mergeNav (\o c -> sendMessage $ Merge c o)
pullWindow = mergeNav (\o c -> sendMessage $ Migrate o c)
pushWindow = mergeNav (\o c -> sendMessage $ Migrate c o)
mergeNav :: (Window -> Window -> X ()) -> Direction2D -> Navigate
mergeNav f = Apply (\o -> withFocused (f o))
-- | Apply a function on the stack belonging to the currently focused group. It
-- works for rearranging windows and for changing focus.
onGroup :: (W.Stack Window -> W.Stack Window) -> X ()
onGroup f = withFocused (sendMessage . WithGroup (return . f))
-- | Send a message to the currently focused sublayout.
toSubl :: (Message a) => a -> X ()
toSubl m = withFocused (sendMessage . SubMessage (SomeMessage m))
instance (Read (l Window), Show (l Window), LayoutClass l Window) => LayoutModifier (Sublayout l) Window where
modifyLayout (Sublayout { subls = osls }) (W.Workspace i la st) r = do
let gs' = updateGroup st $ toGroups osls
st' = W.filter (`elem` M.keys gs') =<< st
updateWs gs'
oldStack <- gets $ W.stack . W.workspace . W.current . windowset
setStack st'
runLayout (W.Workspace i la st') r <* setStack oldStack
-- FIXME: merge back reordering, deletions?
redoLayout (Sublayout { delayMess = I ms, def = defl, subls = osls }) _r st arrs = do
let gs' = updateGroup st $ toGroups osls
sls <- fromGroups defl st gs' osls
let newL :: LayoutClass l Window => Rectangle -> WorkspaceId -> (l Window) -> Bool
-> (Maybe (W.Stack Window)) -> X ([(Window, Rectangle)], l Window)
newL rect n ol isNew sst = do
orgStack <- currentStack
let handle l (y,_)
| not isNew = fromMaybe l <$> handleMessage l y
| otherwise = return l
kms = filter ((`elem` M.keys gs') . snd) ms
setStack sst
nl <- foldM handle ol $ filter ((`elem` W.integrate' sst) . snd) kms
result <- runLayout (W.Workspace n nl sst) rect
setStack orgStack -- FIXME: merge back reordering, deletions?
return $ fromMaybe nl `second` result
(urls,ssts) = unzip [ (newL gr i l isNew sst, sst)
| (isNew,(l,_st)) <- sls
| i <- map show [ 0 :: Int .. ]
| (k,gr) <- arrs, let sst = M.lookup k gs' ]
arrs' <- sequence urls
sls' <- return . Sublayout (I []) defl . map snd <$> fromGroups defl st gs'
[ (l,s) | (_,l) <- arrs' | (Just s) <- ssts ]
return (concatMap fst arrs', sls')
handleMess (Sublayout (I ms) defl sls) m
| Just (SubMessage sm w) <- fromMessage m =
return $ Just $ Sublayout (I ((sm,w):ms)) defl sls
| Just (Broadcast sm) <- fromMessage m = do
ms' <- fmap (zip (repeat sm) . W.integrate') currentStack
return $ if null ms' then Nothing
else Just $ Sublayout (I $ ms' ++ ms) defl sls
| Just B.UpdateBoring <- fromMessage m = do
let bs = concatMap unfocused $ M.elems gs
ws <- gets (W.workspace . W.current . windowset)
flip sendMessageWithNoRefresh ws $ B.Replace "Sublayouts" bs
return Nothing
| Just (WithGroup f w) <- fromMessage m
, Just g <- M.lookup w gs = do
g' <- f g
let gs' = M.insert (W.focus g') g' $ M.delete (W.focus g) gs
when (gs' /= gs) $ updateWs gs'
when (w /= W.focus g') $ windows (W.focusWindow $ W.focus g')
return Nothing
| Just (MergeAll w) <- fromMessage m =
let gs' = fmap (M.singleton w)
$ (focusWindow' w =<<) $ W.differentiate
$ concatMap W.integrate $ M.elems gs
in maybe (return Nothing) fgs gs'
| Just (UnMergeAll w) <- fromMessage m =
let ws = concatMap W.integrate $ M.elems gs
_ = w :: Window
mkSingleton f = M.singleton f (W.Stack f [] [])
in fgs $ M.unions $ map mkSingleton ws
| Just (Merge x y) <- fromMessage m
, Just (W.Stack _ xb xn) <- findGroup x
, Just yst <- findGroup y =
let zs = W.Stack x xb (xn ++ W.integrate yst)
in fgs $ M.insert x zs $ M.delete (W.focus yst) gs
| Just (UnMerge x) <- fromMessage m =
fgs . M.fromList . map (W.focus &&& id) . M.elems
$ M.mapMaybe (W.filter (x/=)) gs
-- XXX sometimes this migrates an incorrect window, why?
| Just (Migrate x y) <- fromMessage m
, Just xst <- findGroup x
, Just (W.Stack yf yu yd) <- findGroup y =
let zs = W.Stack x (yf:yu) yd
nxsAdd = maybe id (\e -> M.insert (W.focus e) e) $ W.filter (x/=) xst
in fgs $ nxsAdd $ M.insert x zs $ M.delete yf gs
| otherwise = fmap join $ sequenceA $ catchLayoutMess <$> fromMessage m
where gs = toGroups sls
fgs gs' = do
st <- currentStack
Just . Sublayout (I ms) defl . map snd <$> fromGroups defl st gs' sls
findGroup z = mplus (M.lookup z gs) $ listToMaybe
$ M.elems $ M.filter ((z `elem`) . W.integrate) gs
-- catchLayoutMess :: LayoutMessages -> X (Maybe (Sublayout l Window))
-- This l must be the same as from the instance head,
-- -XScopedTypeVariables should bring it into scope, but we are
-- trying to avoid warnings with ghc-6.8.2 and avoid CPP
catchLayoutMess x = do
let m' = x `asTypeOf` (undefined :: LayoutMessages)
ms' <- zip (repeat $ SomeMessage m') . W.integrate'
<$> currentStack
return $ do guard $ not $ null ms'
Just $ Sublayout (I $ ms' ++ ms) defl sls
currentStack :: X (Maybe (W.Stack Window))
currentStack = gets (W.stack . W.workspace . W.current . windowset)
-- | update Group to follow changes in the workspace
updateGroup :: Ord a => Maybe (W.Stack a) -> Groups a -> Groups a
updateGroup mst gs =
let flatten = concatMap W.integrate . M.elems
news = W.integrate' mst \\ flatten gs
deads = flatten gs \\ W.integrate' mst
uniNew = M.union (M.fromList $ map (\n -> (n,single n)) news)
single x = W.Stack x [] []
-- pass through a list to update/remove keys
remDead = M.fromList . map (\w -> (W.focus w,w))
. mapMaybe (W.filter (`notElem` deads)) . M.elems
-- update the current tab group's order and focus
followFocus hs = fromMaybe hs $ do
f' <- W.focus `fmap` mst
xs <- find (elem f' . W.integrate) $ M.elems hs
xs' <- W.filter (`elem` W.integrate xs) =<< mst
return $ M.insert f' xs' $ M.delete (W.focus xs) hs
in remDead $ uniNew $ followFocus gs
-- | rearrange the windowset to put the groups of tabs next to eachother, so
-- that the stack of tabs stays put.
updateWs :: Groups Window -> X ()
updateWs = windowsMaybe . updateWs'
updateWs' :: Groups Window -> WindowSet -> Maybe WindowSet
updateWs' gs ws = do
f <- W.peek ws
let w = W.index ws
nes = concatMap W.integrate $ mapMaybe (flip M.lookup gs) w
ws' = W.focusWindow f $ foldr W.insertUp (foldr W.delete' ws nes) nes
guard $ W.index ws' /= W.index ws
return ws'
-- | focusWindow'. focus an element of a stack, is Nothing if that element is
-- absent. See also 'W.focusWindow'
focusWindow' :: (Eq a) => a -> W.Stack a -> Maybe (W.Stack a)
focusWindow' w st = do
guard $ not $ null $ filter (w==) $ W.integrate st
if W.focus st == w then Just st
else focusWindow' w $ W.focusDown' st
-- update only when Just
windowsMaybe :: (WindowSet -> Maybe WindowSet) -> X ()
windowsMaybe f = do
xst <- get
ws <- gets windowset
let up fws = put xst { windowset = fws }
maybe (return ()) up $ f ws
unfocused :: W.Stack a -> [a]
unfocused x = W.up x ++ W.down x
toGroups :: (Ord a) => [(a1, W.Stack a)] -> Map a (W.Stack a)
toGroups ws = M.fromList . map (W.focus &&& id) . nubBy (on (==) W.focus)
$ map snd ws
-- | restore the default layout for each group. It needs the X monad to switch
-- the default layout to a specific one (handleMessage NextLayout)
fromGroups :: (LayoutClass layout a, Ord k) =>
([Int], layout a)
-> Maybe (W.Stack k)
-> Groups k
-> [(layout a, b)]
-> X [(Bool,(layout a, W.Stack k))]
fromGroups (skips,defl) st gs sls = do
defls <- mapM (iterateM nextL defl !!) skips
return $ fromGroups' defl defls st gs (map fst sls)
where nextL l = fromMaybe l <$> handleMessage l (SomeMessage NextLayout)
iterateM f = iterate (>>= f) . return
fromGroups' :: (Ord k) => a -> [a] -> Maybe (W.Stack k) -> Groups k -> [a]
-> [(Bool,(a, W.Stack k))]
fromGroups' defl defls st gs sls =
[ (isNew,fromMaybe2 (dl, single w) (l, M.lookup w gs))
| l <- map Just sls ++ repeat Nothing, let isNew = isNothing l
| dl <- defls ++ repeat defl
| w <- W.integrate' $ W.filter (`notElem` unfocs) =<< st ]
where unfocs = unfocused =<< M.elems gs
single w = W.Stack w [] []
fromMaybe2 (a,b) (x,y) = (fromMaybe a x, fromMaybe b y)
-- this would be much cleaner with some kind of data-accessor
setStack :: Maybe (W.Stack Window) -> X ()
setStack x = modify (\s -> s { windowset = (windowset s)
{ W.current = (W.current $ windowset s)
{ W.workspace = (W.workspace $ W.current $ windowset s) { W.stack = x }}}})