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

Idea: Focus Window Manager #124

Open
jamesmunns opened this issue Jul 3, 2023 · 0 comments
Open

Idea: Focus Window Manager #124

jamesmunns opened this issue Jul 3, 2023 · 0 comments
Assignees
Labels
area: kernel Related to the cross-platform kernel kind: enhancement New feature or request

Comments

@jamesmunns
Copy link
Contributor

jamesmunns commented Jul 3, 2023

This is sorta a design document. In terms of vibes, I'm going for a tiling window manager.

The window manager needs to manage a couple of things:

  • Demuxing keyboard input
    • SOME input (like meta commands) are consumed by the wm itself
    • All other input will be passed to whatever the ACTIVE application window is
  • Demuxing N graphical windows
    • Each app has a window - whose size can change over time
    • Only one window can be ACTIVE at any time, but multiple can be displayed
    • Not all windows are displayed at all times

Thinking hierarchically:

  • One display
    • WM part (tabs or menu or whatever)
    • Apps part
      • N Workspaces - each "a full screen" - only drawn when workspace is active
        • M panels - one per app, each less than or equal workspace size
.-----------.
|___________| <- title/tab bar
|           |
|     *     | <- app space (single panel), has focus
'-----------'    This is "one workspace" with "one app"

.-----------.
|___________|
|     |     | <- app space can be subdivided
|  *  |     | <- only one app has focus/is active, but both can
'-----+-----'    render new updates. This is "one workspace" with
                 "two apps"

      .----------- This workspace is active and has one app
      v
.-----------.
|___________|     ___________
|           |    |     |     |
|     *     |    |     |     |
'-----------'    '-----+-----'
                       ^
                       '-------- This workspace is not active,
                                 and has two apps

How I see it working:

  • An app "connects" to the WM by receiving a client
  • The client can do a few things:
    • Receive key events over a "pipe"
    • Send frame updates at a certain size
    • Ask for "meta" updates, like if the window size has changed

I think the best way is to have the apps render into a frame buffer, the size of their window, and send them to the wm service for rendering.

If the window is not displayed, then the wm immediately returns an error, and maybe gives the app some way to await for being displayed again.

The app should probably be able to (optionally?) set what its minimum size is.

If the window is resized, and the app sends the wrong size window, the frame update is rejected, with a note about what the new size is. The app should resize its buffers and layout, and start sending new buffers.

After drawing a frame, the wm should give back the buffer. This way the app can decide how many frame buffers it wants to have - I think one or two is probably enough. Sending a Box<Frame> back and forth between the wm and the app, we can basically avoid reallocs.

Vocab (TODO make sure this is consistent):

  • An app is ACTIVE if it is being drawn
  • An app is FOCUSED if it is ACTIVE and is getting key input
  • An app is INACTIVE if it is not being drawn
 _____________________________________
|        |   Active    | Inactive     |
|--------+-------------+--------------|
|  Focus |  key + draw | impossible   |
| !Focus | !key + draw | !key + !draw |
'--------+-------------+--------------'

Something like:

async fn run() {
    let client = FocusWMService::new();
    let mut buffer = client.alloc_framebuf().await;
    tracing::debug!(buffer.size, "Got a framebuffer");

    // Get the key stream, give it to the "logic" bit of the app
    let key_stream = client.key_stream();

    let state = Arc::new(Mutex::new(MyAppState::new(key_stream, buffer.size))).await;

    // Logic loop
    spawn({
        let state = state.clone();
        async move {
            loop {
                // Do logic updates.
                //
                // NOTE: If we need to do some kind of background updates
                // even when we don't have focus, we should select on this
                // instead, because we won't get any new keys while the focus
                // has been lost. If we are ACTIVE but not FOCUSED, we still
                // need to redraw and update though.
                //
                // We might want some kind of active/focus hint to reduce updates
                // when we are inactive.
                let _ = key_stream.keys_ready().await;
                let guard = state.lock().await;

                // If the window is closed, bail
                if guard.is_closed() {
                    return Ok(());
                }

                // Get all keys
                while let Some(key) = key.try_get() {
                    guard.update(key);
                }
            }
        }
    }}).await

    // Render loop
    loop {
        let guard = state.lock().await;
        guard.render_to(&mut buffer);
        drop(guard);

        // This gives away ownership of the buffer, but we'll get it back.
        // Since the buffer is heap allocated, this is not an expensive
        // copy. We'll have to figure out how to handle this in a not so
        // expensive way for userspace.
        match client.render_frame_wait(buffer) {
            Ok(buffer_back) => {
                // We rendered and got our frame back. Save it off and
                // go around again.
                buffer = buffer_back;
            }
            Err(Hangup) => {
                // Our window has been closed, we're done here.
                let guard = state.lock().await;
                guard.close();
                return;
            }
            Err(Resize) | Err(Inactive) => {
                // Our window has been resized or our focus has been lost,
                // and the wm threw away our buffer. Wait to get a new frame
                // buffer (which might be differently sized), then update the
                // size and go around again.
                //
                // TODO: Should we dealloc the frame on losing "active" status?
                // The downside is we need to realloc every time active changes.
                // The upside is we don't need a framebuf for ALL apps that are
                // not currently active.
                let new_buffer = client.alloc_framebuf().await;
                let guard = state.lock().await;
                guard.resize_to(buffer.size);
                drop(guard);
                buffer = new_buffer;
            }
        }
    }
}
@hawkw hawkw added this to the beepberry computer v0.2 milestone Jul 10, 2023
@hawkw hawkw added kind: enhancement New feature or request area: kernel Related to the cross-platform kernel labels Jul 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: kernel Related to the cross-platform kernel kind: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants