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:
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
.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With