Safely Script multiple apps that works on the same file type (uti)

Hello.

Here is a way to identify and address applications by their bundle id instead of their displayed name, since that
may vary. It also helps you to find out which application is the default one.

This is thought used in some closed context, where you want to script whatever application a file with a certain uniform type identifier is opened in. But within the context of that single file type (uti)

if the handler defaultAppBundleId doesn’t return a value then it is the assumed one which is default.
It is your responsibility to find out what application that may be.

Here is an example of my usage.


set res to BundleId's defaultAppBundleId("com.apple.applescript.script") --public.filename-extension 
-- set res to defaultApp("public.xml") --public.filename-extension 
log res -- (*com.latenightsw.scriptdebugger*)

set res to BundleId's frontmostBundleId()
log res -- (*com.apple.ScriptEditor2*)

if theAppIsSupported(res) then
	-- sample of how this makes you stay clear of naming conflicts:
	set tMessage to "Supported"
	tell application id res to activate
else
	set tMessage to "Unsupported"
end if
tell me
	activate
	display dialog "Supported"
end tell

on theAppIsSupported(theBundleId)
	considering case
		if theBundleId is in {"com.satimage.Smile", "com.apple.ScriptEditor2", "com.latenightsw.ScriptDebugger"} then
			return true
		else
			error "The default app you use for opening the scripts with is unknown to me."
			return false
		end if
	end considering
end theAppIsSupported


-- The Idea and implementation and any faults is totally mine. © McUsr 2010 and put in the Public Domain.
-- The usually guarrantees about nothing what so ever applies, use it at your own risk.
-- Read the documentation.
-- You are not allowed to post this code elsewhere, but may of course refer to the post at macscripter.net.
-- macscripter.net/viewtopic.php?id=33695
(*
TERMS OF USE. 
This applies only to posting code, as long as you don't post it (post it means displaying it on a web site). you are welcome to do
whatever you want to do with it without any further permission. 
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code. 

You must also state what you eventually have done with the original source. This obviously doesn't matter if you distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.

Credit for having contributed to your product would in all cases be nice!

If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.

The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.

I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code. 

The above paragraphs are also valid if you violate any of my terms.

If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.

the handler on defaultAppBundleId(LSHandlerContentTypeOrTagClass) ” Made considerably shorter and faster by Nigel Garvey is contributed by him, and is © Nigel Garvey 2010 (or earlier ).

*)
script BundleId
	on defaultAppBundleId(LSHandlerContentTypeOrTagClass) ” Made considerably shorter and faster by Nigel Garvey
	-- Not LSHandlerContentTag check out defaults read com.apple.LaunchServices |more
	-- Doesn't work awfully well with public.filename-extension!
	try
		set LSHandlerRoleAll to (do shell script "/usr/bin/defaults read com.apple.LaunchServices | /usr/bin/grep -A 2 " & LSHandlerContentTypeOrTagClass)
		set {tids, text item delimiters} to {text item delimiters, "\""}
		set LSHandlerRoleAll to text item -2 of LSHandlerRoleAll
		set text item delimiters to tids
		return LSHandlerRoleAll
	on error
		return "" -- The app is most probably the assumed one.
	end try
end defaultAppBundleId
	
on frontmostBundleId()
	return id of application (path to frontmost application as Unicode text)
end frontAppBundleId
end script

Before you use this, you should be pretty sure that every app listed are installed on the users machine, -if they will be compiled on that users machine. If they were compiled when shipped, then they would be safe until the users opens and tries to save them. -The solution would be to keep the scripts which addresses a certain app as read only and ensure that the app specific scripts don’t save any properties or anything. You should really experiment a little with this, by removing an app to fully understand how this will work before you ship it. The issue is that the terminology used must be present at compile time.

Hello.

Updated the handler frontmostBundleId - should be considerably faster.

Hi, McUsr.

Couldn’t your defaultAppBundleId() handler simply be:

on defaultAppBundleId(LSHandlerContentTypeOrTagClass)
	-- Not LSHandlerContentTag check out defaults read com.apple.LaunchServices |more
	-- Doesn't work awfully well with public.filename-extension!
	try
		set LSHandlerRoleAll to (do shell script "/usr/bin/defaults read com.apple.LaunchServices | /usr/bin/grep -A 2 " & LSHandlerContentTypeOrTagClass)
		set {tids, text item delimiters} to {text item delimiters, "\""}
		set LSHandlerRoleAll to text item -2 of LSHandlerRoleAll
		set text item delimiters to tids
		return LSHandlerRoleAll
	on error
		return "" -- The app is most probably the assumed one.
	end try
end defaultAppBundleId

?

Hello Nigel.

Edit:
I’ll really check it out. And thanks!

