Tweaking my screenshot macro

In my recent post on the Keyboard Maestro episode of Mac Power Users, I needed a screenshot of a Typinator window and had trouble getting what I wanted. I tried using my SnapClip macro, but I found that when the macro’s user input window appeared,

SnapClip input window

the Typinator window went into the background (as expected) but didn’t come back to the foreground when the KM window was dismissed (not as expected). I think the reason was that the Typinator window isn’t a regular application window, but whatever the reason, I had to take the screenshot “by hand” using ⇧⌘4 instead of using SnapClip. My positioning of the crosshairs was off as I tried to get both the window and a desktop background border, so the image was a little wonky:

Typinator temporary snippet

The borders are clearly of different widths. Also, and this may not be as obvious, the blue border color varies because my ⇧⌘4 screenshot captured some of the window’s shadow.

The answer was to rewrite SnapClip so the screenshot is taken before the Keyboard Maestro user input window appears. Now SnapClip can handle the Typinator window:

Reshot Typinator window

Much better.

The way SnapClip was written, it put up the user input window and used the results of that interaction to send options to a Python script named screenshot, the source code of which is included in the SnapClip post. screenshot called macOS’s built-in screencapture command with the appropriate arguments. To fix the “Typinator window problem,” I had to rewrite both the KM macro and the Python script.

Here’s the new version of SnapClip, which you can also download (you may need to right-click that link to download it instead of displaying its XML).

KM SnapClip

I’m not going to describe the whole thing. It’s basically the same as the old version but with these changes:

As with the old version, I’ve colored the actions to help keep track of the nested Ifs. The outermost, which tests whether a title is given, is light blue-green,      ; the middle one, which tests whether the image is supposed to have a background border, is orange,      ; and the innermost, which tests whether the image is supposed to be uploaded to my server, is purplish-pink,      .

The process-screenshot script is this:

python:
 1:  #!/usr/bin/env python3
 2:  
 3:  import tempfile
 4:  from PIL import Image
 5:  import os
 6:  import subprocess
 7:  import shutil
 8:  from datetime import datetime
 9:  import urllib.parse
10:  import argparse
11:  
12:  # Handle the arguments
13:  desc = 'Process a screenshot saved to a file.'
14:  ep = '''If a title is given, it saves the image to a file on the
15:  Desktop with a filename of the form yyyymmdd-title.png.
16:  If no title is given, the image is put on the clipboard and the
17:  upload option is ignored.'''
18:  parser = argparse.ArgumentParser(description=desc, epilog=ep,
19:            formatter_class=argparse.RawDescriptionHelpFormatter)
20:  parser.add_argument('-b', '--background', help='add desktop background border', action='store_true')
21:  parser.add_argument('-u', '--upload', help='upload to images directory and print URL', action='store_true')
22:  parser.add_argument('-t', '--title', help='image title', type=str)
23:  parser.add_argument('ssfile', help='path to screenshot file')
24:  args = parser.parse_args()
25:  
26:  # Parameters
27:  type = "png"
28:  bgcolor = (85, 111, 137)
29:  border = 32
30:  optimizer = '/Applications/ImageOptim.app/Contents/MacOS/ImageOptim'
31:  
32:  # Add a desktop background border if asked for
33:  if args.background:
34:    snap = Image.open(args.ssfile)
35:    # Make a solid-colored background bigger than the screenshot.
36:    snapsize = tuple([ x + 2*border for x in snap.size ])
37:    bg = Image.new('RGBA', snapsize, bgcolor)
38:    bg.alpha_composite(snap, dest=(border, border))
39:    bg.save(args.ssfile)
40:  
41:  # Optimize the file
42:  subprocess.run([optimizer, args.ssfile], stderr=subprocess.DEVNULL)
43:  
44:  # Save it to a Desktop file if a title was given; otherwise,
45:  # save it to the clipboard
46:  if args.title:
47:    sdate = datetime.now().strftime("%Y%m%d")
48:    desktop = os.environ['HOME'] + "/Desktop/"
49:    fname = f'{desktop}{sdate}-{args.title}.{type}'
50:    shutil.copyfile(args.ssfile, fname)
51:    bname = os.path.basename(fname)
52:  
53:    # Upload the file and print the URL if asked
54:    if args.upload:
55:      year = datetime.now().strftime("%Y")
56:      server = f'user@server.com:path/to/images{year}/'
57:      port = '123456789'
58:      subprocess.run(['scp', '-P', port, fname, server])
59:      bname = urllib.parse.quote(bname)
60:      print(f'https://leancrew.com/all-this/images{year}/{bname}')
61:  else:
62:    subprocess.call(['impbcopy', args.ssfile])
63:  
64:  # Delete the temporary file
65:  os.remove(args.ssfile)

The main differences between it and the screenshot script are:

Otherwise, it follows the logic of screenshot.

Will this be my last version of SnapClip? Of course not. But it gets better every time I rewrite it, and I learn more about both my workflow and the ins and outs of macOS every time I change it.