Home > OS >  How do I switch workspace on all monitors with IndependantScreens layout
How do I switch workspace on all monitors with IndependantScreens layout

Time:12-11

I'm trying to replicate the way PopOs handles workspaces in multi monitor setup

I got partly there from the default way XMonad handles multi monitor systems by using XMonad.Layout.IndependentScreens and each screen having 10 workspaces

That somewhat works, but now each monitor has workspaces that are independant of each other. I want to change that so when I go to workspace 1 on monitor A or B, the other monitor(s) also goes to the workspace with that number

How do I do this?

CodePudding user response:

Yikes, that turns out to be a bit involved. There's no good out-of-the-box way to do it. Here's how I would do it. First, I'd make a way to take some action on each screen:

traverseScreens :: Applicative f =>
    (Screen   i l a sid sd -> f (Screen   i l a sid' sd')) ->
    (StackSet i l a sid sd -> f (StackSet i l a sid' sd'))
traverseScreens f (StackSet cur vis hid flt) = pure StackSet
    <*> f cur
    <*> traverse f vis
    <*> pure hid
    <*> pure flt

The action we're going to take is to swap out the current workspace on that screen for one with the same ScreenId but a different VirtualWorkspace.

-- oof, what is currently named PhysicalWorkspace should really have
-- been named PhysicalWorkspaceId (and similarly for VirtualWorkspace)
type PhysicalWorkspace' = Workspace PhysicalWorkspace (Layout Window) Window
type PhysicalScreen = Screen PhysicalWorkspace (Layout Window) Window ScreenId ScreenDetail

swapVirtualWorkspace ::
    VirtualWorkspace ->
    PhysicalScreen ->
    MaybeT (State [PhysicalWorkspace']) PhysicalScreen
swapVirtualWorkspace vIdNew scr
    | vIdOld == vIdNew = return scr
    | otherwise = do
    (pre, here:post) <- gets (break pIdMatches)
    put (pre    [workspace scr]    post)
    return scr { workspace = here }
    where
    (sId, vIdOld) = unmarshall . tag . workspace $ scr
    pIdMatches ws = unmarshall (tag ws) == (sId, vIdNew)

(You can skip this paragraph if you just want the code and don't care about understanding it.) This action keeps track of the slowly changing collection of hidden workspaces, and may fail (if there's no workspace with the requested VirtualWorkspace for the current screen). To help you read the action: gets (break pIdMatches) searches the current collection of hidden workspaces for one whose ScreenId and VirtualWorkspace match the provided screen's ID and the provided virtual workspace -- i.e. is the Workspace we want to put on the current screen. put (pre [workspace scr] post) puts the workspace that's on the current screen right now into the list of hidden workspaces, and return scr { workspace = here } puts the workspace we found on the current screen; these two together effectively swap a workspace between the current screen and the list of hidden workspaces. Finally, as a special case, if we're already looking at a workspace with the appropriate VirtualWorkspace, we (successfully) don't change anything. (Without this extra case, we would likely not find an appropriate hidden workspace and so mark this as a failed update.)

Now we can write a PhysicalWindowSpace operation that calls swapVirtualWorkspace once for each screen. We'll need to be careful that we put the new collection of hidden workspaces we've computed this way back into the windowspace.

viewOnAllScreens :: VirtualWorkspace -> WindowSet -> WindowSet
viewOnAllScreens vId ws = case mws' of
    Nothing -> ws
    Just ws' -> ws' { hidden = hidden' }
    where
    swapVirtualWorkspaces = traverseScreens (swapVirtualWorkspace vId) ws
    (mws', hidden') = runState (runMaybeT swapVirtualWorkspaces) (hidden ws)

If any of the swaps failed, this leaves the entire WindowSet unchanged -- I think that's a sane default, and you shouldn't have this come up in most cases.

Now you can update your keybindings to use viewOnAllScreens. The guidance in the IndependentScreens documentation looks like this:

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)]]

One possible update might look like this:

keyBindings conf = let modm = modMask conf in fromList $
    {- lots of other keybindings -}
    [ ((m .|. modm, k), windows f)
    | (i, k) <- zip (workspaces' conf) [xK_1 .. xK_9]
    , (f, m) <- [ (viewOnAllScreens i, 0)
                , (viewOnAllScreens i . onCurrentScreen shift i, shiftMask)
                ]
    ]

Caveat lector: I have checked that the above typechecks, but haven't tested its behavior. This certainly wouldn't be the first time I've written a correctly-typed bug...

For completeness, here's the imports I used to type-check the content above:

import Control.Monad.State
import Control.Monad.Trans.Maybe
import Data.List
import Data.Map (fromList)
import XMonad hiding (Screen)
import XMonad.StackSet
import XMonad.Layout.IndependentScreens
  • Related