xmonad icon indicating copy to clipboard operation
xmonad copied to clipboard

[WIP PLACEHOLDER] Brainstorming tree-based window/layout handling

Open geekosaur opened this issue 4 years ago • 2 comments

[07 21:37:06] although… I am not sure it's fair to call our layout nesting a hack, unless you straight up call our layouts a hack [07 21:37:21] I mena, the layout is just a function encoded as constructors [07 21:37:49] sublayouts are as fair game as anything else you could do that way [07 21:42:14] I would call our layouts a hack at this point, without remorse [07 21:42:46] especially the message handling which has absolutely no idea what windows the layout actually sees [07 21:43:44] so if you want layout nesting as a layout, you need to store info about the windows/groups somewhere, for any action you need to send messages, and the message handler doesn't have all the info it needs [07 21:43:48] it's a mess [07 21:44:28] what I'd propose is a tree structure with a layout and possibly some extra extensible data at each node [07 21:45:11] I spent some time thinking about this a couple weeks/months ago, but didn't write anything of it down :-( [07 21:45:49] I'm fairly sure I can recall all of that should we ever seriously brainstorm this, though [07 21:46:08] might be a good time for a wip issue then

[08 19:11:33] geekosaur: Speaking of, how would a tree datatype solve this issue? [08 19:11:42] Just give a separate branch for floating windows? [08 19:12:16] not sure, it was liskin's idea not mine. in any case tree datatype is for layouts not windows so I'm not sure it affects it [08 19:12:39] unless there's a distinct floating layout somrewhere instead of just a map of windows to rects [08 19:14:36] no, the tree would actually hold both layouts and windows [08 19:15:13] so we'd have a root node that wouldn't do nothing special except layout all subnodes with the full rect and then stack the resulting windows on top of one another [08 19:15:38] How would it hold layouts? Just as an optional attribute at each branch, holding the layout function? [08 19:15:39] under this node there'd be a floating layout node, and all windows under this node would be in the floating layer, and it'd have its own focus [08 19:15:55] another node under the root would be the "primary" layout, where most windows will go [08 19:16:13] OK, how would having 2 focus' work? [08 19:16:14] again, this node would remember its own focus [08 19:16:51] every node would just remember which of its subnodes has focus [08 19:17:07] thus there'd be a single window having focus [08 19:17:08] Could you pseudo-yaml format this idea? I'm having a hard time understanding the fullness of this concept. [08 19:17:35] I can try [08 19:18:18] liskin: The floating layer, and sublayout layers, both hold a focus, right? [08 19:26:45] yes. and presumably only one of these layers has the focus at any one time [08 19:27:20] at one point dons tried to implement that with multiple StackSets; it didn't go well. but then the StackSet is arguably a large part of the problem [08 19:31:10] https://on.tty-share.com/s/9uow43Ip7P0KXGKfw3zcU1w0RptHCDrbhw_2HoJhXdrfnnHHSOOYDXKnDzldl4lh264/ [08 19:34:40] I plan to collect the discussions here and create a WIP placeholder issue [08 19:34:58] which we can brainstorm on as and when we get time/motivation [08 19:35:23] geekosaur: I'll send you the yaml once I'm finished with it [08 19:35:25] liskin: What of allowing decoration as a separate function per-node. Meaning tabbed would be a core feature, and not something requiring more effort. [08 19:36:31] jakefromstatefar: yes tabbing would fit naturally into this [08 19:36:49] I just added a sample tabbed panes into my pseudo-yaml [08 19:37:05] What if instead of using an int focus value, using a stackset/other holepunched list datatype for the windows? [08 19:37:23] that's an implementation detail mostly [08 19:37:57] for the sizes of structures that xmonad typically deals with, it doesn't really matter which is which [08 19:39:11] What of background setting? [08 19:39:25] Are we going to include a window node for the root window inside the root node? [08 19:40:09] geekosaur: https://x0.at/fh6O.txt is what I have now [08 19:40:56] jakefromstatefar: root window is unmanaged in the X world, so no [08 19:41:40] we don't need to concern ourselves with setting the background [08 19:42:00] ah [08 20:13:38] With this new tree structure, how would floating windows be handled? E.g:

    root
    - some-node
      - floating: [windows]
    - some-other-node
      - floating: [windows]

What of moving floating windows from one node to another? [08 20:14:34] Programmatically, this should be doable - but what of doing this from the userspace level? [08 20:14:39] I haven't really thought about how you'd manage the structure of that tree :-) [08 20:15:11] I think that floating nodes/layers should be separate from other nodes. [08 20:15:46] And the doFloat function would merely send the window information up to the top branch, into the floating layer attribute. [08 20:16:06] they don't really need to be as long as there are predefined actions that correctly move the window across the tree [08 20:16:34] floating a window and tiling it again is already a lossy operation today, usually [08 20:16:50] And, giving floating windows bounding boxes from a sublayout seems challenging. [08 20:16:57] liskin: Yeah, I wasn't too worried about that. [08 20:17:02] in my layer brainstorming I had a separate floating layer which behaved as _NET_WINDOW_STATE_ABOVE (but actually between that layer and the normal one) and some notion of pushing windows between layers to the focus in the other layer [08 20:18:56] Ooh, with this approach you could move windows around by paths. E.g /**/tabbed/. [08 20:19:08] I wonder how practical things like that would be.

