helloSystem / Menu

Global menu bar written in Qt
42 stars 14 forks source link

[Wayland] Menu positioning on the screen is broken #176

Open probonopd opened 9 months ago

probonopd commented 9 months ago

The menu is placed at the center rather than at the top of the screen.

The panel in Raspberry Pi OS (which seems to work in X11 and Wayland session) apparently uses Layer Shell, a Wayland protocol for desktop shell components, such as panels, notifications and wallpapers.

Unfortunately the iplementation in Raspberry Pi OS is GTK specific. In X11, telling a window to be at the top of the screen was entirely toolkit agnostic.

https://github.com/raspberrypi-ui/wf-panel-pi/blob/21b2e67f0bd44e8066c63c192884b9410ac983bf/src/panel/panel.cpp#L215-L221

There seems to be a Qt specific implementation: https://github.com/KDE/layer-shell-qt

probonopd commented 9 months ago

It seems like "shell" is Wayland terminology for desktop environment components such as desktop backgrounds, panels, the menu, etc.

https://drewdevault.com/2018/07/29/Wayland-shells.html

Under the umbrella of wlroots, 8 Wayland compositors have been collaborating on the design of a new shell for desktop shell components. The result is layer shell (XML). The purpose of this shell is to provide an interface for desktop components like panels, lock screens, wallpapers, on-screen keyboards, notifications, and so on, to display on your compositor.

The layer-shell is organized into four discrete layers: background, bottom, top, and overlay, which are rendered in that order. Between bottom and top, application windows are displayed. A wallpaper client might choose to go in the bottom layer, while a notification could show on the top layer, and a panel on the bottom layer.

So, will this work only on some (wlroots based) "Wayland display servers" (compositors) or is it to guaranteed to work everwhere? With Xorg, there is one universally used display server and if it supports something, you can be sure that it works in all desktop environments. But with Wayland? It's always a gamble which Wayland implementation supports what?

phrxmd commented 9 months ago

So, will this work only on some (wlroots based) "Wayland display servers" (compositors) or is it to guaranteed to work everwhere? With Xorg, there is one universally used display server and if it supports something, you can be sure that it works in all desktop environments. But with Wayland? It's always a gamble which Wayland implementation supports what?

FYI, layer-shell v1 support is in KWin as well: kwayland_server MR #67, kwin MR #202. The Plasma developers intend to transition to layer_shell entirely in the future, deprecating their own plasma_shell protocol that they developed before wlroots and layer_shell were around. I'd reckon it's pretty safe to use. The odd man out is Mutter, but seeing how GNOME-centric it is and what other GNOME-centric issues it has (e.g. lack of SSD), that's unlikely to be the compositor of choice for people running DEs other than GNOME anyway.

probonopd commented 9 months ago

Excellent POC on how to achieve the desired result by @johanmalm:

# On Raspberry Pi OS
sudo apt install liblayershellqtinterface-dev libwayland-bin libwayland-dev
git clone https://github.com/johanmalm/tint
cd tint/
mkdir build
cd build
cmake ..
make -j4

# As a result, the binary now has 3 Wayland-specific dependencies
ldd tint | grep ayl
    libwayland-client.so.0 => /lib/aarch64-linux-gnu/libwayland-client.so.0 (0x00007fff0bb30000)
    libQt5WaylandClient.so.5 => /lib/aarch64-linux-gnu/libQt5WaylandClient.so.5 (0x00007fff0a8e0000)
    libwayland-cursor.so.0 => /lib/aarch64-linux-gnu/libwayland-cursor.so.0 (0x00007fff09d00000)

./tint
# Result: A Qt5 window that sits at the top of the screen in Wayfire (as used in Raspberry Pi OS)

Now the question is, will it also work on Ubuntu and other Wayland based compositors?

tint-zip.zip

probonopd commented 9 months ago

Asked over at https://gitlab.com/wayfireplugins/lxqt-desktop-shell/-/issues/7

marcusbritanicus commented 9 months ago

@probonopd Just a suggestion: You may wanna switch from Qt5 to Qt6. Qt5 has a few quirks on the Wayland side that cannot be fixed. One such quirk appears in the layer-shell-qt implementation. It's not possible to get a popup for a layer-surface. Qt6 seems to have fixed this problem. We have at least two working codes that support Qt 6.5+. One is based on WayQt, and the other is available in theDesk.

Links:

  1. https://gitlab.com/desktop-frameworks/wayqt/-/tree/main/examples/LayerShellIntegration?ref_type=heads
  2. https://github.com/theCheeseboard/libtdesktopenvironment/tree/master/wayland-layer-shell/qt-plugin

While the KDE's implementation requires Qt 6.6, these two above are known to work with at least Qt 6.5. It may even work with 6.3 or 6.4.

probonopd commented 9 months ago

Hi @marcusbritanicus, thanks for your help.

It's not possible to get a popup for a layer-surface.

Coming from the Mac, Windows, and X11, I don't know the concepts of "layers", "shells", "layershells", "surfaces", and "popups". I just know "windows" and "menus".

So I am not entirely sure I understand what you are saying there but maybe it could be causing the issue I have described here?

marcusbritanicus commented 9 months ago