Edit++:
Thank you very much! I tested it and it worked with both cases, I haven’t seen the wood because of the trees!
I am about to steal ahem implement it! :slight_smile:

By the way: I’m currently working on this to provide a way to install code that supports apps that the user may not have on his machine, and without doing any precompiling. :slight_smile: So it is very important for me that this code is as fast as it can be, since it will be used all the time run time.


tell application "Quicksilver" to show large type "Thank You!"

Hello.

This is a script that will run wether the applications addressed is installed or not, if a property is not set, then it will surveil which apps among the supported that are installed. It will only attempt to call a “native” routine if the frontmost app are among the installed. Then it calls a dispatcher that calls that legitimate handler to provide “that” functionality. As long as the editing are correct I believe this to be the least cumbersome way to make scripts with support for several apps. The cornerstone of the whole is a construct I found in Matt Neubergs AppleScript The Definitive Guide it goes like this:


” page 295 Second Edition:
using terms from application "someapp"
 	set s to application "someapp"
	tell application s
		do yourStuff()
	end tell
end using terms from


-- The Idea and implementation and any faults is totally mine. © McUsr 2010 and put in the Public Domain.
-- The usually guarrantees about nothing what so ever applies, use it at your own risk.
-- Read the documentation.
-- You are not allowed to post this code elsewhere, but may of course refer to the post at macscripter.net.
-- macscripter.net/viewtopic.php?pid=131424#p131424
(*
TERMS OF USE. 
This applies only to posting code, as long as you don't post it, you are welcome to do
whatever you want to do with it without any further permission.  Posting includes showing the code on some
web page of your own.
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code. 

You must also state what you eventually have done with the original source. This obviously doesn't matter if you distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.

Credit for having contributed to your product would in all cases be nice!

If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.

The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.

I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code. 

The above paragraphs are also valid if you violate any of my terms.

If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.
*)

-- This is the first test on how to centralize functionality for multiple apps into one
-- which can be altered during installation and reinstallation.
property inited : missing value
on run
	if my inited is missing value then
		Init()
		set my inited to true
	end if
	
	
	set itIsOkToListChosenFile to false
	
	set bidFrontApp to my bundleId's frontmostBundleId()
	if my nativeSupport's frontmostAppAmongstInstalled(bidFrontApp) then
		set itIsOkToListChosenFile to true
	else if not nativeSupport's frontmostAppAmongstSupported(bidFrontApp) then
		tell application "SystemUIServer"
			activate
			
			display dialog "The fronmost application is currently not supported. 
			
Currently supported apps are Script Debugger, Applescript Editor and Smile."  buttons {"Ok"} default button 1 with icon 2
			
		end tell
	end if	
	if itIsOkToListChosenFile is true then
		set thisScript to my nativeSupport's dispatcher_0_(1, bidFrontApp)
		if thisScript is not false then
			tell application "LibraryLister"
				launch
				set myres to getPosForEntity(thisScript)
			end tell
			if myres is not false then
				my nativeSupport's dispatcher_1_(2, bidFrontApp, myres)
			end if
		else
			display dialog "unsaved script"
		end if
	end if
end run

on Init()
	-- builds up our indices for the handlers first
	set my nativeSupport's supportedAppBundleIds to {"com.satimage.Smile", "com.apple.ScriptEditor2", "com.latenightsw.ScriptDebugger"}
	my nativeSupport's setInstalledAppsBundeleIds(my nativeSupport's findInstalledApps())
	set my nativeSupport's handlerList to {"fileAliasForFrontDocument", "listCurrentFile"}
	set my nativeSupport's nativeHandlers to {{my fileAliasForFrontDocument_com_satimage_Smile, my fileAliasForFrontDocument_com_apple_ScriptEditor2, my fileAliasForFrontDocument_com_latenightsw_ScriptDebugger}, {my listCurrentFile_com_satimage_Smile, my listCurrentFile_com_apple_ScriptEditor2, my listCurrentFile_com_latenightsw_ScriptDebugger}}
end Init

