Monday, December 29, 2008

Human Interface Guidelines


This is the recent talk I gave at an internal Training Day for developers of Swedbank.

Although Swedbank Estonia (formely Hansabank) has the best Internet bank in the region according to Global Finance Magazine, we still strive to develop our usability and user interface skills.

The talk was well received and was accompanied with some very nice slides, outlining the history of user interfaces, publicly available HIG documents, usability factors, comm0n design principles and other important points. Therefore it is worth reading for getting a compact introduction to the topic.

Human Interface Guidelines talk slides (PDF)



This time I tried using a minimalistic style for the slides without any (possibly) distracting backgrounds, inspired by Neal Ford's excellent talk "Ancient Philosophers & Blowhard Jamrobees" from the Agile 2008 conference. Neal's slides are mostly back with some nice stock pictures and maybe a few words; they help him talk and not just read what is written on the screen. This helps the listeners to concentrate on the performance of the speaker instead of being distracted by reading, and pictures help to visualise the concept, greatly increasing the influence on the audience. Of course, this kind of presentations need lots of skills and rehearsal from the speaker.

I haven't got as far with eliminating text (read: waste) from the slides, but to my mind this is anyway a great application of the simplicity principle I have talked about. Another principle I also tried to use in the slides (apart from the most important one - consistency) is the aesthetics - people like things that are visually appealing, so consistent style and reasonable animations can make a lot of sense. And please don't use these bundled presentation templates ever again :-)

Anyway, I hope that my slides will be useful and can at least spark some interest for researching this ultra important topic further. There are too many crappy user interfaces out there, so developers, keep this in mind!


Is the scanning of computer networks dangerous?

Looking through some presentations I have done during the last year, I have come upon a talk I gave at the Baltic DB&IS 2008 Conference in Tallinn, Estonia.

This bi-annual academic conference takes places usually in either Estonia, Latvia or Lithuania, but often involves speakers from other countries as well. This year it was convenient for me to participate and talk a little about my open-source networking tool Angry IP Scanner.

The talk was quite successful, although not very academic, but it makes sense to post the slides to give some interesting information on Angry IP Scanner.





