Kivy Window Management on X11

In the previous post we observed the kivy.core.window.Window object and discovered Window management from the Kivy API was quite limited. Since I am working on Linux, and that Linux conventionally use X11 as their window manager, I thought I'd take a look at the Python-Xlib module and see if we could control our Basic Application's geometry. And the good news is: it worked out fine :)

I've not looked into all details; working with X is new for me and I'm not familiar with conventions. Nevertheless, by peeking at some example code here and there on the net, I managed to get hold of our window and change its size.

First, you'll need Python Xlib, which you can get with:
sudo apt-get install python-xlib

Then make a module, which we call kivyXwm.py (for Kivy X Window Manager):
#!/usr/bin/env python
 
from Xlib.display import Display
from Xlib import X
 
def resize(title=str, height=int, width=int):
    TITLE = title
    HEIGHT = height
    WIDTH = width
   
    display = Display()
    root = display.screen().root
    windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), 
        X.AnyPropertyType).value
    for windowID in windowIDs:
        window = display.create_resource_object('window', windowID)
        title = window.get_wm_name()
        pid = window.get_full_property(display.intern_atom('_NET_WM_PID'), 
        X.AnyPropertyType)
        if TITLE in title:
            window.configure(width = WIDTH, height = HEIGHT)
            display.sync()

As I said, I am still a novice with X, but let me try to explain the big lines of the above code:
Line 9 & 10: we get the X display and the root window (which I understand is the main window, which is occupied by your desktop usually).
Line 11: get a list of all windows on the display. This list, however will only be known by their IDs, which is an int, so:
Line  12 to 16: we create an abstract object representing each window and find out if their title match our application's title.
Line 17 & 18: we change the height and width value of our window and we update the display.

Then we modify our Basic Application code like so:
#!/usr/bin/env python

import kivy
kivy.require('1.0.6')

from kivy.app import App
from kivy.uix.button import Button
from kivy.logger import Logger
import kivyXwm

class CoolApp(App):
    icon = 'custom-kivy-icon.png'
    title = 'Basic Application'
   
    def build(self):
        return Button(text='Hello World')
   
    def on_start(self):
        kivyXwm.resize(self.title, 100, 100)
        Logger.info('App: I\'m alive!')
   
    def on_stop(self):
        Logger.critical('App: Aaaargh I\'m dying!')

if __name__ in ('__android__', '__main__'):
    CoolApp().run()


Now in spite of the default size of your Kivy application (defined in the config file at ~/.kivy/config.ini), we set a new size for the application of 100x100. It's not a pretty thing to do, as you can see, although it's fast, it's a two-step procedure: first, Kivy makes a 600x800 window, then X changes its size to 100x100. Truly, it's a job for the GUI to set the Window size property. But this control through Xlib is good for other stuff, like setting the window "always on top" or "skip taskbar" parameters.
Xlib is able to pick up the application window from its title. I am still a bit confused here as to why title is a class attribute and not an instance attribute, but you have to pass self.title in the kivyXwm.resize() function. This may not be the best solution, I wonder what happens if we'd run two instances of the same application with the same title. Windows have their own IDs, but I'm not sure as to how to find the window we want from the list of windows X displays. I'll have to look for an alternative. The bad news is: Python Xlib is poorly documented, there is not even a docstring in the module :(

But that's one small victory, let's see what  more can be done next time!

Kivy Window Management on X11

In the previous post we observed the kivy.core.window.Window object and discovered Window management from the Kivy API was quite limited. Since I am working on Linux, and that Linux conventionally use X11 as their window manager, I thought I'd take a look at the Python-Xlib module and see if we could control our Basic Application's geometry. And the good news is: it worked out fine :)

I've not looked into all details; working with X is new for me and I'm not familiar with conventions. Nevertheless, by peeking at some example code here and there on the net, I managed to get hold of our window and change its size.

First, you'll need Python Xlib, which you can get with:
sudo apt-get install python-xlib

Then make a module, which we call kivyXwm.py (for Kivy X Window Manager):
#!/usr/bin/env python
 
from Xlib.display import Display
from Xlib import X
 
def resize(title=str, height=int, width=int):
    TITLE = title
    HEIGHT = height
    WIDTH = width
   
    display = Display()
    root = display.screen().root
    windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), 
        X.AnyPropertyType).value
    for windowID in windowIDs:
        window = display.create_resource_object('window', windowID)
        title = window.get_wm_name()
        pid = window.get_full_property(display.intern_atom('_NET_WM_PID'), 
        X.AnyPropertyType)
        if TITLE in title:
            window.configure(width = WIDTH, height = HEIGHT)
            display.sync()

As I said, I am still a novice with X, but let me try to explain the big lines of the above code:
Line 9 & 10: we get the X display and the root window (which I understand is the main window, which is occupied by your desktop usually).
Line 11: get a list of all windows on the display. This list, however will only be known by their IDs, which is an int, so:
Line  12 to 16: we create an abstract object representing each window and find out if their title match our application's title.
Line 17 & 18: we change the height and width value of our window and we update the display.

Then we modify our Basic Application code like so:
#!/usr/bin/env python

import kivy
kivy.require('1.0.6')

from kivy.app import App
from kivy.uix.button import Button
from kivy.logger import Logger
import kivyXwm

class CoolApp(App):
    icon = 'custom-kivy-icon.png'
    title = 'Basic Application'
   
    def build(self):
        return Button(text='Hello World')
   
    def on_start(self):
        kivyXwm.resize(self.title, 100, 100)
        Logger.info('App: I\'m alive!')
   
    def on_stop(self):
        Logger.critical('App: Aaaargh I\'m dying!')

if __name__ in ('__android__', '__main__'):
    CoolApp().run()


Now in spite of the default size of your Kivy application (defined in the config file at ~/.kivy/config.ini), we set a new size for the application of 100x100. It's not a pretty thing to do, as you can see, although it's fast, it's a two-step procedure: first, Kivy makes a 600x800 window, then X changes its size to 100x100. Truly, it's a job for the GUI to set the Window size property. But this control through Xlib is good for other stuff, like setting the window "always on top" or "skip taskbar" parameters.
Xlib is able to pick up the application window from its title. I am still a bit confused here as to why title is a class attribute and not an instance attribute, but you have to pass self.title in the kivyXwm.resize() function. This may not be the best solution, I wonder what happens if we'd run two instances of the same application with the same title. Windows have their own IDs, but I'm not sure as to how to find the window we want from the list of windows X displays. I'll have to look for an alternative. The bad news is: Python Xlib is poorly documented, there is not even a docstring in the module :(

But that's one small victory, let's see what  more can be done next time!