Lemme introduce you to a few of these terms so that you'll understand your problem better.. Layers are something that is typically internal to the compositor. Common layers are background, bottom, normal, top, full-screen, overlay etc... Most windows go to "normal" layer. Full-screened views go to "full-screen" layer. All other layers are for DE use. Panels (depending on how you want) can be on bottom layer (Windows-can-cover mode), or on top layer (normal mode). Notifications go typically to overlay so that important notifications are shown even if some app is in full-screen mode. These are guidelines and vary from one compositor to another. But we need not worry about them.

Shells as you have noted above is loosely related to DE. It's not just for backgrounds/panels, but in general how views interact with the user. xdg-shell describes how windows and popups behave. This is one of the core protocols of wayland. Another such shell is ivi-shell - useful for vehicle displays (In-Vehicle Infotainment Shell).

None of these shells describe how panels/background/notifications are to be displayed. So the good guys at wlroots developed the layer-shell protocol. This Layer-Shell is excusively for DE components like bg/panels/notifications... With this, you can tell the compositor to draw your client at a given position, and fix them there. The user cannot move them line normal windows.

As for surfaces, it's a very low-level wayland term. Something like QSurface in Qt. It's the base class for anything that's going to be shown on the screen. So we will not venture there.

Popups is an umbrella term for temporary surfaces that pop in and out of existence: menus and tooltips.

Now that we have the basics, the problem is simple to understand. You're using the layer-shell to display your menu-bar. This means, it no longer behaves like a normal window (which lives by the rules of xdg-shell). This also means that popups (that is, in this case, our menu) does not know how to show itself. We need to "attach" the menu to the menu-bar using get_popup(...) function described by the layer-shell protocol. This is one of the quirks of Qt5 that I was talking about: we have no way to do this "attaching". Which means that the menu behaves like a "normal window" and less like a menu.

I believe the window placement of Wayfire in RPi OS is set to be center. That is any new window will be shown on the center of the screen. So our menu (which now appears to wayfire as a normal window), is shown in the middle of the screen.

If you compile and run the same code in Qt 6.6, the menu should be shown at it's proper position. Of course, for this, you'll need layer-shell-qt from KF6. An alternative I would suggest is WayQt :stuck_out_tongue: Along with layer-shell, you'll get access to a truck load of other protocols, like idle, output management (screen resolution), tasks, screenshots etc..

probonopd commented 9 months ago

Hello @marcusbritanicus. Thanks for your help, you seem to be very knowledgable how to do things for Wayland in Qt. I have to admit that I don't understand much of Wayland and I honestly would prefer not having to deal with surfaces, shells, layers, and protocols - ideally I'd just interact with QWindow and friends (like QMainWindow). As such, I'd like to thell the Menu QWindow to "place yourself at the top of the main screen". If WayQt can help me do that, then it'd be very interesting for me. Especially if it can be linked statically, so that the resulting binary has no additional run-time dependencies, and if it works regardless of whether users are running KWin, Wayfire, or some other compositor. Do you think this would be possible?

marcusbritanicus commented 9 months ago

@probonopd Glad to be of help... :slightly_smiling_face:

As such, I'd like to thell the Menu QWindow to "place yourself at the top of the main screen" ....

By setting the Qt::Popup flag, we do exactly that. But then, when we use layer-shell for our surface, we're telling Qt to package stuff slightly differently. Instead of using a nice wrapping paper, we're asking Qt to use only a sticky tape to wrap our surface. And this presents a problem. Qt (version 5) does not know how to present this menu using sticky-tape. (I have seen a code in lirios which appears to do this, but I'm not very sure if it works.)

Does this mean Qt6 can do it? It turns out that the answer is not so simple...! Qt 6 surely provides a few tools to do achieve this. But it does not support layer-shell-qt natively. So we need a plugin (wayland-shell-integration-plugin, to be specific) to tell Qt how to handle menu packaging with sticky-tape. There is a PR for layer-shell-qt that deals with this.

What this PR tells Qt is simple: When packaging a surface, check if it is a popup. If it's a popup, set the layer-surface, which in this case is the menubar, to be the parent and package it without sticky-tape. Otherwise the surface is to be packages with the sticky-tape.

If WayQt can help me do that, then it'd be very interesting for me.

Even with WayQt, we need the code that tells Qt about the difference between popup and window. And this code will be in the form of a plugin. So you cannot escape the wayland-shell-integration-plugin.

Especially if it can be linked statically, so that the resulting binary has no additional run-time dependencies, and if it works regardless of whether users are running KWin, Wayfire, or some other compositor. Do you think this would be possible?

I do not believe static linkages is possible because of the way Qt is designed. But, if there is compositor support and the layer-shell-plugin is loaded, I do not see why it will not work in all supported compositors.

When I say supported compositors I mean kwin, wayfire, sway, labwc, river, etc. which have the support for layer shell on the compositor side. GNOME's mutter does not support layer-shell, and they don't plan to.

Way forward:

  1. You can keep the current code (which I believe is based on layer-shell-qt) and wait for them to make a release. I do not know if and when that PR will be merged, but there is a good probability that it will be, but the release may take time - they're likely to release it with plasma, rather than before.

  2. Second alternative would be WayQt, and releasing your own plugin. This is what I would suggest: You'll not have much work to do. An example of this layer-shell-plugin already exists, which you can re-use. Along with this, the code to create the layer surface will change to some extent, but should not be excessive.

Whichever way you go, do note that this is possible only with Qt6, unless by some miracle I manage to get it working with Qt5.. :laughing: