Get the X Window System window title in Python

The other day, I was writing in Python the process of waiting for an application in the X environment to open a file, but I wondered why I had to start wmctrl -l many times to get the window title. I thought, so I read the source of wmctrl and wrote a process like that using the pyglet module.

It's a crappy code, but it's provided under a two-clause BSD license, so feel free to use it if you find it helpful.

wmctrl.py


#!/usr/bin/env python3
# vim:fileencoding=utf-8

# Copyright (c) 2014 Masami HIRATA <[email protected]>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     1. Redistributions of source code must retain the above copyright notice,
#        this list of conditions and the following disclaimer.
#
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

__all__ = ["WMCtrl"]

from ctypes import (POINTER,
                    byref,
                    cast,
                    c_buffer,
                    c_char_p,
                    c_int,
                    c_ubyte,
                    c_ulong,
                    memmove,
                    pointer,
                    sizeof)

from pyglet.window.xlib import xlib
from pyglet.compat import asbytes

X_TRUE = xlib.True_
X_FALSE = xlib.False_
X_SUCCESS = xlib.Success
MAX_PROPERTY_VALUE_LEN = 4096

# from X11/Xatom.h
XA_CARDINAL = 6
XA_STRING = 31
XA_WINDOW = 33


class WMCtrl:
    def __init__(self):
        self.display = xlib.XOpenDisplay(None)
        self.root = xlib.XDefaultRootWindow(self.display)
        self.xatom_net_wm_name = self.get_xatom("_NET_WM_NAME")

    def get_xatom(self, name, exists=False):
        only_if_exists = X_TRUE if exists else X_FALSE
        xatom = xlib.XInternAtom(self.display,
                                 asbytes(name),
                                 only_if_exists)
        return xatom

    def get_property(self, window, xatom_property_type, property_name):
        return_xatom_property_type = xlib.Atom()
        return_c_int_format = c_int()
        return_c_ulong_nitems = c_ulong()
        return_c_ulong_bytes_after = c_ulong()
        return_c_ubyte_p_property_orig = pointer(c_ubyte())

        if window is None:
            window = self.root

        xatom_property_name = self.get_xatom(property_name, exists=True)
        if xatom_property_name == 0:
            return None

        status = xlib.XGetWindowProperty(self.display,
                                         window,
                                         xatom_property_name,
                                         0,
                                         MAX_PROPERTY_VALUE_LEN // 4,
                                         X_FALSE,
                                         xatom_property_type,
                                         byref(return_xatom_property_type),
                                         byref(return_c_int_format),
                                         byref(return_c_ulong_nitems),
                                         byref(return_c_ulong_bytes_after),
                                         byref(return_c_ubyte_p_property_orig))

        if status != X_SUCCESS:
            print("Can't get {} property. ({})",
                  property_name,
                  status)

        if xatom_property_type != return_xatom_property_type.value:
            print("Invalid type of {} property. ({} != {})".format(
                  property_name,
                  xatom_property_type,
                  return_xatom_property_type.value))
            xlib.XFree(return_c_ubyte_p_property_orig)
            return None

        bytes_per_item = return_c_int_format.value // 8
        if bytes_per_item == 4:
            bytes_per_item = sizeof(c_ulong)

        property_size = (bytes_per_item * return_c_ulong_nitems.value)
        # +1 is for NUL termination
        return_c_char_p_property = c_buffer(property_size + 1)
        memmove(return_c_char_p_property,
                return_c_ubyte_p_property_orig,
                property_size)
        xlib.XFree(return_c_ubyte_p_property_orig)

        return return_c_char_p_property

    def get_window_list(self):
        c_window_p_client_list = self.get_property(self.root,
                                                   XA_WINDOW,
                                                   "_NET_CLIENT_LIST")

        if c_window_p_client_list is None:
            c_window_p_client_list = self.get_property(self.root,
                                                       XA_CARDINAL,
                                                       "_WIN_CLIENT_LIST")

        if c_window_p_client_list is None:
            print("Can't get _(NET|WIN)_CLIENT_LIST property.")
            return []

        nitems = (len(c_window_p_client_list) - 1) // sizeof(xlib.Window)
        c_window_p_client_list = cast(c_window_p_client_list,
                                      POINTER(xlib.Window * nitems))

        window_list = []
        for window in c_window_p_client_list.contents:
            window_list.append(window)
        return window_list

    def get_window_title(self, window):
        text_property = xlib.XTextProperty()

        status = xlib.XGetTextProperty(self.display,
                                       window,
                                       text_property,
                                       self.xatom_net_wm_name)
        if status == 0 or text_property.nitems < 1 or text_property.value == 0:
            status = xlib.XGetWMName(self.display, window, text_property)
        if status == 0 or text_property.nitems < 1 or text_property.value == 0:
            return None

        c_char_ppp_title = pointer(pointer(c_char_p()))
        c_int_p_count = pointer(c_int())
        xlib.XmbTextPropertyToTextList(self.display,
                                       text_property,
                                       c_char_ppp_title,
                                       c_int_p_count)

        if c_int_p_count.contents.value < 1:
            return None
        return c_char_ppp_title.contents.contents.value.decode('utf-8')

    def get_window_titles(self):
        titles = []
        for window in self.get_window_list():
            title = self.get_window_title(window)
            if title is None:
                continue
            titles.append(title)

        return titles


def main():
    titles = WMCtrl().get_window_titles()
    for title in titles:
        print(title)

if __name__ == '__main__':  # pragma: nocover
    main()

Recommended Posts

Get the X Window System window title in Python
Get the desktop path in Python
Get the script path in Python
What is the X Window System?
Get the desktop path in Python
Get the host name in Python
Get the EDINET code list in Python
Get the title and delivery date of Yahoo! News in Python
Terms closely related to the X Window System
[Python] Get the files in a folder with Python
Get the weather in Osaka via WebAPI (python)
Get the caller of a function in Python
Get and convert the current time in the system local timezone with python
Get date in Python
How to get the files in the [Python] folder
Download the file while viewing the progress in Python 3.x
How to get the variable name itself in python
How to get the number of digits in Python
[Python] Get the numbers in the graph image with OCR
Get the size (number of elements) of UnionFind in Python
Get the value selected in Selenium Python VBA pull-down
Get the URL of the HTTP redirect destination in Python
Get YouTube Comments in Python
Get last month in python
Download the file in Python
Find the difference in Python
Get Terminal size in Python
[Python] Get the previous month
Explicitly get EOF in python
Get Evernote notes in Python
Get Japanese synonyms in Python
Get your heart rate from the fitbit API in Python!
Get the MIME type in Python and determine the file format
Get the number of specific elements in a python list
Get the value while specifying the default value from dict in Python
How to get the last (last) value in a list in Python
Automatically get the port where Arduino is stuck in Python
Get the current date and time in Python, considering the time difference
System trade starting with Python3: Get the latest program code
Get the index of each element of the confusion matrix in Python
Get Leap Motion data in Python.
Maybe in a python (original title: Maybe in Python)
Getting the arXiv API in Python
Python in the browser: Brython's recommendation
Get data from Quandl in Python
Save the binary file in Python
Hit the Sesami API in Python
Get the weather with Python requests
Get the weather with Python requests 2
In the python command python points to python3.8
Implement the Singleton pattern in Python
How to get the Python version
Get, post communication memo in Python
Hit the web API in Python
I wrote the queue in Python
Calculate the previous month in Python
Examine the object's class in python
Use OpenCV with Python 3 in Window
Access the Twitter API in Python
Python Note: Get the current month
The first step in Python Matplotlib