geekosaur avatar Oct 08 '21 20:10 geekosaur

In case the pseudo-yaml example goes away:

    "root node":
      layout: sub-simple  # don't subdivide rect, just stack sublayouts' wins on each other
      focus: 0
      children:
        - "floating layer":
            layout: win-floating  # leaf layout, floating windows
            focus: 1
            children:
              - Window 0x1234
              - Window 0x3456
        - "tiled layer":
            layout: win-tall  # leaf layout, tiled
            focus: 0
            children:
              - Window 0xabcd
              - Window 0x1b1b

        # optionally, instead of ^
        - "tiled/tabbed layer":
            layout: sub-tall  # tall as a super-layout
            focus: 0
            children:
              - "tabbed pane":
                  layout: win-tabbed
                  focus: 0
                  children:
                    - Window 0x…
                    - Window 0x…
              - "tabbed pane":
                  layout: win-tabbed
                  focus: 0
                  children:
                    - Window 0x…
                    - Window 0x…
 

geekosaur avatar Oct 08 '21 20:10 geekosaur

My proposal:

Basic explanation, without implementation details:

root branch
	- function to manage ignored windows
	- ignored windows
		notification
		notification
	- event handler functions (see below)
	- focus value
	- layers (cross-workspace bars, and workspaces)
		- drawbuffer (xmobar/other)
		- workspace
		- workspace
			- attributes
				- name of ws
			- float focus value
			- float management function
			- floats
				- window
				- sublayout (branch.general)
				- window
			- focus value
			- layer management function
			- layers
				- drawbuffer (see detailed tab example)
				- window
				- sublayout (branch.general)
					- attributes
						- tabColor
					- focus value
					- layer management function
					- layers
						- drawbuffer (see detailed tab example)
						- window
						- sublayout
						- window
				- window

Detailed explanation with important implementation details:

atlas/key
    - optional constant attribute
    ~ optional mutable  attribute

    > required constant attribute
    < required mutable  attribute

    ) constant

    Y.Restricted.z
        never gets inherited
        for internal use only

Events
    on
        open                                                                     -- when opened / initialized into the tree
        close                                                                    -- when closed / removed from the tree
        move                                                                     -- when moved in the tree - would include floating
    before
        open                                                                     -- when opened / initialized into the tree
        close                                                                    -- when closed / removed from the tree
        move                                                                     -- when moved in the tree - would include floating

-- possibly remove this from the restricted category, if running multiseat
-- becomes feasible on Wayland
Branch.Restricted.Root
    inherit Branch.General.*
    -- redundant, and here for clarity. The move event would have no effect here.
    inherit Events

    ) typeID :: Byte        = IDs.Restricted.root
    < ignoredHandler :: X() = notificationMover
    < ignored [Branch.Window] = [ … ] -- e.g notification windows -- though, see Branch.DrawBuffer for bars

Branch.Workspace
    inherit Branch.General.*
    ) typeID :: Byte        = IDs.Restricted.workspace
    < floatFocus :: Int     = 0
    < floatLayout :: X()    = windowSnapping
    < floats [Branch.*]     = [ … ]

-- would also be utilized by workspaces
-- would allow a trivial workspace reorganization method
Branch.General
    inherit Events
    > typeID :: Byte        = IDs.general                                        -- non-constant for high extensibility
    ~ layout :: X()         = tall                                               -- or tiled, tabbed(full + Branch.DrawBuffer), etc

    < attributes :: {*} = {
        -- accessible to layout, and layers[].layout
        -- maybe??: -- may become impossible to maintain
        --          transparent variable inheritance and scoping
        -- e.g, for informing compositors
        -- if WayXMonad becomes a reality, this will be a core feature
        ~ opacity           = 1.0
        ~ …
    }

    < focus  :: Int         = 0
    < layers :: [Branch.*]  = [ … ]

Branch.Window
    inherit Events
    ) typeID :: Byte        = IDs.Restricted.window
    > id :: Int             = 0x…
    -- servers window properties (currently X11, future? Wayland)
    < attributes :: ?       = xprop
    -- for things like window tagging
    ~ extraAttributes :: ?  = { … }

