5

In the era of X11, I could do wmctrl -l to list available windows, that I can use in my scripts.

$ wmctrl -l
0x01000050  0 my-pc project1 – Readme.md

But nowadays most application use Wayland. The above command only shows windows that are running with XWayland.

I want to be able to use applications in Wayland mode and at the same time be able to list their windows for my scripts. Is that possible? I am using Arch Linux with KDE.

1

2 Answers 2

3

Yes, it is possible. The idea is to ask kwin for this information. It is done via kwin script. It can only communicate the world with dbus, so we cannot run shell commands in kwin scripts (at least directly). But we can run kwin script from shell script.

Create the following script ~/bin/list_windows.js:

const clients = workspace.clientList();
for (var i = 0; i < clients.length; i++) {
  print(clients[i].caption);
}

Unfortunately, the output to stdout is currently broken, see bug report. But there is a workaround. Turn on the kde systemd startup. Now we can use journalctl to pull the output. The resulting get_list_of_windows script is the following:

#!/usr/bin/env python3

import subprocess
from datetime import datetime


def get_list_of_windows():
   datetime_now = datetime.now()

   script = "/home/andrew/bin/list_windows.js"

   reg_script_number = subprocess.run("dbus-send --print-reply --dest=org.kde.KWin \
                        /Scripting org.kde.kwin.Scripting.loadScript \
                        string:" + script + " | awk 'END {print $2}'",
                           capture_output=True, shell=True).stdout.decode().split("\n")[0]

   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /" + reg_script_number + " org.kde.kwin.Script.run",
                  shell=True, stdout=subprocess.DEVNULL)
   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /" + reg_script_number + " org.kde.kwin.Script.stop",
                  shell=True, stdout=subprocess.DEVNULL)  # unregister number

   since = str(datetime_now)

   msg = subprocess.run("journalctl _COMM=kwin_wayland -o cat --since \"" + since + "\"",
                        capture_output=True, shell=True).stdout.decode().rstrip().split("\n")
   msg = [el.lstrip("js: ") for el in msg]

   return msg


print('\n'.join(get_list_of_windows()))

Now run the script and you will get the output:

$ get_list_of_windows
Рабочий стол по умолчанию — Plasma
Plasma
Is there a way to get list of windows on KDE Wayland? - Unix & Linux Stack Exchange - Vivaldi
get_list_of_windows — Kate
Andrew Shark / Davinci Resolve Scripts · GitLab — Falkon
project1 – README.md

This is in my repo.

1
  • When I run this I get Error org.freedesktop.DBus.Error.UnknownObject: No such object path '/0'. Each time I run it, that number ('/0') increments by one.
    – tobek
    Commented Mar 8 at 3:11
0

Ashark's answer mostly worked for me, but in May 2024 breaking changes in KWin require some tweaks.

The KWin script

Within the script list_windows.js (a file you simply need to create and point the path to in the Python below), workspace.clientList() is no longer the correct call, which should be replaced with workspace.windowList(), making its new code as follows:

for (const window of workspace.windowList()) {
  if (window.caption) print(window.caption);
}

Getting the script output in Python

I needed to change the object paths to the script number from /<num> to /Scripting/Script<num> due to errors such as Error org.freedesktop.DBus.Error.UnknownObject: No such object path '/<num>'. I found the correct script paths by running qdbus org.kde.KWin to list all the valid object paths.

So, I used Ashark's code but with these lines changed to:

   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /Scripting/Script" + reg_script_number + " org.kde.kwin.Script.run",
                  shell=True, stdout=subprocess.DEVNULL)
   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /Scripting/Script" + reg_script_number + " org.kde.kwin.Script.stop",
                  shell=True, stdout=subprocess.DEVNULL)  # unregister number

Here's the whole modified script:

#!/usr/bin/env python3

import subprocess
from datetime import datetime


def get_list_of_windows():
   datetime_now = datetime.now()

   script = "/path/to/your/kwin/script/list_windows.js"

   reg_script_number = subprocess.run("dbus-send --print-reply --dest=org.kde.KWin \
                        /Scripting org.kde.kwin.Scripting.loadScript \
                        string:" + script + " | awk 'END {print $2}'",
                           capture_output=True, shell=True).stdout.decode().split("\n")[0]

   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /Scripting/Script" + reg_script_number + " org.kde.kwin.Script.run",
                  shell=True, stdout=subprocess.DEVNULL)
   subprocess.run("dbus-send --print-reply --dest=org.kde.KWin /Scripting/Script" + reg_script_number + " org.kde.kwin.Script.stop",
                  shell=True, stdout=subprocess.DEVNULL)  # unregister number

   since = str(datetime_now)

   msg = subprocess.run("journalctl _COMM=kwin_wayland -o cat --since \"" + since + "\"",
                        capture_output=True, shell=True).stdout.decode().rstrip().split("\n")
   msg = [el.lstrip("js: ") for el in msg]

   return msg


print('\n'.join(get_list_of_windows()))

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .