Screenshots with SnapSCP

This post covers my second screenshot utility, SnapSCP. It differs from the SnapClip in that it doesn’t put the image on the clipboard, it uploads it to the server that runs this blog and puts a link to the image on the clipboard so I can embed it in a post.

Because I want this post to be self-contained, a lot of the description from the SnapClip post will be repeated. Sorry about that, but I think it’s easier to work through code when all the explanation is in one place.

SnapSCP is modeled on the builtin macOS ⇧⌘4 keyboard shortcut for taking a screenshot of a portion of the screen. (It uses macOS’s screencapture command.) Like ⇧⌘4, it starts by changing your pointer to a crosshair, which you can drag across the screen to select an arbitrary rectangle for capturing.

Screenshot with cursor

Alternately, if you want to select an entire window, tap the spacebar. The pointer will change to a camera, and whatever window the camera is over will turn blue, indicating that it will be what’s captured when you click.

Screenshot with window selection

It’s at this point that SnapSCP diverges from ⇧⌘4. A window pops up on your screen with a editable text field and a checkbox.

SnapSCP

The text field is a description of the image and will form part of the file name. The complete file name will be what you enter in the field prefixed by today’s date in yyyymmdd format and a hyphen and suffixed by .png. For example, the image above is saved on the server as

20170227-SnapSCP.png

and the HTML link to it, which SnapSCP puts on the clipboard, is

<img class="ss"
src="http://leancrew.com/all-this/images2017/20170227-SnapSCP.png"
alt="SnapSCP" title="SnapSCP" />

Note that the “base” name of the file gets used for both the alt and title attributes. The alt attribute is for accessibility, and the title attribute is what pops up in your browser if you hover your mouse pointer over the image.

The class attribute, ss, is defined in ANIAT’s CSS file to center the image and limit its maximum width so it doesn’t spill out into the sidebar over to the right. If you want to use SnapSCP for yourself, you’ll need to adjust some of this to fit your situation.

You can use spaces in the name field. The link for the top image is

<img class="ss"
src="http://leancrew.com/all-this/images2017/20170225-Screenshot%20with%20window%20selection.png"
alt="Screenshot with window selection"
title="Screenshot with window selection" />

The alt and title attributes have the spaces, and the src attribute replaces them with %20 in accordance with the standard rules for URL encoding.

You might be wondering why, since I write my posts in Markdown, I use full-blown HTML for embedded images. Mainly, it’s because I’ve never liked Markdown’s “bang syntax” for images. I don’t find

![SnapSCP](http://leancrew.com/all-this/images2017/20170227-SnapSCP.png "SnapSCP") {.ss}

easier to read or to type than the HTML equivalent.1

If you select “Background border”, SnapSCP will put a blue border around the screenshot, just like you see in the screenshot above. Although this option appears for both arbitrary rectangle and window screenshots, it’s intended to be used only for the latter.

The blue background border is my attempt to strike a happy medium between the two types of window screenshots ⇧⌘4 and screencapture can produce: a window with a big dropshadow border,

Window with border

or a bare window with no edges,

Window with no border

The dropshadow border is way too big and lots of people in the Mac world hate it, but the edgeless window gives me vertigo; I feel as if I’ll walk off the edge and fall to my death. It doesn’t even look like a window. SnapSCP’s blue border (which is the same as SnapClip’s) is relatively narrow but gives the sense of a window with the Desktop behind it, which is what I’m looking for in a screenshot.

Window with SnapClip border

The color of the border is Solid Aqua Dark Blue, which is, by an amazing coincidence, the color I use for my Desktop.

Desktop color chooser

Now that what SnapSCP does and how it looks, let’s see how it’s built. It’s a Keyboard Maestro macro with a hot key of ⌃⇧4:

SnapSCP macro

The first step of the macro is the execution of this Python script:

python:
 1:  #!/usr/bin/python
 2:  
 3:  import Pashua
 4:  import tempfile
 5:  import Image
 6:  import sys, os, os.path
 7:  import subprocess
 8:  import urllib
 9:  from datetime import date
10:  
11:  # Parameters
12:  dstring = date.today().strftime('%Y%m%d')
13:  type = "png"
14:  localdir = os.environ['HOME'] + "/Pictures/Screenshots"
15:  tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
16:  bgcolor = (61, 101, 156)
17:  border = 16
18:  optipng = '/usr/local/bin/optipng'
19:  server = 'username@leancrew.com:path/to/images2017/'
20:  port = '9876'
21:  
22:  # Dialog box configuration
23:  conf = '''
24:  # Window properties
25:  *.title = Snapshot
26:  
27:  # File name text field properties
28:  fn.type = textfield
29:  fn.default = Snapshot
30:  fn.width = 264
31:  fn.x = 54
32:  fn.y = 40
33:  fnl.type = text
34:  fnl.default = Name:
35:  fnl.x = 0
36:  fnl.y = 42
37:  
38:  # Border checkbox properties
39:  bd.type = checkbox
40:  bd.label = Background border
41:  bd.x = 10
42:  bd.y = 5
43:  
44:  # Default button
45:  db.type = defaultbutton
46:  db.label = Save
47:  
48:  # Cancel button
49:  cb.type = cancelbutton
50:  '''
51:  
52:  # Capture a portion of the screen and save it to a temporary file.
53:  status = subprocess.call(["screencapture", "-io", "-t", type, tfname])
54:  
55:  # Exit if the user canceled the screencapture.
56:  if not status == 0:
57:    os.remove(tfname)
58:    print "Canceled"
59:    sys.exit()
60:  
61:  # Open the dialog box and get the input.
62:  dialog = Pashua.run(conf)
63:  if dialog['cb'] == '1':
64:    os.remove(tfname)
65:    print "Canceled"
66:    sys.exit()
67:  
68:  # Add a desktop background border if asked for.
69:  snap = Image.open(tfname)
70:  if dialog['bd'] == '1':
71:    # Make a solid-colored background bigger than the screenshot.
72:    snapsize = tuple([ x + 2*border for x in snap.size ])
73:    bg = Image.new('RGB', snapsize, bgcolor)
74:    bg.paste(snap, (border, border))
75:    bg.save(tfname)
76:  
77:  # Rename the temporary file using today's date (yyyymmdd) and the 
78:  # name provided by the user.
79:  name = dialog['fn'].strip()
80:  fname =  '{localdir}/{dstring}-{name}.{type}'.format(**locals())
81:  os.rename(tfname, fname)
82:  bname = os.path.basename(fname)
83:  
84:  # Optimize the PNG.
85:  subprocess.call([optipng, '-quiet', fname])
86:  
87:  # Upload the file via scp.
88:  subprocess.call(['scp', '-P', port, fname, server])
89:  
90:  # Generate a link to the uploaded image.
91:  bname = urllib.quote(bname)
92:  print '<img class="ss" src="http://leancrew.com/all-this/images2017/{bname}" alt="{name}" title="{name}" />'.format(**locals())

The script relies on two nonstandard libraries, i.e., two libraries that don’t come with macOS. The first is Pashua, which handles the SnapClip window and its controls. Pashua is an application written by Carsten Blüm and has libraries for several scripting languages, including Python.

The second nonstandard library is Pillow, which handles the addition of the border to the screenshot. Pillow is a fork of and drop-in replacement for PIL, the Python Imaging Library, a venerable piece of software that’s been around for about two decades.

In addition to Pashua and Pillow, SnapClip also uses OptiPNG, which recompresses PNG files in place losslessly. I typically get a 30–40% reduction in file size on screenshots, definitely enough to make it worthwhile. I installed OptiPNG through Homebrew, but there are other ways to get it.

Let’s go through the script.

Lines 11–20 set up a bunch of parameters that will be used later in the program. If you need to customize the script, this is probably where you’ll do it. Line 12 sets the date string for the file name prefix. Line 13 sets the image type. The screenshots are temporarily saved in a Screenshots folder in my Pictures folder, so Line 14 sets the variable localdir to point there. Line 15 then uses the tempfile library to create a secure temporary file for the screenshot. Line 16 sets the color of the border to match the RGB parameters of Solid Aqua Dark Blue, and Line 17 sets the width of the border to 16 pixels. Lines 18 gives the full path to the impbcopy commands. Finally, Line 19 sets the login name and path to the images folder on the server and Line 20 sets the server’s SSH port number[^port] as a string.

[port]: The standard port number for SSH is 22, but many web hosts—mine included—use a different port number. I don’t know if this is done more for obfuscation or to prevent collisions, but I know it’s fairly common.

Lines 23–50 set up the geometry of the Pashua window. I won’t go through every line here. It should be fairly obvious what each section does, and you can get the details from the Pashua documentation.

Line 53 runs screencapture via the subprocess module. It’s launched in interactive mode (-i), does not capture the dropshadow if capturing a window (-o), and saves the image in PNG format (-t type) to the temporary file (tfname).

Lines 56–59 stop the program and delete the temporary file if the user aborts the screenshot by hitting the Escape key or ⌘-Period. Because the output of this script is saved to a Keyboard Maestro variable, the print command in Line 57 signals a later step in the macro that the user canceled.

Line 62 runs Pashua to put up the SnapClip window and collect its input.

Lines 63–66 stop the program and delete the temporary file if the user clicks the Cancel button in the SnapClip window. Again, the print command in Line 64 is used to signal a later step in the macro.

Lines 69–75 check to see if the user asked for a Desktop background border and add it if necessary. Line 65–66 creates a solid-colored image that’s two border widths larger in each dimension than the raw screenshot. Lines 66–67 then paste the raw screenshot on top of the solid-colored image and save the result back to the temporary image file.

Lines 79–80 take the string typed into the text field (fn) and turns it into a full path to the permanent file name. Line 81 then renames the image file. Line 82 pulls out just the name of the file (no path components) and saves it to the variable bname for later use.

Line 84 uses optipng to reduce the size of the image file.

Line 87 then uploads the file to the server via the SCP protocol. For this command to work smoothly, the user must have set up SSH login credentials on both the local computer and the server. I’ll describe how that’s done in the next post in this series.

Finally, Line 91 URL-encodes the file name and Line 92 constructs and prints the <img> tag for the uploaded file.

Keyboard Maestro takes the output of the script and saves it to the ssurl variable. Under normal circumstances, ssurl will contain the <img> tag, but if the user canceled the process at either of the two steps described above, ssurl will contain the string “Canceled.”

The next step in the macro is a conditional. Keyboard Maestro tests the value of ssurl, and if it’s not “Canceled,” the value is put on the clipboard and the “Glass” sound is played to tell the user that the macro is complete. If the value of ssurl is “Canceled,” then the macro just stops.

Note that this macro leaves a copy of the screenshot in the “Screenshots” folder because I like having a local backup.

I find this macro very efficient at getting screenshots up onto the server and into my posts. What makes it work without a hassle, though, is the way I have my Macs set up to automatically handle the passing of SSH credentials to the server. That’s the topic of the final post in this series.


  1. Attributes aren’t part of John Gruber’s Markdown, but they are part of PHP Markdown Extra, which is—with a little tweaking—what I use on ANIAT. ↩︎