A script library to get the path of the frontmost document

Hi there, here’s a handler that can be used to integrate some apps with DEVONthink. The other script should make it easy to expand this handler with the apps you want to use. (Time to give something back, DEVONthink 3 Beta 2 is great :blush:)

The basic idea is quite simpel: If we get the path of a file which is open in an app outside of DEVONthink we can act on this file.

Once we got the path we can do whatever we like, e.g.

  • lookup if it is already in an open database or
  • open it in DEVONthink or
  • reveal the file in Finder or

it is up to your imagination what you do with the path (and the AppleScript dictionary of the app in question… - otherwise there’s always UI scripting :crazy_face:).

By the way: One nice “side effect” is that this handler is not limited to DEVONthink. In fact it has nothing to do with it. You can use it for nearly everything. I’ll see if I post some use cases later but would be happy to hear how (and if) this works for you.

Not sure if this is necessary: You have to do something with the path… This is not a script but a building block.

Enjoy :blush:

(If I used code without mentioning the original scripter I apologize, did not keep track from the beginning)

The handler:

-- Get the path of the front document (of nearly any application you've listed in thisHandlerCovers)
--
-- For a quick test:
-- 1) activate an app that is listed in thisHandlerCovers and make sure you've opened a saved file in this application
-- 2) uncomment the first two lines in this script and run the script
--
-- To add more apps to this handler use the "frontAppName" script:
-- 1) open the "frontAppName" script in Script Editor (or whatever you use to run scripts)
-- 2) activate the app you want to add
-- 3) go back to Script Editor and run the "frontAppName" script (this copies the process name)
-- 4) now you've got the process name in the clipboard with which you can start to discover how to get this apps / this documents path. (I'm sorry for all the edits, I use this since months and totally forgot to mention basic parts..)

# front_testing()
# set thePath to path_to_front_document()

