Skip to content

Add toplevel geometry tracker protocol. #7

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 267 additions & 0 deletions protocols/shell-foreign-toplevel-geometry-tracker-v1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="shell_foreign_toplevel_geometry_tracker_v1">
<copyright>
Copyright © 2025 outfoxxed

Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.

THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>

<description summary="protocol allowing clients to track foreign toplevel geometry">
This protocol allows clients to track the geometry of toplevels
relative to outputs or workspaces, enabling workspace overviews
and window picking.

This protocol is privileged and should be limited to trusted clients.

The key words "must", "must not", "required", "shall", "shall not",
"should", "should not", "recommended", "may", and "optional" in this
document are to be interpreted as described in IETF RFC 2119.

Warning! The protocol described in this file is intended as a stopgap
and is expected to be superseded by a solution in wayland-protocols.
Clients should not assume this protocol will continue to exist in the
future.
</description>

<interface name="shell_foreign_toplevel_geometry_tracker_manager_v1" version="1">
<description summary="manager for geometry trackers">
This global creates and synchronizes geometry trackers.
</description>

<enum name="error">
<entry name="live_trackers"
summary="destroyed geometry tracker manager while trackers were live"
value="1"/>
</enum>

<request name="destroy" type="destructor">
<description summary="destroy the manager">
Destroy the manager. Destroying the manager prior to
the destruction of all trackers created by it is a protocol
error, as the manager is responsible for synchronization.
</description>
</request>

<event name="done">
<description summary="all geometry tracker events have been sent">
This event signals that all events related to created geometry
trackers and their members have been sent.
</description>
</event>

<request name="get_output_tracker">
<description summary="create a geometry tracker for a given output">
Creates a geometry tracker which tracks toplevels on a given output.
</description>

<arg name="tracker" type="new_id" interface="shell_foreign_toplevel_geometry_tracker_v1"/>
<arg name="output" type="object" interface="wl_output"/>
</request>

<request name="get_workspace_tracker">
<description summary="create a geometry tracker for a given workspace">
Creates a geometry tracker which tracks toplevels on a given workspace.
</description>

<arg name="tracker" type="new_id" interface="shell_foreign_toplevel_geometry_tracker_v1"/>
<arg name="workspace" type="object" interface="ext_workspace_handle_v1"/>
</request>
Comment on lines +80 to +87
Copy link
Member Author

Choose a reason for hiding this comment

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

@vaxerski thoughts on splitting this apart similarly to the image capture protocol? I'm not sure what sources other than a workspace or output would make sense but it might make sense to at least make workspace support optional.

Copy link

Choose a reason for hiding this comment

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

meh, I don't see any other possible tracker, so I don't really care

</interface>

<interface name="shell_foreign_toplevel_geometry_tracker_v1" version="1">
<description summary="tracks relative geometry of intersecting objects">
A geometry tracker tracks the relative geometry of toplevels
intersecting with an output or workspace.
Comment on lines +92 to +93
Copy link

Choose a reason for hiding this comment

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

So this is "intersecting", as in a window visible on two outputs shows up on both trackers? As opposed to only showing up on a single tracker for this window's "main" output (i.e. one that it receives scale or frame callbacks for). Would be good to clarify

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. The primary output and workspace can be determined via existing toplevel and workspace protocols if the compositor wishes to send only one, but any toplevel even partially on the workspace should be listed.


Upon creation, an 'area' event must be sent.
</description>

<enum name="error">
<entry name="reset_toplevel_list"
summary="attempted to assign a toplevel list to the tracker multiple times"
value="1"/>
<entry name="destroyed_toplevel_list"
summary="toplevel list used in a geometry tracker was destroyed"
value="2"/>
</enum>

<request name="destroy" type="destructor">
<description summary="destroy the tracker">
Destroy the tracker. All tracker members must receive a
finished event following this request.
</description>
</request>

<event name="area">
<description summary="reports the geometry of the tracked area">
Comment on lines +114 to +115
Copy link

Choose a reason for hiding this comment

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

I would rename this to size maybe, because "area" reads like it has x and y coordinates

This event reports the geometry of the tracked area.

Reported width and height should match the dimensions of the
tracked area in logical pixels.
Comment on lines +118 to +119
Copy link

Choose a reason for hiding this comment

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

What does this mean? Isn't this the tracked area? Or this is some other area (which?) that matches the tracked area?

Copy link
Member Author

Choose a reason for hiding this comment

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

It was a couple different things during editing. Must've forgotten to clean that up.