-- © McUsr and Put in public domain 2010. You may use it as you will but not post it elsewhere.
-- Find some other post of mine in code exchange and read what that implies.
script nativeSupport
	property installedAppsBundleIds : {}
	property supportedAppBundleIds : {}
	property nativeHandlers : {}
	property handlerList : {}
	on setInstalledAppsBundeleIds(listOfUTId)
		set my nativeSupport's installedAppsBundleIds to listOfUTId
	end setInstalledAppsBundeleIds
	
	on frontmostAppAmongstInstalled(thisBundleId)
		-- returns true if frontmost app is among installed.
		if thisBundleId is in my nativeSupport's installedAppsBundleIds then
			return true
		else
			return false
		end if
	end frontmostAppAmongstInstalled
	
	on frontmostAppAmongstSupported(thisBundleId)
		-- returns true if frontmost app is among supported.
		if my bundleId's frontmostBundleId() is in my nativeSupport's supportedAppBundleIds then
			return true
		else
			return false
		end if
	end frontmostAppAmongstSupported
	
	
	on dispatcher_0_(functIdx, uti)
		-- 0 because it takes 1 parameter! every handler must return a result
		global _McUsrs_nativeFuncToDispatch
		local theRes, uti_index
		set uti_index to my nativeSupport's IndexOfItem(uti, my nativeSupport's installedAppsBundleIds)
		set _McUsrs_nativeFuncToDispatch to item uti_index of item functIdx of my nativeSupport's nativeHandlers
		set theRes to _McUsrs_nativeFuncToDispatch()
		return theRes
	end dispatcher_0_
	
	on dispatcher_1_(functIdx, uti, andAParameter)
		-- 1 because it takes 1 parameter! every handler must return a result
		global _McUsrs_nativeFuncToDispatch
		local theRes, uti_index
		set uti_index to my nativeSupport's IndexOfItem(uti, my nativeSupport's installedAppsBundleIds)
		set _McUsrs_nativeFuncToDispatch to item uti_index of item functIdx of my nativeSupport's nativeHandlers
		set theRes to _McUsrs_nativeFuncToDispatch(andAParameter)
		return theRes
	end dispatcher_1_
	
	on IndexOfItem(theItem, theList) -- credit to Emmanuel Levy but I modified it with the considering case statements
		considering case
			set text item delimiters to return
			set theList to return & theList & return
			set text item delimiters to {""}
			try
				-1 + (count (paragraphs of (text 1 thru (offset of (return & theItem & return) in theList) of theList)))
			on error
				0
			end try
		end considering
	end IndexOfItem
	
	on findInstalledApps()
		-- returns the installed apps on the users machine
		script o
			property l : {}
		end script
		repeat with aBundleId in my nativeSupport's supportedAppBundleIds
			try
				tell application "Finder"
					set a to name of application file id aBundleId
					set end of o's l to contents of aBundleId
				end tell
			end try
		end repeat
		return o's l
	end findInstalledApps
	
end script

-- © McUsr and Put in public domain 2010. You may use it as you will but not post it elsewhere.
-- Find some other post of mine in code exchange and read what that implies.
script bundleId
	on defaultAppBundleId(LSHandlerContentTypeOrTagClass) -- Made considerably shorter and faster by Nigel Garvey
		-- Not LSHandlerContentTag check out defaults read com.apple.LaunchServices |more
		-- Doesn't work awfully well with public.filename-extension!
		try
			set LSHandlerRoleAll to (do shell script "/usr/bin/defaults read com.apple.LaunchServices | /usr/bin/grep -A 2 " & LSHandlerContentTypeOrTagClass)
			set {tids, text item delimiters} to {text item delimiters, "\""}
			set LSHandlerRoleAll to text item -2 of LSHandlerRoleAll
			set text item delimiters to tids
			return LSHandlerRoleAll
		on error
			return "" -- The app is most probably the assumed one.
		end try
	end defaultAppBundleId
	
	on frontmostBundleId()
		-- returning bundleids that should be compatible down to Tiger, as the application properties
		-- like id of application were made accessible with AS version 2.0 I use system profiler
		-- since I currently didn't have any way to test what works under Tiger, but system profiler does!
		set Major to ((system attribute "sysv") mod 4096 div 16)
		if Major is 4 then -- TIGER
			set ASVersion to (do shell script "/usr/sbin/system_profiler -detailLevel mini SPFrameworksDataType  |/usr/bin/grep AppleScript: -A5 |/usr/bin/sed -n 's/Version:\\(.*\\)/\\1/p'")
			set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
			set ASVersion to every text item of ASVersion
			set ASVersion to text item -1 of ASVersion as Unicode text
			set AppleScript's text item delimiters to ""
			set ASVersion to characters 1 thru 3 of ASVersion as Unicode text
			set AppleScript's text item delimiters to tids
			if ASVersion < "2.0" then
				tell application "System Events"
					set thisId to bundle identifier of every process whose frontmost is true
					-- stolen from kiwilegal Thanks !
					if (count thisId) is greater than 1 then
						set thisId to item 1 of thisId
					end if
				end tell
				return thisId as Unicode text
			else
				return id of application (path to frontmost application as Unicode text)
			end if
			tell application (path to frontmost application as string) to return (get name) -- from the post :
		else if Major ≥ 5 then
			-- this should work properly on both Leo and Snow 'pard
			return id of application (path to frontmost application as Unicode text)
		else
			tell me
				activate
				display alert "getCurrentAppsBundleId: Versions of Mac Os X earlier than 10.4.0 is unsupported: your version is 10." & Major & "xx"
				error number -128
			end tell
		end if
	end frontmostBundleId
