arbtt 0.4.4 → 0.4.5
raw patch · 17 files changed
+774/−134 lines, 17 filesdep +Win32setup-changed
Dependencies added: Win32
Files
- README +24/−0
- Setup.hs +31/−1
- arbtt.cabal +43/−10
- modpath.iss +157/−0
- setup.iss +61/−0
- src/Capture.hs +13/−75
- src/Capture/Win32.hs +28/−0
- src/Capture/X11.hs +81/−0
- src/Data.hs +37/−14
- src/Data/Binary/StringRef.hs +93/−0
- src/Graphics/Win32/Window/Extra.hsc +113/−0
- src/System/Win32/Mutex.hsc +32/−0
- src/TimeLog.hs +35/−24
- src/capture-main.hs +20/−10
- src/dump-main.hs +2/−0
- src/recover-main.hs +2/−0
- src/stats-main.hs +2/−0
README view
@@ -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 ===========
Setup.hs view
@@ -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"
arbtt.cabal view
@@ -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
+ modpath.iss view
@@ -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;
+ setup.iss view
@@ -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" +
src/Capture.hs view
@@ -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
+ src/Capture/Win32.hs view
@@ -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
+ src/Capture/X11.hs view
@@ -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 ""+
src/Data.hs view
@@ -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 #-}+
+ src/Data/Binary/StringRef.hs view
@@ -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)++
+ src/Graphics/Win32/Window/Extra.hsc view
@@ -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))
+ src/System/Win32/Mutex.hsc view
@@ -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))+
src/TimeLog.hs view
@@ -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+
src/capture-main.hs view
@@ -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
src/dump-main.hs view
@@ -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
src/recover-main.hs view
@@ -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
src/stats-main.hs view
@@ -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