Skip to content


WinXP Desktop Shell Replacement, 2nd Release

[Update: Newer releases are available]

As a follow-on from the last release, I've uploaded the latest iteration of my EvilDesk shell replacement for Windows XP.

The changelog goes something like this:

  • much more robust installer, which should address most of the problems that were reported with the last release.

  • improved window state tracking. Console windows now respect the desktop state.
  • improved multi-mon behaviour.
  • improved window title display for really long window titles when alt-tab'ing.
  • new putty context menu plugin (read the docs for details)
  • experimental tweaks for changing the target slit and gravity for plugins (so you don't have to have everything on the right hand side of the screen). Registry editor required; no GUI. See docs for details.
  • fleshed out the Tweakage menu, making it more useful. The shell will use RunAs for the system level items, for those of us running without admin privileges.

Some random notes/comments:

  • During my debugging, I found that WinLogon doesn't like spaces in paths, so I've changed the default installation destination to \\EvilDesk on your system drive. Sorry about that, but it is a Windows bug.
  • You MUST have admin privileges to install the package; the installer will not modify your shell, so this is safe. Most people will be running with full admin rights anyway. (if you're one of those people, you should be reading The Non-Admin Blog)
  • Sadly, you can't use RunAs to invoke the control panel without invoking explorer.exe for the admin user. I'm open to suggestions. For now, control panel is launched using your privileges via explorer.exe.
  • On a related note, people running XP Home might not be able to launch the control panel. Tough luck, you should be running XP Pro anyway.
  • Gaim still has weird interactions (probably other gtk+ apps will do too). If you rapidly switch desktops, the gaim window sometimes gets mashed up, as though its layout engine has gotten confused. Not sure if thats my bug or GTK's bug. The workaround is to close the chat window when that happens.
  • Thunderbird/firefox: again, rapid desktop switching sometimes confuses them, causing them to not restore from minimized state properly. I have added code to spot this situation and make it less inconvenient. Again, not sure if (how?) it's my bug, or a bug in the toolkit used by those apps.
  • MSDN help browser: seems to do something funky with its top-level window (could be GDI+ related) that has some weird interactions when using the Win-W popup menu in evildesk.

If you want to download and install it, you should first read the following disclaimer:


The software is provided as-is in binary form only with no warranty. I am not liable for any bad mojo, be it mental, physical or meta-physical, that arises from its use. By downloading it, you assert that you will not decompile or reverse engineer the software in any way, and that you will not re-distribute it to any third party in any form without my express consent. All rights reserved; All liabilities disclaimed.


  • Download evildesk.msi
  • As the Administrator, install it, by double clicking on it. Note that this act will not change the shell of the administrator.
  • As the user(s) that plan to use the shell, login and run the "Set Shell" shortcut from the Start Menu:
    Start | Programs | Evil, as in Dr. | Shell | Set Shell
  • You should now reboot. Yes, rebooting sucks, but if you don't reboot, the Win-E will cause explorer.exe to load the taskbar. This is an issue with explorer.exe and affects all replacement shells. Sorry.
  • When next you login, you'll be running my shell.

EvilDesk 0.7 Released

Well, the bug fixes I made in the last release left me feeling empowered again, so I've followed up with a couple more features and some more bug fixes:

  • Added new MATCH CREATE window matching configuration option, which allows actions to be taken when windows are created. For example, you can cause all VMWare windows to start on a given workspace, or have your mp3 player automatically stick to all workspaces when it starts up.
  • Fixed a slit layout calculation bug.
  • Avoid blocking the flasher when the flashing app (eg: gaim 2.0 beta) hangs itself
  • Balloon tips now display the body of the balloon text, instead of the tooltip from the tray area, and will rise all the way to the top of the z-order.
  • Improved detection of deleted tray tooltips, so that balloon tips are not wiped out at the wrong time.
  • When a window was made sticky, it would remain in the minimized state on the inactive workspaces. This has now been corrected.
  • Fixed a string termination bug in the PuTTY plugin for sessions that have spaces in their names.
  • Environmental variables are now expanded when processing the MergeDirs directive in the .evdm file

EvilDesk 0.6 Released

Having finally found some time to myself, I thought it was about time that I push out a release with the changes that I've been using for the last few months. Some of these changes are based on feedback from users; keep it coming folks! :)

Feature changes in the new release include:

  • Revised plugin loading system. Slits are defined via the new SLIT directive and plugins are loaded via the new LOAD directive in the .evdm file.
  • Slit windows will hide themselves when an application goes fullscreen (and come back when it leaves fullscreen mode). I've tested this with PowerPoint (when viewing the slideshow) and PuTTY (alt-enter) and it seems to work ok.
  • Altered the gravity of the tray plugin so that it sinks below the flasher. Why? When double-clicking on the gaim tray icon, the first click would open the buddy list, causing it to flash. The flasher would bump the tray up, causing the second click to land on a different tray icon--usually the wifi icon, popping up the wireless network selector.
  • Added more internet-facing apps to the SaferExec line in the default configuration. No idea what that means? Read more on "Browsing the Web and Reading E-mail Safely as an Administrator".

Bug fixes include:

  • Fixed a race condition where windows could bleed across to the target workspace when switching workspaces.
  • Fall back to the system default icon in the task switcher and flasher if the window in question has no icon of its own.

Go get EvilDesk!

Merry Christmas!

MinimizeToTray + thunderbird == lower memory usage

Having just set up the mozilla calendar plugin to launch when I open thunderbird, I was wondering how much memory I was going to be sacrificing to the GUI god. I was very pleasantly surprised to see that thunderbird.exe was consuming less than 2MB when minimized.

I put this down to MinimizeToTray. My guess is that it's calling SetProcessWorkingSetSize(GetCurrentProcess(), 0xffffffff, 0xffffffff) to swap the process out of memory when minimized.

Yep, after a while, the memory usage creeps back up again, as thunderbird does its background bits and pieces, like checking mail.

Lesson of the day: PRINT in T-SQL has side effects

Not strictly PHP this one, but worth mentioning to the people that use SQL Server from PHP. This is an example based on a stored procedure that I spent too much time debugging today:

   IF @@ROWCOUNT = 0

This always fails--why? The PRINT statement resets the @@ROWCOUNT variable back to 0. Nasty eh?

On a related note, PRINT generates 'SUCCESS_WITH_INFO' diagnostic records (one for each print call), so you can capture the output over ODBC. A gotcha here is that, with SQL Server at least, once you start pulling diagnostic records, you must pull them all, otherwise you block your database connection, leading to invalid cursor state errors when you try to fetch.

Double bad day for PRINT in SQL Server for me.

redefining malloc() is evil

Especially when it's in an external library, like say, perl. It's a recipe for heap corruption when pointers allocated by the real malloc are passed to the redefined free and vice versa. Nasty. Whats worse is when that library redefines malloc(), free(), calloc() and realloc(), but leaves strdup() alone.

Guess how long it took to track that one down on windows, where valgrind is sorely missed? Too long, that's how long.

How did we live without valgrind!?

PDO goes stable

With the release of PHP 5.1 stable, it's time to mark PDO as stable too. I've pushed the first batch of stable PDO extensions out to PECL.

Now that PDO 1.0 is out the door, we can start looking at new features. I've put together a preliminary list of things, which can be found on the tip of the 5.1 branch.

Reasonable suggestions for additions to this list are welcomed. I'm not sure that everything on that list will make it into PDO 1.1, because I'd like to keep the development cycle shorter, rather than longer. There's always PDO 1.2 to think of, after all.

Happy PDO'ing!

Calling SQLBindParameter() to bind SQL_TIMESTAMP_STRUCT as SQL_C_TYPE_TIMESTAMP, avoiding a datetime overflow.

I've spent a lot of time with ODBC recently, working on interfacing our PostalEngine product with SQL Server. One of the things that caused me a LOT of trouble is binding date/time values into our stored procedure calls. One of the reasons that it was hard work was lack of documentation-by-example.

This particular part of the product is a SOAP server implemented using gSOAP, and since we need to be able handle people born before the 70's, we're using ISO8601 strings for datetime values rather than the unix time_t type. We parse the string (watching out for timezones) and store it into a SQL_TIMESTAMP_STRUCT and bind a pointer to that struct as an input parameter.

Our ODBC code tries to be smart by not being too smart; when we call out to the stored procedures in the database, we bind parameters using the best fitting native C types for the data we're sending in. For each parameter in the procedure, we execute the following code:

    SWORD sqltype, ctype, scale, nullable;
    UDWORD precis;
    struct odbc_param *param = &params[i];
    SQLDescribeParam(stmt->stmt, i+1, &sqltype, &precis, &scale, &nullable);
    SQLBindParameter(stmt->stmt, i+1, param->inout, ctype, sqltype,
         precis, scale, (SQLPOINTER)param->buf, param->size,

The timestamp parameters are already set up with param->buf pointing to the SQL_TIMESTAMP_STRUCT, ctype is set to SQL_C_TYPE_TIMESTAMP, and param->size is set to sizeof(SQL_TIMESTAMP_STRUCT).

When this is run, the execute fails with SQLSTATE 22008, Date time overflow. Everything looks correct in the code, and most of the values we're passing are based on the description of the parameter provided by ODBC, so what's going on? It took me several hours to figure out the answer; it's the precision field screwing things up. The driver reports a precision of 16, which is the size of the timestamp structure in bytes. However, when binding the parameter, ODBC wants the precision to reflect the number of human-readable characters; 16 is too small, so the datetime would overflow it. I found that 22 is the magic number; I now have code like this:

    SWORD sqltype, ctype, scale, nullable;
    UDWORD precis;
    struct odbc_param *param = &params[i];
    SQLDescribeParam(stmt->stmt, i+1, &sqltype, &precis, &scale, &nullable);
    if (ctype == SQL_C_TYPE_TIMESTAMP) {
        precis = 22;
    SQLBindParameter(stmt->stmt, i+1, param->inout, ctype, sqltype,
         precis, scale, (SQLPOINTER)param->buf, param->size,

and my datetime values can be bound and passed correctly.

Interestingly, you don't have this same problem when using SQLBindCol() to bind rowset cols to a SQL_TIMESTAMP_STRUCT.

So, there we have it; I posted this here in the hope that it saves someone else a few hours of scratching around in the ass-end of google's search results.

Why no ACAP?

So, I'm sitting here swearing at my "big three" applications; Gaim, Firefox and Thunderbird, because they work great if you only ever use a single machine, but are a real pain in the ass if you move between machines.

The biggest pain is Gaim; it stores its buddy list configuration in a local file, and when it starts up, it merges the buddy lists with the server side data. If you have two or more machines, only one of them will have the most up-to-date settings. If you've done a lot of reorganization to your buddy list, and switch to one of the other machines, you'll have all your buddies re-added to the server-side buddy list in their old locations, and also have the new locations from the server-side added to the local copy on disk. You need to re-organize the buddy list again. Repeat for each additional machine that you use.

Thunderbird and firefox are in a similar boat; even using IMAP, it's "impossible" (as in, too damned painful) to keep your thunderbird message filtering rules and junk mail training data synchronized. Firefox; it would be nice to sync passwords and Sage feed lists between machines.

So, why isn't there some magical way to look after this? Surprisingly, there is actually a standard protocol for exactly this purpose; it's called ACAP:

ACAP is the Application Configuration Access Protocol, an internet protocol for accessing client program options, configurations, and preference information remotely. ACAP is a solution for the problem of client mobility on the Internet. Almost all Internet applications currently store user preferences, options, server locations, and other personal data in local disk files. These leads to the unpleasant problems of users having to recreate configuration set-ups, subscription lists, addressbooks, bookmark files, folder storage locations, and so forth every time they change physical locations.

This sounds perfect, but why don't "the big three" apps support it? It might have something to do with the lack of ACAP implementations. Where are they? Is anybody working on them? Is anybody selling them? What sounds like a great idea from 1997 still isn't here with any real presence in 2005. What went wrong?

I've had a quick look at the ACAP RFC and it seems overly complicated (it's based on IMAP--urgh!) so perhaps this has something to do with it. Maybe there just wasn't enough demand for ACAP, so it got shelved.

Whatever the reason, I wonder why we don't have even a simplified ACAP-style preferences server or service today. Am I just looking in the wrong places?

Oracle's PHP Developer of the year 2005

You may remember that Oracle Magazine were asking for nominations to honour people that have worked in some Oracle-related facility over the past year. Someone nominated me for the "PHP Developer of the year" category, and I "won" the award.

I'm normally a bit sceptical of awards because they're somewhat arbitrary and tend to be rather intangible, however, Oracle Magazine have gone the whole nine yards to counter that intangibility aspect; I received this weighty chunk of glass as the physical manifestation of the award:

I suspect that I won the award as a result of my efforts in developing PDO, the PDO OCI driver and my role in the redesign of the oci8 extension. Antony Dovgal has put in a similar amount of effort (perhaps even more) in his work on Oracle support in PHP. As far as I'm concerned, Tony, both our names are on the award :)

If you're really interested, you can read the blurb from the magazine here; paraphrasing-for-print has slightly twisted my words, but it's more or less what I said.

Some observations about that chunk of glass:

  • it's difficult to photograph
  • it's invisible on my white mantelpiece
  • it's waaay too easy to get finger and palm prints all over it