end script

on fileAliasForFrontDocument_com_satimage_Smile()
	local thisScript, s
	using terms from application "Smile"
		set s to application id "com.satimage.Smile"
		tell s
			if (count its script windows) is greater than 0 then
				try
					set thisScript to path name of its front window as alias
					return thisScript
				on error
					return false
				end try
			else
				return false
			end if
		end tell
	end using terms from
end fileAliasForFrontDocument_com_satimage_Smile


on fileAliasForFrontDocument_com_latenightsw_ScriptDebugger()
	local thisScript, s
	using terms from application "Script Debugger 4.5"
		set s to application id "com.latenightsw.ScriptDebugger"
		tell s
			if (count its documents) is greater than 0 then
				try
					set thisScript to file spec of its document of its script window 1 as alias
					return thisScript
				on error
					return false
				end try
			else
				return false
			end if
		end tell
	end using terms from
end fileAliasForFrontDocument_com_latenightsw_ScriptDebugger

on fileAliasForFrontDocument_com_apple_ScriptEditor2()
	
	local thisScript
	tell application id "com.apple.ScriptEditor2"
		if (count its documents) is greater than 0 then
			try
				set thisScript to POSIX file (path of its front document) as alias -- as Unicode text
				return thisScript
			on error
				return false
			end try
		else
			return false
		end if
	end tell
end fileAliasForFrontDocument_com_apple_ScriptEditor2

on listCurrentFile_com_satimage_Smile(thePosition)
	if thePosition is not false then
		using terms from application "Smile"
			set s to application id "com.satimage.Smile"
			tell s
				tell its window 1
					activate
					set its selection to {thePosition, 1}
					tell application "System Events"
						tell application process id "com.satimage.Smile"
							tell its window 1
								key code 124
								key code 123
							end tell
						end tell
					end tell
				end tell
			end tell
		end using terms from
	end if
	return null
end listCurrentFile_com_satimage_Smile

on listCurrentFile_com_latenightsw_ScriptDebugger(thePosition)
	local s
	if thePosition is not false then
		using terms from application "Script Debugger 4.5"
			set s to application id "com.latenightsw.ScriptDebugger"
			tell s
				tell its window 1
					activate
					set character range of selection of (its document) to {thePosition, 1}
					tell application "System Events"
						tell application process id "com.latenightsw.ScriptDebugger"
							tell its window 1
								key code 124
							end tell
						end tell
					end tell
				end tell
			end tell
		end using terms from
	end if
	return null
end listCurrentFile_com_latenightsw_ScriptDebugger


on listCurrentFile_com_apple_ScriptEditor2(thePosition)
	local s
	if thePosition is not false then
		
		tell application id "com.apple.ScriptEditor2"
			tell its window 1 to activate
			tell its document 1
				try
					set selection to insertion point thePosition
				on error e number n
					tell me
						activate
						display dialog e & " : " & n
					end tell
				end try
				tell application "System Events"
					tell window 1 of application process id "com.apple.ScriptEditor2"
						tell its window 1
							key code 124
						end tell
					end tell
				end tell
			end tell
		end tell
	end if
	return null
end listCurrentFile_com_apple_ScriptEditor2

It needs an update in order to work with Tiger

Hello.

It is updated in post #6 to work with Tiger. Under both AppleScript versions before 2.0 and after.

Hello.

It turns out that the dispatcher mechanism i devised in the post above, makes some havoc at least when the script is split in two parts the way you can load libraries in Script Debugger. There are some bug in AppleScript according to Script Debuggers help file, which makes it confuse any properties in the second script loaded. This stops this technique to work properly. I’ll update this post as soon as I have tested the dispatcher, when created as a stand alone unit for usage from a script server.

the free/os progs:

rcdefaultapp

http://github.com/Grayson/defaultapp/blob/master/defaultapp.m

and

http://github.com/AlanQuatermain/SetAppAffinity

Hello!

I have updated the main script to handle the case that you try to list something not saved by the LibraryLister, that I haven’t used in a year or so.

I am coming back to that!

Hello!

Removed another bug, a missing not for an if test, I am not sure is necessary after all!

Hello!

I post this here, since this is the right realm for it:

I have had the most disturbing experience, that the application process name diverts from the application name.

So, the safe name to obtain the application process name, is to first get the bundle identfier, and then get the application process name.

Like this for instance


tell application id "com.apple.systemevents" to set beginning of frontApp to bundle identifier of first application process whose frontmost is true and visible is true

set end of frontApp to name of (application id (item 1 of frontApp))
” do your stuff with the application process ...