Posted: September 3, 2012 | Author: Aurélien | Filed under: KDE | Tags: kidmp |
I have (too) many side projects. Many won’t ever be formally released. Today I am releasing one of them: KidMP, a kid-friendly media player.
This project started with an unusual need: for a long time, my daughter Clara has been watching movies on my laptop while we were traveling. Depending on who was driving, either my wife or I would start the movie on the laptop for her, then pass her the laptop once the movie has started.
This was not the most practical setup, but it worked reasonably well. A few years ago however, a schedule conflict prevented me from going on vacation with the rest of the family. As my wife would be driving alone, there was no way she could setup the laptop for my daughter. The day before they left, I started a quick hack: a media player which would be usable by someone who can’t read, and is not (yet!) computer-fluent. KidMP was born.
Over time, I worked on and off on this project, improving it after each family trip. Today I think KidMP is in a good-enough state to be released. It has been battle-field tested during several trips, used by the whole family, I believe it works reasonably well.
Quoting the README, here are the features which makes KidMP a kid-friendly player:
- Large, customizable thumbnails
- No need to be able to read
- Fully usable with only 3 keys: left arrow, right arrow and space (or return)
- Automatically resume playing at last position
- Start in fullscreen by default, difficult to quit by accident
It is designed to be used on a laptop in situations where using the touchpad is not easy: laptop is not on a table and kids are not precise enough with a touchpad. That is why it is primarily designed to be used with the keyboard.
Early versions used covers from IMDB, but I realized kids have difficulties recognizing them, so I added a way to grab screenshots from the actual movie and use them as thumbnails. I first thought I would have to do all screenshoting myself, but it turns out kids love this feature: every once in a while, Clara will go through the movie collection and change all the pictures!
Selecting movies is done through a cover-flow like strip:

When a movie is playing, pressing Space pauses playback and brings a toolbar at the bottom of the screen:

The application is written in PyKDE4, using a mix of QML (for the movie selector) and regular widgets. You can get a tarball from kde-apps.org or clone the git repository.
I hope your kids enjoy KidMP!

Posted: August 8, 2012 | Author: Aurélien | Filed under: Gwenview, KDE |
Now that KDE Applications 4.9 has been released, it is about time I blog about what changes you will find in Gwenview 2.9.
New Fullscreen Appearance
Appearance of fullscreen mode has been reworked. It no longer uses custom css-based widgets for the fullscreen toolbar. Gwenview now uses your current widget style, with a dark color scheme. I also added a subtle twist to the regular widgets in the form of some light shadows and highlights.


Another change in this bar is the use of square thumbnails. Gwenview 2.8 used to show classic scaled-down thumbnails, but this resulted in space loss as there were empty areas around each thumbnails. Starting with 2.9, thumbnails are also cropped to fit in a square, making better use of the limited available space.
The fullscreen color scheme is a copy of Obsidian Coast theme (to avoid dependency headaches), but you can use any color scheme you want. Because of lack of time, the option is hidden for now, so you have to use kwriteconfig to set it.
For example, here is how to use the “Wonton Soup” theme:
kwriteconfig --file gwenviewrc --group FullScreen --key FullScreenColorScheme WontonSoup
The name of installed color schemes can be found in $KDEDIR/share/apps/color-schemes/, where $KDEDIR is your KDE installation folder (usually /usr). You can also pass a full path to a custom color scheme if you want.
To reset to Gwenview default color scheme, use:
kwriteconfig --file gwenviewrc --group FullScreen --key FullScreenColorScheme ""
Fullscreen Browse
This is probably one of the most important changes ever made in Gwenview. Before 2.9, Gwenview featured three different modes: browse, view, fullscreen. This is no longer the case in Gwenview 2.9: fullscreen is independent. This means you can now go fullscreen in browse mode.

Going fullscreen while browsing gives you a more immersive experience while you go through your pictures. It is quite nice on your regular computer, but makes even more sense when you connect your laptop to the big TV in the living room to show pictures to your guests.
Interestingly, this feature has been a long time coming. I had the idea in mind for at least two years. When I read iPhoto 11 announcement I felt like “Damn, now if I implement this, people will say I am just copying Apple!”. I gave a first try at implementing it for Gwenview 2.8, but got stuck, decided to go for the transitions instead. For 2.9 I was able to resurrect and finish the work I started six months ago.
This change implied a few keyboard shortcut changes. Here are the new shortcuts:
- F11: Toggle fullscreen on and off
- Enter: Toggle between browse and view mode
- Esc: Leave fullscreen (this one will change in 2.10, see bug 302898)
Auto-hiding Bird-Eye View
Gwenview 2.8 got rid of scrollbars on images, and introduced a bird-eye view to pan through the image. The bird-eye view is handy, but it is in the way of the image. In Gwenview 2.9, it now automatically hides itself after a short delay, showing back only while zooming or scrolling.
New Appearance for Dragged Images
When you drag images to a folder you now get a nicer preview: images are spread like a fan, giving a more natural feeling.

Optional Lock-Zoom Feature
This is a late addition, an hidden feature I am not satisfied about yet.
Before 2.8, Gwenview used to keep the zoom and position: if you were viewing image PICT0001.jpg, zoomed to 400% and scrolled to the bottom-right corner, then when you went to PICT0002.jpg, Gwenview would keep the zoom at 400% and try to keep the scroll position to the bottom-right corner.
This changed in 2.8, an unwanted side-effect of a code rewrite to make it possible to implement transitions. Bug 291759 was filed, asking to restore remembering of zoom state and position.
With the help of others I came up with a new implementation of this feature, but after using it for a few days, I realized I personally preferred when Gwenview reseted the zoomon image changes. Thinking more about it and discussing it with others, we realized locking zoom and position makes sense when you are triaging imported images, but does not make sense when you are showing images around. Problem: both use cases are valid use cases of Gwenview :/. Since release deadline was approaching I decided to come up with the following workaround:
The default behavior remains the same as Gwenview 2.8: zoom is not locked by default. A new hidden config option has been added to toggle this behavior. You can enable it with:
kwriteconfig --file gwenviewrc --group ImageView --key ShowLockZoomButton true
When this option is enabled, a padlock button appears to the right of the zoom slider. When the padlock button is checked, zoom and position are locked when browsing images.

As I said, I am not fully satisfied with that: the zoom widget is already quite cluttered, and I don’t like adding more clutter to it. We’ll see if I can come up with something better/smarter for 2.10.
That’s it for this version, hope you will enjoy Gwenview 2.9!

Posted: August 5, 2012 | Author: Aurélien | Filed under: Misc., Tips | Tags: arccos, cars, disney, dvd, linux, rip |
Latest addition to my kids DVD library is the Cars 2 DVD. They usually watch movies using a small PC running XBMC, so I started to rip it. I use either K9Copy or Ogmrip to rip DVDs, both get the job done without problem these days.
Not so lucky
Unfortunately Cars 2 DVD is different: Even if it can play it, VLC lists 99 titles on the DVD, which sounds a bit too much. K9Copy crashes on it. Ogmrip gets stuck when it tries to rip any track.
As I discovered later, Disney DVDs use a new protection system named ArccOS. Searching on the Internet I realized I was not the only one having this problem, but appart from similarly unlucky users, I only found Windows shareware applications claiming to be able to rip those Disney DVDs.
Delusional
At this point I was desperate enough that I started a Windows XP VM and tried a few of these applications. Two of them failed, but the third one correctly ripped Cars 2… With a large watermark, it’s a shareware after all, there is got to be a catch. As I couldn’t find any other solution, I reluctantly paid 40€ for the full version :(.
Note that I am not extremist to the point of refusing to pay for software, but I have a problem with paying 4 times the price of a legally bought DVD to be able to exercise my right to make a copy of it.
Hope after all

Shortly after paying, I switched to another tab of my browser and noticed a search I made actually uncovered an interesting forum thread. This is where I learnt about ArccOS, but also where I discovered HandBrake, a free software, multi-platform, video transcoder application I have never heard about… I installed it from the Ubuntu PPA, tried it… You guessed it: It can rip Cars 2 DVD without any problem…
So as a conclusion, even if HandBrake documentation claims it is not a DVD ripper, if a DVD does not let itself get ripped, do not capitulate before giving HandBrake a try. You could very well save a few dozen euros in the process…

Posted: July 11, 2012 | Author: Aurélien | Filed under: Gwenview, KDE | Tags: akademy, bugzilla, tallinn |
I was in the beautiful town of Tallinn last week, attending Akademy 2012. It was great to hang out again with other KDE developers around a laptop or a drink.

We had an awesome Karaoké night which started slowly but got a lot more fun when MC Aaron grabbed the microphone and convinced us to sing… Sébastien Renard and I even thought we could sing “Born in the USA”. our interpretation was later described to us as “courageous” :). There are pictures of us in action, hopefully no video of this performance is online…
On a more serious note, I noticed a recurring topic during this Akademy: Bugzilla. Jeroen van Meeuwen delivered a great presentation explaining the way Kolab Systems AG uses Bugzilla and how KDE could benefit by doing the same, we also had further discussions on how to take advantage of milestones, setting appropriate bug statuses, or integrating bugs.kde.org with our wikis. All those discussions got me motivated to improve my Bugzilla skills. In particular I’d like to try using milestones for the next releases of Gwenview… we’ll see how it goes.
My BoF session about going the Extra Mile went well. It felt great to see many contributors willing to give a hand there. I plan to write another blog post to get things started once I am done with a few preliminary tasks.
Another very interesting session for me was the color management one. I think I am finally starting to understand what this is all about. After the session I spent part of the afternoon with Boudewijn Rempt of Krita fame, working on adding color management support to Gwenview. It is not done yet, but I am confident it should be ready for KDE SC 4.10.
Finally, after a busy week, my wife joined me on Saturday and we went for some sightseeing over the week-end.

