There is an abundance of utilities that will take screenshots under macOS, but I had some specific requirements:
The script will be run by FastScripts 3 by way of a keyboard shortcut.
The screenshot is of the front window of the active app only.
The file name will include the date and time in a format of my choosing.
After taking a screenshot, the script will notify me of the total number of screenshot files in the Screenshots folder.
To test this script, create a “Screenshots” folder in your home folder and then run the script in a script editor. If there is not an active app, or if the script cannot take a screenshot of the active app (which is rare), the script does nothing. I have tested this script on Ventura only.
-- revised 2023.03.07
use framework "AppKit"
use framework "Foundation"
use scripting additions
on main()
set activeAppID to getActiveAppID()
set homeFolder to current application's |NSURL|'s fileURLWithPath:(current application's NSHomeDirectory())
set ssFolder to homeFolder's URLByAppendingPathComponent:"Screenshots" isDirectory:true
set ssFile to "Screenshot on " & getDate() & ".png"
set ssFile to ((ssFolder's URLByAppendingPathComponent:ssFile isDirectory:false)'s |path|()) as text
set ssCount to (getFileCount(ssFolder) + 1)
try
captureWindow(activeAppID, ssFile, ssCount)
end try
end main
on getActiveAppID()
set activeApp to current application's NSWorkspace's sharedWorkspace()'s frontmostApplication()
return activeApp's processIdentifier()
end getActiveAppID
on getDate()
set theDate to current application's NSDate's now()
set dateFormatter to current application's NSDateFormatter's new()
dateFormatter's setDateFormat:"yyyy.MM.dd 'at' HH.mm.ss"
return (dateFormatter's stringFromDate:theDate) as text
end getDate
on getFileCount(theFolder)
set fileManager to current application's NSFileManager's defaultManager()
set theFiles to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
set thePredicate to current application's NSPredicate's predicateWithFormat:"lastPathComponent BEGINSWITH 'Screenshot'"
return ((theFiles's filteredArrayUsingPredicate:thePredicate)'s |count|())
end getFileCount
on captureWindow(activeAppID, ssFile, ssCount)
tell application "System Events" to tell (first process whose unix id is activeAppID)
set {x, y} to position of window 1
set {w, h} to size of window 1
end tell
set positionSize to " " & x & "," & y & "," & w & "," & h & " "
do shell script "screencapture -t png -R" & positionSize & quoted form of ssFile -- thanks ionah
if ssCount = 1 then
display notification "1 file" with title "Window Capture"
else
display notification (ssCount as text) & " files" with title "Window Capture"
end if
end captureWindow
main()
My script saves the screenshot files in PNG format, but it is easily modified to use another format. To makes this change, replace “png” in two places in the following code lines with the desired image format extension. This requires my script as revised on 2023.03.07.
set ssFile to "Screenshot on " & getDate() & ".png"
do shell script "screencapture -t png -R" & positionSize & quoted form of ssFile
I took a screenshot of my script in Script Debugger with different image formats and the file sizes were as shown below. The screencapture man page does not specify GIF as a supported image format, although it did work on my Ventura computer.
I often find it useful to merge multiple screenshots in a PDF file and that’s the purpose of the script contained below. It assumes that the screenshot files are in the ~/Screenshots folder in PNG format. To test, simply open in a script editor and run.
use framework "AppKit"
use framework "Foundation"
use framework "Quartz"
use scripting additions
on main()
set homeFolder to current application's |NSURL|'s fileURLWithPath:(current application's NSHomeDirectory())
set desktopFolder to homeFolder's URLByAppendingPathComponent:"Desktop" isDirectory:true
set ssFolder to homeFolder's URLByAppendingPathComponent:"Screenshots" isDirectory:true
set pdfFile to "Screenshots Merged on " & getDate() & ".pdf"
set pdfFile to desktopFolder's URLByAppendingPathComponent:pdfFile isDirectory:false
set ssFiles to getFiles(ssFolder)
set ssCount to ssFiles's |count|()
if ssCount > 0 then makePDF(ssFiles, ssCount, pdfFile)
if ssCount = 0 then
display notification "No screencapture files" with title "Screenshot Merge"
else if ssCount = 1 then
display notification "1 page in PDF" with title "Screenshot Merge"
else
display notification (ssCount as text) & " pages in PDF" with title "Screenshot Merge"
end if
end main
on getDate()
set theDate to current application's NSDate's now()
set dateFormatter to current application's NSDateFormatter's new()
dateFormatter's setDateFormat:"yyyy.MM.dd 'at' HH.mm.ss"
return (dateFormatter's stringFromDate:theDate) as text
end getDate
on getFiles(theFolder)
set fileManager to current application's NSFileManager's defaultManager()
set theFiles to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
set thePredicate to current application's NSPredicate's predicateWithFormat:"pathExtension ==[c] 'png'"
set theFiles to (theFiles's filteredArrayUsingPredicate:thePredicate)
set sortDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"path" ascending:true selector:"localizedStandardCompare:"
return theFiles's sortedArrayUsingDescriptors:{sortDescriptor}
end getFiles
on makePDF(ssFiles, ssCount, pdfFile)
set pdfDoc to current application's PDFDocument's new()
repeat with i from 0 to (ssCount - 1)
set theImage to (current application's NSImage's alloc()'s initWithContentsOfURL:(ssFiles's objectAtIndex:i))
set thePDFPage to (current application's PDFPage's alloc's initWithImage:theImage)
(pdfDoc's insertPage:thePDFPage atIndex:i)
end repeat
pdfDoc's writeToURL:pdfFile
end makePDF
main()
The script in post 1 works by using the position and size of the frontmost window. This does the job but the screenshot includes a few pixels of the screen behind the window’s rounded corners.
To avoid this, the following script first attempts to take a screenshot of the frontmost window by using the window’s ID. This will fail with apps that are not scriptable, and when that happens the process mentioned above is used.
use framework "AppKit"
use framework "Foundation"
use scripting additions
on windowcapture()
set {activeApp, activeAppID} to getActiveAppID()
set homeFolder to current application's |NSURL|'s fileURLWithPath:(current application's NSHomeDirectory())
set ssFolder to homeFolder's URLByAppendingPathComponent:"Screenshots" isDirectory:true
set ssFile to "Windowcapture " & getDate() & ".png"
set ssFile to ((ssFolder's URLByAppendingPathComponent:ssFile isDirectory:false)'s |path|()) as text
set ssCount to (getFileCount(ssFolder) + 1)
try
tell application activeApp to set windowID to id of window 1
do shell script "screencapture -ox -l " & windowID & space & quoted form of ssFile
on error
tell application "System Events" to tell (first process whose unix id is activeAppID)
if not (exists window 1) then error number -128
set {x, y} to position of window 1
set {w, h} to size of window 1
end tell
set positionSize to " " & x & "," & y & "," & w & "," & h & " "
do shell script "screencapture -t png -R" & positionSize & quoted form of ssFile -- thanks ionah
end try
if ssCount = 1 then
display notification "1 file" with title "Windowcapture"
else
display notification (ssCount as text) & " files" with title "Windowcapture"
end if
end windowcapture
on getActiveAppID()
set activeApp to current application's NSWorkspace's sharedWorkspace()'s frontmostApplication()
set activeAppName to activeApp's localizedName() as text
set activeAppID to activeApp's processIdentifier()
return {activeAppName, activeAppID}
end getActiveAppID
on getDate()
set theDate to current application's NSDate's now()
set dateFormatter to current application's NSDateFormatter's new()
dateFormatter's setDateFormat:"HHmmss" -- edit as desired
return (dateFormatter's stringFromDate:theDate) as text
end getDate
on getFileCount(theFolder)
set fileManager to current application's NSFileManager's defaultManager()
set theFiles to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
set thePredicate to current application's NSPredicate's predicateWithFormat:"pathExtension == 'png'"
return ((theFiles's filteredArrayUsingPredicate:thePredicate)'s |count|())
end getFileCount
windowcapture()
I opened the script in post 1 in Script Debugger and ran it while the app Bean had a document open and received an error message (missing value doesn’t understand the “filteredArrayUsingPredicate_”). Thinking that Bean may not be scriptable, I opened a Numbers document, received the same error.
The script expects to find a Screenshots folder in your Home folder and will throw an error if its not found. The script should probably test for that but it doesn’t.
The script takes a screenshot of the frontmost window. So, if the script is run from within Script Debugger, it should make a screenshot of the frontmost Script Debugger window. It works OK in my testing.
For normal use, the script has to be run in the background by way of the Script menu (which is enabled in Script Editor) or by way of a utility like FastScripts. I don’t have the Bean app, but the script does work in my testing with Numbers.
BTW, the script in post 1 should work with just about any app, and it shouldn’t make any difference if it’s scriptable.
AppKit and Foundation are Objective-C classes that are used by the script. They have nothing to do with script libraries, and you don’t have to take any action for them to work. The same is true of scripting additions. The script should work as written, other than making the Screenshots folder.
Looking through the script, it mentions both “screenshot” and “screenshots” and I’ve tried the folder with both names, still nothing. I’ll try playing with it again later tonight, possibly I’ll come up with an answer.
By the way, thanks for posting both here and in the “shortcuts” area. Keeps me engaged working with both (the “backup shortcuts” shortcut works like a charm).
Your script works perfectly . . . once one puts the Screenshots folder in the proper location. I had originally created it in my Documents folder, once put in the Home folder, bingo!
I use the following script to view screenshots with the Quick Look utility. The path has to be changed to the correct value, and the file extension has to be changed if it’s other than PNG.
use framework "Foundation"
set theFolder to "/Users/Robert/Screenshots/" -- change to correct value
set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
set fileManager to current application's NSFileManager's defaultManager()
set folderContents to fileManager's contentsOfDirectoryAtURL:(theFolder) includingPropertiesForKeys:{} options:4 |error|:(missing value)
set thePredicate to current application's NSPredicate's predicateWithFormat:"pathExtension ==[c] 'png'"
set theFiles to ((folderContents's filteredArrayUsingPredicate:thePredicate)'s valueForKey:"path")'s sortedArrayUsingSelector:"localizedStandardCompare:"
set commandPath to "/usr/bin/qlmanage"
set commandArguments to {"-p"} & theFiles
set theTask to current application's NSTask's new()
theTask's setLaunchPath:commandPath
theTask's setArguments:commandArguments
set theResult to theTask's launchAndReturnError:(missing value)