Piero V.

Zbar, GTK and Python 3

Last week, we had Hackweek at Tor. Instead of doing what we usually do, we worked on small Tor-related projects for 5 days. I chose to work with intrigeri and boyska from Tails to help them improving the pluggable transports on Tails.

There are several ways to get Tor bridges. One is requesting them to BridgeDB through email. The answer contains the bridge lines both as text and in a QR code. So, the first objective of our Hackweek team was to create a proof-of-concept to load these QR codes through a webcam.

Tails’s connection wizard is written in Python. A trivial approach is capturing frames from the webcam with OpenCV, then decoding them with pyzbar. This would have worked, but Tails is a live system, and an OpenCV installation requires a lot of space.

I soon discovered that OpenCV is the de facto way to deal with webcams in Python. I have tried some other alternatives, but they did not work. I have also tried to use a low-level approach based on the V4L2 API. But dealing with the conversion of color spaces and creating a generally reliable solution is burdensome.

However, the Zbar project is very modular: it contains a decoder library, some wrappers, bindings for other languages, and higher-level GTK/Qt widgets that developers can use to add a QR code functionality to their apps!

The GTK widget has Python bindings, too, and it exactly is what Tails’s Tor Connection assistant could use since it is written in Python with the GTK. However, my initial impression was that they were only for GTK 2 and Python 2, because so I read in the configure.ac. Indeed, I found a python-zbargtk package in old Debian versions.

The truth is that:

  1. I did not know that GIR is a specific term for binding-related things with GTK and GLib stuff;
  2. I had missed the Python 2 or 3 GIR Gtk widget section on Zbar’s README.

So, if you are in a similar situation, the correct way to solve this problem is using something like this code:

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('ZBar', '1.0')
from gi.repository import Gtk, ZBar

def decoded_text(zbar, text):
    print('Scanned text:', text)

win = Gtk.Window()
win.connect('destroy', Gtk.main_quit)
zbar = ZBar.Gtk.new()
zbar.connect('decoded_text', decoded_text)
zbar.set_video_device('/dev/video0')
win.add(zbar)
win.show_all()
Gtk.main()

You can have a look at tests/test_gi.py in Zbar’s repo to get a complete example of how to use the widget object.

Sadly, it does not work on Debian because Zbar needs the libgirepository1.0-dev package at build time to generate ZBar-1.0.typelib. I have asked the maintainer to include it for the next release. I think it will not add dependencies: I have tried to use the generated typelib with the GI_TYPELIB_PATH environment variable and libzbargtk0 from Debian repos, and it worked.

Before finding this information, my solution was to create a Python module just to call zbar_gtk_new. Any GtkWidget is also a GObject, and you can pass it from C to Python by using pygobject_new (an API of PyGObject): pygobject_new(G_OBJECT(zbar_widget)). However, the resulting Python object will lack all the specific methods of the C object. In this case, it was not a big deal because the video device can be set also through properties (e.g., zbar.props.video_device = '/dev/video0'). Signals are independent from the typelib as well. The only missing features were the decode of GdkPixbufs, and the possibility to set the video size.

But GObject Introspection is the correct solution, and a powerful one, I must say.

Anyway, diving into this whole machinery was a lot of fun for me. My travel was much longer than needed and very indirect, for sure. But I have learned a lot, and I hope my errors can be useful to someone else 😄️.