diff --git a/README b/README
--- a/README
+++ b/README
@@ -38,6 +38,30 @@
 http://darcs.nomeata.de/arbtt/doc/users_guide/
 Beware that this will also reflect the latest version.
 
+Creating the Windows Installer
+==============================
+
+The file setup.iss contains an installer script for Inno Setup and can be used
+to create the windows installer for arbtt. It can be used under wine. To build
+arbtt under Windows, you need to install the Haskell Platform. Because the
+Haskell Platform ships an older version of the w32api package from mingw, you
+also need to download w32api-3.14-mingw32-dev.tar.gz and copy at least the files
+include/psapi.h and lib/libpsapi.a over the files installed by the Haskell
+Platform. For the pcre-light package, you need to install the pcre library.
+Unless you run a German version of Windows, you’ll need to adjust the path to
+the pcre3.dll file in setup.iss. Install Inno Setup. Create the documentation
+(make -C doc) and configure arbtt with the --with-ISCC-flag:
+$ wine runhaskell Setup.hs configure --with-ISCC='C:\Programme\Inno Setup 5\ISCC.exe'
+again adjusting the path if you do not have a German version of Windows. This
+will put the version name into setup.iss and create the output file as
+dist/arbtt-setup-<version>.exe.
+
+Download links:
+ * http://hackage.haskell.org/platform/2009.2.0.2/HaskellPlatform-2009.2.0.2-setup.exe
+ * http://sourceforge.net/projects/mingw/files/MinGW%20API%20for%20MS-Windows/
+ * http://gnuwin32.sourceforge.net/downlinks/pcre.php
+ * http://www.jrsoftware.org/download.php/is-unicode.exe
+
 Development
 ===========
 
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,3 +1,33 @@
 module Main where
+
 import Distribution.Simple
-main = defaultMain
+import Distribution.Simple.Program
+import Distribution.Simple.Setup
+import Distribution.Simple.LocalBuildInfo
+import Distribution.Simple.Utils
+import Distribution.PackageDescription
+import Distribution.Text
+import System.FilePath
+import System.Directory
+
+main = defaultMainWithHooks simpleUserHooks
+	{ hookedPrograms = [isccProgram]
+	, postBuild = myPostBuild
+	}
+
+isccProgram = simpleProgram "ISCC"
+
+myPostBuild _ flags pd lbi = do
+	case lookupProgram isccProgram (withPrograms lbi) of
+	  Nothing -> warn verb $ "The INNO Setup compile ISCC was not found, skipping the " ++
+	                         "creation of the windows setup executable."
+	  Just configuredProg -> do
+	  	writeFile includeFilename $ "AppVerName=" ++ display (package pd) ++ "\n"
+	  	rawSystemProgram verb configuredProg
+		             	["/Odist","/F"++setupFilename,"setup.iss"]
+		removeFile includeFilename
+  where verb = fromFlag (buildVerbosity flags)
+	setupFilename = display (pkgName (package pd)) ++
+	                "-setup-" ++
+			display (pkgVersion (package pd))
+	includeFilename = "dist" </> "setup-app-ver-name.iss"
diff --git a/arbtt.cabal b/arbtt.cabal
--- a/arbtt.cabal
+++ b/arbtt.cabal
@@ -1,5 +1,5 @@
 name:               arbtt
-version:            0.4.4
+version:            0.4.5
 license:            GPL
 license-file:       LICENSE
 category:           Desktop
@@ -23,23 +23,41 @@
 
 extra-source-files:
     categorize.cfg, arbtt-capture.desktop, README,
-    doc/arbtt.xml, doc/fptools.css, doc/Makefile
+    doc/arbtt.xml, doc/fptools.css, doc/Makefile,
+    setup.iss, modpath.iss
 
 executable arbtt-capture
     main-is:            capture-main.hs
     hs-source-dirs:     src
     build-depends:
-        base == 4.*, filepath, directory, mtl, time, unix,
-        X11 > 1.4.4, bytestring, binary
-    extra-libraries: Xss
+        base == 4.*, filepath, directory, mtl, time,
+        bytestring, binary
     other-modules:
         Data
+        Data.Binary.StringRef
         Capture
         TimeLog
         UpgradeLog1
-        Graphics.X11.XScreenSaver
-        System.Locale.SetLocale
 
+    if os(windows) 
+        extra-libraries: psapi
+        cpp-options:    -DWIN32
+        ghc-options:    -optl-mwindows
+        other-modules:
+            Capture.Win32
+            Graphics.Win32.Window.Extra
+            System.Win32.Mutex
+        build-depends:
+             Win32
+    else
+        extra-libraries: Xss
+        other-modules:
+            Capture.X11
+            Graphics.X11.XScreenSaver
+            System.Locale.SetLocale
+        build-depends:
+            X11 > 1.4.4, unix
+
 executable arbtt-stats
     main-is:            stats-main.hs
     hs-source-dirs:     src
@@ -47,10 +65,15 @@
         base == 4.*, parsec == 2.*, containers, pcre-light
     other-modules:
         Data
+        Data.Binary.StringRef
         Categorize
         TimeLog
         Stats
-        System.Locale.SetLocale
+    if os(windows) 
+        cpp-options:    -DWIN32
+    else
+        other-modules:
+            System.Locale.SetLocale
 
 executable arbtt-dump
     main-is:            dump-main.hs
@@ -59,8 +82,13 @@
         base == 4.*, parsec == 2.*, containers
     other-modules:
         Data
+        Data.Binary.StringRef
         TimeLog
-        System.Locale.SetLocale
+    if os(windows) 
+        cpp-options:    -DWIN32
+    else
+        other-modules:
+            System.Locale.SetLocale
 
 executable arbtt-recover
     main-is:            recover-main.hs
