Open sonakinci41 opened 3 years ago
Similar question here.
Can I get an opening windows list from Wayland via this projects?
The generic wlroots answer on how to do this is to use the wlr_foreign_toplevel_management_v1
protocol to get information about exising windows from the compositor. The compositor must support this protocol itself, but this is common on wlroots-based compositors so likely not an issue. This is the protocol that most third-party panels (and similar tools) use to get information on windows, and it also allows them to control them (e.g. focus a specific window when you click on it in the panel).
For pywlroots
, handlers for this protocol are not yet part of the codebase, but I just submitted a PR for it here so this should be usable soon: https://github.com/flacjacket/pywlroots/pull/63
How to use this?
I suggest checking out the source for wlrctl which can be used to list windows: https://git.sr.ht/~brocellous/wlrctl
@flacjacket @m-col
To anyone reading this, please help me out if you can.
I have been attempting to use the foreign toplevel management protocol with both pywayland
and pywlroots
for a very long time, and have failed at every turn.
With pywayland
and a generated set of protocol files that includes the protocol in question, I hit a NotImplementedError
in protocol_core/message.py
, because the protocol appears to use ArgumentType.NewId
in the event()
method decorator, and the c_to_arguments
method in message.py
just has a "TODO" comment and raises that error. So there's no code to handle the argument. I don't know what this means or how to fix it or get around it.
With pywlroots
, which seems to have the protocol available to import without taking extra steps (the only example I can find of this in actual use is libqtile
), I end up with a segmentation fault and core dump. Maybe I'm missing a setup step?
I'm just installing the latest pywayland
and pywlroots
with pip
. But with pywayland
I use the scanner technique from this comment:
https://github.com/flacjacket/pywayland/issues/8#issuecomment-987040284
These problems I'm running into happen either when I do an initial display.roundtrip()
right after establishing a handler callback, or skip the roundtrip and go directly into a display.dispatch()
loop.
I've used every scrap of the very limited information I've been able to find online about how to use either of these techniques, and have worked through a lot of initial confusion about how to use any of this stuff, but these problems are like brick walls that I don't know how to overcome.
Using other more standard protocols to get some info out of the Wayland compositor doesn't seem to be a problem. So I don't think I'm using pywayland
wrong overall.
I've been testing in a VM running Fedora 40, with sway and Hyprland. The results have been the same in either environment. The registry dump shows the zwlr_foreign_toplevel_manager_v1
protocol in the list for both compositors.
Any clues to getting this working would be super helpful. I'm trying to give a keymapper the ability to do app-specific modmapping/keymapping on wlroots
compositors.
The keymapper already has methods that work in several common Wayland environments that were not based on wlroots
(GNOME, KDE Plasma, Cinnamon) and in some environments like sway
and Hyprland where there were alternative methods available (i3ipc
and hyprpy
) to get app_id
and title
of the active window. But I'd really, really like to get wlroots
in general working, which should allow the keymapper to (hopefully) support some coming updates to popular Linux desktops like the new Pop!_OS COSMIC, Budgie, LXQt 2.x, etc. Most new Wayland environments seem to be using wlroots
for convenience, and should all have this foreign toplevel management protocol available. Unless I'm completely misunderstanding the situation.
I'll put the current state of the script below, in an expandable tag, in case anyone has some insights to offer. The script has the wlroots
import/method uncommented currently, just because it was the last thing I tried (again). The pywayland
imports and methods are commented out. The script works in general, as long as I don't try to establish the foreign toplevel manager object through bind()
or create()
. Information comes out for the registry, wl_seat
and wl_output
without any apparent issue.
Please try PR #64
It attempts to fix the "new id" problem and I used it with the foreign toplevel manager protocol.
@heuer
Definitely gets me past the initial problem, but then reveals that apparently all my handlers aren't set up quite right. Or the dispatchers. Or something.
Do you have a working example of actually using the protocol to get stuff like title, app_id and state events?
Also, I'm still hitting my dispatch()
loop and then never seeing any new events show up in the terminal after that. So I have lots of fundamental misunderstandings of things, and not a lot of working examples to draw on.
But the finish line is already much closer.
I haven't studied your code in detail, to be honest, but you'll have to import the generated protocols. I.e. if the generated protocols are located in a module relative to the current module:
from .protocol.wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_manager_v1 import \
ZwlrForeignToplevelManagerV1, ZwlrForeignToplevelManagerV1Proxy, ZwlrForeignToplevelHandleV1
The protocol implementations in pywlroots are for compositors which want to provide the messages, your client has to use the generated protocols.
I hope this gives a hint into the right direction. I am on vacation and I'll come back later to this issue if you get no other help.
Maybe PR #55 helps regarding the dispatching problem, although it does not implement the foreign toplevel protocol.
@heuer
Thanks for your time. Enjoy your vacation. Even that import example is very helpful. I'll come back if I can't get things working, or let you know if I break through.
The link is interesting. Much different than any other example I've seen. Dispatch comes before the loop even, and then it goes roundtripping if it needs to... Makes more sense to me already.
This is mostly doing what I wanted it to do (showing me app class and window title whenever the focused app changes), but I still have a problem with not really understanding how to do the dispatch loop without saturating a CPU core (or thread) constantly. I have to use time.sleep()
to slow down the loop, and I'm pretty sure that's not how it's supposed to work. But without the extra roundtrip()
in the loop, I get absolutely nothing coming out after the initial setup phase.
I'll have to take another look at it tomorrow.
I found a way to keep the event loop from going out of control and using CPU constantly, but it looks nothing like any example I've seen. It involves getting the Wayland file descriptor after connecting to the display object, and then using that as a way to control the loop (instead of just using display.dispatch() != -1
).
In one script:
while True:
# Wait for the Wayland file descriptor to be ready
rlist, wlist, xlist = select.select([self.wl_fd], [], [])
if self.wl_fd in rlist:
# self.display.dispatch() # won't show me new events
self.display.roundtrip()
In another script:
def wayland_event_callback(fd, condition, display):
if condition & GLib.IO_IN:
# display.dispatch() # dispatch() fails to prompt new events to appear
# dispatch() also seems to trigger the callback to get called many times in a loop,
# but without any new useful events appearing, while roundtrip() just shows
# the new events that I need to see, as they happen.
display.roundtrip() # gets new events to appear immediately
return True
def main():
... (other code)
# GLib.idle_add(wayland_event_callback)
GLib.io_add_watch(wl_fd, GLib.IO_IN, wayland_event_callback, display)
display.roundtrip() # get the event cycle started (callback never gets called without this)
# Run the main loop
# dbus.mainloop.glib.DBusGMainLoop().run()
GLib.MainLoop().run()
Another aspect is avoiding the use of dispatch()
entirely, which seems to either cause me problems with a non-blocking event loop, and/or just never gets any new events to print out. So I'm using only roundtrip()
calls. Neither I nor any version of ChatGPT no matter how "smart" can explain why I have to do things this way. Doesn't seem to make much sense. Any input on this is welcome.
Anyhoo, here are a couple of scripts that are working for me, and I added the printing out of the whole list of running applications in the output, even though I'm only interested in the class/title of the focused app window.
This first script is just a straight establishing of the foreign toplevel management protocol being supported, and then a continuous printing out of the "active" application class and window title (and the app list).
(Edited this to be the version I converted to encapsulate the Wayland-related functions in a class, like the other one.)
This other script is more complicated, integrating the same kind of extraction of the active window info into a module that also creates a D-Bus service (with GLib
) that can be queried to get the info:
[!TIP]
FYI, for these hidden "spoiler" expandable things you need a blank line between the</summary>
HTML tag and the "```py" tag that starts the code block, or everything after that gets misinterpreted for some reason.
@heuer
Your hints and examples allowed me to put together window context providers for both the zwlr_foreign_toplevel_manager_v1
interface in compositors that use the wlr_foreign_toplevel_management_unstable_v1
protocol, and a closely related method for the upcoming Pop!_OS COSMIC desktop environment. Just needed some additional XML files from their cosmic-protocols
GitHub repo:
cosmic-toplevel-info-unstable-v1.xml
cosmic-workspace-unstable-v1.xml
There's some kind of dependency on the "workspace" protocol, so there's an error when generating the Python protocol files with it.
And then the object names were a bit different (and might change when they implement a new draft "state" protocol, we'll see how that goes):
# from protocols.wlr_foreign_toplevel_management_unstable_v1.zwlr_foreign_toplevel_manager_v1 import (
# ZwlrForeignToplevelManagerV1,
# ZwlrForeignToplevelManagerV1Proxy,
# ZwlrForeignToplevelHandleV1,
# )
# COSMIC-specific protocol module, using their own namespace
# XML files downloaded from `cosmic-protocols` GitHub repo, in 'unstable' folder
from protocols.cosmic_toplevel_info_unstable_v1.zcosmic_toplevel_info_v1 import (
ZcosmicToplevelInfoV1,
ZcosmicToplevelInfoV1Proxy,
ZcosmicToplevelHandleV1
)
The code is otherwise basically identical to the more generic wlroots
method, which has been confirmed to work on multiple Wayland compositors so far (Niri, Qtile, Hyprland, sway).
Weirdly the COSMIC protocol that seemed closest in naming turned out to not be the one I needed to look at:
cosmic-toplevel-management-unstable-v1.xml
Thanks for your help with this. It was instrumental.
Hello, I want to write a panel with pygtk for wlroots based desktops. However, in my research, I could not see a way to list open windows. I thought I could do this with pywayland by reviewing the Waybar and logging the error on the link. I decided to open an error log as I saw a lack of documentation on this subject. I'm sorry if I'm doing something wrong. Can you give a simple example that allows you to list and manipulate existing windows? Thanks.