Russian dolls and jewelry

Invulnerability!



(More pictures available on Flickr)
Posted: June 28, 2012 | Author: Aurélien | Filed under: KDE | Tags: akademy, bof, bugs, extramile, ui |

Humps for 1 mile by Wendy on Flickr
If you have been reading my blog for a while, you know I tend to get obsessed with fixing little details in KDE applications which annoy me. I like doing this, but this is the kind of task which cannot be done by a lonesome coder if one wants to make a significant difference.
Other free software projects have launched initiatives in the past to get those little details ironed out. You may have heard about Ubuntu Papercuts, Fedora Fit and Finish or Gnome Every Detail Matters initiatives.
I want to start a similar initiative for KDE. To do so, I registered a BoF this year at Akademy, named “the Extra Mile”. The goal of this BoF is not to get started at fixing bugs right away, but rather discuss how we can organize ourself to make this initiative a success. I want to discuss topics such as:
- Which communication tool should we use (Bugzilla, Community Wiki, Forum, Mailing Lists?) and how should we use it
- How to approach those bugs. Should we work on many apps at a time, fixing common bugs? Should we identify and fix as many bugs as possible within one app? Or should we go for a mix of both?
- What is the best way to engage with application developers and raise awareness on these kind of issues
We are going to need people with different profiles to get this to work: developers interested in fixing bugs in their and/or other applications, UI specialists, but also users willing to report and triage those issues.
If you are interested, the BoF is scheduled on Thursday 10:30, room 227. I also created a wiki page for it.
PS: Please do not use the comments on this blog post to report issues you want to get fixed. It’s just not going to scale 🙂 When the BoF is over we will announce how we are going to tackle those issues, at this time your input will be very much appreciated.

Posted: May 11, 2012 | Author: Aurélien | Filed under: KDE | Tags: toolbars, ui |
It’s been a long time since I last wrote about user interface mistakes in KDE applications. Here is a new addition to this series.
Starting with KDE 4.0, KDE applications ship with toolbars containing both icons and texts.
This setup makes it easier to understand the action associated with a toolbar button. Additionally, buttons with icon and text provide bigger mouse targets.
The price to pay for this is toolbars can become quite wide, to the point where they barely fit on 1024 x something screens (think netbooks or video projectors). To make matters worse, translations often increase text widths: French and German translations for example are known for being quite verbose.
If you maintain a KDE (or Qt) application, here are some simple steps you can follow to reduce the width of your toolbars without compromising too much.
Step #1: Do you really need that button?
This first step is quite obvious: go through your toolbars and check if some of the buttons present in the default configuration can be removed. If they are seldom used, maybe it is OK if their action is only reachable from the menubar?
You probably made such decisions while the application was created. Nevertheless it’s a good idea to revisit those decisions from time to time, based on your increased knowledge of the way your application is used.
Step #2: Icon-only buttons
Hide the text of some buttons. Since Qt 4.6, QAction has a “priority” property which defaults to QAction::NormalPriority. Set it to QAction::LowPriority to get an icon-only button in your toolbar. Peter Penz did it for Dolphin, I did the same for Gwenview. It obviously saves quite some horizontal space.

