Follow-the-pointer mini screencast Python app for Ubuntu

I had this idea for a “screencast” which follows the mouse pointer around, rather than making you nominate a fixed screen area; ideal for little demo animated GIFs of how to do a thing on websites. No existing screencast app seems to do this, so I threw a quick thing together to do it for me. Python (because that’s what I use for native apps that can’t be done in pure QML) and Gtk (because there’s no point in using Qt for this since the Python bindings are weird, I use Qt/QML for Ubuntu SDK apps but this can’t be done there anyway because the phone is Mir rather than X and app confinement will prevent screenshots anyway).

Lots of hardcoded things, so it’s not a proper useful app, but it works for what I needed it for. Note that it contains a hardcoded mouse pointer image, because getting the mouse pointer image is insane and requires you to talk directly to X, which is possible with Python but needs loads of extra libraries, and look life’s just too short what are you thinking. Also uses modern Gdk and GI, not pygtk which is ancient and yet what all the existing posted code samples are. Probably should use Cairo from GI too but cairo.CONTENT_COLOR doesn’t seem to exist there. Anyway, if you need to take screenshots from Python using modern Gtk, or you want to do a little animated gif screencast which follows the mouse around, here you go.

import time
from gi.repository import Gdk
import cairo
from PIL import Image
from PIL.GifImagePlugin import getheader, getdata
import StringIO

CURSOR = """iVBORw0KGgoAAAANSUhEUgAAAA0AAAAUCAYAAABWMrcvAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI
WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gIHETMhphEADwAAAfJJREFUOMuVkztvE1EQhb+b9cq+
3s2CIU7hH+GOxpWxoOIhOR1UaTBUQEmFIA1C1BTUceWCyp1Lp7HcpCFVIgpLK0XRyo4Uy3cfuUOz
tlDMIxlpym/OvXPmAHwCfGCDG5S4rvsNuHsTUKrVauw4zlfgznVBOT4+lkqlkiilvgC3rwOKtVaO
jo4kCIJEKfURuPU/UNI0FUAODw/F9/0EeAcE/wJlsViItVYAGY1GUi6XE+AtsAmoP0IXFxcrNUCG
w+ESfJXbsQbKbDYTY8wKAmQwGIjWOgZ2Ae8qKFEUrakB0u/3RWttgGdXQTk9PZXz8/M1NUB6vZ5o
rRdAG9BLUMIw/KsaIPv7+1ZrPQceASUFyGQyoVgsrtp1Xer1uj05OZHlc+I4dpRSU2vt4w2ALMvI
sowwDOl2u4gInU5HOY4zMca8Mcbsishza+0LYLE6o/F4LLVaLfE8Lz07O5P5fC5BECTAS6CWX0kA
aAf4sLOzQ7vdTqMoGiilFq7rbrdaLZWmqRqPx9tpmvaACDBABiBbW1tJoVD4DjwEnvq+H0dRJNPp
dGnyk3xzq7KO43SB+3mmNj3PO9jb27vMskwajYYBPuexWfn0Hrj3W5YcoFUqlUyz2TTlcnkOvAYq
S0jln7vMt2LzQSXgQd4/gCHwE4gBfgH0ew11MKHjYAAAAABJRU5ErkJggg=="""


def shot(width, height):
    # Take a screenshot
    t = time.time()
    w=Gdk.get_default_root_window()
    _, mousex, mousey, _ = w.get_pointer()
    s=Gdk.Window.create_similar_surface(w, cairo.CONTENT_COLOR, width, height)
    ctx=cairo.Context(s)
    Gdk.cairo_set_source_window(ctx,w,-mousex + (width/2),-mousey + (height/2))
    ctx.paint()
    return (t, s)

SECONDS_DURATION = 3
WIDTH = 300
HEIGHT = 150
FPS = 25
surfaces = []
start = time.time()
last = time.time()
while (time.time() - start) < SECONDS_DURATION:
    while (time.time() - last) < (1.0/FPS):
        time.sleep(0.01)
    last = time.time()
    surfaces.append(shot(WIDTH,HEIGHT))

# now, convert each surface to a PIL PNG then write as a gif

buffer = StringIO.StringIO()
buffer.write(CURSOR.decode("base64"))
buffer.seek(0)
cursor_image = Image.open(buffer)
fp = open("anim.gif", "wb")
previous = None
for t, surface in surfaces:
    sio = StringIO.StringIO()
    surface.write_to_png(sio)
    sio.seek(0)
    im = Image.open(sio)
    im.paste(cursor_image, (WIDTH/2, HEIGHT/2), mask=cursor_image)
    im = im.convert('RGB').convert('P', palette=Image.WEB)
    del surface

    # Fixme: add specific delay (from t) for each frame, and add looping
    if not previous:
        for s in getheader(im) + getdata(im):
            fp.write(s)
    else:
        for s in getdata(im):
            fp.write(s)
    previous = im.copy()
fp.close()

print "Now optimise with gifsicle -b -O3 anim.gif"

More in the discussion (powered by webmentions)