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