Branch.DrawBuffer
    inherit Events
    ) typeID :: Byte         = IDs.Restricted.buffer
    < transparent :: Boolean = True                                              -- click-through-able
    > focusable :: Boolean   = False                                             -- unable to hold focus
    > method :: X()          =
        DrawMethods.strut                                                        -- you know
        -- OR
        DrawMethods.top                                                          -- always on top
        -- OR
        DrawMethods.bottom                                                       -- always on bottom
        -- OPTIONALLY
        DrawBuffer.grabWindow (\w -> w.attributes "title" == "e.g")              -- ensures that target window is grabbed and held
            . DrawMethods.strut                                                  -- .top or .bottom would work here too.

    ~ child :: Branch.* = Branch.Window 0x…                                      -- for wrapping bars, mostly
    < attributes :: {*} = { … }                                                  -- see tabs example in example tree

    -- provide an automatic method for use with bars
    < bounds :: Rect = [0, 0, 1920, 16]
    > function :: Branch.DrawBuffer -> X() = tabDeco                             -- gets passed self, highly extensible, other examples: drawChild

ManageHook:
-- would have access to properties of a newly created Branch, including
-- Branch.parent.parent.parent... If so needed.
-- e.g if branch.typeID == window, branch.Events.close = shell 'echo closed window!'

Detailed example tree:

-- in runtime, not in someone's configuration, this is dynamic, of course
-- attributes not shown here, but listed above, can be inferred how you
-- please. Some of the above are examples, others defaults. Use your
-- intuition here, I'm lazy ;).
Branch.Root {
    ignored = [Branch.Window 0x…, Branch.Window 0x…]                             -- 2 notifications are open
    Events.on.close = shell "shutdown-script.sh"                                 -- when session ends, shutdown
    Events.on.open  = shell "startup-apps.sh"                                    -- when session begins, run startup apps
    layers  =
        [ Branch.DrawBuffer {                                                    -- xmobar
            transparent = False                                                  -- still usable
            focusable = False                                                    -- can't hold focus
            method    = grabWindow (\w -> w.attributes
                "title" == "xmobar") . DrawMethods.strut                         -- a strut is desired
            bounds    = [0, 0, ratio 1, auto]                                    -- take up top x pixels of screen, xmobar decides x
            function  = roundCorners . drawChild                                 -- round corners, and draw the child layer
          }
        , Branch.Workspace {
            attributes = { name="example"  }
            floatLayout = snapWindows 5px
            floats  = [Branch.Window 0x…]                                        -- a dialog
            layout  = resizeable . tall
            focus   = 0
            layers  =
            [ Branch.General {
                layout     = tabbed
                attributes = { tabHeight=16 }
                focus      = 2
                layers     =
                [ Branch.DrawBuffer {
                    -- makes space occupied by fading tab bar is still clickable
                    transparent = True                                           -- changes, managed by method()
                    focusable   = False                                          -- can't hold focus
                    method      = DrawMethods.top
                    attributes  = {
                        opacity = 0                                              -- inactive/stagnant
                    }
                    bounds      = [autoCenter, 10, tabWidthDynamic, get tabHeight]
                    function    = fadeWhenStagnant . activeEdge . roundCorners . tabs
                    -- ^ A theoretically beautiful implementation of
                    -- xmonad's tabs. Only shows up when holding mouse
                    -- at very top of window, or when switching between
                    -- windows in this sublayout. It also has rounded
                    -- corners. ;) (this will require a change in
                    -- xmonads framing code)
                  }
                , Branch.Window 0x…
                ]
              }
            -- That was a lot of typing, I'm sure you don't want to be
            -- forced to read it twice. Just know, that you could have a
            -- dual subTabbed / tabbed layoutBuilder setup here. (or any
            -- other sublayout, for that matter)
            , anotherOneOfTheAbove
            ]
          }
        , anotherOneOfTheAbove -- you get it... workspaces, dynamic
        ]
    }

Explanation

  • Root contains all the workspaces, logically. The entire stackset does this now, it's a good idea.
  • Workspaces manage their own floating windows. This does not belong in Root. That would be unmanageable.
    • Floats aren't inside General because: trackFloating - a highly desirable feature.
  • General is what each sublayout will be. It also contains attributes that most other Branch.* children need for functioning. Every branch has a focus. This way, sublayouts with existing layouts like accordion may function properly without strange jumping. Attributes are here because sublayouts may want to have some inter-op, and having semi-persistent DrawBuffer information may be useful.
  • DrawBuffer is used for decoration, struts, and bars. This one's probably the most extensible one here. Many existing features (listed under Why > Features) could be implemented better with this node. Using this, it's also possible to draw bounding boxes independently of any other node. Making this (haven't re-located this feature yet) possible.
  • Window. A window, enough said.

Why do this?

  • The stackset doesn't provide enough flexibility for the layouts we have currently. This has resulted in hacky solutions such as the current implementations of: sublayouts and trackFloating.
    • Liskin and Geekosaur know more on this subject than I. More was discussed in the IRC snippets above.
  • This allows for making things like tabs core features, and not contrib chain-hacks.
  • These features will receive massive improvements in maintainability, reliability and stability:
    • gridSelect
    • treeSelect
    • showWName
    • prompts
    • bars

As a plus, my pseudocode is already pretty close to Haskell.
Meaning, this' suitable as a springboard template to get this thing movin'!

mikenrafter avatar Oct 09 '21 03:10 mikenrafter