5

I'm using screenshots as regression tests for GUI software. Before deploying each new version, a series of automatic tasks is run against the old and the new version, screenshots are generated after each command in both cases, and the results are compared. ImageMagick's import command has been working very well fo that.

Recently I added right click menus. Unfortunately, import -window 'id' doesn't capture these menus.

Which command line tools on Ubuntu can take a screenshot of a window, and all windows on top of it?

That is, which tools can, instead of taking a screenshot of a window corresponding to a window ID, take a screenshot of the entire screen and truncate it to the boundaries a given window?

I haven't been able to get this result in a simple way with any of the tools listed at What is the terminal command to take a screenshot?.

Jacob Vlijm
  • 83,767
Clément
  • 271
  • 2
  • 14
  • could very well be scripted, with a similar (altered) process like used here: http://askubuntu.com/questions/578728/how-can-i-take-a-screenshot-from-a-window-with-customizable-margins/578787#578787. My question is: would that script do, if I change the selection of the front most window into selection of a specific window? Let me know, can be done. – Jacob Vlijm Feb 07 '16 at 12:11
  • @JacobVlijm: No; popup menus (like right click) are implemented as separate screen windows, so a section of a single window won't do. – Clément Feb 07 '16 at 15:15
  • I believe you misunderstand the linked answer, it does not take a section, but the whole window. – Jacob Vlijm Feb 07 '16 at 15:41
  • See updated answer. – Jacob Vlijm Feb 07 '16 at 16:41

3 Answers3

6

Using shutter and wmctrl, an edited version of this script does pretty much exactly what you describe: it takes a screenshot of the area, a specific window covers on your screen, no matter if and how the window is (partially) below other windows.

The marge around the window, to be included in the screenshot, is arbitrary; set it to zero if you like.

In practice

  • I have an Inkscape window on my screen, with id 0x0520000e, partially covered by a few gedit windows.
  • I run the script with the window id and the marge (in px) around the window as arguments:

    python3 <script> 0x0520000e 10 10 10 10 
    

    (where 10 10 10 10 is the marge in px around the window on the left/right/top/bottom. Set to 0 to have no marge in the image)

    The result:

    enter image description here

The script

#!/usr/bin/env python3
import subprocess
import time
import sys

"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---

get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
time.sleep(0.5)
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])

command = "shutter -s="+(",").join([x,y,w,h])+" -e"
subprocess.call(["/bin/bash", "-c", command])

How to use

  • The script uses Shutter and wmctrl:

    sudo apt-get install wmctrl shutter
    
  • Copy the script below into an empty file, save it as custom_screenshot.py.

  • Run it by the command:

    python3 /path/to/custom_screenshot.py <window_id> <left> <right> <top> <bottom>
    

    where , <left> <right> <top> <bottom> are the marges you'd like to keep in the image around the window, like in this answer.

    Example command:

    python3 /path/to/custom_screenshot.py 0x0520000e 20 20 20 20
    

Explanation

  • In Shutter, it is possible to take a screenshot of a defined area of the desktop.

  • With the window id as an argument, the script looks up the window's exact position with the help of wmctrl (wmctrl -lG to be precise), and the output of xprop -id <window_id> (in the line _NET_FRAME_EXTENTS(CARDINAL) = 0, 0, 28, 0 for example).

  • Subsequently, a screenshot is taken from the found area, with an arbitrary marge.

Note

The script does not overwrite existing screenshots. New screenshots are named:

outputfile_1.png
outputfile_2.png
outputfile_3.png

and so on...


EDIT

Since you mentioned in a comment that speed is an issue:

Based on this script, if we do exactly the same trick, but use Scrot instead of Shutter, we can skip the sleep 0.5 and make it a lot faster:

The script

#!/usr/bin/env python3
import subprocess
import sys
import os

"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---

get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])

# setting default directories / filenames
home = os.environ["HOME"]
temp = home+"/"+".scrot_images"
img_in = temp+"/in.png"
# if you prefer, you can change the two line below:
output_directory = home+"/"+"scrot_images" # output directory
filename = "outputfile"                    # filename
# creating needed directories
for dr in [temp, output_directory]:
    if not os.path.exists(dr):
        os.mkdir(dr)
# creating filename (-number) to prevent overwriting previous shots
n = 1
while True:
    img_out = output_directory+"/"+filename+"_"+str(n)+".png"
    if os.path.exists(img_out):
        n = n+1
    else:
        break
# Take screnshot, crop image
subprocess.call(["scrot", img_in])
subprocess.Popen(["convert", img_in, "-crop", w+"x"+h+"+"+x+"+"+y, "+repage", img_out])

To use

Use it exactly like the first script, only:

  • This script needs scrot, imagemagick and wmctrl

    sudo apt-get install imagemagick wmctrl scrot
    
  • images will be stored in ~/scrot_images

Explanation

While the first script uses the command line option of Shutter to shoot a defined section of the desktop, Scrot does not support that. It only takes a screenshot of the whole screen.

We can combine however imagemagick's option to make an out-take of an image, with the method to find the exact window's coordinates we used in the first script, and crop the image accordingly.
Since Scrot is extremely light weight and quick, even combined with imagemagick's crop action, we have a pretty fast way of making screen shots of a window's area.

Still not fast enough?

Not sure if it is needed, but with a bit of rewriting (see script below), it would be possible to make a series of shots even faster by first shoot the whole series, then (afterwards) do the cropping. Assuming the window would stay in its position, this would save a considearble amount of the time:

  • Shooting only with Scrot (no cropping):

    real    0m0.263s
    user    0m0.205s
    sys     0m0.037s
    
  • Shooting, including cropping:

    real    0m0.363s
    user    0m0.293s
    sys     0m0.040s
    

Serial shooting

Finally, as an example to make a series of screenshots, the script below, as suggested in the EDIT.
This one first shoots all images in a row, then does the cropping on all created images at once.

Use the script like the second one, but with one additional argument: the number of shoots in a row, for example:

python3 /path/to/custom_screenshot.py 0x0520000e 0 0 0 0 20

to make 20 screenshots of window 0x0520000e at a row (could be hundreds), no marge around the window.

The script

#!/usr/bin/env python3
import subprocess
import sys
import os

"""
On different window managers, the window geometry as output of wmctrl differs slightly.
The "deviation" should compensate these differences. Most likely appropriate (tested) settings:
Unity: 0, Gnome: -36, Xfce (Xubuntu): -26, KDE (Kubuntu): 0
"""
#---
deviation = 0
#---

get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
# targeted window
target = sys.argv[1]; arg = sys.argv[2:]
f_data = [l.split() for l in get("wmctrl -lG").splitlines() if target in l][0][2:6]
xt_data = get("xprop -id "+target).split()
xt_i = xt_data.index("_NET_FRAME_EXTENTS(CARDINAL)")
xt = [int(n.replace(",", "")) for n in xt_data[xt_i+2:xt_i+6]]
# set data for screenshot command
x = str(int(f_data[0])-int(arg[0])-xt[0])
y = str(int(f_data[1])-int(arg[2])-xt[2]+deviation)
w = str(int(f_data[2])+int(arg[0])+int(arg[1])+xt[0]+xt[1])
h = str(int(f_data[3])+int(arg[3])+int(arg[2])+xt[2]+xt[3])
# setting default directories / filenames
home = os.environ["HOME"]
temp = home+"/"+".scrot_images"
# if you prefer, you can change the two line below:
output_directory = home+"/"+"scrot_images" # output directory
filename = "outputfile"                    # filename
# creating needed directories
for dr in [temp, output_directory]:
    if not os.path.exists(dr):
        os.mkdir(dr)
# do the shooting
t = 0; l = []; shots = int(sys.argv[6])
while t < shots:
    img_temp = temp+"/"+str(t)+"in.png"
    l.append(img_temp)
    # reading arguments,arranging commands to perform
    subprocess.call(["scrot", img_temp])
    t += 1
# do the cropping on all images in a row
for img in l:
    n = 1
    while True:
        img_out = output_directory+"/"+filename+"_"+str(n)+".png"
        if os.path.exists(img_out):
            n = n+1
        else:
            break
    subprocess.call(["convert", img , "-crop", w+"x"+h+"+"+x+"+"+y, "+repage", img_out])
Jacob Vlijm
  • 83,767
  • I'm wary of the sleep(0.5) call; there's going to be many screenshots (in the order of 300), so waiting .5 seconds between each isn't very good. – Clément Feb 07 '16 at 15:14
  • @Clément see updated answer. – Jacob Vlijm Feb 07 '16 at 16:41
  • Sorry for the confusion; I thought mentioning that screenshots were taken after every command in the program would be enough :) Thanks for updating the answer! – Clément Feb 07 '16 at 17:42
  • Just to clarify: this gets the boundaries of the window, then crops a screenshot of the whole screen, right? – Clément Feb 07 '16 at 17:43
  • @Clément the second script, indeed. Will add an explanation after dinner. – Jacob Vlijm Feb 07 '16 at 17:53
  • The code is quite readable already; thanks a lot :) +1 – Clément Feb 07 '16 at 18:21
  • @Clément added a small section. Especially the last part might be useful to consider if you need to shoot a series. With a small addition / editing, easily possible to run a series of hundreds of screenshots automatically in a row. – Jacob Vlijm Feb 07 '16 at 18:49
  • Thanks! There are two very good answers at this point; I'll wait a bit longer before picking one :) – Clément Feb 07 '16 at 18:50
4

Use import with the -screen option, like

import -screen -window 'id' test.png
-2

Read man import. In my .bashrc (actually in a file sourced by .bashrc) I have:

alias tshhmmss='date +%y%b%d-%H%M%S'
screenshot ()
{
   import -window root ~/var/screenshot/$(tshhmmss)_screendump.png
}
waltinator
  • 36,399