Screen captures with file upload

Things have come full circle. Several years ago I wrote a little script called snapftp, which combined screen capturing with file uploading via FTP to streamline the process of including screenshots in my posts here. As I mentioned last night, this later evolved into snapflickr, a similar script for screen capturing and uploading to Flickr. Now that I’ve returned to hosting images on my web server, I need to return to something similar to snapftp, but I want this one to be smarter.

First, I won’t be using FTP anymore. I never had any security issues with FTP, but it just makes more sense to use SCP. Second, I want to eliminate (or at least greatly reduce) the chance of duplicate file names. And finally, I’m going to implement this as a Keyboard Maestro macro, which gives me a bit more flexibility in divvying up the work between Python and shell scripts.

Using the new macro, called SnapSCP, is only slightly more complicated than using the SnapClip macro. I invoke it through the ⌃⇧4 keyboard combination, choose a window or rectangular area to capture, give the image file a name, and decide whether it should have a border. The file is then saved locally in my ~/Pictures/Screenshots folder and is uploaded to an images directory on the blog server. An HTML <img> tag is put on the clipboard for pasting into a post.1

Here’s the GUI presented for setting the name and choosing whether to add a border:


The Name field is used to populate the alt and title attributes of the img, and it’s also part of the file name. But it isn’t the complete file name, because that would make it too easy to create file name conflicts. So the file name is whatever’s written in the Name field, prefixed by the date in yyyymmdd format. Thus, the file name of the image above is 20150125-SnapSCP.png. Although I could never enter unique names over the course of a year, month, or even a week, I figure I can remember to give unique names during a single day.

Here’s the Keyboard Maestro macro:

Keyboard Maestro SnapSCP

The first Python script is

 1:  #!/usr/bin/python
 3:  import Pashua
 4:  import tempfile
 5:  import Image
 6:  import sys, os
 7:  import subprocess
 8:  from datetime import date
10:  # Local parameters
11:  dstring ='%Y%m%d')
12:  type = "png"
13:  localdir = os.environ['HOME'] + "/Pictures/Screenshots"
14:  tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
15:  bgcolor = (61, 101, 156)
16:  bigshadow = (25, 5, 25, 35)
17:  smallshadow = (0, 0, 0, 0)
18:  border = 16
20:  # Dialog box configuration
21:  conf = '''
22:  # Window properties
23:  *.title = Snapshot
25:  # File name text field properties
26:  fn.type = textfield
27:  fn.default = Snapshot
28:  fn.width = 264
29:  fn.x = 54
30:  fn.y = 40
31:  fnl.type = text
32:  fnl.default = Name:
33:  fnl.x = 0
34:  fnl.y = 42
36:  # Border checkbox properties
37:  bd.type = checkbox
38:  bd.label = Background border
39:  bd.x = 10
40:  bd.y = 5
42:  # Default button
43:  db.type = defaultbutton
44:  db.label = Save
46:  # Cancel button
47:  cb.type = cancelbutton
48:  '''
50:  # Capture a portion of the screen and save it to a temporary file.
51:["screencapture", "-ioW", "-t", type, tfname])
53:  # Exit if the user canceled the screencapture.
54:  if not os.access(tfname, os.F_OK):
55:    os.remove(tfname)
56:    sys.exit()
58:  # Open the dialog box and get the input.
59:  dialog =
60:  if dialog['cb'] == '1':
61:    os.remove(tfname)
62:    sys.exit()
64:  # Add a desktop background border if asked for.
65:  snap =
66:  if dialog['bd'] == '1':
67:    # Make a solid-colored background bigger than the screenshot.
68:    snapsize = tuple([ x + 2*border for x in snap.size ])
69:    bg ='RGB', snapsize, bgcolor)
70:    bg.paste(snap, (border, border))
73:  # Rename the temporary file using today's date (yyyymmdd) and the 
74:  # name provided by the user.
75:  name = dialog['fn'].strip()
76:  fname =  '{localdir}/{dstring}-{name}.{type}'.format(**locals())
77:  os.rename(tfname, fname)
79:  print fname

Most of this follows the same logic as the script in the SnapClip macro, so I won’t repeat that explanation here. The main differences are:

As you can see in the KM screenshot, the output of this script, if any, is saved to a KM variable called fname. If this variable exists and is not empty, the image file is then uploaded to the server through this one-line shell script:

scp -P 22 "$KMVAR_fname"

The -P option to scp should be set to the server’s SSH port. As you’ve probably guessed, my server’s SSH port isn’t actually set to 22.

The first argument is the local file, which we get from the KMVAR_fname environment variable. For each variable defined in a macro, Keyboard Maestro sets an environment variable of the form KMVAR_variable that can be accessed from any script. The quotes around the variable are there to accommodate characters (like spaces) that have special meaning to the shell.

