Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a screenshot of a gtk.Window

For testing and documentation purposes I would like to create a screenshot of a gtk.Window object. I'm following a basic pygtk sample at the moment. The example with my modifications looks like the following:

import gtk

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)
    win.show_all()

    if win.is_drawable() is not True:
        raise RuntimeError("Not drawable?")

    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
                            width, height)
    screenshot = pixbuf.get_from_drawable(win, win.get_colormap(),
                                          0, 0, 0, 0, width, height)
    screenshot.save('screenshot.png', 'png')

if __name__ == '__main__':
    main()
    gtk.main()

I'm ending up with an error when the get_from_drawable method is invoked.

TypeError: Gdk.Pixbuf.get_from_drawable() argument 1 must be gtk.gdk.Drawable, not gtk.Window

Apparently the screenshot example that I merged into the basic example is using a gtk.gdk.Window that inherits from gtk.gdk.Drawable.

My questions:

  • Is there another way to make the Pixbuf object capture the window?
  • Can I make a gtk.Window a gtk.gdk.Window or find the gtk.gdk.Window functionality within the gtk.Window?
like image 537
Michael Mulich Avatar asked Sep 22 '11 16:09

Michael Mulich


1 Answers

The key difference to understand is that gtk.gdk.Window isn't a "window" in the sense that most people think of. It's not a GUI element, it's just a section of the screen that acts as a logical display area, as explained in the documentation. In that way, it is more of a low-level object.

A gtk.Window object (the traditional "window") contains a window attribute which is the gtk.gdk.Window object that is used by the gtk.Window. It gets created when the window is initialized (in this case, after the call to win.show_all()).

Here's a revision of your code that saves the image correctly:

import gtk
import gobject

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)
    win.show_all()

    # Set timeout to allow time for the screen to be drawn
    # before saving the window image
    gobject.timeout_add(1000, drawWindow, win)

def drawWindow(win):
    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)

    # Retrieve the pixel data from the gdk.window attribute (win.window)
    # of the gtk.window object
    screenshot = pixbuf.get_from_drawable(win.window, win.get_colormap(), 
                                          0, 0, 0, 0, width, height)

    screenshot.save('screenshot.png', 'png')

    # Return False to stop the repeating interval
    return False

if __name__ == '__main__':
    main()
    gtk.main()

The timeout is necessary because even though the gtk.gdk.Window object has been created, the screen still hasn't been drawn, until gtk.main() starts running, I think. If you read the pixel buffer right after win.show_all(), it will save an image, but the image will be the section of the screen that the window will later take up. If you want to try it for yourself, replace the call to gobject.timeout_add with a simple call to drawWindow(win).

Note that this method saves an image of the GTK window, but not the border of the window (so, no title bar).

Also, as a side point, when defining a function that was called using gobject.timeout_add, you don't actually have to use return False explicitly, it just has to evaluate to a value equivalent to 0. Python return None by default if there is no return statement in the function, so the function will only be called once by default. If you want the function to be called again after another interval, return True.

Using signals

As noted by liberforce, instead of using a timeout, a better method would be to connect to the signals that are used by GTK to communicate events (and state changes in general).

Anything that inherits from gobject.GObject (which all widgets do, basically) has a connect() function which is used to register a callback with a given signal. I tried out a few signals, and it appears that the one we want to use is expose-event, which occurs anytime a widget needs to be redrawn (including the first time it is drawn). Because we want the window to be drawn before our callback occurs, we'll use the connect_after() variant.

Here's the revised code:

import gtk

# Don't use globals in a real application,
# better to encapsulate everything in a class
handlerId = 0

def main():
    button = gtk.Button("Hello")
    scroll_win = gtk.ScrolledWindow()
    scroll_win.add(button)
    win = gtk.Window(gtk.WINDOW_TOPLEVEL)
    win.add(scroll_win)

    # Connect to expose signal to allow time
    # for the window to be drawn
    global handlerId 
    handlerId = win.connect_after('expose-event', drawWindow)

    win.show_all()

def drawWindow(win, e):
    width, height = win.get_size()
    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)

    # Retrieve the pixel data from the gdk.window attribute (win.window)
    # of the gtk.window object
    screenshot = pixbuf.get_from_drawable(win.window, win.get_colormap(), 
                                          0, 0, 0, 0, width, height)
    screenshot.save('screenshot.png', 'png')

    # Disconnect this handler so that it isn't
    # repeated when the screen needs to be redrawn again
    global handlerId
    win.disconnect(handlerId)

if __name__ == '__main__':
    main()
    gtk.main()
like image 95
voithos Avatar answered Oct 05 '22 18:10

voithos