Dolphin Toolbar
I couldn’t come with strong rules to decide whether a button should show text or not. You have to use your gut feeling here. Nevertheless here are some hints to give you an idea of situations where it may make sense to use icon-only buttons.
- The icon is common enough that it does not need text to be understood. Media icons (play, pause, next, previous), print, zoom or trash buttons come to mind. There are probably others.
- The button is part of a button group and works as a radio button, for example the view modes in Dolphin (icon, compact and detailed), the format or alignment buttons in a word processor.
-
The action for this button is about manipulating the UI rather than an essential part of the task the user wants to accomplish. For example an action to toggle a sidebar.
- The action is part of a group of two opposed actions. In this case one of the two actions can often come without text, its meaning being defined by the other action. For example: Undo / Redo, Back / Forward.
Remember those are just hints: they may not make sense for your application. Additionally, make sure not to overdo it to the point of having only icon-only buttons!
Step #3: Shorten those texts!
For the remaining buttons which kept their texts, try to shorten the text to one or two words only, put a longer text in the tooltip. This serves two purposes:
-
It reduces the button width
-
It brings back value to the tooltip: don’t you hate it when you move the mouse hover a button and the tooltip which appears stupidly repeats the button text?

So the “View” button is about… “View”! Thank you Captain Obvious! (This is fixed for 4.9)
When defining the action text, make use of KUIT: mark the text using “@action:intoolbar”, This should give a hint to the translator that this string should be kept short.
When defining the action tooltip, remember the HIG says tooltips should be written using sentence capitalization, not title capitalization. QAction is not being very helpful there: when no tooltip is set, it falls back to using the “text” property as a tooltip, and the “text” property uses title capitalization :/. That is one more good reason to define tooltips yourself.
Conclusion
String freeze for KDE SC 4.9 starts on May 19th, so if your application is in KDE SC, you still have a few days left to check if your application toolbar could be improved!

Posted: April 21, 2012 | Author: Aurélien | Filed under: KDE | Tags: greeter, lightdm, qml |
During this Ubuntu cycle I have been working on-and-off on LightDM, mainly helping out David Edmundson on liblightdm-qt (the Qt wrapper for LightDM library), and the LightDM KDE greeter. The initial, quite ambitious, plan was to try to ship Kubuntu 12.04 with it by default. We quickly realized that would not happen, but we wanted to at least ensure LightDM KDE would be in a usable-enough state to be included in Ubuntu 12.04 archive.
So here it is, release 0.1.0 of this new greeter, featuring:
- 3 QML-based themes
- Guest support
- A System Settings module
- A few bugs (maybe) and design quirks (definitely!)
You can get the tarball from KDE download server or clone the GIT repository. (Note that you need LightDM itself for the KDE greeter to work. The LightDM daemon and Qt libraries can be downloaded from the LightDM project page). Kubuntu 12.04 users can get it with “apt-get install lightdm-kde-greeter”.
It still has some rough edges (it’s a 0.1.0 release after all!), but I have been using it for a few months now without trouble.
Enough talk already, here comes some screenshots:
“Classic” theme:

“User Bar” theme:

“User List” theme:

System Settings module:


We hope you like it!
Update: 0.1.1 is out, fixing an issue with user pictures not appearing in “User Bar” and “User List” themes. You can get it from download.kde.org.
Posted: April 11, 2012 | Author: Aurélien | Filed under: KDE | Tags: blue systems, canonical, fsday, kubuntu |
Hot on the heels of Jonathan, let me announce I am leaving Canonical at the end of the month, joining Blue Systems to work on KDE and Kubuntu.
I am quite excited by this new adventure, and looking forward to the beginning of next month.
Since I will be working 5 days a week on KDE and Kubuntu, I am going to stop asking for donations. Thanks a lot to all of you who supported my work during this period.
Posted: March 30, 2012 | Author: Aurélien | Filed under: Gwenview, KDE | Tags: bug, pcx, png, qt, tga |
A weird bug
In December last year, Falk Krönert filled a strange bug: Gwenview failed to load some PNG images, which other applications were able to display flawlessly. I couldn’t reproduce it back then with Gwenview 2.7.3, so couldn’t dig any further.
Falk did not give up so easily though. After further investigations, he discovered the bug only happened with images whose width would be 2560 or 2808. Since I still couldn’t reproduce it, he provided me with a QEmu image of his openSUSE installation so that I could investigate (now that is one dedicated bug reporter!)
Investigating
Running the QEmu image, I was finally able to reproduce the bug. I started investigating, installing the necessary packages to build Gwenview and my beloved cgdb, learning how to use Zypper in the process.
Step one, an image format from the past…
I quickly discovered Qt was not able to discover the image format of the PNG. The code looked like this:
QBuffer buffer;
buffer.setBuffer(&mData); // mData contains the first 256 bytes of the image
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
mFormat = reader.format();
if (mFormat.isEmpty()) {
return false;
}
In the case of this image, “mFormat” was supposed to contain “png”, but it was empty instead.
In case you are not familiar with Qt image plugin code, it works like this: Qt provides a base class, QImageIOHandler, which image decoders must inherit from. so there is a QJpegHandler, a QPngHandler and so on. Some of these handlers are provided by Qt directly in QtGui.so, others are provided by Qt and kdelibs (and possibly others) as plugins installed in “plugins/imageformats”.
Here is QImageReader::format() code:
QByteArray QImageReader::format() const
{
if (d->format.isEmpty()) {
if (!d->initHandler())
return QByteArray();
return d->handler->canRead() ? d->handler->format() : QByteArray();
}
return d->format;
}
Debugger was telling me d->handler was correctly created by initHandler(), but QPngHandler::canRead() was never called!
Quite puzzled by this, I finally resorted to place a breakpoint in QImageIOHandler constructor. Once I hit the breakpoints, I printed the backtrace, and was astonished to find myself inside the PCX handler provided by kdelibs! This explained why QPngHandler::canRead() was not called, QImageReader was calling PCXHandler::canRead(), which unsurprisedly returned false.
This does not explain how Qt decided the image was a PCX in the first place. To decide which handler to use, initHandler() calls all implementations of QImageIOHandler::canRead(), the first one to answer true wins. PCXHandler::canRead() implementation is not very selective: it returns true if the first byte of the image is 10 (According to Wikipedia it should be possible to check at least the first two bytes, need to look into this).
Here is an hex-dump of the first bytes of the PNG image:
89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00 00 0a 00 00 00 00 02 08 02 00 00 00 1b 8e 12 |................|
There are a few 10 (0a in the dump) values but the first byte is definitely not 10.
Maybe the PCX loader is looking at the wrong position? I decided to place a breakpoint in PCXHandler::canRead() and print device->pos(). Sure enough, it sayd the position was 18, instead of the expected 0… If you look at position 18 (starting from 0) in the dump there is indeed a 0a value.
It is interesting to look at what this 0a stands for: The PNG specification tells us a PNG starts with an 8 byte signature, followed by a series of chunks. The first chunk must be an IHDR chunk. This chunk starts with “IHDR”, followed by four bytes representing the image width, in most significant byte order. This means the first four bytes in the second line of the dump (00 00 0a 00) are the image width, and indeed, 0a00 is the hexadecimal for 2560… Do you remember the bug was only happening for some image sizes? If the width had been 2559, the IHDR chunk would have started with 00 00 09 ff, and thus the PCX handler would not have identified this image as a PCX.
Another format gets in…
While the PCX handler could be improved to use more than one byte as a signature, it is not the real problem: had device->pos() been correctly set to 0, it wouldn’t have identified the image as PCX. QImageIOHandler::canRead() documentation explicitly says:
When reimplementing canRead(), make sure that the I/O device (device()) is left in its original state (e.g., by using peek() rather than read()).
Someone was not playing by the rules and was altering the device position before PCXHandler::canRead() was reached.
I was starting to suspect one of the other image handlers was doing something nasty. To try to isolate the culprit, I first removed all KDE image plugins but the PCX handler. No luck, still buggy. I then removed all Qt image plugins (PNG was still there as it is one of those built into QtGui.so) Bingo! The image was now being correctly identified. Using a quick dichotomy, I found out the TGA plugin was the nasty boy.
QTgaHandler::canRead() looks like this:
bool QTgaHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("QTgaHandler::canRead() called with no device");
return false;
}
QTgaFile tga(device);
return tga.isValid();
}
Looking at the code you can probably guess it: QTgaFile constructor does not take care of not altering QIODevice state. Looking at the Git history for this handler shows it was copied from QtQuick3D to Qt before the 4.8.0 release, which explains why there was no problem when running Gwenview on top of Qt 4.7.
It is a bit sad however that this code was brought in instead of using KDE TGA handler, which has been around for a while, does not suffer from this bug and have (limited) write supports whereas the Qt handler is read-only.
Fixing time
I worked around this bug in Gwenview by taking advantage of the fact that QImageReader accept an optional “format” parameter, which can be used as a hint: QImageReader will first try to use an image handler which supports this format.
I was not using this feature until now because I thought passing a format would make QImageReader fail if the image was not of the specified format. It turns out I was wrong: the format parameter is just a hint, if the image cannot be loaded with a handler implementing this format, QImageReader will try the others. I should probably propose a patch for the documentation to reflect that.
The real fix however is to teach QTgaHandler proper manners. I just posted a fix on qt-project.org, hopefully it will get in.
Gwenview 2.8.2 (from upcoming KDE SC 4.8.2), has the work-around in but if you haven’t upgraded yet, you can fix the bug by simply removing the TGA handler plugin. Look for a file named libqtga.so. It should be in:
- openSUSE (64 bits): /usr/lib64/qt4/plugins/imageformats/
- Ubuntu|Debian (64 bits): /usr/lib/x86_64-linux-gnu/qt4/plugins/imageformats/
(I don’t know the location for 32 bits systems)
So to summarize, a rogue TGA handler was tricking a sloppy PCX handler into identifying a PNG file into a PCX file, what a mess!

