Windowcapture Script

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()
3 Likes

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.

FILE EXTENSION - FILE SIZE
gif - 268 KB
jpg - 584 KB
png - 699 KB
pdf - 1.6 MB
tiff - 15.9 MB

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.

Homer712. Thanks for testing my script in post 1.

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.

Quick (possibly stupid question, remember, I’m still learning) question. At the very top of your script there is this:

All I have in my “Script Libraries” is this:

Could that be the issue?

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!

1 Like

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)