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

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

Open
geekosaur opened this issue Oct 8, 2021 · 2 comments
Open

Comments

@geekosaur
Copy link
Contributor

geekosaur commented Oct 8, 2021

[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
Copy link
Contributor Author

geekosaur commented Oct 8, 2021

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…
 

@mikenrafter
Copy link
Contributor

mikenrafter commented Oct 9, 2021

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'!

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

2 participants