2007-09-19

Tray icons in DWM

DWM is a pretty neat window manager—it's very flexible and can be customized exactly to suit one's needs: I've tried several dynamic tiling managers, and so far only DWM doesn't get in my way and actually is more comfortable than traditional WMs.

Configurability of DWM may sound strange to some: there is none. All customization is done through editing the source code. This may seem daunting, but it really isn't: there isn't much code (below 2000 SLOC) and it's all so clear, that even someone without a background on X11 can get to hacking and learn something in the process. Plus, many immediately configurable settings are gathered in a single header file.

Given this, even though DWM may be mean-and-lean, it's easy to extend it with any feature one would dream of. For me, it was system tray support (you know, those little 22×22 applets docked near your clock). As I'm a KDE user, I'm very much used to little conveniences such as klipper. (Yes, that's right, I'm using DWM with KDE, just by setting KDEWM environment variable.) And vanilla DWM can't handle those properly—every tray icon gets its own ordinary window, which, as you can see, isn't terribly convenient:

[a screenshot showing tray icons on vanilla DWM]

To fix that, we first need to see how tray icons are handled in X11. It turns out there is no single standard of doing that. Well, there is the FreeDesktop one, but it's overly complicated and (fortunately) not really used (yet), except maybe for Gnome. KDE3 and third-party apps use a simpler method: they set certain window property, which get spotted by the WM (or other program) which then position and/or swallow the window accordingly.

Two atoms seem to be used for that: WM_WINDOW_ROLE set to Tray Icon and _KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR pointing to the parent window. Some programs use both.

So all we need to do is spot appropriate properties and check if they're right; if either the role is tray icon, or the KDE-specific property is set at all, we can put the tray window to the right side of the screen. Here, much better:

[improved tray handling]

2007-09-11

Cross-compiling Qt4/Win on Linux

I've recently been playing rediscovering Qt with Qt4. Qt is a great cross-platform programming environment for GUI apps—the best I've used, at least. Anyway, as I put my app together, I thought that I should release it as a windows binary as well—both to use (and test!) this Qt cross-platform compatibility and because I expect the majority of the tool users would be windows users. Alas, I don't have a working windows os (I do have installed some 120-day evaluation version of win2003 somewhere, but even if it works, which I doubt, given some hardware changes since I've last launched it, I think the evaluation period is over anyway), let alone windows development environment.

So, I immediately thought about cross-compiling. Cross-compiling is compiling for another target platform than the host platform (where we run compilation). Generally, mingw32-gcc works marvels for linux-to-win32 cross-compilation and I've been using it some time ago for GTK apps. That was easier, because GTK/win is available in compiled form, so I just needed to cross-compile my own apps. For Qt4/win, no mingw32 binaries are readily available. So I had to take a go at compiling it myself.

After some googling, I've found that although some people tried to achieve that, none of them succeeded. This turned me down a little bit, but I'm not a person which would run away from a challenge.

The main problem with this cross-compilation is Qt4 build system. Although it seems to support cross-compilation, it really can only do it out-of-the-box between different Unix platforms. Since mingw32 is not strictly a Unix platform and different codepaths need to be compiled, this approach fails. So what we need to do is trick the buildsystem into thinking that win32 codepaths apply to unix.

Without further ado, I present you with step-by-step instructions on how to cross-compile Qt4/win. First, though, you need to obtain cross-compiling environment. On gentoo it's simple. Run the following as root:
emerge crossdev
crossdev -t mingw32

Well, simple as it may be, compilation was broken on my box on one step. It seems that it didn't find the right compiler to compile win32api. What was needed is
gcc-config mingw32-4.2.0
source /etc/profile

After that I've re-issued the crossdev -t mingw32 command and everything worked.

Now for the compilation of Qt4/win. You need to have Qt4/X11 already installed on your system, as we'll use Qt utilities already available on the system. Make sure you don't the following as root, because the build system for some reason insists on installing DLLs in /usr/bin (doesn't break if it can't, so no harm).

  1. Get Qt4/win sources. I've used 4.3.1 edition.
  2. Unpack them. Enter the directory.
  3. Trick the buildsystem into using win32 feature files:
    cd mkspecs/features
    ln -s win32 unix
    cd ../..
  4. Download the patch. Apply it.
    patch -p1 < qt4win-cross-mingw.patch
  5. Make sure qmake doesn't use compilation paths meant for unix
    find src -name '*.pro' -o -name '*.pri' | xargs sed -i -e 's/\(^\|[^_/]\)unix/\1linux/g;'
  6. Make qmake use compilation paths meant for Windows.
    find src -name '*.pro' -o -name '*.pri' | xargs sed -i -e 's/\(^\|[^_/]\)win32\([^-]\|$\)/\1unix\2/g;'
  7. Trick configure into using system utilities. for f in moc rcc uic qmake; do ln -s `which $f` bin; done
  8. Make sure we don't build them:
    echo qmake: > qmake/Makefile.unix
    for f in `ls src/tools`; do echo TEMPLATE = subdirs > src/tools/$f/$f.pro; done
  9. We need some files from the Qt/X11 source, specifically configure script and config.tests directory. I've got them from my qt-copy (from KDE repo); you can use that or download Qt/X11 sources. Put them in the main source directory.
  10. Configure. These options are carefully chosen not to break compilation (except -prefix and -prefix-install; the latter doesn't seem to work anyway).
    ./configure -prefix $PWD -xplatform win32-g++ -no-largefile -exceptions -no-accessibility -no-qt3support -make libs -prefix-install $PWD -no-reduce-exports
  11. Make a missing makefile.
    qmake -spec $PWD/mkspecs/win32-g++ src/winmain/winmain.pro -o src/winmain/Makefile
  12. Build.
    make
  13. Done!

If you have Wine, you can even test it. Just make sure the dlls are where Wine can find them: cp /usr/mingw32/usr/bin/mingwm10.dll lib/*.dll /home/divide/.wine/c/windows/system/. To compile the demos, do
cd demos
qmake -spec ../mkspecs/win32-g++ demos.pro
make
wine demos/affine/affine.exe

A screenshot of affine Qt4 demo under wine

The only problem I've found with using Qt4 in wine is that the latter doesn't implement some font handling functions and it sometimes breaks font rendering where text is laid out dynamically—esp. QGraphicsView and QTextEdit seem to be the worst offenders.