Posted: February 3, 2012 | Author: Aurélien | Filed under: Misc. | Tags: pyqt, webkit |
(This is part 2 of the PyQt+WebKit experiments series)
In Part 1 I described how to embed WebKit in a PyQt application and how to expose PyQt objects in WebKit and manipulate them with JavaScript.
Even if you are a great JavaScript master, you can’t avoid the occasional typo while writing JavaScript code in your application. This can be quite frustrating with QtWebKit because it likes to stay quiet: it won’t tell you about any error.
Let’s have a look at an example.
First here is loader.py, a simple Python script which loads a block of HTML:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QWebView(self)
layout = QVBoxLayout(self)
layout.setMargin(0)
layout.addWidget(self.view)
def main():
app = QApplication(sys.argv)
window = Window()
html = open(sys.argv[1]).read()
window.show()
window.view.setHtml(html)
app.exec_()
if __name__ == "__main__":
main()
And here is “broken.html”, our broken HTML code:
<html>
<head>
<script>
function brokenFunction(arg1, arg2) {
var result;
result = arg1 * 2;
result += arg2;
resul /= 4;
return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(brokenFunction(2, 3));
</script>
</body>
</html>
Notice the missing ‘t’ in “resul /= 4”?
The last-resort, grandpa-debugged-js-this-way, debugging tool is still there: the mighty alert() function. Just stuff your code with calls to alert() and be happy… Anyone ever wrote code like that?
<html>
<head>
<script>
function brokenFunction(arg1, arg2) {
var result;
result = arg1 * 2;
alert("1");
result += arg2;
alert("2");
resul /= 4;
alert("3");
return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(brokenFunction(2, 3));
</script>
</body>
</html>
Easy enough, no? With the great alert() function we can quickly pinpoint the bug in our brokenFunction() is between alert(“2”) and alert(“3”).
Can we do better?
alert()-style debugging gets old very fast. Clicking that “OK” button is a pain. Fortunately, there is a way to get more useful feedback from our PyQt application.
The job of the QWebView class is to show the content of a QWebPage instance. By default QWebView creates its own instance of QWebPage, but it is possible to replace this instance with our own QWebPage. The QWebPage class has a few virtual methods. Among them, the javaScriptConsoleMessage() method is the one we are looking for: it is called every time console.log() is called from JavaScript.
Here is an implementation of WebPage which uses Python logging module to get JavaScript console messages out:
import logging
from PyQt4.QtWebKit import *
class WebPage(QWebPage):
"""
Makes it possible to use a Python logger to print javascript console messages
"""
def __init__(self, logger=None, parent=None):
super(WebPage, self).__init__(parent)
if not logger:
logger = logging
self.logger = logger
def javaScriptConsoleMessage(self, msg, lineNumber, sourceID):
self.logger.warning("JsConsole(%s:%d): %s" % (sourceID, lineNumber, msg))
And here is “loader-log.py”, a loader which uses this class:
import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
from webpage import WebPage
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QWebView(self)
self.view.setPage(WebPage())
layout = QVBoxLayout(self)
layout.setMargin(0)
layout.addWidget(self.view)
def main():
app = QApplication(sys.argv)
window = Window()
html = open(sys.argv[1]).read()
window.show()
window.view.setHtml(html)
app.exec_()
if __name__ == "__main__":
main()
If we load “broken.html” with “loader-log.py” we get the following on stderr:
$ python loader-log.py broken.html
WARNING:root:JsConsole(undefined:0): ReferenceError: Can't find variable: resul
That should make it easier to find and fix our bug, even if we don’t get very useful file names or line numbers.
javaScriptConsoleMessage() receives all console messages. This means our logger will also print out calls to console.log(). Here is “console-log.html”:
<html>
<head>
<script>
function chattyFunction(arg1, arg2) {
var result;
result = arg1 * 2;
console.log("result" + result);
result += arg2;
console.log("result" + result);
result /= 4;
console.log("result" + result);
return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(chattyFunction(2, 3));
</script>
</body>
</html>
When loaded with “loader-log.py”, we get this output:
$ python loader-log.py console-log.html
WARNING:root:JsConsole(about:blank:5): result: 4
WARNING:root:JsConsole(about:blank:5): result: 7
WARNING:root:JsConsole(about:blank:5): result: 1.75
Not good enough?
Getting the output of console.log() is nice, but modern browsers have much more efficient tools: if you open “broken.html” with Rekonq and look at the output in the Web Inspector Console, you can not only see console output, but you can also easily inspect your HTML tree and many other things.

Like us, Rekonq uses QtWebKit, so is there a way to get a similar tool?
It is actually possible. Rekonq uses a class named QWebInspector. All that is necessary to get a nice inspector tool for our application is to:
- Get the QWebView page
- set the QWebSettings.DeveloperExtrasEnabled attribute on this page
- Instantiate a QWebInspector
- Pass the view page to the inspector
Here is “loader-webinspector.py”, a new HTML loader which can show a web inspector when one presses F12:
import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
from webpage import WebPage
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.view = QWebView(self)
self.setupInspector()
self.splitter = QSplitter(self)
self.splitter.setOrientation(Qt.Vertical)
layout = QVBoxLayout(self)
layout.setMargin(0)
layout.addWidget(self.splitter)
self.splitter.addWidget(self.view)
self.splitter.addWidget(self.webInspector)
def setupInspector(self):
page = self.view.page()
page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
self.webInspector = QWebInspector(self)
self.webInspector.setPage(page)
shortcut = QShortcut(self)
shortcut.setKey(Qt.Key_F12)
shortcut.activated.connect(self.toggleInspector)
self.webInspector.setVisible(False)
def toggleInspector(self):
self.webInspector.setVisible(not self.webInspector.isVisible())
def main():
app = QApplication(sys.argv)
window = Window()
html = open(sys.argv[1]).read()
window.show()
window.view.setHtml(html)
app.exec_()
if __name__ == "__main__":
main()
The four steps I described are done in the “setupInspector()” method.
And here is “console-webinspector.html”:
<html>
<head>
<script>
function chattyFunction(arg1, arg2) {
var result;
result = arg1 * 2;
console.log("result: %d", result);
result += arg2;
console.warn("result: %d", result);
result /= 4;
console.error("result: %d", result);
return result;
}
</script>
</head>
<body>
Complex computation:
<script>
document.write(chattyFunction(2, 3));
</script>
</body>
</html>
It is similar to “console-log.html” but takes advantage of two new features which do not work with the previous approach:
- printf-style formatting: that is, printing the value of
result with "result: %d", result, not "result: " + result
- log categorization: you can use console.warn() and console.error() to get different type of output. These methods worked with the previous approach, but the categorization was lost.
Loading “console-webinspector.html” with “loader-webinspector.py” and pressing F12 we get this:

Closing words
These two approaches should help you track down the nastiest bugs in your embedded JavaScript code. The Web Inspector approach is probably the most powerful one, but the Python logging approach can also be useful when tracking down bugs where it is more practical to have one single log output for both the PyQt and the JavaScript sides.

You must be logged in to post a comment.