Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to merge a newly spawned window into a sublayout/group? Preferably if already focued on the group. #372

Open
copper4eva opened this issue Aug 28, 2020 · 17 comments

Comments

@copper4eva
Copy link

Problem Description

Someone made a reddit post asking something very similar 11 days ago, with no responses (except me and himself):
https://www.reddit.com/r/xmonad/comments/ib0mx7/merge_spawned_window_into_sublayout/
He specifically wanted to make a keybinding that basically does this behavior for his terminal using subtabbed.

Personally, I would want it where if you're already focused on a group of tabs, then any newly spawned windows (whether it be terminals or whatever) would immediately get added to the tabbed group.

Is there any way currently to do either of these ideas?

Configuration File

Here's the parts of my config file that are related to my subtabbed layout, both the keybindings (that are related) and layout hook:

    , ("M-h", windows W.swapUp)   -- Move up in stack
    , ("M-j", focusUp)-- Focus down in stack (skips any tabs)
    , ("M-k", focusDown)  -- Focus up in stack (skips any tabs)
    , ("M-l", windows W.swapDown) -- Move down in stack

    , ("M-C-h", withFocused (sendMessage . mergeDir W.focusUp')) -- Merge up
    , ("M-C-j", onGroup W.focusUp') -- Switch focus up tabs
    , ("M-C-k", onGroup W.focusDown') -- Switch focus down tabs
    , ("M-C-l", withFocused (sendMessage . mergeDir W.focusDown')) -- Merge Down

    , ("M-C-t", withFocused (sendMessage . UnMerge)) -- Untab current window
myLayout =
     --spacingRaw False (Border 10 0 10 0) True (Border 0 10 0 10) True $
     addTabs shrinkText myTabConfig $
     subLayout [0] (Simplest) $
     boringWindows $
     ResizableTall 1 (3/100) (1/2) []

  where
     myTabConfig = def { fontName = "xft:monospace:size=10" }
@copper4eva
Copy link
Author

So, to update this issue, the OP of that reddit thread came up with something that almost works:
, ("M-<Return>", spawn (terminal c) >> withFocused (sendMessage . mergeDir W.focusUp'))
The issue with this is, for whatever reason, the mergeDir gets run then the new terminal window spawns. Very strange to me that it works like that.

I was wondering if there was a hook module that would help run this. Like there is this module:
https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-InsertPosition.html
for controlling what position a new window starts in. Is there not a module that can run the mergeDir command for every new window? Maybe this module:
https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-ManageHelpers.html

I'm hoping that running this with a hook solves the issue of the mergeDir occurring before the new window spawns (since obviously we want it to happen afterwards).

@geekosaur
Copy link
Contributor

geekosaur commented Sep 5, 2020 via email

@copper4eva
Copy link
Author

copper4eva commented Oct 16, 2020

The spawn is run in the background, so it will run at an indeterminate time relative to anything else. The closest you will get to what you want is to add a flag to ExtensibleState and check the flag in the manageHook and run your code there with liftX.

Can you elaborate on this at all? Sorry for extremely late reply, I've been busy with other stuff. But I'm motivated to attempt getting XMonad to automatically tab newly spawned windows together.

I don't know how to use liftX. I tried using it in my managehook first and failed. Here's a reddit post I made about it:
https://www.reddit.com/r/xmonad/comments/jbuj9k/how_do_i_use_liftx/

@geekosaur
Copy link
Contributor

geekosaur commented Oct 16, 2020 via email

@copper4eva
Copy link
Author

Take a look at XMonad.Actions.SpawnOn. In fact the spawnAndDo function in there might already be useful to you.

    myManageHook = composeAll
    [ resource  =? "lxqt" --> doIgnore
    --, className =? "XTerm"        --> doFloat
    --, className =? "XTerm"        --> (liftX (withFocused (sendMessage . mergeDir W.focusUp')) >> doFloat)
    --, className =? "XTerm"        --> (withFocused (sendMessage . mergeDir W.focusUp'))
    , spawnAndDo (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm"
    , resource  =? "lxqt-panel"       --> doIgnore ]

So I tried different things and this is what I've gotten. If I uncomment the one with liftX, that actually compiles and "works". I'm not entirely sure with liftX is supposed to do lol, but I don't think it's what I want. I do not want the "doFloat" to run, and that is of course what ultimately happens to xterm. I only put the doFloat in there while trying to get liftX to do anything without running an error. I think if I replaced doFloat with something that does nothing (say for example if there was literally a doNothing command) then I think that line might work correctly.

spawnAndDo sounds more like what I want. But right now I get an error when trying to use it:

    xmonad.hs:157:7: error:
    * Couldn't match expected type `Query (Endo WindowSet)'
                  with actual type `X ()'
    * In the expression:
        spawnAndDo
          (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm"
      In the first argument of `composeAll', namely
        `[resource =? "lxqt" --> doIgnore,
          spawnAndDo
            (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm",
          resource =? "lxqt-panel" --> doIgnore]'
      In the expression:
        composeAll
          [resource =? "lxqt" --> doIgnore,
           spawnAndDo
             (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm",
           resource =? "lxqt-panel" --> doIgnore]
    |
157 |     , spawnAndDo (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm"
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:157:19: error:
    * Couldn't match type `X ()' with `Query (Endo WindowSet)'
      Expected type: ManageHook
        Actual type: X ()
    * In the first argument of `spawnAndDo', namely
        `(withFocused (sendMessage . mergeDir W.focusUp'))'
      In the expression:
        spawnAndDo
          (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm"
      In the first argument of `composeAll', namely
        `[resource =? "lxqt" --> doIgnore,
          spawnAndDo
            (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm",
          resource =? "lxqt-panel" --> doIgnore]'
    |
157 |     , spawnAndDo (withFocused (sendMessage . mergeDir W.focusUp')) "XTerm"
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

@geekosaur
Copy link
Contributor

geekosaur commented Oct 16, 2020 via email

@copper4eva
Copy link
Author

spawnAndDo takes a manageHook, not an action. liftX permits an action to be run in a manageHook. So you don't want liftX in the commented-out ones, but need it in the spawnAndDo. As for the doFloat, you want to remove it and the preceding >>. (I'm having trouble responding to these on github, all I get is a file dialog instead of text entry.)

    , spawnAndDo (liftX (withFocused (sendMessage . mergeDir W.focusUp')) ) "XTerm"

Alright, I tried this. Which I believe is basically what you asked me to put together. Unfortunately still gives the same kind of error:

xmonad.hs:158:7: error:
    * Couldn't match expected type `Query (Endo WindowSet)'
                  with actual type `X ()'
    * In the expression:
        spawnAndDo
          (liftX (withFocused (sendMessage . mergeDir W.focusUp'))) "XTerm"
      In the first argument of `composeAll', namely
        `[resource =? "lxqt" --> doIgnore,
          spawnAndDo
            (liftX (withFocused (sendMessage . mergeDir W.focusUp'))) "XTerm",
          resource =? "lxqt-panel" --> doIgnore]'
      In the expression:
        composeAll
          [resource =? "lxqt" --> doIgnore,
           spawnAndDo
             (liftX (withFocused (sendMessage . mergeDir W.focusUp'))) "XTerm",
           resource =? "lxqt-panel" --> doIgnore]
    |
158 |     , spawnAndDo (liftX (withFocused (sendMessage . mergeDir W.focusUp')) ) "XTerm"
    |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:158:19: error:
    * Couldn't match type `()' with `Endo WindowSet'
      Expected type: ManageHook
        Actual type: Query ()
    * In the first argument of `spawnAndDo', namely
        `(liftX (withFocused (sendMessage . mergeDir W.focusUp')))'
      In the expression:
        spawnAndDo
          (liftX (withFocused (sendMessage . mergeDir W.focusUp'))) "XTerm"
      In the first argument of `composeAll', namely
        `[resource =? "lxqt" --> doIgnore,
          spawnAndDo
            (liftX (withFocused (sendMessage . mergeDir W.focusUp'))) "XTerm",
          resource =? "lxqt-panel" --> doIgnore]'
    |
158 |     , spawnAndDo (liftX (withFocused (sendMessage . mergeDir W.focusUp')) ) "XTerm"
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

@geekosaur
Copy link
Contributor

geekosaur commented Oct 16, 2020 via email

@copper4eva
Copy link
Author

copper4eva commented Oct 16, 2020

Oh, I see, you're doing this in the wrong place. spawnAndDo replaces the spawn action via keybinding or whatever. If you want to capture any window in the manageHook, you just want the inside of the spawnAndDo. But I'm unclear as to what exactly you're trying to accomplish at this point.

If you're unclear on what I'm trying to accomplish then rereading the OP hopefully makes things clear.

I originally just wanted some way to have newly spawned windows merge into a sublayout. In this case, the sublayout is a tabbed grouping. It would be nice if there was some simple way of doing this, to just have any window become part of the subtabbed automatically.

At the moment, I can't get anything to work. I'm thinking I might finally bite the bullet and learn how haskell actually works, and hopefully in that process I can come up with something. Since it appears no one has an easy solution to getting this to work.

Thanks for the help regardless btw.

@slotThe
Copy link
Member

slotThe commented Jun 19, 2021

@copper4eva any updates; did you figure this out?

@liskin
Copy link
Member

liskin commented Jun 19, 2021

The same problem with manageHooks being run before managing the window was recently encountered in the context of window swallowing: #416 (comment) and a solution that works is to have a handleEventHook that replaces xmonad core handling of the event, calls manage and then performs some additional action (in this case sendMessage (Merge …)). This is implemented here: #562

So what I think we can quite easily do is to add a generic afterManageHook for this. It's totally doable in -contrib, too, just extract https://github.com/xmonad/xmonad/blob/e08ddc9f04c1bd23a5126ef400e9a5b2d6a32541/src/XMonad/Main.hs#L310-L314 into a handleEventHook that invoked the hook afterwards and then returns All False to replace the core handling of the event. See also 4a6f216#diff-d62d3a0de30d0cd68e38b6316053c90d9d99762e6a361526c47ca6f7b87f6737 for how to create custom extensible hooks in -contrib.

@liskin liskin mentioned this issue Jun 19, 2021
4 tasks
@copper4eva
Copy link
Author

@copper4eva any updates; did you figure this out?

I switched to bspwm. So no, I did not. But it looks like from liskin's comment that XMonad has added a feature that would help with his.

So, to update this issue, the OP of that reddit thread came up with something that almost works:
, ("M-<Return>", spawn (terminal c) >> withFocused (sendMessage . mergeDir W.focusUp'))
The issue with this is, for whatever reason, the mergeDir gets run then the new terminal window spawns. Very strange to me that it works like that.

I was wondering if there was a hook module that would help run this. Like there is this module:
https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-InsertPosition.html
for controlling what position a new window starts in. Is there not a module that can run the mergeDir command for every new window? Maybe this module:
https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-ManageHelpers.html

I'm hoping that running this with a hook solves the issue of the mergeDir occurring before the new window spawns (since obviously we want it to happen afterwards).

IIRC the only issue with this was the order of the spawn terminal and mergeDir were random. Obviously you want the terminal to spawn, then to run the merge command. It sounds like from liskin's comment that there's a new function or something that can help control this order of events. Which was implemented for the sake of swallowing, which would make sense. I could see how window swallowing would run into the same issue.

Random rant below, don't have to read it:

I don't have a great understanding of Haskell at all, and now that I've switched off of xmonad I likely never will. But dealing with this issue was quite frustrating. You wouldn't think that something as trivial as the order in which two commands decide to run would be such a problem. This would never be a problem in bspwm. With xmonad, whenever I wanted to do something really out of the box, and there wasn't a convenient contrib for it, it was very difficult to get things done. And sometimes would require more hacky methods, rather than more fundamentally foolproof methods. With bspwm I have never felt limited like this. They just kind of did an amazing job of just giving you the tools you need to do whatever you want in bspwm.

Sorry if that last paragraph comes off as overly negative. It was an attempt at being critically constructive. Again, I don't understand haskell very well, so this is probably more of a me issue. I just feel like something like merging subtabs automatically shouldn't have been such a freaking ordeal. With bspwm, you can subscribe to any event that happens in the window manager. And you can easily control the order at which commands are ran. So for this problem, you would just subscribe to a node being added, then you can do whatever you want to it very easily. bspwm does not have groupings or subtabs though. But you could still do this with the suckless program tabbed. Maybe one day bspwm will add the ability for monocle layout as a child node.

@ohechtl
Copy link

ohechtl commented Dec 16, 2021

Hi, I've also ran into this, but I have no intention moving away from XMonad.
What I'm trying to accomplish is to spawn any kind of window from rofi (dmenu) to a currently focused sublayout with a keyboard shortcut.

here's the shortcut which does not compile currently:
("M-S-p", spawnAndDo (liftX (withFocused (sendMessage $ pullGroup L))) "rofi -show drun"),

compiler tells me this error:

• Couldn't match type ‘()’
                 with ‘Data.Semigroup.Internal.Endo XMonad.Core.WindowSet’
  Expected type: XMonad.Core.ManageHook
    Actual type: XMonad.Core.Query ()
• In the first argument of ‘spawnAndDo’, namely
    ‘(liftX (withFocused (sendMessage $ pullGroup L)))’
  In the expression:
    spawnAndDo
      (liftX (withFocused (sendMessage $ pullGroup L))) "rofi -show drun"
  In the expression:
    ("M-S-p", 
     spawnAndDo
       (liftX (withFocused (sendMessage $ pullGroup L)))
       "rofi -show drun")

Could you help me with it?
What I've understand from the error is that the ManageHook needs an Endo WindowSet rather than an unit (). I'm fairly good with Haskell but I don't know much about the XMonad internals. What should I input here?

@geekosaur
Copy link
Contributor

Try appending >> idHook:

("M-S-p", spawnAndDo (liftX (withFocused (sendMessage $ pullGroup L)) >> idHook) "rofi -show drun"),

But this too has a problem: xmonad won't have focused the window until after this runs. And pullGroup does not use withFocused, it determines the focused window itself — but as I said, at this point it's not focused yet. I am not sure you can do this from a manageHook.

@ohechtl
Copy link

ohechtl commented Dec 16, 2021

Thanks for the quick reply! :)

My fellow XMonader friend said that this could be solved with tracking the last focused window/group in a state, and use that state here to be able to determine which group should be the newly spawned window be merged to. Although I don't really know how could I accomplish this. Do you have any suggestion for this?

@geekosaur
Copy link
Contributor

Well. I know what they're talking about, but I don't know SubLayouts well enough to know how to do it properly. I can say the "state" they're talking about is https://hackage.haskell.org/package/xmonad-contrib-0.17.0/docs/XMonad-Util-ExtensibleState.html; you would define your own ExtensibleState instance to hold the group.

@ohechtl
Copy link

ohechtl commented Dec 16, 2021

Thanks, will look into it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants