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) niri-ipc: Include window positions within the workspace columns in Window state and output #1265

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

yrkv
Copy link

@yrkv yrkv commented Mar 14, 2025

This is a WIP draft to extend the IPC and the behavior of niri msg -j windows to support listing where each window is in terms of column/row indices, as well as (client-side) compute the geometry of all windows. This allows for development of much more interactive/useful status bars for niri.

  • Add three new fields into niri-ipc::Window:
    • tile_coordinates -- Location of the window's tile within a workspace in terms of indices.
    • tile_size -- Size of the tile.
    • geometry -- [x,y,width,height] of the tile relative to the workspace's top-left corner, in pixels.
  • Add two new window events into the event stream to maintain the above state:
    • One for when a collection of tiles/windows are resized at once.
    • Another for when a collection of tiles/windows are shifted over by some number of columns.

Current limitations of this draft:
As of yet, this does not expose pos/size for both tile and window, only for the tile.
Additionally, there is no consideration of tabbed windows yet.

Discussion of the geometry field:
(edit: agreement has been reached to not include it, don't bother reading this paragraph)
Window state geometry is never actually set on the niri server side, it's computed and filled in client-side for niri msg -j windows. This is pretty awkward, but it seems better IMO than leaving it to every single user to implement, especially since many uses of this will be in environments such as shell scripts where doing the necessary math would suck. It might be better to make a new niri msg window-geometries request or something for this in particular, and remove it from Window state. It's difficult to actually set the window geometry in the server because it would require spamming the event stream a bit (but it's not a completely unreasonable amount, so this would also be feasible).

@yrkv
Copy link
Author

yrkv commented Mar 14, 2025

2025-03-13 13-06-54

Also, here's a gif of a prototype eww widget that uses this functionality, to demonstrate what it can be used for.

Comment on lines 1007 to 1011
/// Location of the tile relative to the top-left corner of the workspace scrollbox itself.
/// [x,y,width,height].
///
/// This is NOT set within niri state, but gets computed client-side within `niri msg windows`
pub geometry: Option<[f64; 4]>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be included; the clients can compute on their own. We can provide helpers and docs. (Bars will want to connect to the socket directly rather than niri msg event-stream anyway)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with putting that in helpers, so I removed it completely 👍

/// Location of the window within a workspace in terms of (column index, tile index in column).
///
/// Unset for floating windows.
pub tile_coordinates: Option<(usize, usize)>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub tile_coordinates: Option<(usize, usize)>,
pub tile_pos_in_scrolling_layout: Option<(usize, usize)>,

Idk? Kind of a mouthful still but I want to communicate this.

pub tile_coordinates: Option<(usize, usize)>,
/// Size of the tile this window is in, if applicable.
///
/// Unset for floating windows and for windows being dragged with a mouse.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any problems with setting this for those windows?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't the most convenient at the time but I've since added it.

Comment on lines 1176 to 1179
BatchResizeTiles {
/// Pairs consisting of an id and the new size of each window.
id_size_pairs: Vec<(u64, (f64, f64))>,
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WindowPositionsAndSizesChanged {
    changes: Vec<(u64, PositionAndSize)>,
}

I think like this, and put all position and size related properties into a new struct, maybe? Also inline that struct into Window instead of individual fields. What to call it though? Geometry comes to mind, but it's also a Wayland-specific different concept so...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked this way of organizing it, it cleans it up quite a bit. I went with WindowLocation for the name of the struct that contains position/size information, so WindowsLocationsChanged is the event name. If you prefer your original suggestion I don't have any strong preference

///
/// NOTE: Do not emit this in conjunction with setting the value any other way,
/// as it could result in an invalid column value if applied on top of an updated value.
BatchShiftColumns {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed with change like above

/// Size of the tile this window is in, if applicable.
///
/// Unset for floating windows and for windows being dragged with a mouse.
pub tile_size: Option<(f64, f64)>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then:

  • tile_pos_in_workspace_view: Option<(f64, f64)> set for floating windows ("workspace view" is also used for gradients relative-to in the config)
  • window_pos_in_workspace_view: Option<(f64, f64)> same but windows themselves (can omit for now)
  • window_size: Option<(i32, i32)> on Wayland windows themselves can only be integer sized

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the rest of the changes to the Window location state once everything else is confirmed

@YaLTeR
Copy link
Owner

YaLTeR commented Mar 16, 2025

also gonna cc @calops for feedback on this

Copy link
Owner

@YaLTeR YaLTeR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this overall structure looks good. Let's add the other stuff and see how it looks.

Comment on lines +137 to +139
let Some(w) = self.windows.get_mut(&id) else {
continue; // should never be reached
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should just .expect() it since niri should emit events properly.

@@ -1038,6 +1038,20 @@ pub struct Window {
///
/// If the window isn't floating then it is in the tiling layout.
pub is_floating: bool,
/// Position and size related properties of the Window.
pub location: WindowLocation,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah as much as I have concerns about Geometry, I think it works better than Location for this. Let's go with Geometry

Copy link
Author

@yrkv yrkv Mar 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sgtm.

In case you want to see the other options I considered:
I spent a while perusing synonyms, so here's all the ones I thought were viable: WindowLocation, WindowGeometry, WindowArea, WindowLayoutInfo, WindowArrangement. Could also add "Info" to location or geometry to indicate its purpose clearer, WindowLocationInfo or WindowGeometryInfo.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, out of these WindowLayout also sounds good (I'd like to avoid a third word). Not sure which one I like more been Geometry and Layout

// Gather position/size information ahead of time, since we have to traverse
// the entire layout to get tile cell positions.
let mut window_locations = HashMap::new();
for (_, _, ws) in layout.workspaces() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be a separate loop from the one below. This function is called every single time niri does anything, so two loops instead of one is too costly. Perhaps convert the loop below to go over workspaces() instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be best to make a variant of layout.with_windows that provides the window/tile geometry information needed? Alternatively, it could just be added into with_windows...

I mostly implemented it that way to minimize changes to anything which may affect anything else. I don't immediately see that two loops over the windows is definitely too costly without seeing some benchmark showing it, but I agree it should be merged into one regardless for code quality reasons.

Copy link
Author

@yrkv yrkv Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to get some more discussion on this, I'm seeing two reasonable ways to do this and I don't quite like either, so I'd like to hear what you prefer.

  1. Something similar to Layout::with_windows, but it provides the WindowLayout to the provided closure too.
  2. Replace the layout.with_windows call with a loop over workspaces as you mentioned. The catch is that we also have to consider the ongoing interactive move. The nicest way to do so that I can see so far is to make a closure same as before, but just use it locally and not pass it to layout.with_windows. I'm not the biggest fan of this pattern, but it should work fine.

I'm currently leaning towards (2), and to handle the ongoing interactive move I'm restructuring it like so:

let mut f = |mapped: &Mapped,
             window_layout: WindowLayout,
             ws_id: Option<WorkspaceId>| {
    // OMITTED: essentially the same closure that was previously given to `layout.with_windows`
};

if let Some(tile) = layout.current_interactive_move_tile() {
    f(tile.window(), WindowLayout {/* omitted */}, None);
}
for (_, _, ws) in layout.workspaces() {
    for (tile, cell) in ws.tiles_with_workspace_positions() {
        f(tile.window(), WindowLayout {/* omitted */}, Some(ws.id()));
    }
}

Basically, my question is if you want that bit of structure to be in State::ipc_refresh_windows or somewhere in Layout. (i.e. make a new fn Layout::for_each_window_with_window_layout or something like that)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think Layout::for_each_window_with_window_layout would make sense. The structure will need to be in niri-ipc either way, but maybe Layout will need its own copy (or maybe not).

If it doesn't add much overhead to fetch the layout, then perhaps just add it to with_windows() directly?

kassick added a commit to kassick/niri-wselector that referenced this pull request Apr 7, 2025
Use the location field introduced by YaLTeR/niri#1265, if available. Windows are placed in the tiling order, with the focused windowd placed last
@druskus20
Copy link

I was able to use this PR to make a workspace / column widget - thanks for the work!

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

Successfully merging this pull request may close these issues.

3 participants