(a nice picture of Tallinn's silhouette is a bonus)


Wednesday, November 26, 2008

Use LightZone from F-Spot as external editor

In a previous post I have shown how to teach LightZone, a non-destructive photo editor, to open files passed from the command-line (for some strange reason it doesn't do it out-of-the-box).

Now, I want to be able to use LightZone as an external editor from F-Spot, which I use for my photo workflow.

F-Spot has a convenient Open With menu when right-clicking on a photo, we just need to add LightZone to this menu. After some research, it appear that F-Spot uses the standard desktop entry specification files to populate this menu. These files can be located either in /usr/share/applications or in user's home, ~/.local/share/applications. You can use either location, but I prefer the latter one, because I have unpacked LightZone into my home as well.

Here is the working lightzone.desktop file:

[Desktop Entry]
Version=1.0
Type=Application
Name=LightZone Photo Editor
Exec=LightZone %u
TryExec=LightZone
Icon=/home/USERNAME/LightZone/LightZone_32.png
Terminal=false
Categories=Graphics;2DGraphics;Photography;RasterGraphics;GTK;
StartupNotify=true
MimeType=image/tiff;image/jpeg;image/x-canon-cr2;image/x-canon-crw;image/x-nikon-nef;image/x-pentax-pef;

This file expects that you have followed the previous post and already have LightZone in the PATH which accepts a filename to open on the command-line.
  • Exec - this line specifies what command to run, %u means to pass the selected file's URL on the command-line. For some reason with trial and error, I found that F-Spot is only able to pass URLs like this, specifying %f doesn't work with F-Spot. But if you look at the LightZoneOpener.java source, you will see that it supports URLs as well as filenames.
  • Icon - change this one to the full path of LightZone's icon, it supplied in the original archive.
  • Categories - this specifies where LightZone will appear in the ''Applications'' menu.
  • MimeType - here you must list all image mime types that you want LightZone to open. This is especially important for RAW files. As I own a Canon camera, I have most of my photos in CR2 format, so I need to be sure that image/x-canon-cr2 is in the list. I have also specified CRW, NEF and PEF mime types for older Canon, Nikon and Pentax cameras, respectively. These mime types are already registered in Ubuntu Intrepid (not sure about other distributions). Here is some info on how to register new mime types in Gnome, in case your camera's format is not registered yet.



After chosing the Open With->LightZone, F-Spot will ask whether to create a new version for the file. Select 'No' - this won't work with RAW files and LightZone anyway, because LightZone saves files automatically with the _lzn.jpg suffix and F-Spot doesn't know about it.

Getting this right requires patching F-Spot (I am going to do that later). For now, you will have to import the saved file manually, if you want it to appear in F-Spot.


Monday, November 24, 2008

Enable HTTP proxy in Gnome automatically

I have a laptop with Linux (currently Ubuntu) which I use both at home and at work. The corporate security policy requires everyone to use the HTTP proxy server with authentication for web access, so when I come to work I had to manually enable it, and then disable again at home - not very convenient.

As a side note, Firefox 3+ is great in respecting the global or system-wide proxy configuration (System->Preferences->Network proxy or gnome-network-preferences) as well as gnome-terminal is very nice to set the http_proxy environment variable automatically when proxy is configured, making most command-line tools respect the global proxy setting as well, which is very cool.

So, before network profiles have arrived to Gnome or NetworkManager (I have seen some related commits in Gnome SVN), I still want to enable the proxy automatically depending on my location. Thankfully, NetworkManager supports execution of scripts when it brings interfaces up or down, so this is not difficult at all.

At least on Ubuntu, NetworkManager executes the scripts that are located in /etc/NetworkManager/dispatcher.d/ when it brings interfaces up. Inside of the script I can detect whether I am at work by checking the domain name in /etc/resolv.conf provided by the corporate DHCP server, or the beginning of the assigned IP address if domain can't be used for any reason.

OK, here is the working script for Ubuntu Karmic, Jaunty and Intrepid (Gnome 2.24+), see notes below for older versions. I have this script in /etc/NetworkManager/dispatcher.d/02proxy, because 01ifupdown already exists there.

It is an updated version, attempting to make the script suitable for more general use, eg in our company we now provide it in a .deb package for all Ubuntu-based laptops.

#!/bin/bash
# The script for automatically setting the proxy server depending on location.
# Put it under /etc/NetworkManager/dispatcher.d/02proxy
# Create also the /etc/NetworkManager/proxy_domains.conf, specifying the mapping of
# DHCP domains to proxy server addresses, eg "example.com proxy.example.com:3128"
# Written by Anton Keks

PROXY_DOMAINS="/etc/NetworkManager/proxy_domains.conf"

# provided by NetworkManager
INTERFACE=$1
COMMAND=$2

function gconf() {
sudo -E -u $USER gconftool-2 "$@"
}

function saveUserConfFile() {
echo "DOMAIN_USER=$DOMAIN_USER" > $CONF_FILE;
echo "DOMAIN_PWD_BASE64="`echo $DOMAIN_PWD | base64` >> $CONF_FILE;
echo "PROXY_HOST=$PROXY_HOST" >> $CONF_FILE;
echo "PROXY_PORT=$PROXY_PORT" >> $CONF_FILE;
}

function enableProxy() {
PROXY_HOST=`cat $PROXY_DOMAINS | grep $DOMAIN | sed 's/.* \+//' | sed 's/:.*//'`
PROXY_PORT=`cat $PROXY_DOMAINS | grep $DOMAIN | sed 's/.*://'`

# check if authentication is required
http_proxy=http://$PROXY_HOST:$PROXY_PORT/ wget com 2>&1 | grep "ERROR 407"
if [ $? -eq 0 ]; then
AUTH_REQUIRED="true"
CONF_FILE=$HOME/.proxy:$DOMAIN

if [ ! -e $CONF_FILE ]; then
DOMAIN_USER=`sudo -E -u $USER zenity --entry --text "Login name for domain $DOMAIN"`
DOMAIN_PWD=`sudo -E -u $USER zenity --entry --text "Password for domain $DOMAIN" --hide-text`
saveUserConfFile
fi

# load user proxy settings
. $CONF_FILE
# decode password
DOMAIN_PWD=`echo $DOMAIN_PWD_BASE64 | base64 -d`

# get Kerberos ticket (if it's configured)
if echo $DOMAIN_PWD | sudo -E -u $USER kinit $DOMAIN_USER; then
KLIST_INFO=`sudo -E -u $USER klist | fgrep Default`
sudo -E -u $USER notify-send -i gtk-info "Domain login" "Kerberos ticket retrieved successfully: $KLIST_INFO"
fi
else
AUTH_REQUIRED="false"
fi

# setup proxy
gconf --type string --set /system/proxy/mode "manual"
gconf --type bool --set /system/http_proxy/use_http_proxy "true"
gconf --type string --set /system/http_proxy/host $PROXY_HOST
gconf --type int --set /system/http_proxy/port $PROXY_PORT
gconf --type bool --set /system/http_proxy/use_same_proxy "true"
gconf --type bool --set /system/http_proxy/use_authentication $AUTH_REQUIRED
gconf --type string --set /system/http_proxy/authentication_user $DOMAIN_USER
gconf --type string --set /system/http_proxy/authentication_password $DOMAIN_PWD

# notify
sudo -E -u $USER notify-send -i gtk-info "Proxy configuration" "Your proxy settings have been set to: $DOMAIN_USER@$PROXY_HOST:$PROXY_PORT"
}

function disableProxy() {
gconf --type string --set /system/proxy/mode "none"
gconf --type bool --set /system/http_proxy/use_http_proxy "false"
gconf --type string --set /system/http_proxy/host ""
gconf --type bool --set /system/http_proxy/use_authentication "false"
gconf --type string --set /system/http_proxy/authentication_user ""
gconf --type string --set /system/http_proxy/authentication_password ""
}

# wait for gnome-settings-daemon to appear, ie until user logs in
for i in {1..100}; do
if [ ! `pidof gnome-settings-daemon` ]; then
sleep 5;
echo "Waiting for gnome-settings-daemon to appear..."
else
break
fi
done
if [ ! `pidof gnome-settings-daemon` ]; then
echo "gnome-settings-daemon is not running. exiting."
exit 1
fi

# steal environment from the current non-root user
XENV=`xargs -n 1 -0 echo </proc/$(pidof gnome-settings-daemon)/environ`
# init DBUS connection string in order to reach gconfd
eval export `echo "$XENV" | fgrep DBUS_SESSION_BUS_ADDRESS=`
eval export `echo "$XENV" | fgrep USER=`
eval export `echo "$XENV" | fgrep HOME=`
eval export `echo "$XENV" | fgrep DISPLAY=`
eval export `echo "$XENV" | fgrep XAUTHORITY=`

if [ $COMMAND != 'up' ]; then
disableProxy;
exit
fi

DOMAIN=`cat /etc/resolv.conf | grep domain | sed 's/domain \+//'`
# check if we need to set proxy settings for this domain
if [[ -e $PROXY_DOMAINS && ! `cat $PROXY_DOMAINS | grep $DOMAIN` ]]; then
echo "Proxy is not required for domain $DOMAIN"
disableProxy
else
echo "Setting proxy for domain $DOMAIN"
enableProxy
fi

Don't forget to:
  • give this script execute permissions
  • have gconftool-2, zenity and kinit installed (gconf2, zenity, krb5-user packages in Ubuntu). Install gconf-editor as well for a graphical config editor.
  • create /etc/NetworkManager/proxy_domains.conf, specifying the mapping of DHCP domains to proxy server addresses, eg "example.com proxy.example.com:3128". Specify each domain on a new line.
The script doesn't need you to hardcode your username and the proxy password anymore - the script will ask you for these values on first run and then store them in $HOME/.proxy:$DOMAIN file, so the script is now perfectly usable on multiuser machines and doens't bug you in case of 'unknown' domains.

For more functionality, it even tries to retrieve the Kerberos ticket for you, if the kerberos is configured properly in /etc/krb5.conf. You can check if this is the case by running this on the command-line:
kinit your-user-name; klist
This works very well for me and saves several mouse clicks every morning :-)

Note to Gnome 2.22 and older users (Ubuntu Hardy, etc): I had this script initially done in Hardy, but after upgrading to Intrepid (Gnome 2.24) it stopped working. The reason was that starting from Gnome 2.24, the gconf setting of /system/http_proxy/use_http_proxy is not the primary one and has been replaced by /system/proxy/mode, which takes one of three values: 'auto', 'manual' and 'none'. In Intrepid, if you set only /system/http_proxy/use_http_proxy as before - it has no effect, you need to set /system/proxy/mode to manual, and this will set the value of the old setting to 'true' automatically.

Another thing introduced with Intrepid is the need to set the DBUS_SESSION_BUS_ADDRESS environment variable (the script steals it from the x-session-manager process) - this is because gconfd has switched to DBUS from CORBA for a communication protocol. If you have older Gnome, then you may omit these 2 lines involving DBUS.

Enjoy!


Wednesday, November 12, 2008

Opening files with LightZone from command-line

LightZone is a very useful commercial photo editor with some unique features like non-destructive and layer-based editing. To my mind, the developers has taken a very clever approach to save resulting edits inside of smaller-size JPEG files (thumbnails), so that any program can be used for previewing the resulting image, but opening of the file in LightZone will load the original image and show all the edits again with the possibility to make any changes and export a full-resolution image. The cool thing here is that edited files are very small especially when compared to 10Mb+ source RAW photos, contain all the editing history and can be previewed quickly with any software. And all this runs on Linux (thanks to Java - write once, run almost anywhere).

The only problem with LightZone (at least the Linux version) is that it doesn't accept filenames from the command-line! You have to start the program and select the file manually using the embedded file browser. Of course, this is not an option if you want to run LightZone from another application as an external editor, eg from F-Spot (more on this in a later post).

To make a long story short, I have written a small Java program that takes a filename on the command-line and then modifies LightZone preferences, so when you run it next time it will directly open the specified image.

Here it is:

import java.io.*;
import java.net.*;
import java.util.prefs.*;

/**
* LightZoneOpener - will modify LightZone preferences to open the
* specified file (image) on next startup.
* This is useful to force LightZone to open a particular file from
* the command-line, just run this code before starting LightZone.
*
* @author Anton Keks
*/
public class LightZoneOpener {

public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Please specify filename to open in LightZone");
System.exit(1);
}
String filename = args[0];
if (filename.startsWith("file:"))
filename = new URI(filename).getPath();
File file = new File(filename).getCanonicalFile();
if (!file.exists()) {
System.err.println(file + " doesn't exist!");
System.exit(2);
}
File fileDir = file.getParentFile();

// set image folder as current one
Preferences folderPrefs = Preferences.userRoot().node("com/lightcrafts/ui/browser/folders");
int i = 0; File dir = file;
while ((dir = dir.getParentFile()) != null) {
folderPrefs.put("BrowserTreePath" + i++, dir.getName().isEmpty() ? "/" : dir.getName());
}
folderPrefs.remove("BrowserTreePath" + i);

// set selected image in the current folder
Preferences appPrefs = Preferences.userRoot().node("com/lightcrafts/app");
appPrefs.put("BrowserSelectionMemory" + fileDir.getPath().hashCode(), file.getPath());
// tell LightZone that last startup was OK just in case
appPrefs.put("StartupSuccessful", "true");

System.out.println("LightZone is now ready to open " + file + " on next start");
}
}

Here is what to do:
  1. save it to LightZoneOpener.java
  2. compile with javac LightZoneOpener.java
  3. run with java LightZoneOpener

Then you can create a small script that will automate the stuff for you (save it to ~/bin/LightZone):
#!/bin/bash
java -cp ~/bin LightZoneOpener "$@"
~/LightZone/LightZone

This assumes that you have extracted LightZone to ~/LightZone (in your home dir) and have the following two files in the ~/bin dir: the compiled LightZoneOpener.class and the script file LightZone (don't forget to set the execute permission with chmod a+x ~/bin/LightZone)

Here is all this pre-compiled. Just extract the file directly in your home and it will put all needed files into the bin directory. After next login your local bin will be in $PATH, so you will be able to use it.

Now you can run LightZone filename on the command-line in Linux (or using Alt+F2)! Have fun!