Is there a way to get list of windows on KDE Wayland?

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.

Asked By: Ashark

||

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.

Answered By: Ashark

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()))
Answered By: Scott Morse
Categories: Answers Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.