on path_to_front_document()
	tell application "System Events"
		set frontProcess to name of first process whose frontmost is true
	end tell
	
	set thisHandlerCovers to {"BBEdit", "DEVONthink 3", "Finder", "HoudahSpot", "Marked 2", "MultiMarkdown Composer", "OmniOutliner", "TableFlip", "TextEdit", "Tinderbox 8", "Electron", "Preview", "QuickTime Player", "FoxTrot Professional Search", "Curio", "PhotoStickies", "Numbers", "Nisus Writer Pro"}
	
	if thisHandlerCovers does not contain frontProcess then
		tell application "SystemUIServer"
			activate
			display dialog "Dieses Skript unterstützt diese App (noch) nicht"
		end tell
		return
		
	else
		
		if frontProcess = "BBEdit" then -- BBEdit
			tell application "BBEdit"
				set myFile to get file of front document of window 1
			end tell
			set thePath to POSIX path of myFile
			return thePath
		end if
		
		if frontProcess = "Finder" then -- Finder
			tell application "Finder"
				set theSelection to selection
				set theItem to item 1 of theSelection
				if class of theItem = alias file then
					set theItem to POSIX path of (original item of theItem as text)
				else
					set theItem to item 1 of theSelection as text
				end if
				set thePath to POSIX path of theItem
			end tell
			return thePath
		end if
		
		
		if frontProcess = "HoudahSpot" then -- HoudahSpot
			tell application id "com.houdah.HoudahSpot4"
				tell document 1
					set theSelection to selection
					set thisResult to item 1 of theSelection
					set thePath to path of thisResult
				end tell
			end tell
			return thePath
		end if
		
		
		if frontProcess = "Marked 2" then -- Marked 2
			tell application "Marked 2"
				set thePath to path of document 1
				return thePath
			end tell
		end if
		
		
		if frontProcess = "MultiMarkdown Composer" then -- MultiMarkdown Composer
			tell application "MultiMarkdown Composer"
				set doc to document of window 1
				set theFile to file of doc
			end tell
			set thePath to POSIX path of theFile
			return thePath
		end if
		
		
		if frontProcess = "OmniOutliner" then -- OmniOutliner
			tell application "OmniOutliner"
				set theFile to file of document 1
				set thePath to POSIX path of theFile
			end tell
			return thePath
		end if
		
		
		if frontProcess = "TableFlip" then -- TableFlip
			tell application "System Events"
				tell process "TableFlip"
					set theLocation to value of attribute "AXDocument" of window 1
				end tell
			end tell
			set theLocation to text_item_delimiters(theLocation, "//")
			set theLocation to text item 2 of theLocation
			try
				set thePath to my decode_Text(theLocation)
				return thePath
			on error eMsg number eNum
				error "Can't urlDecode: " & eMsg number eNum
			end try
		end if
		
		
		if frontProcess = "TextEdit" then -- TextEdit
			tell application "TextEdit"
				set doc to document of window 1
				set thePath to path of doc
			end tell
			return thePath
		end if
		
		
		if frontProcess = "Tinderbox 8" then -- Tinderbox 8
			tell application "System Events"
				try
					tell application process "Tinderbox"
						tell window 1
							set theLocation to the value of attribute "AXDocument"
							set theName to the value of attribute "AXTitle"
						end tell
					end tell
				on error error_message number error_number
					if the error_number is not -128 then display alert "Upps, das hat nicht geklappt" message error_message as warning
					return
				end try
			end tell
			set theLocation to text_item_delimiters(theLocation, "//")
			set theLocation to text item 2 of theLocation
			set thePath to my decode_Text(theLocation)
			return thePath
		end if
		
		
		if frontProcess = "Electron" then -- Visual Studio Code 
			tell application "System Events"
				tell application process "Electron"
					tell window 1
						set theLocation to the value of attribute "AXDocument"
					end tell
				end tell
			end tell
			set theLocation to text_item_delimiters(theLocation, "//")
			set theLocation to text item 2 of theLocation
			try
				set thePath to decode_Text(theLocation)
				return thePath
			on error eMsg number eNum
				error "Can't urlDecode: " & eMsg number eNum
				return
			end try
		end if
		
		
		if frontProcess = "Preview" then -- Preview
			tell application id "com.apple.Preview"
				set thePath to path of document 1
			end tell
			return thePath
		end if
		
		
		if frontProcess = "QuickTime Player" then -- QuickTime Player
			tell application id "com.apple.QuickTimePlayerX"
				set thePath to POSIX path of (get file of document 1)
			end tell
			return thePath
		end if
		
		
		if frontProcess = "FoxTrot Professional Search" then -- FoxTrot Professional Search
			tell application "System Events"
				tell process "FoxTrot Professional Search"
					if pop up button 3 of splitter group 1 of group 1 of window 1 exists then
					else
						tell application id "com.ctmdev.FoxTrotPro"
							set theName to name of window 1
							set TextItems to my text_item_delimiters(theName, "/")
							set pathBeginning to ""
							set thePathBeginningItems to items 2 thru -1 of TextItems
							repeat with thisPart in thePathBeginningItems
								set pathBeginning to pathBeginning & "/" & thisPart
							end repeat
							set pathEnding to item 1 of TextItems
							set cleanedPathEnding to my remove_From_String(pathEnding, "  —  ")
							set thePath to pathBeginning & "/" & cleanedPathEnding
						end tell
					end if
				end tell
			end tell
			return thePath
		end if
		
		
		if frontProcess = "Curio" then -- Curio
			tell application "System Events"
				tell process "Curio"
					set theLocation to the value of attribute "AXDocument" of window 1
				end tell
				set theLocation to my text_item_delimiters(theLocation, "//")
				set theLocation to text item 2 of theLocation
				try
					set thePath to my decode_Text(theLocation)
				on error eMsg number eNum
					error "Can't urlDecode: " & eMsg number eNum
				end try
			end tell
			return thePath
		end if
		
		
		if frontProcess = "PhotoStickies" then -- PhotoStickies
			tell application "System Events"
				tell process "PhotoStickies"
					set theFileName to name of window 1
				end tell
			end tell
			tell application id "DNtp"
				try
					set theResults to my lookup_filename(theFileName)
					set thePath to path of item 1 of theResults
				on error error_message number error_number
					if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
				end try
			end tell
			return thePath
		end if
		
		
		if frontProcess = "Numbers" then -- Numbers
			tell application "Numbers"
				set theFile to file of document 1
				set thePath to POSIX path of theFile
			end tell
			return thePath
		end if
		
		
		if frontProcess = "Nisus Writer Pro" then -- Nisus Writer Pro
			tell application id "com.nisus.NisusWriter"
				set thePath to path of document 1
			end tell
			return thePath
		end if
		
	end if
end path_to_front_document

on text_item_delimiters(theText, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	set TextItems to text items of theText
	set AppleScript's text item delimiters to d
	return TextItems
end text_item_delimiters

on decode_Text(theText)
	local str
	try
		return (do shell script "/bin/echo " & quoted form of theText & " | perl -MURI::Escape -lne 'print uri_unescape($_)'")
	on error eMsg number eNum
		error "Can't urlDecode: " & eMsg number eNum
	end try
end decode_Text

on remove_From_String(theText, CharOrString)
	local ASTID, theText, CharOrString, lst
	set ASTID to AppleScript's text item delimiters
	try
		considering case
			if theText does not contain CharOrString then ¬
				return theText
			set AppleScript's text item delimiters to CharOrString
			set lst to theText's text items
		end considering
		set AppleScript's text item delimiters to ASTID
		return lst as text
	on error eMsg number eNum
		set AppleScript's text item delimiters to ASTID
		error "Can't RemoveFromString: " & eMsg number eNum
	end try
end remove_From_String

on lookup_filename(theFileName)
	tell application id "DNtp"
		try
			set theDatabases to databases
			set theResults to {}
			repeat with thisDatabase in theDatabases
				set thisDatabasesResults to lookup records with file theFileName in thisDatabase
				set theResults to theResults & thisDatabasesResults
			end repeat
			if theResults = {} then display notification "Datei nicht in offenen Datenbanken enthalten!" with title "Lookup DEVONthink"
			return theResults
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		end try
	end tell
end lookup_filename

on front_testing()
	tell application "System Events"
		set frontmostProcess to first process where it is frontmost
		set visible of frontmostProcess to false
		repeat while (frontmostProcess is frontmost)
			delay 0.2
		end repeat
		set secondFrontmost to first process where it is frontmost
	end tell
end front_testing

The script to add more apps to the handler:

-- Run this script to get the app name of the frontmost process
-- It's built to be run from Script Editor.app or Script Debugger.app (in order to make it useful for everybody)
-- If you want to run it from an app launcher like Alfred or Keyboard Maestro comment out the first line ("front_testing()")
-- "front_testing" code: https://stackoverflow.com/a/13097996 (modified)
-- "GetApplicationCorrespondingToFrontProcess" code: https://stackoverflow.com/a/6935608 (modified)

front_testing()

set frontAppName to GetApplicationCorrespondingToFrontProcess()

set the clipboard to frontAppName as string

on GetApplicationCorrespondingToFrontProcess()
	tell application "System Events"
		set process_name to name of first process whose frontmost is true
		set process_bid to get the bundle identifier of process process_name
		set application_name to name of (application processes where bundle identifier is process_bid)
	end tell
	return application_name
end GetApplicationCorrespondingToFrontProcess

on front_testing()
	tell application "System Events"
		set frontmostProcess to first process where it is frontmost
		set visible of frontmostProcess to false
		repeat while (frontmostProcess is frontmost)
			delay 0.2
		end repeat
		set secondFrontmost to first process where it is frontmost
	end tell
end front_testing

Again: It’s not limited to DEVONthink :grinning:

To use this as a script library save the handler as a script inside /Users/USERNAME/Library/Script Libraries/.

You can then call it from every other script by using set thePath to script "Path to front document"'s path_to_front_document() (if you’ve chosen the filename I used)

2 Likes

Interesting… I’ll have to have a closer look at this later.

Do note (to anyone): Using the path of a file in DEVONthink is only advised for temporary functions, i.e. don’t store that path outside DEVONthink for later use. The internal path could change.

Hi @pete31 - I have a quick question that’s related to your frontmost document script.

As you know, the PDF Expert portion was a real lifesaver for me! My old process used to drive me crazy. Thanks a ton for sharing!

When running a similar process in Alfred, in my version of the script, I always close the frontmost document after grabbing its path (because I have no more interest working on it using that particular application). In most cases, it’s something as simple as “close document 1”, but in others it can be a little more complicated and involve GUI scripting, etc. Have you implemented anything similar?

The reason I ask is because I wanted to add DEVONThink to the list of apps that the script could grab the path of the frontmost document from, but I was getting hung up on something. Namely, I’d like to do a quick check to tell if the frontmost document is, in fact, a lone/solo document window or simply a document that’s selected and being previewed in Widescreen/Standard mode in the main viewer. The reason is that I only want to close the window if its a lone/solo document (i.e., not being viewed via preview in the main viewer). By chance, do you know how to add a check for window/viewer status?

Using your nomenclature from above, here’s how I think you’d start the script (i.e., with no checks for closing the window - just grabbing the path of the frontmost document or a selected one in the viewer). Do you have any suggestions for checking the status of the window, under the conditions described above?

	if frontProcess = to "DEVONthink 3" then -- DEVONThink
	    tell application id "DNtp"
		    try
			   set theSelection to the selection
			   if theSelection is {} then error "Please select a record"
			   set thePath to the path of the first item of theSelection
			   return thePath
		    end try
	    end tell

As always, thanks a ton for all of your help! I really appreciate it.

All the best!

Here’s a basic teaching edition snippet…

tell application id "DNtp"
	-- viewer window: This is a main window
	-- document window: This is a document window
	-- think window: This is either a viewer or a document window
	
	set frontmostWindow to think window 1
	set windowClass to (class of frontmostWindow)
	
	set currentRecord to (content record of frontmostWindow)
	
	if windowClass is equal to document window then
		close frontmostWindow
	end if
end tell
2 Likes

Thanks a ton, Sensei @BLUEFROG!!! It works perfectly, and your explanation of the window types was extremely helpful, too!

You’re very welcome. :slight_smile: