February 26, 2021 at 10:04 PM by Dr. Drang
I’ve often said that some of the things I write here are more for my benefit than for yours. I figure that going through the effort of writing out a procedure and posting it will help me remember it. And if I can’t remember the procedure itself, at least I’ll remember that I wrote about it once, and I’ll be able to search the blog for it.
So it really pisses me off to realize that I’ve solved a problem, written about it, and then had absolutely no memory of it whatsoever when the problem reappeared. I am talking, unfortunately, about SnapClip again. You’re tired of it, I’m tired of it, but I need to get this out because there’s a much better solution to the “path to
optipng” problem than what I came up with earlier today. You can find the answer on the Keyboard Maestro Wiki and, damningly, here on this very blog in a post from only four years ago.
I was finally reminded that this was a solved problem by a tweet from Vítor Galvão. He suggested starting my Python script with a change to the PATH environment variable: adding both
/usr/local/bin. Having extra directories in your PATH doesn’t hurt anything, even if some of those directories don’t exist. Vítor does this with Ruby scripts he runs from within Alfred.
But in Keyboard Maestro there’s a better way.
- Open up the Variables preference pane and add a variable called
Set it to whatever you would like your PATH to be when you run shell scripts within Keyboard Maestro. I use1
/Users/drdrang/Dropbox/bin: /Users/drdrang/opt/anaconda/bin: /opt/homebrew/bin: /usr/local/bin: /usr/bin: /bin: /usr/sbin: /sbin
Note that I’ve put each directory on its own line to make it easy to read. The real value of ENV_PATH has all those lines mashed together:
- Never worry about your PATH again. Every executable in those directories will be available to you as if you were working at the Terminal. No need to figure out absolute paths.
So my final (I hope) iteration on the SnapClip script is this:
1: #!/usr/bin/python3 2: 3: import Pashua 4: import tempfile 5: from PIL import Image 6: import sys, os 7: import subprocess 8: import shutil 9: from datetime import datetime 10: 11: # Local parameters 12: type = "png" 13: localdir = os.environ['HOME'] + "/Pictures/Screenshots" 14: tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir) 15: bgcolor = (85, 111, 137) 16: border = 16 17: desktop = os.environ['HOME'] + "/Desktop/" 18: fname = desktop + datetime.now().strftime("%Y%m%d-%H%M%S." + type) 19: 20: # Dialog box configuration 21: conf = b''' 22: # Window properties 23: *.title = Snapshot 24: 25: # Border checkbox properties 26: bd.type = checkbox 27: bd.label = Add background 28: bd.x = 10 29: bd.y = 60 30: 31: # Save file checkbox properties 32: sf.type = checkbox 33: sf.label = Save file to Desktop 34: sf.x = 10 35: sf.y = 35 36: 37: # Default button 38: db.type = defaultbutton 39: db.label = Clipboard 40: 41: # Cancel button 42: cb.type = cancelbutton 43: ''' 44: 45: # Capture a portion of the screen and save it to a temporary file. 46: status = subprocess.call(["screencapture", "-io", "-t", type, tfname]) 47: 48: # Exit if the user canceled the screencapture. 49: if not status == 0: 50: os.remove(tfname) 51: sys.exit() 52: 53: # Open the dialog box and get the input. 54: dialog = Pashua.run(conf) 55: if dialog['cb'] == '1': 56: os.remove(tfname) 57: sys.exit() 58: 59: # Add a desktop background border if asked for. 60: snap = Image.open(tfname) 61: if dialog['bd'] == '1': 62: # Make a solid-colored background bigger than the screenshot. 63: snapsize = tuple([ x + 2*border for x in snap.size ]) 64: bg = Image.new('RGBA', snapsize, bgcolor) 65: bg.alpha_composite(snap, dest=(border, border)) 66: bg.save(tfname) 67: 68: # Optimize the file. 69: subprocess.call(['optipng', '-quiet', tfname]) 70: 71: # Put the image on the clipboard. 72: subprocess.call(['impbcopy', tfname]) 73: 74: # Save to Desktop if asked for. 75: if dialog['sf'] == '1': 76: shutil.copyfile(tfname, fname) 77: 78: # Delete the temporary file 79: os.remove(tfname)
As you can see, there’s no more need to provide absolute paths to
optipng in the “local parameters” section. They appear as short, simple strings in Lines 69 and 72. Setting up the proper environment in Keyboard Maestro, which I have known how to do for four years, solves everything and works on every Mac.
I promise I will stop writing about SnapClip even if I find another improvement.
No, my username is not “drdrang,” it’s my real name. ↩