This event must be sent upon creation of the tracker, and
whenever the tracked area changes. This event is double-buffered
and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="width" type="int"/>
<arg name="height" type="int"/>
Comment on lines +127 to +128
Copy link

Choose a reason for hiding this comment

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

With fractional scale the logical size isn't necessarily an int. While there's currently no good way to request a fractional size for e.g. fullscreen windows or anchored layer-shell elements or the lock surface, at least in niri the area is tracked in fractional logical pixels for the purpose of toplevel placement and such. So might be good to have some forward compatibility here when fractional sizes become more widely representable?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was probably thinking in physical pixels when I wrote it, but logical pixels make more sense if the workspace is on a single monitor.

This does highlight the issue of a single workspace spanning multiple monitors with mismatched geometry though. I'll have to revise that.

To account for workspaces spanning multiple monitors with potentially different DPIs or scaling ratios, should the compositor just pick some number that makes the math work out for relative positioning or something else you think?

Copy link

Choose a reason for hiding this comment

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

Not sure about others, niri layout uses fractional logical coords and sizes everywhere and it seems to work alright (though, niri also doesn't do workspaces spanning several monitors). I expect that others either do the same or round sizes to logical ints

Copy link
Member Author

Choose a reason for hiding this comment

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

Niri and Hyprland don't care about this since workspaces cant be on more than one monitor at once, but its likely a concern for KDE and such.

</event>

<event name="margins">
<description summary="reports insets into the tracked area">
This event reports margins into the tracked area where toplevels
may not normally be placed, such as exclusive zones of layer
surfaces. Clients may use this information to avoid showing
gaps in visual representations of the tracked area.
Comment on lines +133 to +136
Copy link

Choose a reason for hiding this comment

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

In niri (and other tiling WMs too) there are separate margins for tiled windows and for floating windows, i.e. floating windows can go to the edges of the screen, while tiled windows can have extra "outer gaps". I'm guessing these margins should not include these tiling-only "outer gaps"? Would be good to clarify

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be specifying outer gaps, as you can move floating windows over layer surfaces as well as the gap region. For compositors that don't let you move windows over an exclusive layer, I'm thinking that region shouldn't be included in the tracked size.


Reported margins must be in the same coordinate space as
those reported by 'area'.

This event may be sent upon creation of the tracker, and
whenever insets change. This event is double-buffered
and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="left" type="int"/>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
Comment on lines +147 to +150
Copy link

Choose a reason for hiding this comment

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

Same here, not necessarily ints, can be fractional logical.

</event>

<event name="entered">
<description name="an entity has entered the tracker area">
This event reports that a toplevel has entered the tracking
area. Untracked entities must not receive 'entered' events.
</description>

<arg name="member" type="new_id" interface="shell_foreign_toplevel_geometry_tracker_member_v1"/>
</evnt>

<request name="track_toplevels">
<description summary="begins tracking toplevels">
Copy link

Choose a reason for hiding this comment

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

Question: should this include toplevels currently being interactively dragged with the cursor? I suppose this should be up to compositor policy; in niri even if dragged floating windows should be included, I would probably still exclude dragged tiled windows.

Copy link
Member Author

Choose a reason for hiding this comment

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

Most clients I can think of would prefer a stream of events as position changes, matching how the toplevels are visible to the user. E.g. animations switching window positions and mouse drags should be reported as they happen, not as a single reposition at the end.

This request asks the compositor to track all toplevels created by the
given toplevel list that enter the tracking area.

Upon request, all toplevels that qualify as members of the tracker
must be provided via an 'enter' event.

Attempting to set the toplevel list more than once is an error, as
is destroying the toplevel list prior to the destruction of
this object.
</description>

<arg name="list" type="object" interface="ext_foreign_toplevel_list_v1"/>
</request>
</interface>

<interface name="shell_foreign_toplevel_geometry_tracker_member_v1" version="1">
<description summary="tracked entity visible on a geometry tracker">
A toplevel tracked by a geometry tracker.

Upon creation, toplevel, geometry, and optionally stacking order
events must be sent by the compositor.
</description>

<request name="destroy" type="destructor">
<description summary="destroy the member">
Destroys the tracker member object.
</description>
</request>

<event name="left">
<description summary="the toplevel has left the tracked area">
The toplevel has left the tracked area. A toplevel must not be
considered to have left until the toplevel's geometry has no
intersection with the geometry tracker.
Comment on lines +195 to +197
Copy link

Choose a reason for hiding this comment

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

In niri, windows can scroll outside a workspace, but they for all intents and purposes remain on that workspace. How should it behave wrt. this entered and left event? Would be good to clarify

Copy link
Member Author

Choose a reason for hiding this comment

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

I was thinking the entire workspace strip should be considered to be the workspace, including parts that are currently out of view.

Copy link

Choose a reason for hiding this comment

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

What about minimized (on a workspace) or otherwise hidden (in niri's case, unfocused in a tabbed column) windows?

Copy link
Member Author

Choose a reason for hiding this comment

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

Minimized windows should not be included in the workspace.

Tabs I'm unsure of. We could either stack the windows directly on top of eachother in position events or not send them at all. I'm leaning towards stacking them.


Following this event, the member object should be destroyed and no more
events will be sent to it. If the toplevel later re-enters the tracked
area it will do so as a new object.

This event is double-buffered and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>
</event>

<event name="geometry">
<description summary="geometry of the toplevel has changed">
New toplevel geometry information is available.
This event is sent upon creation of the member object or changes
to the toplevel's geometry.

Compositor policy dictates which window decorations are included
in the toplevel's geometry, but excluding non-window extents
such as shadows is recommended.

This event is double-buffered and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="x" type="int"/>
<arg name="y" type="int"/>
Comment on lines +222 to +223
Copy link

Choose a reason for hiding this comment

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

These definitely shouldn't be ints, there's nothing in Wayland that restricts windows to integer logical placement

<arg name="width" type="int"/>
<arg name="height" type="int"/>
Comment on lines +224 to +225
Copy link

Choose a reason for hiding this comment

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

While window geometry can only be integer logical currently due to Wayland, if you include compositor decorations, then it easily goes into fractional logical size

</event>

<event name="above">
<description summary="the toplevel is now above another">
This event reports that the toplevel is now directly above the
given sibling in Z-order.

This event is double-buffered and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="sibling" type="object" interface="shell_foreign_toplevel_geometry_tracker_member_v1"/>
</event>

<event name="below">
<description summary="the toplevel is now below another">
This event reports that the toplevel is now directly below the
given sibling in Z-order.

This event is double-buffered and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="sibling" type="object" interface="shell_foreign_toplevel_geometry_tracker_member_v1"/>
</event>
Comment on lines +228 to +250
Copy link

Choose a reason for hiding this comment

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

I guess there can't be cycles between these two? Also, if the compositor restacks windows, should it send all changed "above" and "below" for all tracked objects? I.e. if you click on a bottommost window and it goes all the way to the top, should it send a ton of "above" requests (one for each other visible window), plus a ton of "below" requests? Sounds a bit spammy

Copy link
Member Author

Choose a reason for hiding this comment

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

Only a single one. Placing a toplevel above the topmost current one would implicitly require it to be above all the others as well. As such no more than one above/below should be sent per reposition.

Copy link

Choose a reason for hiding this comment

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

So if the toplevel is placed in the middle, only a single "above" (for the window immediately below) or "below" (for the window immediately above) suffices?

Copy link

Choose a reason for hiding this comment

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

Also, what about tiled windows, which also technically have an above/below ordering, but for most intents and purposes it doesn't matter (since the geometries don't overlap), so the compositor can reorder them very frequently (it's convenient in the code, and it technically does show up visually in some ways, but probably not important enough to spam the protocol)?

Copy link
Member Author

Choose a reason for hiding this comment

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

So if the toplevel is placed in the middle, only a single "above" (for the window immediately below) or "below" (for the window immediately above) suffices?

Yes. Which one is sent is up to the compositor.

Also, what about tiled windows, which also technically have an above/below ordering, but for most intents and purposes it doesn't matter (since the geometries don't overlap)

It shouldn't usually matter yeah. I think it makes sense to include it in case a client wants to make visual effects based on it though. Best to leave it open to that sort of thing.

so the compositor can reorder them very frequently (it's convenient in the code, and it technically does show up visually in some ways, but probably not important enough to spam the protocol)?

I'm personally not very concerned about protocol spam on events that are triggered by user input, especially relatively infrequent ones. Wayland effectively batches events so if you're sending one (geometry changed) you might as well send more (stacking order changed).


<event name="toplevel">
<description summary="this member has been marked as a toplevel">
This event sets the toplevel the tracker member refers to. This
event can only be sent once, prior to the first 'done' event
following the creation of the member object.

A 'left' event must be sent upon closure of the toplevel.

This event is double-buffered and must be followed by a
'shell_foreign_toplevel_geometry_tracker_manager_v1.done' event.
</description>

<arg name="toplevel" type="object" interface="ext_foreign_toplevel_handle_v1"/>
</event>
</interface>
</protocol>