13

Is it possible to capture the screen (or a window) using Haskell in a Windows environment? (ie, taking a screenshot every few minutes or so). If so, how would one go about doing this (again, in Haskell, for a Windows environment)?

More info: I'm a beginner to Haskell. A friend wants to cut development costs by having me whip together some programs for his accounting firm, but he insists that I use Haskell. He wants a tool that will allow him to monitor the desktops of different Windows XP workstations. It would likely have to be a client/server type application. He only needs to monitor desktop activity, hence why he doesn't want any of the expensive management software that is already on the market. I have sifted through lots of documentation, and only got as far as finding wxHaskell, but I couldn't find much on capturing the screen, especially for Windows environments.

Matvey Aksenov
  • 3,802
  • 3
  • 23
  • 45
M. Ferguson
  • 133
  • 6
  • Have you considered just calling an external program from Haskell which takes a screenshot? – Probie Aug 15 '12 at 03:01
  • 1
    @Probie Indeed, I have considered just making the client and server in Haskell, and sending the screenshots at different intervals. BUT, this seems so counter-intuitive. Why even bother using Haskell to begin with if only such a small portion of the software is actually Haskell. Alas, it's something that the Haskell community should address. If we want Haskell to be more prominent, we will most definitely need to solve these types of problems. – M. Ferguson Aug 15 '12 at 03:09

3 Answers3

19

The Approach Tikhon mentioned is correct. Just to add some code to the answer he gave above

import Graphics.Win32.Window
import Graphics.Win32.GDI.Bitmap
import Graphics.Win32.GDI.HDC
import Graphics.Win32.GDI.Graphics2D

main = do desktop   <- getDesktopWindow -- Grab the Hwnd of the desktop, GetDC 0, GetDC NULL etc all work too
          hdc       <- getWindowDC (Just desktop) -- Get the dc handle of the desktop
          (x,y,r,b) <- getWindowRect desktop -- Find the size of the desktop so we can know which size the destination bitmap should be
                                             -- (left, top, right, bottom)
          newDC     <- createCompatibleDC (Just hdc) -- Create a new DC to hold the copied image. It should be compatible with the source DC
          let width  = r - x -- Calculate the width
          let height = b - y -- Calculate the Height
          newBmp    <- createCompatibleBitmap hdc width height -- Create a new Bitmap which is compatible with the newly created DC
          selBmp    <- selectBitmap newDC newBmp -- Select the Bitmap into the DC, drawing on the DC now draws on the bitmap as well
          bitBlt newDC 0 0 width height hdc 0 0 sRCCOPY -- use SRCCOPY to copy the desktop DC into the newDC
          createBMPFile "Foo.bmp" newBmp newDC  -- Write out the new Bitmap file to Foo.bmp
          putStrLn "Bitmap image copied" -- Some debug message
          deleteBitmap selBmp -- Cleanup the selected bitmap
          deleteBitmap newBmp -- Cleanup the new bitmap
          deleteDC newDC      -- Cleanup the DC we created.

This was just quickly put together, but it saves a screenshot of to a file named Foo.bmp. Ps. To whomever wrote the Win32 Library, nicely done :)

Phyx
  • 2,697
  • 1
  • 20
  • 35
14

You can also do it in a cross-platform way with GTK.

That would not be much different from doing it with C: Taking a screenshot with C/GTK.

{-# LANGUAGE OverloadedStrings #-}

import Graphics.UI.Gtk
import System.Environment
import Data.Text as T

main :: IO ()
main = do
    [fileName] <- getArgs
    _ <- initGUI
    Just screen <- screenGetDefault
    window <- screenGetRootWindow screen
    size <- drawableGetSize window
    origin <- drawWindowGetOrigin window
    Just pxbuf <-
        pixbufGetFromDrawable
            window
            ((uncurry . uncurry Rectangle) origin size)
    pixbufSave pxbuf fileName "png" ([] :: [(T.Text, T.Text)])
Community
  • 1
  • 1
Rotsor
  • 13,655
  • 6
  • 43
  • 57
  • This approach looks very clean and straightforward. I'll definitely keep it in mind. Although it would be very similar, I would want to see some examples in Haskell, being that I'm still a beginner. – M. Ferguson Aug 15 '12 at 22:58
  • @M.Ferguson, here you go. It turned out to be slightly different: I did not find the equivalent to `gdk_get_default_root_window`, so an additional call to `screenGetDefault` was needed. – Rotsor Aug 16 '12 at 16:56
  • @Rotsor `gdk_get_default_root_window` is wrapped with `Graphics.UI.Gtk.Gdk.DrawWindow.drawWindowGetDefaultRootWindow`, not the easiest thing to find though – Matvey Aksenov Aug 18 '12 at 10:23
  • @willbush, thanks for the edit. Sorry for it having been rejected. – Rotsor Jun 29 '16 at 08:08
12

You should be able to do this with the Win32 API. Based on What is the best way to take screenshots of a Window with C++ in Windows?, you need to get the context of the window and then copy the image from it using GetWindowDC and BitBlt respectively.

Looking around the Haskell Win32 API documentation, there is a getWindowDC function in Graphics.Win32.Window. This returns an IO HDC. There is a bitblt function in Graphics.Win32.GDI.Graphics2D. This function takes an HDC along with a bunch of INTs which presumably correspond to the arguments it takes in C++.

Unfortunately, I don't have a Windows machine handy, so I can't write the actual code. You'll have to figure out how to use the Win32 API functions yourself, which might be a bit of a bother.

When you do, it would be great if you factored it into a library and put it up on Hackage--Windows does not usually get much love in Haskell land (as I myself show :P), so I'm sure other Windows programmers would be grateful for an easy way to take screenshots.

Community
  • 1
  • 1
Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214