Skip to content

x11: use the full screen size for the screen frame, not _NET_WORKAREA - #217

Open
probonopd wants to merge 1 commit into
gnustep:masterfrom
probonopd:fix/screen-frame-not-workarea
Open

x11: use the full screen size for the screen frame, not _NET_WORKAREA#217
probonopd wants to merge 1 commit into
gnustep:masterfrom
probonopd:fix/screen-frame-not-workarea

Conversation

@probonopd

@probonopd probonopd commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The X11 backend used _NET_WORKAREA as the monitor/screen frame on single-monitor setups. But the window Y-flip in _OSFrameToXFrame is done against the full screen height, so when the work area is shorter than the screen (because panels/struts reserve part of it) every window positioned at the top of the screen ends up shifted down by that difference.

A concrete symptom: a full-width menu bar / top panel requested at y = 0 lands several pixels below the upper screen border. (I ran into this when I made an AppImage for Gershwin Desktop's Menu.app and ran this on a Ubuntu 22.04 KDE neon 6.0 Live ISO. With the patch applied, it works correctly.)

Suggested change: A screen frame in the AppKit/OpenStep model is the whole display; the usable area left after panels and struts is a separate concept applications query on their own. This drops the _NET_WORKAREA override so the screen frame uses the full output size, consistent with the Y-flip in _OSFrameToXFrame, and removes the now-unused _workAreas helper.

References:

cc @pkgdemon

The X11 backend used _NET_WORKAREA as the monitor/screen frame on
single-monitor setups.  But the window Y-flip in _OSFrameToXFrame is done
against the full screen height, so when the work area is shorter than the
screen (panels/struts reserving part of it) every window positioned at the
top of the screen ends up shifted down by that difference.

A screen frame in the AppKit/OpenStep model is the whole display; the
usable area after panels and struts is a separate concept applications
query on their own.  Drop the _NET_WORKAREA override so the screen frame
uses the full output size, consistent with the Y-flip, and remove the now
unused _workAreas helper.
@probonopd
probonopd requested a review from fredkiefer as a code owner August 2, 2026 20:03
@fredkiefer
fredkiefer requested a review from rfm August 2, 2026 20:19
@fredkiefer

Copy link
Copy Markdown
Member

@rfm added this code few months ago, so he should have the say on whether we drop this again. Either way is fine with me.

@rfm

rfm commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

As I recall, the current code was written to make the tops of menus available so allow them to be moved and the first menu item selected reliably, and achieved that objective. Before the last lot of changes, the top part of our menus would be hidden behind the top bar controlled by many window managers. However, it's hardly surprising if there are bugs: it was a quick/simple fix.

The problem I have here is with this sentence:

A screen frame in the AppKit/OpenStep model is the whole display; the usable area left after panels
and struts is a separate concept applications query on their own.

The current model is precisely because my understanding was that an OpenStep/AppKit display is the usable display, because apps expect to be able to draw anywhere on screen, and that when OpenStep was designed for display postscript, there were no window managers, no struts, and no difference between the physical screen and the usable screen.

I'm happy to be educated otherwise, but what are the AppKit/gnustep-gui methods used by apps to to query panels and struts, and where can I see examples of Apple and GNUstep app source using those methods to position windows/menus?

@probonopd

Copy link
Copy Markdown
Contributor Author

Hi @rfm, thanks for your quick response. Obviously my experience with GNUstep is not as deep as yours, but here are my thoughts:

AppKit distinguishes between the physical display (NSScreen.frame) and the usable display area (NSScreen.visibleFrame). Both Apple and GNUstep implement this - GNUstep's visibleFrame is in NSScreen.m and subtracts the menu-bar height for the relevant interface style. So the "panels and struts" concept does have a dedicated query path (visibleFrame); frame is explicitly the full screen.

I agree the current work-area code was a reasonable quick fix to keep menus reachable below the WM's top bar. My concern is narrower: it is internally inconsistent. The screen frame (monitors[].frame, what NSScreen reports) is set from _NET_WORKAREA, but the Y-flip when placing windows (_OSFrameToXFrame) converts against xScreenSize.height, the full screen height. When those differ, every window positioned at the top of the (NSScreen) coordinate space is shifted down by the difference - which is why a full-width top bar requested at y = 0 lands below the upper border.

That inconsistency bites either way:

  • With the current code, an app that wants to sit at the true top of the display (a menu bar / panel) cannot reach y = 0 at all; it is pushed down by fullScreen - workArea.
  • With the frame set to the full output (this patch), the coordinate spaces agree, and apps that want to avoid the WM's reserved area use visibleFrame / the workarea themselves - which is the AppKit model.

I'd suggest we fix the consistency (whichever direction) rather than leave the flip and the frame disagreeing. The alternative direction (keep the work-area frame but flip against the same height) has the same side effect of moving top-positioned windows, while keeping NSScreen.frame != the whole display, which diverges from the documented frame/visibleFrame contract.

For the Gershwin Menu.app this matters because it is a full-width top bar that deliberately wants the very top edge; with the work-area frame it could only ever reach fullScreen - workArea pixels down. I'm happy to adjust the patch if you'd rather go another way, e.g. keep the work-area frame and make the Y-flip consistent. Just let me know your preference.

@rfm

rfm commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I wouldn't claim to be deeply experienced with the gui/backend as I don't write gui apps and hardly ever touch it, and I don't actually know why I'm not seeing any issues if _OSFrameToXFrame if getting things wrong, but I'm perfectly prepared to accept there is a bug there.

I also don't understand why you want to draw at the top of the physical screen when the window manager is going to ensure that its own topbar, another window outside of GNUstep control, will hide anything you draw.

My understanding is that there are at least three areas to consider:
The physical display size
The usable display size (a GNUstep app can't display anything in a region where the WM puts its windows behind other windows) that GNUstep wants to draw its main menu (and perhaps a dock) into
The 'visibleRect' that an app will want to position its windows in, in order to avoid them being hidden by menu/dock.

Now, what I did with _NET_WORKAREA was to try to conflate the physical area with the usable area, on the theory that if the GNUstep app can never display outside the usable area, then the usable area corresponds to the NSScreen rect as far as the GNUstep app is concerned.

Maybe that's wrong, and the NSScreen rect should correspond to the physical display, but if so, the GUI drawing code needs to understand the distinction between the physical screen rectangle and the usable screen rectangle somehow, so that it doesn't try to position the main menu, menubar etc outside of the usable part of the display (which is what was previously happening).

So unless I'm missing something, I think we can either:

Drop the workarea adjustment so that the screen rectangle is the physical size as reported by X, but at the same time keep the workarea code to find the usable screen rectangle, pass the usable rectangle back to the GUI. Add a new internal method to find the usable rectangle, adjust the -visibleFrame method to incorporate the usable rectangle, and adjust the menu code to use the usable rectangle information to ensure that main menus/menubars are appropriately constrained to be within the usable area of the screen.

or

We can have the NSScreen rectangle report the usable (to the GNUstep app) area of the physical screen, and fix any errors in the OpenStep<->X coordinate mapping resulting from that.

Does that make sense?

@rfm

rfm commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I think this is a design/policy thing that Fred should decide on: I can see arguments either for both ways (I was lazy and chose the option that looked like least work). However, as far as I can see this PR on its own just breaks placement of menus and windows when used with the current main window managers: it would need to be coupled with all the changes to get menu placement and the -visibleFrame calculation correct.

@probonopd

Copy link
Copy Markdown
Contributor Author

Hi @rfm, @fredkiefer,

As I understand it, the underlying issue is that NSScreen.frame and the OpenStep <-> X coordinate conversion currently use different heights (_NET_WORKAREA vs. the full X screen). Because they disagree, a window positioned at the top of frame no longer maps to the physical top of the display.

There seem to be two consistent ways to fix this:

Option (a): NSScreen.frame is the physical display

  • Drop the _NET_WORKAREA adjustment so NSScreen.frame always reports the full monitor size (this patch).
  • Keep reading _NET_WORKAREA, but expose it separately as the usable area (for example via NSScreen.visibleFrame or an equivalent internal API).
  • Applications that need to avoid reserved areas such as panels or docks use visibleFrame, while frame continues to represent the entire display.

With this approach, a window positioned at the top of frame maps to the physical top of the display because both frame and the existing OpenStep <-> X coordinate conversion use the full screen height.

This also matches the AppKit/OpenStep model, where frame describes the physical display and visibleFrame describes the usable portion.

Option (b): NSScreen.frame is the usable area

  • Keep applying _NET_WORKAREA, so NSScreen.frame already represents the usable rectangle.
  • Change the OpenStep <-> X coordinate conversion so it uses the workarea height instead of the full X screen height.
  • Applications continue using frame as they do today.

With this approach, a window positioned at the top of frame also maps to the physical top of the display because the coordinate conversion now uses the same height as frame.

Personally, I slightly prefer option (a). It cleanly separates the physical screen geometry from the window manager's usable work area, matches the existing AppKit semantics, and avoids making the meaning of NSScreen.frame depend on _NET_WORKAREA. Ideally, it should be paired with exposing the workarea separately (for example through visibleFrame), so applications that need the usable rectangle do not lose that information.

Both approaches resolve the inconsistency. The main difference is whether we want NSScreen.frame to represent the physical display or the usable area going forward.

@rfm

rfm commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I think to do this we would need to:

  1. Change the backend code back to reporting the full display size.
  2. Add backend method to report usable rect
  3. Change NSScreen visibleRect to use the new method to get the usable rect. If the theme has no menu bar,vthis would be the visibleFrame.
    If the theme has a menu bar, the visibleFrame code could ask the main menu for it's height, and return the visible frame calculated by subtracting the menu bar height from the usable rect

The main menu in turn could ask NSScreen for visibleFrame, and position itself by its own height above it, because it knows that vusibleFrame has already taken it's height into account.

When changing themes we would need to be careful to change main menu height first, then get NSScreen to update, then reposition menus if necessary.

@DTW-Thalion

Copy link
Copy Markdown
Contributor

Some measurements on this, all with a single monitor.

The work area is only applied when the server reports exactly one RandR
output, because monitorsCount still holds screen_res->noutput when that test
runs. On the Xorg dummy driver, which reports 16 outputs, a work area 40 rows
shorter than the screen was ignored and the frame stayed 1920x1080. On Xvfb,
which reports one output, the same property gave 1920x1040. So whether
NSScreen.frame is the full screen or the work area depends on how many
connectors the driver lists, which would account for a machine showing no
problem while another shows a top bar out of place.

Where the override does apply, the shift is real. With 40 rows reserved, a 60
pixel bar placed at the top of NSScreen.frame was mapped at X row 40 rather
than row 0.

Separately, _workAreas sets origin.y to zero instead of flipping it, so a
panel at the bottom of the screen gives the same frame as one at the top.
Measured with a bottom panel: a bar placed at the bottom of NSScreen.frame was
mapped at X rows 1030 to 1079, over a panel occupying rows 1040 to 1079, and
the top 40 rows could not be reached at all.

#226 corrects both of those inside the current model. It does not settle
whether frame should be the physical display or the usable area.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants