lxqt-panel icon indicating copy to clipboard operation
lxqt-panel copied to clipboard

Wayland taskbar support v2

Open gfgit opened this issue 1 year ago • 93 comments

Depends on #2041 (WM abstraction) Replaces #2031

This is an experimental implementation on taskbar, desktopswitch and showdesktop functionality for KWin Wayland compositor. It uses following protocols:

  • org-kde-plasma-virtual-desktop
  • plasma-window-management

(Taken from plasma-wayland-protocols repository)

Code is inspired by libtaskmanager/waylandtasksmodel.cpp

Ideally the Wayland backend should have sub-backends targeting specific compositors when no standard protocol is available.

It also uses layer shell protocol to place panel on screen edge.

NOTE: panel auto-hide is not working yet

gfgit avatar Mar 26 '24 11:03 gfgit

Nice! Taskbar under kwin_wayland works again I see.

NOTE: panel auto-hide is not working yet

It does here :) (had always worked on this branch).

stefonarch avatar Mar 26 '24 12:03 stefonarch

Something has gone wrong I think, I recompiled the changes this afternoon and taskbar is gone again.

stefonarch avatar Mar 26 '24 18:03 stefonarch

Taskbar is back here now I see, and crash on clicking showdesktop is avoided too.

stefonarch avatar Mar 28 '24 13:03 stefonarch

Just noticed a different behavior here compared to wlroots and X11 (any WM): the taskbar buttons only activates windows, but don't toggle minimize/restore.

stefonarch avatar Mar 28 '24 14:03 stefonarch

From my tests it looks that export XDG_SESSION_DESKTOP="KDE in the startscript is not needed (anymore).

Another remaining issue is the "Maximize" item in menu which is always greyed out.

stefonarch avatar Apr 01 '24 06:04 stefonarch

NOTE: panel auto-hide is not working yet

Is this not implemented yet, or, the implementation gives some problems?

Edit:

   // Emulate auto-hide on Wayland
   // NOTE: we cannot move window out of screen so we make it smaller

   // NOTE: a cleaner approach would be to use screen edge protocol
   // but it's specific to KWin

From the code it seems to me that you have problems about how. With layer-shell, we can set the margin to a negative value. For a bottom panel, setting the bottom margin to -PANEL_HEIGHT would mean that the panel is effectively hidden.

However, the issue of how to show the panel becomes a problem. With wayfire, we can use wayfire-shell or wayfire-desktop-shell, both of which provide an equivalent of screen-edge protocol.

But in general, we can partially hide the panel, instead of resizing the panel. For example, set the bottom margin to -PANEL_HEIGHT + PANEL_HIDE_SIZE. This would leave a few pixels sticking out. If the mouse pointer enters the panel, then it can be revealed completely. In place of QPropertyAnimation, you can apply QVariantAnimation and change the bottom margin value from -PANEL_HEIGHT + PANEL_HIDE_SIZE to 0 and other way around.

marcusbritanicus avatar Apr 01 '24 08:04 marcusbritanicus

Are negative margin value allowed by layer shell? Didn't know about it! Current implementation sort of works but on my setup it triggers auto-show even if cursor is not yet at screen edge. This makes it impossible to click for example statusbar of applications or other UI elements near screen edge because panel shows first and you end up click the panel instead. It may be a bug in kwin mouse logic

gfgit avatar Apr 01 '24 11:04 gfgit

Well... It's not specifically, disallowed. On wayfire, we simply use negative margins. I've not tested on other compositors, though.

marcusbritanicus avatar Apr 01 '24 11:04 marcusbritanicus

Negative layershell margins may end up on other outputs or don't receive frame / configure events anymore because the surface is not actually visible (which revealed a bug in the GTK layershell wrapper in the past), depending on the compositor. (Although I'd actually categorize the shows-up-on-other-output as a compositor bug). AFAIR a discussion in the ext-layershell protocol MR concluded to not allow negative margins at all (but memory is a bit fuzzy there).

Another option that should always work is hiding the panel and replacing it with a transparent hitbox without exclusive zone. On mouse movement on the hitbox that one is hidden and the panel is shown again.

This implements it in Python with GTK but something similar should be possible with Qt as well.
#!/usr/bin/env python3

from functools import partial

import gi
gi.require_version("GLib", "2.0")
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
gi.require_version('GtkLayerShell', '0.1')
from gi.repository import GLib, Gdk, Gtk, GtkLayerShell


class Context:
	def __init__(self):
		self.panel = Panel(self, auto_hide_seconds=5)
		self.dummy = HitBox(self)
		self.show_panel()

	def log(self, msg):
		print(f"[{self.__class__.__name__}] {msg}")

	def hide_panel(self):
		self.log("Hiding panel")
		self.dummy.show()
		self.panel.hide()

	def show_panel(self):
		self.log("Showing panel")
		self.dummy.hide()
		self.panel.show()


class Base(Gtk.Window):
	def __init__(self, context):
		super().__init__()
		self.context = context
		self.connect('motion-notify-event', self.on_mouse_move)
		self.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
		self.set_size_request(-1, 30)
		GtkLayerShell.init_for_window(self)
		GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.BOTTOM, True)
		GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.LEFT, True)
		GtkLayerShell.set_anchor(self, GtkLayerShell.Edge.RIGHT, True)
		self.log = partial(context.__class__.log, self)
		self.log("init done")

	def on_mouse_move(self, src, event):
		pass

	def add_style(self, css):
		if isinstance(css, str):
			css = css.encode('utf-8')
		css_provider = Gtk.CssProvider()
		css_provider.load_from_data(css)
		style_context = Gtk.StyleContext()
		style_context.add_provider_for_screen(
			Gdk.Screen.get_default(),
			css_provider,
			Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
		)


class Panel(Base):
	def __init__(self, *args, auto_hide_seconds=3, **kwargs):
		super().__init__(*args, **kwargs)
		GtkLayerShell.set_layer(self, GtkLayerShell.Layer.BOTTOM)
		GtkLayerShell.auto_exclusive_zone_enable(self)
		self.auto_hide_seconds = auto_hide_seconds
		self.timer = None

	def timer_rearm(self):
		if self.timer:
			GLib.source_remove(self.timer)
		self.timer = GLib.timeout_add_seconds(
			self.auto_hide_seconds, self.on_timer,
			priority=GLib.PRIORITY_LOW
		)

	def on_timer(self):
		self.log("Timer hit")
		self.context.hide_panel()
		self.timer = None
		return False

	def show(self):
		super().show()
		self.timer_rearm()

	def on_mouse_move(self, src, event):
		self.timer_rearm()


class HitBox(Base):
	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		#self.set_size_request(-1, 2)
		GtkLayerShell.set_layer(self, GtkLayerShell.Layer.TOP)
		self.set_name('hitbox')
		#self.add_style('#hitbox { background-color: transparent; }')
		self.add_style('#hitbox { background-color: rgba(255, 128, 128, 0.5); }')

	def on_mouse_move(self, src, event):
		self.context.show_panel()


if __name__ == '__main__':
	context = Context()
	Gtk.main()

The variant also has the benefit that rather than just having a transparent non-exclusive hitbox, one could also anchor the replacement surface to e.g. the bottom-left edge and show a small button instead which - when triggered - hides the replacement surface and restores the panel one. That replacement surface would then be non-exclusive, only be the width of the button and using the top layer so its always visible on top of usual windows.

Consolatis avatar Apr 01 '24 12:04 Consolatis

Current implementation sort of works but on my setup it triggers auto-show even if cursor is not yet at screen edge. This makes it impossible to click for example statusbar of applications or other UI elements near screen edge because panel shows first and you end up click the panel instead. It may be a bug in kwin mouse logic

I didn't notice that as I don't really use it, but it's on all compositors the same, getting triggered at panel height and not screen border.

stefonarch avatar Apr 01 '24 15:04 stefonarch

Current implementation sort of works but on my setup it triggers auto-show even if cursor is not yet at screen edge. This makes it impossible to click for example statusbar of applications or other UI elements near screen edge because panel shows first and you end up click the panel instead. It may be a bug in kwin mouse logic

I didn't notice that as I don't really use it, but it's on all compositors the same, getting triggered at panel height and not screen border.

That could also be solved by using the transparent replacement surface, either via the button-only variant or by defining a 1px or 2px height of the surface (added a commented out call to the GTK example above).

Consolatis avatar Apr 01 '24 15:04 Consolatis

Under X11, the panel is moved out of the screen appropriately, such that only 4 pixels (PANEL_HIDE_SIZE) of its height or width remain visible, depending on whether it's horizontal or vertical. Then its opacity is made zero. Those invisible 4 pixels serve to show the panel on mouse-over.

Since the panel is never positioned between screens, this method works fine. Of course, there are other details/options, like animation and auto-hiding on overlapping windows.

tsujan avatar Apr 01 '24 16:04 tsujan

Btw what about https://github.com/lxqt/lxqt/discussions/2531#discussioncomment-8966097 ?

stefonarch avatar Apr 05 '24 06:04 stefonarch

Btw what about lxqt/lxqt#2531 (comment) ?

I'm ok for extracting layer shell code and make a new PR

gfgit avatar Apr 05 '24 09:04 gfgit

Code is inspired by libtaskmanager/waylandtasksmodel.cpp

Awesome that the code works for you but in this case the inspiration seems was rather strong and seems to include copied code from Plasma in multiple files as the commented out lines still references the original class and the general structure of the code (for example org_kde_plasma_window_icon_changed). We do not mind this as it's free software after all but would appreciate it if the original license headers could be preserved, thanks!

The original license headers are the two SPDX- lines at the top of the file, e.g. SPDX-License-Identifier: LGPL-2.1-only. Since lxqt is LGPL v2.1, you can specifically choose to re-use the code from KDE under the LGPL v2.1.

Sodivad avatar Apr 18 '24 07:04 Sodivad

seems to include copied code from Plasma in multiple files

In that case, the original codes should be referenced by comments like "Adapted from...".

tsujan avatar Apr 22 '24 09:04 tsujan

@Sodivad Hi, I've updated the license headers. Is it ok like that?

gfgit avatar Apr 22 '24 14:04 gfgit

Rebased on current master

gfgit avatar Jun 08 '24 15:06 gfgit

I notice 2 regressions now: desktop switch is the "n.d" one (not supported on this platform: wayland) and showdesktop doesn't work too anymore.

stefonarch avatar Jun 08 '24 19:06 stefonarch

@gfgit @stefonarch @tsujan A request.

This PR introduces support for plasma wayland based taskbar and pager. However, before this can reviewed don't you think it's better to discuss how we will introduce support for wlroots-based taskbar too? Otherwise, we will unnecessarily have to refactor the code.

One of the ways we can go ahead is via modules - basically plugins for the taskbar plugin. The wayland backend code will load the suitable plugin based on the compositor and user's choice. We can use the ILXQtTaskbarAbstractBackend class as to develop the plugin interface. Then, the backends for all other platforms can be easily developed.

The biggest advantage in this approach is that we can easily change the backend code without affecting the rest of the project. Secondly, we can add support for different compositors based on their strengths - sway, wayfire, hyprland can use IPC, others can use what they provide. If nothing is available, the generic wlroots backend can be used.

If we introduce these modules (the framework for modules) before we review this PR, then we can refactor the plasma code into a module rather than merge this and ten go for a refactor later.

marcusbritanicus avatar Jun 09 '24 10:06 marcusbritanicus

I think that makes sense. Labwc (generic wlroots backend), kwin and wayfire will be the priority atm as both qterminal dropdown and lxqt-runner are unusable on sway and hyprland is on a quite weird way probably afaik ("As we’re in the middle of slowly but surely moving off of depending on wlroots for our backend(s),").

stefonarch avatar Jun 09 '24 11:06 stefonarch

As we’re in the middle of slowly but surely moving off of depending on wlroots for our backend(s),").

What does that mean? Who's moving away from wlr for the backends?

Even if we directly move away from wlroots, we still need the modules-based approach.

marcusbritanicus avatar Jun 09 '24 13:06 marcusbritanicus

The wayland backend code will load the suitable plugin based on the compositor....

@marcusbritanicus, yes, a modular approach is the best way, considering the fact that LXQt is and will remain modular, as far as possible.

As @stefonarch mentioned, with the current state of affairs, supporting LabWC, Wayfire and kwin_wayland is our priority.

Who's moving away from wlr for the backends?

I'm also curious to know the source of that quotation, but I think it's about hyprland ;) If so, it's none of our concern....

tsujan avatar Jun 09 '24 13:06 tsujan

I'm also curious to know the source of that quotation, but I think it's about hyprland ;) If so, it's none of our concern....

https://hyprland.org/news/update40/

stefonarch avatar Jun 09 '24 14:06 stefonarch

@marcusbritanicus I very much agree on plugin system. We could somewhat reuse the technique of LXQt panel plugins. Also I think we should drop "Taskbar" from class name, as not it's used in other parts of the code not related to taskbar plugin itself. I suggest ILXQtAbstractWMInterface.

gfgit avatar Jun 09 '24 21:06 gfgit

I like the sound of ILXQtAbstractWMInterface.

As they say, no good deed goes unpunished, and you (@gfgit) have done a fantastic job on lxqt-panel thus far. So, I think it is only fitting that you should develop the plugin interface for this as well. Will you please do the honours?

Until the first draft of the interface is ready, we have a few things to mull about:

  1. Do we add support for ext-foreign-toplevel-list (wayland)? What compositors have support for this currently? Eventually, ext-foreign-toplevel group (3 protocols in all), will replace wlr-foreign-toplevel.
    • Since this spec is incomplete without ext-foreign-toplevel-management, wlroots does not support it.
    • Plasma also does not support this because plasma needs many more features.
  2. Do we add support for xdg-toplevel-icon protocol?
    • This protocol is used to get the icons for each window, as set by the client. A fallback will be using app-id if this is not set.
    • This will be soon supported by Qt, Plasma (6.2) and Wlroots. GTK, I know not.
  3. What backends do we provide support for, by default?
    • X11
    • Plasma Wayland
    • Generic Wlroots (labwc needs this)
    • Wayfire
    • Any other?
  4. What metrics do we use to chose the backend.
    • Wayfire supports both generic wlroots and dedicated backends. Wayfire also has support for compositor plugins, which means people are free to develop a third backend.
    • Do we hard-code this process? In #2046, I have used a hard-coded approach. This is the easiest from dev POV, and will work for now, but it lacks the flexibility.
    • Alternatively, we could add a hidden option in the settings (meaning, not shown in GUI) for the time being. This solves the issue of user-developed backends. Also, if new backends becomes available, we may not have to modify the code at all.

marcusbritanicus avatar Jun 10 '24 06:06 marcusbritanicus

I suggest ILXQtAbstractWMInterface.

I agree.

tsujan avatar Jun 10 '24 10:06 tsujan

@gfgit @marcusbritanicus Any updates on this?

stefonarch avatar Jun 26 '24 07:06 stefonarch

@gfgit @marcusbritanicus Any updates on this?

I am waiting for @gfgit to do this. He is much much better at it, than I am. Once the framework is developed, I will port wlroots to this.

marcusbritanicus avatar Jun 26 '24 09:06 marcusbritanicus

I will port wlroots to this.

I'm looking forward to that. Currently I use a slightly changed version of your old branch to have task-bar under labwc and wayfire.

So, we're all waiting for @gfgit ;)

tsujan avatar Jun 26 '24 16:06 tsujan