The second argument is the remote directory to which the file is uploaded. Because I have SSH keys set up, I don’t need to provide a password—scp looks in the ~/.ssh/ directories on both the server and my local machine for the information necessary to log in securely.

The final Python script generates the img tag and puts it on the clipboard.

 1:  #!/usr/bin/python
 3:  from datetime import date
 4:  import os
 5:  import os.path
 6:  import urllib
 8:  # File names: with and without date prefix and extension.
 9:  fname = os.path.basename(os.environ['KMVAR_fname'])
10:  name = os.path.splitext(fname)[0].split('-', 1)[-1]
11:  fname = urllib.quote(fname)
13:  print '<img class="ss" src="{fname}" alt="{name}" title="{name}" />'.format(**locals())

Like the shell script, it accesses the file name through the KMVAR_fname environment variable. It extracts the parts necessary for the src, alt, and title attributes, and combines them into a full image tag. Line 11 URL-encodes the file name, which isn’t actually necessary but seems like good practice. I’ve noticed, for example, that Transmit encodes when you right-click on an item and choose the Copy URL command.

Because I use LaunchBar’s Clipboard History feature, I don’t have to paste the image tag immediately after running SnapSCP. I can take several screenshots in a row, in any order, and then paste the links into a post later. This is one of the many advantages of writing your own scripts—you can create commands that not only fit in with how you think, but also fit in with the rest of the tools you use.

  1. Although I write in Markdown, I don’t use the Markdown syntax for images. I’ve never found it especially intuitive, and it isn’t convenient for assigning classes to the image. But one of the great things about Markdown is that it allows you to drop down into HTML when you want to. 

Screen captures to clipboard again

Back in 2011, in an attempt to reduce site traffic when this blog was on a different host and was being run on WordPress, I began using my Flickr account to host the screen captures that accompany many of my posts. I wrote a little utility, called snapflickr to make the process of capturing and uploading easier. Last year, I added the ability to save the screen captures to the clipboard. I did this so I could open the screen images quickly in Acorn for editing, but later found myself using it to get online receipts (captured from, for example, Amazon’s invoice pages) to add to my expense reports in Numbers. This worked, but was a little clumsy—negotiating the additional options in the utility slowed down the process of getting a screenshot.

SnapFlickr options

Now that I have a static site and have switched hosts, it seemed like a good time to

  1. Stop using Flickr as a poor man’s CDN and go back to hosting images on the blog’s server.
  2. Split the utility in two: one that sends screenshots to the clipboard, and another that uploads them to the server.

I’m still tweaking the one that uploads to the server, but the screen capturing clipboard utility is ready for its closeup. It’s a Keyboard Maestro macro called SnapClip that looks like this in the KM Editor:

Keyboard Maestro SnapClip

When I press the ⌃⌥⌘4 keyboard combination, the cursor goes into screen capture mode, much as it would if I pressed the system standard ⇧⌘4 or ⌃⇧⌘4 combos. But after I’ve chosen the window or screen rectangle I want to capture, SnapClip displays this window:


The checkbox allows me to put a dark blue border around the captured image, which is the style I’ve adopted for full-window captures. I think it makes the captured image look more like a window on the Desktop than if the window is captured “bare.”

Of course, providing the option for the border means I can’t just use what’s built into the ⌃⇧⌘4 command. The Python script that SnapClip runs is this:

 1:  #!/usr/bin/python
 3:  import Pashua
 4:  import tempfile
 5:  import Image
 6:  import sys, os
 7:  import subprocess
 9:  # Local parameters
10:  type = "png"
11:  localdir = os.environ['HOME'] + "/Pictures/Screenshots"
12:  tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
13:  bgcolor = (61, 101, 156)
14:  border = 16
16:  # Dialog box configuration
17:  conf = '''
18:  # Window properties
19:  *.title = Snapshot
21:  # Border checkbox properties
22:  bd.type = checkbox
23:  bd.label = Background border
24:  bd.x = 10
25:  bd.y = 40
27:  # Default button
28:  db.type = defaultbutton
29:  db.label = Clipboard
31:  # Cancel button
32:  cb.type = cancelbutton
33:  '''
35:  # Capture a portion of the screen and save it to a temporary file.
36:["screencapture", "-ioW", "-t", type, tfname])
38:  # Exit if the user canceled the screencapture.
39:  if not os.access(tfname, os.F_OK):
40:    os.remove(tfname)
41:    sys.exit()
43:  # Open the dialog box and get the input.
44:  dialog =
45:  if dialog['cb'] == '1':
46:    os.remove(tfname)
47:    sys.exit()
49:  # Add a desktop background border if asked for.
50:  snap =
51:  if dialog['bd'] == '1':
52:    # Make a solid-colored background bigger than the screenshot.
53:    snapsize = tuple([ x + 2*border for x in snap.size ])
54:    bg ='RGB', snapsize, bgcolor)
55:    bg.paste(snap, (border, border))
58:  # Put the image on the clipboard and delete the temporary file.
59:  impbcopy = os.environ['HOME'] + '/Dropbox/bin/impbcopy'
60:[impbcopy, tfname])
61:  os.remove(tfname)

As you can see from the import lines at the top, this script uses two nonstandard libraries: Pashua and Image.

The Pashua library provides the interface to Carsten Blüm’s Pashua application. This is a great little utility for adding simple GUIs to scripts. It’s similar in many ways to Platypus, but I find it generally easier to use.

The Image library is one of the modules in the Python Imaging Library (PIL), the old standby for image editing in Python. PIL is what I use to add the border to the screenshot.

Lines 9–14 define the parameters that govern the specifics of the rest of the program: the image format for the screenshot, the location and name of the temporary image file, and the size and color of the border.

Lines 16–32 specify the layout of the window shown above.

Line 36 captures the image by running OS X’s screencapture command. I have the options set to start the process in window capture mode, but it’s easy to switch to rectangle capture mode by pressing the space bar. Because it might need to add a border, SnapClip saves the captured image to disk using the temporary file name defined in Line 12.

Lines 43–47 put up the window and collect the user input. If I click the “Background border” checkbox, Lines 49–56 open the image file, add the border, and save the edited file back to disk.

Finally, Lines 58–61 put the image on the clipboard and clean up by deleting the temporary image file. I use Alec Jacobson’s impbcopy command line tool for this. It’s a clever little program that mimics the builtin pbcopy command, but for image data instead of text.

While this is not quite as efficient as the standard ⌃⇧⌘4 keyboard combo when I don’t want a border (it requires an extra tap on the Return key), it’s far easier to use when I do. And I prefer to remember just one keyboard combination for putting screen captures on the clipboard.

DNS migration

As predicted, I made a boneheaded mistake last night when switching DNS records to the site’s new host. Things seem to be working now, and if you’re reading this, it means the DNS propagation has made it to your service provider.

I got interrupted while in the middle of following Digital Ocean’s instructions. I had changed settings at my domain registrar and was in the middle of configuring the domain in DO’s control panel. I’d just set the A record when I had to leave the computer for while. When I came back, I thought I was done and didn’t go on to set the CNAME record. Later, after the DNS changes had propagated to my service provider, the server errors told me I’d screwed something up. Luckily, it wasn’t hard to figure out and fix.

There were a couple of things I actually did well. First, before making any DNS changes, I tested the site on the new server to make sure I’d copied everything over and had configured Apache correctly. One way to do this is to substitute the IP number of the new server wherever or would appear in a URL. For example,

would be the way to address this page on the new server.1 That’s kind of a pain in the ass, though, and it doesn’t help me test my various redirection and rewriting rules.2 To make it easier to test everything before “going live,” I added this line to the /etc/hosts file on my local computer.

The /etc/hosts file takes precedence over DNS, so it directed all my URLs to the new server. I commented the line out (by putting a hash mark at the front) when I wasn’t testing.

With sometimes pointing to the old server and sometimes to the new server, I needed to make sure I knew which one I was polling at any given time. The simplest way I found to do this was with curl:

curl -v

The -v option puts curl in verbose mode. In this mode, it doesn’t just fetch the data, it also prints a running log of the conversation with the server. Included in the conversation is the IP number of the server.

Another option I found useful was -L, which causes curl to follow any redirections. Combining -L with -v let me track the redirections to make sure they worked.

I won’t be surprised to find other mistakes over the next few days, but I think the big ones are behind me.

Update 1/22/15 10:28 AM
Well, that didn’t take long. I forgot to change Apache’s DirectoryIndex setting to include index.xml files. That screwed up the RSS feed. It’s fixed now, but some of the feed-syncing services will never check again.

  1. I hope it’s obvious to you that that’s a fake IP number. It’s not even legal. 

  2. This blog has changed from Blosxom to Movable Type to WordPress to static, and the rewriting rules allow very old links to still work. 

Site housekeeping

Yesterday I started up a VPS at Digital Ocean,1 installed the bare minimum2 to get Apache going, and copied most of over there. So far, my tests show that everything that should be working is—a nice bonus for having a static site. I’ll be moving the rest of the stuff during the next day or so and will then make the necessary DNS changes.

It seems inevitable that something will break at some point in the process, and the site will stop working. I’m hoping to keep that to minimum, but I’ll apologize in advance for any interruptions that occur. No one should be denied, even for a minute, this incredibly valuable content.

  1. I used Joe Fake Name Steel’s affiliate link, so I hope he got the credit. My link is, of course, an affiliate link, too. 

  2. Hello apt-get, my old friend.