raboof / notion

Tiling tabbed window manager
https://notionwm.net/
GNU Lesser General Public License v2.1
268 stars 63 forks source link

Merging workspaces in mod_xrandr #14

Open edmcman opened 9 years ago

edmcman commented 9 years ago

I normally run notion with my laptop in a dock that is connected to two external monitors. When I disconnect my laptop from the dock and use the built-in laptop monitor, Notion is smart enough to put all my workspaces onto the single screen. When I have two screens, though, I usually use Mod1+1..0 to switch between workspaces. When the screens are merged, Mod1+1...0 is mapped to the workspaces from one of the screens. The workspaces from the other screen is still accessible, for instance via Mod1+>, Mod1+<, or Mod1+K K, but it makes them much more difficult to access.

Is it possible to merge the workspaces of each screen? For instance, if I had window A in workspace 1 on screen 1, and window B in workspace 1 on screen 2, when I switch to one screen I'd like to have window A and B in workspace 1.

raboof commented 7 years ago

There might be 2 issues here: when there were 4 workspaces on each screen before merging, after merging Mod1+1..8 should be mapped to those 8 workspaces. Is that not the case? Then that could arguably be a bug.

The "merging" behaviour you describe is probably possible (though not particularly easy) to achieve using lua configuration. Take a look at the default https://github.com/raboof/notion/blob/master/mod_xrandr/cfg_xrandr.lua#L236 . What this does is at the bottom:

if randr_screen_change_notify_hook then
    randr_screen_change_notify_hook:add(mod_xrandr.screenlayoutupdated)
end

This makes sure when a monitor is connected/disconnected, mod_xrandr.screenlayoutupdated is called. You're free to either replace this configuration entirely with your own implementation, or leave it in place but remove this hook and replace it with your own code (http://notion.sourceforge.net/notionconf/node6.html#SECTION00620000000000000000).

If you get this working then this would certainly be something worth sharing with the community!

edmcman commented 7 years ago

@raboof, the workspaces behave as you describe, so this seems more like a feature request than a bug.

I'll see if I can change the screen layout update hook to do what I want.

edmcman commented 7 years ago

This ended up being more complicated than I thought.

I was able to move all the managed regions of the source workspace (let's say the 2nd workspace on screen 2) to the same workspace of the destination screen. But since normally these managed regions are WTilings, the result is that there are two full-screen tile regions assigned to the same workspace. I can still switch between them by using Mod+K K, but this was definitely not what I wanted, and IMHO worse than the original behavior.

All this made me realize I'm not completely sure how I want workspaces to be merged. After some thought, the following two options seemed to make sense to me for WTilings:

  1. If we are merging one WTiling into an existing one, just split the existing one, put the original content on the left/top and the new content on the right/bottom.
  2. If we are merging one WTIling into an existing one, attach the new tile as a different "tab". I know visually how this would look, but I'm unsure how this is implemented in notion. Maybe a WTiling can multiplex too.

It's unclear to me right now what other types of regions can be managed by a workspace, and thus that might need to be merged. Do you have any idea? Is there an easy way to view the current object management structure in notion? I'm doing it in lua right now.

raboof commented 7 years ago

@edmcman yeah, lua is probably the way to go... notionflux makes that sometimes a bit easier to use, but isn't super convenient either ;)

wilhelmy commented 4 years ago

In case you're still interested 4 years later, I've done my best to improve notionflux usability in the meantime and the new-and-improved one which has a REPL and tab completion is part of the 4.0 release (go get it while it's hot).

edmcman commented 4 years ago

I'll give it a shot. At the very least, it's probably a good excuse to try out notion 4.0

edmcman commented 4 years ago

@wilhelmy Is there any way to dump a tree of the object hierarchy? Or any examples of how to iterate it?

wilhelmy commented 4 years ago

Iterating depends on what you're starting from, I started writing a function library https://gist.github.com/wilhelmy/07ef81bea6d1a8f78bfa1c6d0cf34e9f#file-repl-lua (in case you have more useful functions to add there, I'm curious whether or not gist supports PRs ;)

lua> repl.inspect_object(ioncore.current())
+ WRootWin: 0x240cc68
 + WScreen: 0x25416a8
  + WGroupWS: 0x23fee98
   + WTiling: 0x2537c78
    + WFrame: 0x2555b78
     + WGroupCW: 0x2561ef8
      + WClientWin: 0x255a438
nil

which iteratively calls obj:manager() until nil is returned. There's also :parent() as well as .__parentclass, for which there aren't functions yet).

The other function can be used as follows: repl.inspect_table(repl) (or os, _G and so forth). I have this aliased to ls locally because I use it often, also in conjunction with getmetatable, i.e. ls(getmetatable(ioncore.current()) or ls(getmetatable(obj).__index).

lua> repl.inspect_table(repl)
inspect_table   function: 0x255daf0
inspect_object  function: 0x255efb0

As for iterating over the entities managed by a workspace or frame, that's done using :mx_i(function), which I suppose described here: http://notion.sourceforge.net/notionconf/node7.html#SECTION00718000000000000000 and it seems to be the same function for any kind of object that has it (but I didn't go poke around to figure out where it gets inherited from)

lua> WFrame.mx_i
function: 0x2324510
lua> WMPlex.mx_i == WFrame.mx_i
true
wilhelmy commented 4 years ago

Hmm, it seems there's also managed_i() in at least WFrame, WTiling and WGroupCW. Not sure which class that's inherited from either.

And a for_all_workspaces_do function exists as well, defined in cfg_xrandr.lua

lua> for_all_workspaces_do(repl.inspect_object)
+ WRootWin: 0x240cc68
 + WScreen: 0x240db68
  + WGroupWS: 0x23ea4d8