@@ -69,8 +97,13 @@
         base == 4.*, parsec == 2.*, containers
     other-modules:
         Data
+        Data.Binary.StringRef
         TimeLog
-        System.Locale.SetLocale
+    if os(windows) 
+        cpp-options:    -DWIN32
+    else
+        other-modules:
+            System.Locale.SetLocale
 
 source-repository head
     type:     darcs
diff --git a/modpath.iss b/modpath.iss
new file mode 100644
--- /dev/null
+++ b/modpath.iss
@@ -0,0 +1,157 @@
+// ----------------------------------------------------------------------------
+//
+// Inno Setup Ver:  5.2.1
+// Script Version:  1.3.1
+// Author:          Jared Breland <jbreland@legroom.net>
+// Homepage:		http://www.legroom.net/software
+//
+// Script Function:
+//	Enable modification of system path directly from Inno Setup installers
+//
+// Instructions:
+//	Copy modpath.iss to the same directory as your setup script
+//
+//	Add this statement to your [Setup] section
+//		ChangesEnvironment=yes
+//
+//	Add this statement to your [Tasks] section
+//	You can change the Description or Flags, but the Name must be modifypath
+//		Name: modifypath; Description: &Add application directory to your system path; Flags: unchecked
+//
+//	Add the following to the end of your [Code] section
+//	setArrayLength must specify the total number of dirs to be added
+//	Dir[0] contains first directory, Dir[1] contains second, etc.
+//		function ModPathDir(): TArrayOfString;
+//		var
+//			Dir:	TArrayOfString;
+//		begin
+//			setArrayLength(Dir, 1)
+//			Dir[0] := ExpandConstant('{app}');
+//			Result := Dir;
+//		end;
+//		#include "modpath.iss"
+// ----------------------------------------------------------------------------
+
+procedure ModPath();
+var
+	oldpath:	String;
+	newpath:	String;
+	pathArr:	TArrayOfString;
+	aExecFile:	String;
+	aExecArr:	TArrayOfString;
+	i, d:		Integer;
+	pathdir:	TArrayOfString;
+begin
+
+	// Get array of new directories and act on each individually
+	pathdir := ModPathDir();
+	for d := 0 to GetArrayLength(pathdir)-1 do begin
+
+		// Modify WinNT path
+		if UsingWinNT() = true then begin
+
+			// Get current path, split into an array
+			RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', oldpath);
+			oldpath := oldpath + ';';
+			i := 0;
+			while (Pos(';', oldpath) > 0) do begin
+				SetArrayLength(pathArr, i+1);
+				pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1);
+				oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath));
+				i := i + 1;
+
+				// Check if current directory matches app dir
+				if pathdir[d] = pathArr[i-1] then begin
+					// if uninstalling, remove dir from path
+					if IsUninstaller() = true then begin
+						continue;
+					// if installing, abort because dir was already in path
+					end else begin
+						abort;
+					end;
+				end;
+
+				// Add current directory to new path
+				if i = 1 then begin
+					newpath := pathArr[i-1];
+				end else begin
+					newpath := newpath + ';' + pathArr[i-1];
+				end;
+			end;
+
+			// Append app dir to path if not already included
+			if IsUninstaller() = false then
+				newpath := newpath + ';' + pathdir[d];
+
+			// Write new path
+			RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', newpath);
+
+		// Modify Win9x path
+		end else begin
+
+			// Convert to shortened dirname
+			pathdir[d] := GetShortName(pathdir[d]);
+
+			// If autoexec.bat exists, check if app dir already exists in path
+			aExecFile := 'C:\AUTOEXEC.BAT';
+			if FileExists(aExecFile) then begin
+				LoadStringsFromFile(aExecFile, aExecArr);
+				for i := 0 to GetArrayLength(aExecArr)-1 do begin
+					if IsUninstaller() = false then begin
+						// If app dir already exists while installing, abort add
+						if (Pos(pathdir[d], aExecArr[i]) > 0) then
+							abort;
+					end else begin
+						// If app dir exists and = what we originally set, then delete at uninstall
+						if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then
+							aExecArr[i] := '';
+					end;
+				end;
+			end;
+
+			// If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path
+			if IsUninstaller() = false then begin
+				SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True);
+
+			// If uninstalling, write the full autoexec out
+			end else begin
+				SaveStringsToFile(aExecFile, aExecArr, False);
+			end;
+		end;
+
+		// Write file to flag modifypath was selected
+		//   Workaround since IsTaskSelected() cannot be called at uninstall and AppName and AppId cannot be "read" in Code section
+		if IsUninstaller() = false then
+			SaveStringToFile(ExpandConstant('{app}') + '\uninsTasks.txt', WizardSelectedTasks(False), False);
+	end;
+end;
+
+procedure CurStepChanged(CurStep: TSetupStep);
+begin
+	if CurStep = ssPostInstall then
+		if IsTaskSelected('modifypath') then
+			ModPath();
+end;
+
+procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
+var
+	appdir:			String;
+	selectedTasks:	AnsiString;
+begin
+	appdir := ExpandConstant('{app}')
+	if CurUninstallStep = usUninstall then begin
+		if LoadStringFromFile(appdir + '\uninsTasks.txt', selectedTasks) then
+			if Pos('modifypath', selectedTasks) > 0 then
+				ModPath();
+		DeleteFile(appdir + '\uninsTasks.txt')
+	end;
+end;
+
+function NeedRestart(): Boolean;
+begin
+	if IsTaskSelected('modifypath') and not UsingWinNT() then begin
+		Result := True;
+	end else begin
+		Result := False;
+	end;
+end;
diff --git a/setup.iss b/setup.iss
new file mode 100644
--- /dev/null
+++ b/setup.iss
@@ -0,0 +1,61 @@
+[Setup]
+; NOTE: The value of AppId uniquely identifies this application.
+; Do not use the same AppId value in installers for other applications.
+; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
+AppId={{1DB6EA4F-D387-432D-A739-283E0E916AF6}
+AppName=arbtt
+;AppVerName=arbtt-0.4.4
+#include "dist\setup-app-ver-name.iss"
+AppPublisher=Joachim Breitner
+AppPublisherURL=http://www.joachim-breitner.de/projects#arbtt
+AppSupportURL=http://www.joachim-breitner.de/projects#arbtt
+AppUpdatesURL=http://www.joachim-breitner.de/projects#arbtt
+DefaultDirName={pf}\arbtt
+DefaultGroupName=arbtt
+AllowNoIcons=yes
+OutputBaseFilename=arbtt-setup
+Compression=lzma
+SolidCompression=yes
+; Is there a point in displaying the LICENSE file?
+; LicenseFile=LICENSE
+InfoBeforeFile=README.Win32
+ChangesEnvironment=yes
+
+[Tasks]
+Name: modifypath; Description: "Add arbtt binaries to the system path"
+Name: autorun; Description: "Start to capture data upon system start"
+Name: runcapture; Description: "Start to capture data after the installation"
+
+[Files]
+Source: "dist\build\arbtt-dump\arbtt-dump.exe"; DestDir: "{app}\bin"
+Source: "dist\build\arbtt-stats\arbtt-stats.exe"; DestDir: "{app}\bin"
+Source: "dist\build\arbtt-capture\arbtt-capture.exe"; DestDir: "{app}\bin"
+Source: "dist\build\arbtt-recover\arbtt-recover.exe"; DestDir: "{app}\bin"
+Source: "C:\Programme\GnuWin32\bin\pcre3.dll"; DestDir: "{app}\bin"
+Source: "categorize.cfg"; DestDir: "{userappdata}\arbtt"; Flags: onlyifdoesntexist uninsneveruninstall
+Source: "doc\users_guide\*.*"; DestDir: "{app}\doc";
+
+[Icons]
+Name: "{group}\Edit categorize.cfg"; Filename: "wordpad.exe"; Parameters: """{userappdata}\arbtt\categorize.cfg"""; Flags: useapppaths
+Name: "{group}\{cm:UninstallProgram,arbtt}"; Filename: "{uninstallexe}"
+Name: "{group}\arbtt documentation"; Filename: "{app}\doc\index.html"
+Name: "{commonstartup}\arbtt-capture"; Filename: "{app}\bin\arbtt-capture.exe"; Comment: "Collects data for computer useage statistics"; Tasks: autorun
+
+[Run]
+Filename: "{app}\bin\arbtt-capture.exe"; Description: "Start collecting usage data"; Flags: nowait; Tasks: runcapture
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+Name: "german"; MessagesFile: "compiler:Languages\German.isl"
+
+[Code]
+function ModPathDir(): TArrayOfString;
+	var
+	Dir:	TArrayOfString;
+	begin
+	setArrayLength(Dir, 1)
+	Dir[0] := ExpandConstant('{app}\bin');
+	Result := Dir;
+end;
+#include "modpath.iss"
+
diff --git a/src/Capture.hs b/src/Capture.hs
--- a/src/Capture.hs
+++ b/src/Capture.hs
@@ -1,77 +1,15 @@
-module Capture where
-
-import Data
-import Graphics.X11
-import Graphics.X11.Xlib.Extras
-import Graphics.X11.XScreenSaver (getXIdleTime)
-import Control.Monad
-import Control.Exception (bracket)
-import Control.Applicative
-import Data.Maybe
-import Data.Time.Clock
-import System.IO
-
-setupChecks :: IO ()
-setupChecks = do
-	dpy <- openDisplay ""
-        xSetErrorHandler
-	let rwin = defaultRootWindow dpy
-	a <- internAtom dpy "_NET_CLIENT_LIST" False
-	p <- getWindowProperty32 dpy a rwin
-	when (isNothing p) $ do
-		hPutStrLn stderr "arbtt: ERROR: No _NET_CLIENT_LIST set for the root window"
-	closeDisplay dpy
-
-captureData :: IO CaptureData
-captureData = do
-	dpy <- openDisplay ""
-        xSetErrorHandler
-	let rwin = defaultRootWindow dpy
-
-	a <- internAtom dpy "_NET_CLIENT_LIST" False
-	p <- getWindowProperty32 dpy a rwin
-
-	wins <- case p of
-		Just wins -> return (map fromIntegral wins)
-		Nothing   -> return []
-
-	(fsubwin,_) <- getInputFocus dpy
-	fwin <- followTreeUntil dpy (`elem` wins) fsubwin
-
-	winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) wins
-
-	it <- fromIntegral `fmap` getXIdleTime dpy
-
-	closeDisplay dpy
-	return $ CaptureData winData it
-
-getWindowTitle :: Display -> Window -> IO String
-getWindowTitle dpy =  myFetchName dpy
-
-getProgramName :: Display -> Window -> IO String
-getProgramName dpy = fmap resName . getClassHint dpy
-
--- | Follows the tree of windows up until the condition is met or the root
--- window is reached.
-followTreeUntil :: Display -> (Window -> Bool) -> Window -> IO Window 
-followTreeUntil dpy cond = go
-  where go w | cond w    = return w
-             | otherwise = do (r,p,_) <- queryTree dpy w
-	                      if p == 0 then return w
-			                else go p 
-
--- | better than fetchName from X11, as it supports _NET_WM_NAME and unicode
---
--- Code taken from XMonad.Managehook.title
-myFetchName :: Display -> Window -> IO String
-myFetchName d w = do
-	let getProp =
-	    	(internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
-		`catch`
-		(\_ -> getTextProperty d w wM_NAME)
-
-	    extract prop = do l <- wcTextPropertyToTextList d prop
-			      return $ if null l then "" else head l
+{-# LANGUAGE CPP #-}
 
-	bracket getProp (xFree . tp_value) extract `catch` \_ -> return ""
+module Capture (
+#ifdef WIN32
+	module Capture.Win32
+#else
+	module Capture.X11
+#endif
+	) where
 
+#ifdef WIN32
+import Capture.Win32
+#else
+import Capture.X11
+#endif
diff --git a/src/Capture/Win32.hs b/src/Capture/Win32.hs
new file mode 100644
--- /dev/null
+++ b/src/Capture/Win32.hs
@@ -0,0 +1,28 @@
+module Capture.Win32 where
+
+import Data
+import Control.Monad
+import Control.Exception (bracket)
+import Control.Applicative
+import Data.Maybe
+import Data.Time.Clock
+import System.IO
+
+import Graphics.Win32.Window.Extra
+
+setupCapture :: IO ()
+setupCapture = do
+	return ()
+
+captureData :: IO CaptureData
+captureData = do
+	titles <- fetchWindowTitles
+	foreground <- getForegroundWindow
+
+	let winData = map (
+		\(h,t,p) -> (h == foreground, t, p)
+		) titles
+
+	it <- fromIntegral `fmap` getIdleTime
+
+	return $ CaptureData winData it
diff --git a/src/Capture/X11.hs b/src/Capture/X11.hs
new file mode 100644
--- /dev/null
+++ b/src/Capture/X11.hs
@@ -0,0 +1,81 @@
+module Capture.X11 where
+
+import Data
+import Graphics.X11
+import Graphics.X11.Xlib.Extras
+import Control.Monad
+import Control.Exception (bracket)
+import Control.Applicative
+import Data.Maybe
+import Data.Time.Clock
+import System.IO
+
+import System.Locale.SetLocale
+import Graphics.X11.XScreenSaver (getXIdleTime, compiledWithXScreenSaver)
+
+setupCapture :: IO ()
+setupCapture = do
+        unless compiledWithXScreenSaver $
+		hPutStrLn stderr "arbtt [Warning]: X11 was compiled without support for XScreenSaver"
+	dpy <- openDisplay ""
+        xSetErrorHandler
+	let rwin = defaultRootWindow dpy
+	a <- internAtom dpy "_NET_CLIENT_LIST" False
+	p <- getWindowProperty32 dpy a rwin
+	when (isNothing p) $ do
+		hPutStrLn stderr "arbtt: ERROR: No _NET_CLIENT_LIST set for the root window"
+	closeDisplay dpy
+
+captureData :: IO CaptureData
+captureData = do
+	dpy <- openDisplay ""
+        xSetErrorHandler
+	let rwin = defaultRootWindow dpy
+
+	a <- internAtom dpy "_NET_CLIENT_LIST" False
+	p <- getWindowProperty32 dpy a rwin
+
+	wins <- case p of
+		Just wins -> return (map fromIntegral wins)
+		Nothing   -> return []
+
+	(fsubwin,_) <- getInputFocus dpy
+	fwin <- followTreeUntil dpy (`elem` wins) fsubwin
+
+	winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) wins
+
+	it <- fromIntegral `fmap` getXIdleTime dpy
+
+	closeDisplay dpy
+	return $ CaptureData winData it
+
+getWindowTitle :: Display -> Window -> IO String
+getWindowTitle dpy =  myFetchName dpy
+
+getProgramName :: Display -> Window -> IO String
+getProgramName dpy = fmap resName . getClassHint dpy
+
+-- | Follows the tree of windows up until the condition is met or the root
+-- window is reached.
+followTreeUntil :: Display -> (Window -> Bool) -> Window -> IO Window 
+followTreeUntil dpy cond = go
+  where go w | cond w    = return w
+             | otherwise = do (r,p,_) <- queryTree dpy w
+	                      if p == 0 then return w
+			                else go p 
+
+-- | better than fetchName from X11, as it supports _NET_WM_NAME and unicode
+--
+-- Code taken from XMonad.Managehook.title
+myFetchName :: Display -> Window -> IO String
+myFetchName d w = do
+	let getProp =
+	    	(internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
+		`catch`
+		(\_ -> getTextProperty d w wM_NAME)
+
+	    extract prop = do l <- wcTextPropertyToTextList d prop
+			      return $ if null l then "" else head l
+
+	bracket getProp (xFree . tp_value) extract `catch` \_ -> return ""
+
diff --git a/src/Data.hs b/src/Data.hs
--- a/src/Data.hs
+++ b/src/Data.hs
@@ -8,6 +8,7 @@
 import Data.Binary
 import Data.Binary.Put
 import Data.Binary.Get
+import Data.Binary.StringRef
 import Control.Applicative
 import Control.Monad
 
@@ -60,17 +61,18 @@
 
 -- Data.Binary instances
 
-instance Binary a => Binary (TimeLogEntry a) where
- put tle = do
+instance StringReferencingBinary a => StringReferencingBinary (TimeLogEntry a) where
+ ls_put strs tle = do
  	-- A version tag
  	putWord8 1
 	put (tlTime tle)
 	put (tlRate tle)
-	put (tlData tle)
- get = do
+	ls_put strs (tlData tle)
+ ls_get strs = do
  	v <- getWord8
-	when (v /= 1) $ error $ "Wrong TimeLogEntry version tag " ++ show v
-	TimeLogEntry <$> get <*> get <*> get
+	case v of
+	 1 -> TimeLogEntry <$> get <*> get <*> ls_get strs
+	 _ -> error $ "Unsupported TimeLogEntry version tag " ++ show v
 
 instance Binary UTCTime where
  put (UTCTime (ModifiedJulianDay d) t) = do
@@ -81,13 +83,34 @@
 	t <- get
 	return $ UTCTime (ModifiedJulianDay d) (fromRational t)
 
-instance Binary CaptureData where
- put cd = do
+instance ListOfStringable CaptureData where
+  listOfStrings = concatMap (\(b,t,p) -> [t,p]) . cWindows
+
+instance StringReferencingBinary CaptureData where
+-- Versions:
+-- 1 First version
+-- 2 Using ListOfStringable
+ ls_put strs cd = do
  	-- A version tag
- 	putWord8 1
-	put (cWindows cd)
-	put (cLastActivity cd)
- get = do
+ 	putWord8 2
+	ls_put strs (cWindows cd)
+	ls_put strs (cLastActivity cd)
+ ls_get strs = do
  	v <- getWord8
-	when (v /= 1) $ error $ "Wrong CaptureData version tag " ++ show v
-	CaptureData <$> get <*> get
+	case v of
+	 1 -> CaptureData <$> get <*> get
+	 2 -> CaptureData <$> ls_get strs <*> ls_get strs
+	 _ -> error $ "Unsupported CaptureData version tag " ++ show v
+
+  -- | 'getMany n' get 'n' elements in order, without blowing the stack.
+  --   From Data.Binary
+getMany :: Binary a => Int -> Get [a]
+getMany n = go [] n
+ where
+    go xs 0 = return $! reverse xs
+    go xs i = do x <- get
+                 -- we must seq x to avoid stack overflows due to laziness in
+                 -- (>>=)
+                 x `seq` go (x:xs) (i-1)
+{-# INLINE getMany #-}
+
diff --git a/src/Data/Binary/StringRef.hs b/src/Data/Binary/StringRef.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Binary/StringRef.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE FlexibleInstances, UndecidableInstances, TypeSynonymInstances, OverlappingInstances#-}
+
+module Data.Binary.StringRef 
+	( ListOfStringable(..)
+	, StringReferencingBinary(..)
+	, ls_encode
+	, ls_decode
+	) where
+
+import Data.Binary
+import Data.Binary.Put
+import Data.Binary.Get
+import Control.Monad
+import Data.List
+import Data.ByteString.Lazy (ByteString)
+
+class StringReferencingBinary a => ListOfStringable a where
+  listOfStrings :: a -> [String]
+
+-- | An extended version of Binary that passes the list of strings of the
+-- previous sample
+class StringReferencingBinary a where
+ ls_put :: [String] -> a -> Put
+ ls_get :: [String] -> Get a
+
+------------------------------------------------------------------------
+-- Instances for the first few tuples
+
+instance (StringReferencingBinary a, StringReferencingBinary b) => StringReferencingBinary (a,b) where
+    ls_put strs (a,b)           = ls_put strs a >> ls_put strs b
+    ls_get strs                 = liftM2 (,) (ls_get strs) (ls_get strs)
+
+instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c) => StringReferencingBinary (a,b,c) where
+    ls_put strs (a,b,c)         = ls_put strs a >> ls_put strs b >> ls_put strs c
+    ls_get strs                 = liftM3 (,,) (ls_get strs) (ls_get strs) (ls_get strs)
+
+instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c, StringReferencingBinary d) => StringReferencingBinary (a,b,c,d) where
+    ls_put strs (a,b,c,d)       = ls_put strs a >> ls_put strs b >> ls_put strs c >> ls_put strs d
+    ls_get strs                 = liftM4 (,,,) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs)
+
+instance (StringReferencingBinary a, StringReferencingBinary b, StringReferencingBinary c, StringReferencingBinary d, StringReferencingBinary e) => StringReferencingBinary (a,b,c,d,e) where
+    ls_put strs (a,b,c,d,e)     = ls_put strs a >> ls_put strs b >> ls_put strs c >> ls_put strs d >> ls_put strs e
+    ls_get strs                 = liftM5 (,,,,) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs) (ls_get strs)
+
+
+instance StringReferencingBinary a => StringReferencingBinary [a] where
+    ls_put strs l  = ls_put strs (length l) >> mapM_ (ls_put strs) l
+    ls_get strs    = do n <- (ls_get strs) :: Get Int
+                        ls_getMany strs n
+
+-- | 'ls_get strsMany n' ls_get strs 'n' elements in order, without blowing the stack.
+ls_getMany :: StringReferencingBinary a => [String] -> Int -> Get [a]
+ls_getMany strs n = go [] n
+ where
+    go xs 0 = return $! reverse xs
+    go xs i = do x <- ls_get strs
+                 -- we must seq x to avoid stack overflows due to laziness in
+                 -- (>>=)
+                 x `seq` go (x:xs) (i-1)
+{-# INLINE ls_getMany #-}
+
+instance StringReferencingBinary String where
+	ls_put strs s = case elemIndex s strs of
+		Just i | 0 <= i && i  < 255 - 2 ->
+			put (fromIntegral (succ i) :: Word8)
+		_ ->    put (0 :: Word8) >> put s
+	ls_get strs = do
+		tag <- get
+		case tag :: Word8 of
+		  0 -> get
+		  i -> return $! strs !! fromIntegral (pred i)
+
+{-
+instance Binary a => StringReferencingBinary a where
+	ls_put _ = put
+	ls_get _ = get
+-}
+
+instance StringReferencingBinary Char where { ls_put _ = put; ls_get _ = get }
+instance StringReferencingBinary Int  where { ls_put _ = put; ls_get _ = get }
+instance StringReferencingBinary Integer  where { ls_put _ = put; ls_get _ = get }
+instance StringReferencingBinary Bool  where { ls_put _ = put; ls_get _ = get }
+
+ls_encode :: StringReferencingBinary a => [String] -> a -> ByteString
+ls_encode strs = runPut . ls_put strs
+{-# INLINE ls_encode #-}
+
+-- | Decode a value from a lazy ByteString, reconstructing the original structure.
+--
+ls_decode :: StringReferencingBinary a => [String] -> ByteString -> a
+ls_decode strs = runGet (ls_get strs)
+
+
diff --git a/src/Graphics/Win32/Window/Extra.hsc b/src/Graphics/Win32/Window/Extra.hsc
new file mode 100644
--- /dev/null
+++ b/src/Graphics/Win32/Window/Extra.hsc
@@ -0,0 +1,113 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module Graphics.Win32.Window.Extra
+	( fetchWindowTitles
+	, getForegroundWindow
+	, getIdleTime
+	) where
+
+import Graphics.Win32.GDI.Types
+import System.Win32.Types
+import System.Win32.Process
+import System.Win32.File    ( closeHandle )
+import System.IO
+import Control.Exception    ( bracket )
+import Control.Monad
+
+import Foreign
+
+import Data.IORef 
+
+#include <windows.h>
+
+type EnumWindowsProc = HWND -> LPARAM -> IO Bool
+
+foreign import ccall "wrapper"
+  mkEnumWindowsProc :: EnumWindowsProc -> IO (FunPtr EnumWindowsProc)
+
+foreign import stdcall safe "windows.h EnumWindows"
+  c_EnumWindows :: (FunPtr EnumWindowsProc) -> LPARAM -> IO Bool
+
+foreign import stdcall unsafe "windows.h GetWindowTextW"
+  c_GetWindowText :: HWND -> LPWSTR -> Int -> IO Int
+
+foreign import stdcall unsafe "windows.h GetWindowTextLengthW"
+  c_GetWindowTextLength :: HWND -> IO Int
+
+foreign import stdcall unsafe "windows.h GetForegroundWindow"
+  c_GetForegroundWindow :: IO HWND
+
+foreign import stdcall unsafe "windows.h GetLastInputInfo"
+  c_GetLastInputInfo :: Ptr LASTINPUTINFO -> IO Bool
+
+foreign import stdcall unsafe "windows.h GetTickCount"
+  c_GetTickCount :: IO DWORD
+
+foreign import stdcall unsafe "windows.h GetWindowThreadProcessId"
+  c_GetWindowThreadProcessId :: HWND -> LPDWORD -> IO DWORD
+
+foreign import stdcall unsafe "psapi.h GetProcessImageFileNameW"
+  c_GetProcessImageFileName :: HANDLE -> LPTSTR -> DWORD -> IO DWORD
+
+foreign import stdcall unsafe "windows.h SetLastError"
+  c_SetLastError :: DWORD -> IO ()
+
+foreign import stdcall unsafe "windows.h IsWindowVisible"
+  c_IsWindowVisible :: HWND -> IO Bool
+
+data LASTINPUTINFO = LASTINPUTINFO DWORD deriving (Show)
+
+instance Storable LASTINPUTINFO where
+    sizeOf = const (#size LASTINPUTINFO)
+    alignment = sizeOf
+    poke buf (LASTINPUTINFO t) = do
+        (#poke LASTINPUTINFO, cbSize) buf ((#size LASTINPUTINFO) :: UINT)
+        (#poke LASTINPUTINFO, dwTime) buf t
+    peek buf = do
+        t <- (#peek LASTINPUTINFO, dwTime) buf
+        return $ LASTINPUTINFO t
+
+fetchWindowTitles :: IO [(HWND, String,String)]
+fetchWindowTitles = do
+	resultRef <- newIORef []
+	callback <- mkEnumWindowsProc $ \winh _ -> do
+                v <- c_IsWindowVisible winh -- only consider visible windows
+                if not v then return True else do
+                proc <- alloca $ \pid_p -> do
+                        c_GetWindowThreadProcessId winh pid_p 
+                        pid <- peek pid_p
+                        bracket (openProcess pROCESS_QUERY_INFORMATION False pid) closeHandle $ \ph ->
+                                allocaArray0 (#const MAX_PATH) $ \c_test -> do
+                                        c_SetLastError (#const ERROR_SUCCESS)
+                                        r <- c_GetProcessImageFileName ph c_test ((#const MAX_PATH)+1)
+                                        err <- getLastError
+                                        if r == 0 && err /= (#const ERROR_SUCCESS)
+                                         then do hPutStrLn stderr $ "GetProcessImageFileName returned error " ++ show err ++ "."
+                                                 return ""
+                                         else peekTString c_test
+                                         
+		len <- c_GetWindowTextLength winh
+		str <- allocaArray0 len  $ \c_test -> do
+                        c_SetLastError (#const ERROR_SUCCESS)
+			r <- c_GetWindowText winh c_test (len+1)
+			err <- getLastError
+			if r == 0 && err /= (#const ERROR_SUCCESS)
+                         then do hPutStrLn stderr $ "GetWindowText returned error " ++ show err ++ "."
+                                 return ""
+			 else peekTString c_test
+                unless (str `elem` ["", "Default IME"]) $ do -- Ignore some windows by default
+                        modifyIORef resultRef ((winh,str,proc):)
+                return True
+	c_EnumWindows callback 0
+	readIORef resultRef
+
+getForegroundWindow :: IO HWND
+getForegroundWindow = c_GetForegroundWindow
+
+-- | Idle time in milliseconds
+getIdleTime :: IO Integer
+getIdleTime = with (LASTINPUTINFO 0) $ \lii_p -> do
+		failIfFalse_ "GetLastInputInfo" $ c_GetLastInputInfo lii_p
+		LASTINPUTINFO lii <- peek lii_p
+		now <- c_GetTickCount
+		return (fromIntegral (now - lii))
diff --git a/src/System/Win32/Mutex.hsc b/src/System/Win32/Mutex.hsc
new file mode 100644
--- /dev/null
+++ b/src/System/Win32/Mutex.hsc
@@ -0,0 +1,32 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module System.Win32.Mutex 
+	( claimMutex
+	) where
+
+import System.Win32.Types
+import Foreign
+
+#include <windows.h>
+
+type LPSECURITY_ATTRIBUTES = Ptr ()
+
+foreign import stdcall unsafe "windows.h CreateMutexW"
+  c_CreateMutex :: LPSECURITY_ATTRIBUTES -> Bool -> LPCTSTR -> IO HANDLE
+
+foreign import stdcall unsafe "windows.h SetLastError"
+  c_SetLastError :: DWORD -> IO ()
+
+-- | Given a unique identifier in the string, this function tries to create
+--   and claim a mutex based on that name. Returns 'True' if the mutex was claimed.
+claimMutex :: String -> IO Bool
+claimMutex lockfilename = do
+	let name = "Local\\" ++ filter (/='\\') lockfilename
+	withTString name $ \c_str -> do
+	c_SetLastError (#const ERROR_SUCCESS)
+	handle <- c_CreateMutex nullPtr True c_str
+	err <- getLastError
+	if handle == nullPtr
+	  then errorWin "CreateMutex"
+	  else return (err /= (#const ERROR_ALREADY_EXISTS))
+
diff --git a/src/TimeLog.hs b/src/TimeLog.hs
--- a/src/TimeLog.hs
+++ b/src/TimeLog.hs
@@ -8,6 +8,7 @@
 import Control.Monad
 import Data.Time
 import Data.Binary
+import Data.Binary.StringRef
 import Data.Binary.Get
 import Data.Function
 import Data.Char
@@ -22,28 +23,34 @@
 
 -- | Runs the given action each delay milliseconds and appends the TimeLog to the
 -- given file.
-runLogger :: Binary a => FilePath -> Integer -> IO a -> IO ()
-runLogger filename delay action = forever $ do
+runLogger :: ListOfStringable a => FilePath -> Integer -> IO a -> IO ()
+runLogger filename delay action = flip fix Nothing $ \loop prev -> do
 	entry <- action
 	date <- getCurrentTime
 	createTimeLog False filename
-	appendTimeLog filename (TimeLogEntry date delay entry)
+	appendTimeLog filename prev (TimeLogEntry date delay entry)
 	threadDelay (fromIntegral delay * 1000)
+	loop (Just entry)
+
 	
 createTimeLog :: Bool -> FilePath -> IO ()
 createTimeLog force filename = do
 	ex <- doesFileExist filename
 	when (not ex || force) $ BS.writeFile filename magic
 
-appendTimeLog :: Binary a => FilePath -> TimeLogEntry a -> IO ()
-appendTimeLog filename = BS.appendFile filename . encode
+appendTimeLog :: ListOfStringable a => FilePath -> Maybe a -> TimeLogEntry a -> IO ()
+appendTimeLog filename prev = BS.appendFile filename . ls_encode strs
+  where strs = maybe [] listOfStrings prev
 
-writeTimeLog :: Binary a => FilePath -> TimeLog a -> IO ()
-writeTimeLog filename tl = do createTimeLog True filename
-			      mapM_ (appendTimeLog filename) tl
+writeTimeLog :: ListOfStringable a => FilePath -> TimeLog a -> IO ()
+writeTimeLog filename tl = do
+	createTimeLog True filename
+	foldM_ go  Nothing tl
+  where go prev v = do appendTimeLog filename prev v
+  		       return (Just (tlData v))
 
 -- | This might be very bad style, and it hogs memory, but it might help in some situations...
-recoverTimeLog :: Binary a => FilePath -> IO (TimeLog a)
+recoverTimeLog :: ListOfStringable a => FilePath -> IO (TimeLog a)
 recoverTimeLog filename = do
 	content <- BS.readFile filename
         start content
@@ -53,15 +60,16 @@
 		  then do putStrLn $ "WARNING: Timelog starts with unknown marker " ++
 				show (map (chr.fromIntegral) (BS.unpack startString))
 		  else do putStrLn $ "Found header, continuing... (" ++ show (BS.length rest) ++ " bytes to go)"
-		go rest off
-        go input off = do mb <- tryGet False input off
-	 	          flip (maybe (return [])) mb $
-		     	  	\(v,rest,off') -> if BS.null rest
-			                          then return [v]
-			                          else (v :) <$> go rest off'
-	tryGet retrying input off = catch (
+		go Nothing rest off
+        go prev input off = do
+		mb <- tryGet prev False input off
+		flip (maybe (return [])) mb $ \(v,rest,off') ->
+			if BS.null rest
+			then return [v]
+			else (v :) <$> go (Just (tlData v)) rest off'
+	tryGet prev retrying input off = catch (
 			do -- putStrLn $ "Trying value at offset " ++ show off
-			   let (v,rest,off') = runGetState get input off
+			   let (v,rest,off') = runGetState (ls_get strs) input off
 			   evaluate rest
 			   when retrying $
 			   	putStrLn $ "Succesfully read value at position " ++ show off
@@ -74,21 +82,24 @@
 			     then do putStrLn $ "End of file reached"
 			             return Nothing
 			     else do putStrLn $ "Trying at position " ++ show (off+1) ++ "."
-			             tryGet True (BS.tail input) (off+1)
+			             tryGet prev True (BS.tail input) (off+1)
 			)
+	  where strs = maybe [] listOfStrings prev
 
-readTimeLog :: Binary a => FilePath -> IO (TimeLog a)
+readTimeLog :: ListOfStringable a => FilePath -> IO (TimeLog a)
 readTimeLog filename = do
 	content <- BS.readFile filename
         return $ runGet start content
   where start = do
   		startString <- getLazyByteString (BS.length magic)
 		if startString == magic
-		 then go
+		 then go Nothing
 		 else error $
 		 	"Timelog starts with unknown marker " ++
 			show (map (chr.fromIntegral) (BS.unpack startString))
-        go = do v <- get
-		m <- isEmpty
-		if m then return [v]
-		     else (v :) <$> go
+        go prev = do v <- ls_get strs
+	  	     m <- isEmpty
+		     if m then return [v]
+		          else (v :) <$> go (Just (tlData v))
+	  where strs = maybe [] listOfStrings prev
+
diff --git a/src/capture-main.hs b/src/capture-main.hs
--- a/src/capture-main.hs
+++ b/src/capture-main.hs
@@ -1,15 +1,18 @@
+{-# LANGUAGE CPP #-}
 module Main where
 
 import Control.Monad
 import Control.Concurrent
 import System.Directory
 import System.FilePath
-import Graphics.X11.XScreenSaver (compiledWithXScreenSaver)
 import System.IO
-import System.Posix.IO
 import System.IO.Error
+#ifdef WIN32
+import System.Win32.Mutex
+#else
+import System.Posix.IO
+#endif
 import System.Exit
-import System.Locale.SetLocale
 import System.Console.GetOpt
 import System.Environment
 import Data.Maybe
@@ -18,6 +21,7 @@
 import Capture
 import TimeLog
 import UpgradeLog1
+import CommonStartup
 
 import Paths_arbtt (version)
 
@@ -47,14 +51,20 @@
      ]	     
 
 -- | This is very raw, someone ought to improve this
-lockFile filename = flip catch (\e -> hPutStrLn stderr ("arbtt [Error]: Could not aquire lock for " ++ filename ++"!") >> exitFailure) $ do
-    fd <- openFd (filename  ++ ".lck") WriteOnly (Just 0o644) defaultFileFlags
-    setLock fd (WriteLock, AbsoluteSeek, 0, 0)
+lockFile filename = do
+#ifdef WIN32
+    success <- claimMutex filename
+    unless success $ do
+    	hPutStrLn stderr ("arbtt [Error]: Could not aquire lock for " ++ filename ++"!")
+	exitFailure
+#else
+    flip catch (\e -> hPutStrLn stderr ("arbtt [Error]: Could not aquire lock for " ++ filename ++"!") >> exitFailure) $ do
+        fd <- openFd (filename  ++ ".lck") WriteOnly (Just 0o644) defaultFileFlags
+        setLock fd (WriteLock, AbsoluteSeek, 0, 0)
+#endif       
 
 main = do
-    setLocale LC_ALL (Just "") 
-    unless compiledWithXScreenSaver $
-    	hPutStrLn stderr "arbtt [Warning]: X11 was compiled without support for XScreenSaver"
+    commonStartup
     
     args <- getArgs
     flags <- case getOpt Permute options args of
@@ -78,5 +88,5 @@
     let captureFile = dir </> "capture.log"
     lockFile captureFile
     upgradeLogFile1 captureFile
-    setupChecks
+    setupCapture
     runLogger captureFile (sampleRate * 1000) captureData
diff --git a/src/dump-main.hs b/src/dump-main.hs
--- a/src/dump-main.hs
+++ b/src/dump-main.hs
@@ -12,6 +12,7 @@
 
 import TimeLog
 import Data
+import CommonStartup
 
 import Paths_arbtt (version)
 
@@ -36,6 +37,7 @@
 
 
 main = do
+  commonStartup
   args <- getArgs
   flags <- case getOpt Permute options args of
           (o,[],[]) | Help `notElem` o  && Version `notElem` o -> return o
diff --git a/src/recover-main.hs b/src/recover-main.hs
--- a/src/recover-main.hs
+++ b/src/recover-main.hs
@@ -12,6 +12,7 @@
 
 import TimeLog
 import Data
+import CommonStartup
 
 import Paths_arbtt (version)
 
@@ -39,6 +40,7 @@
 
 
 main = do
+  commonStartup
   args <- getArgs
   flags <- case getOpt Permute options args of
           (o,[],[]) | Help `notElem` o  && Version `notElem` o -> return o
diff --git a/src/stats-main.hs b/src/stats-main.hs
--- a/src/stats-main.hs
+++ b/src/stats-main.hs
@@ -18,6 +18,7 @@
 import Data
 import Categorize
 import Stats
+import CommonStartup
 
 import Paths_arbtt (version)
 
@@ -74,6 +75,7 @@
 
 
 main = do
+  commonStartup
   args <- getArgs
   flags <- case getOpt Permute options args of
           (o,[],[]) | Help `notElem` o  && Version `notElem` o -> return o