+ WRootWin: 0x240cc68
 + WScreen: 0x240db68
  + WGroupWS: 0x23f0278

+ WRootWin: 0x240cc68
 + WScreen: 0x240db68
  + WGroupWS: 0x23f9818

+ WRootWin: 0x240cc68
 + WScreen: 0x240db68
  + WGroupWS: 0x24137c8

+ WRootWin: 0x240cc68
 + WScreen: 0x25416a8
  + WGroupWS: 0x2542868

+ WRootWin: 0x240cc68
 + WScreen: 0x25416a8
  + WGroupWS: 0x23fee98

+ WRootWin: 0x240cc68
 + WScreen: 0x25416a8
  + WGroupWS: 0x2407258

nil
edmcman commented 4 years ago

Thanks @wilhelmy! This is a huge help.

edmcman commented 4 years ago

Alright, I finally made some progress on this! This code iterates over workspaces and builds a table that maps from the workspace number to the list of workspaces with that number.

https://gist.github.com/edmcman/b31e8dcea0515d5bb4859412d6dfe8d5

I am testing (for now) on a machine with only one screen, but here's what it outputs:

lua> build_map()
1
WGroupWS: 0x55e9a58121f8
2
WGroupWS: 0x55e9a58137b8
3
WGroupWS: 0x55e9a58149b8
4
WGroupWS: 0x55e9a5815798
5
WGroupWS: 0x55e9a5816588
6
WGroupWS: 0x55e9a5817718
0
WGroupWS: 0x55e9a5811058
-1
WGroupWS: 0x55e9a5818408
nil

The last workspace with index -1 is the scratchpad.

So now I just need to modify move_if_needed in cfg_xrandr.lua. Say we're moving workspace n to a new screen. If the destination screen doesn't have a workspace n, then we can hopefully move the workspace to the new screen and assign it number n. If a workspace n already exists, then rather than attaching the workspace to the new screen, I'll attach the content of the workspace to the workspace n on the target screen. Hopefully I can use the existing code on move_if_needed that does this for the scratchpad.

edmcman commented 4 years ago

I think I'm very close to having this working:

https://gist.github.com/edmcman/b31e8dcea0515d5bb4859412d6dfe8d5#file-cfg_xrandr-lua

The roadblock at the moment is that for normal workspaces the child is a WTiling, which does not have an attach method. In my head, I was thinking that we could have a WFrame multiplex between the WTilings from different screens, like this:

Workspace
  WFrame
    WTiling (original)
      WFrame
      WFrame
    WTiling (removed screen n)
      WFrame

But I'm not sure if this is possible or makes sense, since normally only a single WFrame is on the screen at a time.

Brainstorming a few solutions:

wilhelmy commented 4 years ago

I'm not sure whether I have valuable input here... @raboof, are you aware of any reason why WTiling doesn't have a :attach()? Maybe it should have one?

raboof commented 4 years ago

I'm not sure having multiple WTiling's on one screen makes sense - how would that work for the user?

I think moving all windows to one existing tile might be a good first step. Later perhaps we could distribute windows to be placed in a 'similar place' than where they came from?

(to be clear. I think the current behavior is probably still the best default, but I can see the appeal of having the behavior you describe available as an alternative)

edmcman commented 4 years ago

I'm not sure having multiple WTiling's on one screen makes sense - how would that work for the user?

I wasn't strongly advocating for this. It was just how I imagined things working before I thought about it too much.

I think moving all windows to one existing tile might be a good first step. Later perhaps we could distribute windows to be placed in a 'similar place' than where they came from?

Yes, this seems reasonable. I'll see if I can figure out how to do this. I guess I need to find a WFrame or WGroupCW and attach to them?

(to be clear. I think the current behavior is probably still the best default, but I can see the appeal of having the behavior you describe available as an alternative)

I have some "standard" workspace mappings that I use. For instance, workspace 1 on one screen is usually my chat and email. So when I remove a screen, I still want those windows to be on workspace 1 so I can find them easily.

The most problematic case is when I have more than 5 workspaces on each screen. Then when they are merged using the default behavior, I can't access them all via keyboard shortcuts. I can still find them by cycling through, but it is really annoying.

raboof commented 4 years ago

Yes, this seems reasonable. I'll see if I can figure out how to do this. I guess I need to find a WFrame or WGroupCW and attach to them?

Probably, this will probably take some experimentation :)

For instance, workspace 1 on one screen is usually my chat and email. So when I remove a screen, I still want those windows to be on workspace 1 so I can find them easily.

Yes, that makes a ton of sense. I guess the current behavior will do that 'if you are lucky'. It would be neat to have this more 'reliable', but it's not so easy to see how that should work. X11 has the concept of a 'primary window', but I guess it's not always the 'primary' screen that you'd like to keep 'stable'.

The most problematic case is when I have more than 5 workspaces on each screen. Then when they are merged using the default behavior, I can't access them all via keyboard shortcuts. I can still find them by cycling through, but it is really annoying.

Yes, I can see that ;).

edmcman commented 4 years ago

I've made a little progress. First, by accident, I was able to achieve nested frames/tiles.

notion-weird

You can see that I apparently moved a bunch of empty workspaces into a WFrame, which wasn't quite what I was trying to do. But it did seem to work OK.

Perhaps more interestingly, I realized why I didn't like the default behavior of notion in the first place. When switching to a single screen, a bunch of empty workspaces from the first screen pushed the workspaces from the second screen so that they weren't accessible by numbered keys anymore. So perhaps the easiest solution would be to move empty workspaces to the end of the workspace list so that non-empty workspaces get first dibs on numbered positions. This seems a lot less complicated and would make things usable for me.