File/Folder Duality of Applications: How to treat like either?

Hi All,

In regards to the dual-behavior of .app files (er, folders) on OSX, how does this affect operating on them with AS? I

n short, I have an Automator service that takes one or more files from the Finder as its input, and this service does not work if a .app file is the input. In this case I can fix it by using the path (HFS or POSIX) of the .app file and removing the terminating “:” or “/” in order to get to its extension, which is what I’m after in this case. But in the general case where I want to operate on a .app file as a file and not as a folder, what is a safe practice?

Now the long version:

*** WARNING ***
THE SCRIPTS IN THIS POST PERFORM ACTIONS THAT COULD RESULT IN SYSTEM INSTABILITY OR LOSS OF DATA. USE THESE SCRIPTS OR MODIFIED VERSIONS OF THESE SCRIPTS AT YOUR OWN RISK.
*** WARNING ***

Below, I present my poorly made solution for the difficulty in adding programs to the “open with” context menu in OSX. Both are scripts meant to be run from Automator.

First is a script for adding applications to the “open with” menu: it takes a list of files, has the user point to one or more applications on the system, and adds all extensions of the list of files to each application selected such that they will now appear in the “open with” menu for every filetype in the list of files.

Second is a script for removing applications from the “open with” menu: it takes a single application and presents the list of the filetypes for which it will appear on the “open with” menu. The user selects one or more filetypes from the list and and the application is removed from the “open with” menu for those types.

Both scripts work by editing the …SomeApp.app/Contents/Info.plist file within each application. This method works for programs so long as they’re not signed in such a way that they break (OS X won’t let them run) after you modify their contents.

(the third script below is the code that can be used in an Applescript action in Automator. It simply points to a script file and runs the appropriate handler with the appropriate arguments)

Script 1: Adds chosen application(s) to the “open with” menu for the filetypes of the files used as the input


on AddDefaultApplications(FileList)
	set ExtList to {}
	
	repeat with i in FileList
		if text item -1 of (i as text) is not ":" or (i as text) contains ".app" then
			set end of ExtList to beginning of parseLine(end of parseLine(i as text, "."), ":")
		end if
	end repeat
	
	if (count of ExtList) > 0 then
		set OpenWithApps to choose file with prompt "Select applications to add to the \"Open With\" menu" default location (path to applications folder) of type {"app"} with multiple selections allowed
		if OpenWithApps is false then return 0
		
		repeat with AnApp in OpenWithApps
			
			set AppName to AnApp as text
			
			set InfoPlistFile to missing value
			
			try
				set InfoPlistFileName to (AppName & "Contents:Info.plist")
				set InfoPlistFile to InfoPlistFileName as alias
			on error
				display alert "Couldn't get Info.plist file" message (AnApp as text) as critical
			end try
			
			if InfoPlistFile is not missing value then
				set ExtIsNew to {}
				repeat with i in ExtList
					set end of ExtIsNew to true
				end repeat
				
				set InfoRef to open for access (InfoPlistFile as text)
				
				set InfoContents to read InfoRef
				set InfoContents to paragraphs of InfoContents
				set NewInfoContents to {}
				set SectionNumber to 1
				repeat with aLine in InfoContents
					if SectionNumber is 1 and (aLine as string) contains "CFBundleTypeExtensions" then
						set SectionNumber to SectionNumber + 1
					else if SectionNumber is 2 then
						if (aLine as string) contains "</array>" then
							repeat with i from 1 to count of ExtList
								if (item i of ExtIsNew) then
									set end of NewInfoContents to (tab & tab & tab & tab & "<string>" & (item i of ExtList) as string) & "</string>"
								end if
							end repeat
							set SectionNumber to SectionNumber + 1
						else
							repeat with i from 1 to count of ExtList
								if (item i of ExtIsNew) and (aLine as string) contains (item i of ExtList) then
									set item i of ExtIsNew to false
								end if
							end repeat
						end if
					end if
					set end of NewInfoContents to aLine as string
				end repeat
				
				close access InfoRef
				
				set NewInfoFileName to (InfoPlistFile as text) & ".tmp" as text
				set NewInfoRef to open for access NewInfoFileName with write permission
				set eof of NewInfoRef to 0
				
				repeat with i from 1 to count of NewInfoContents
					if i > 1 then write return to NewInfoRef
					write item i of NewInfoContents to NewInfoRef
				end repeat
				
				close access NewInfoRef
				
				set NewInfoFile to NewInfoFileName as alias
				
				set Scpt to "cp " & quoted form of POSIX path of InfoPlistFile & space & quoted form of (POSIX path of InfoPlistFile & ".bak")
				
				try
					do shell script Scpt with administrator privileges
					
					set Scpt to "mv " & quoted form of POSIX path of NewInfoFile & space & quoted form of POSIX path of InfoPlistFile
					
					do shell script Scpt with administrator privileges
					
					set Scpt to "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f " & quoted form of POSIX path of AnApp
					do shell script Scpt
				on error
					display alert "Failed to modify Info.plist file" as critical
					return 0
				end try
				
			end if
		end repeat
	end if
end AddDefaultApplications

on parseLine(theLine, delimiter)
	-- This came from Nigel Garvey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {delimiter}
	set theTextItems to theLine's text items
	set AppleScript's text item delimiters to astid
	
	repeat with i from 1 to (count theTextItems)
		if (item i of theTextItems is "") then set item i of theTextItems to missing value
	end repeat
	
	return theTextItems's every text
end parseLine

Script 2: Presents user with list of filetypes (extensions) for which the chosen application will appear on the “open with” menu. Selected filetypes are then removed

on ModifyTypeExtensions(FileList)
	set AppName to (beginning of FileList) as text
	if end of parseLine(AppName, ".") is "app:" then
		
		set AppNameNice to beginning of parseLine(end of parseLine(AppName, ":"), ".")
		
		set InfoPlistFile to missing value
		
		try
			set InfoPlistFileName to (AppName & "Contents:Info.plist")
			set InfoPlistFile to InfoPlistFileName as alias
		on error
			display alert "Couldn't get Info.plist file" message (AnApp as text) as critical
		end try
		
		if InfoPlistFile is not missing value then
			
			set InfoRef to open for access (InfoPlistFile as text)
			
			set InfoContents to read InfoRef
			set InfoContents to paragraphs of InfoContents
			set NewInfoContents to {}
			set EndInfoContents to {}
			set ExtList to {}
			set SectionNumber to 1
			repeat with aLine in InfoContents
				if SectionNumber is 1 then
					set end of NewInfoContents to aLine as string
					if (aLine as string) contains "CFBundleTypeExtensions" then
						set SectionNumber to SectionNumber + 1
					end if
				else if SectionNumber is 2 then
					if (aLine as string) contains "<string>" then
						set end of ExtList to beginning of parseLine(end of parseLine(aLine as text, "<string>"), "</string>")
					else if (aLine as string) contains "</array>" then
						set end of EndInfoContents to aLine as string
						set SectionNumber to SectionNumber + 1
					else
						set end of NewInfoContents to aLine as string
					end if
				else
					set end of EndInfoContents to aLine as string
				end if
			end repeat
			
			close access InfoRef
			
			set RemoveExtList to choose from list ExtList with prompt "Select type extensions that should be REMOVED for " & AppNameNice with multiple selections allowed without empty selection allowed
			
			if RemoveExtList is false then return 0
			
			set NewInfoFileName to (InfoPlistFile as text) & ".tmp" as text
			set NewInfoRef to open for access NewInfoFileName with write permission
			set eof of NewInfoRef to 0
			
			repeat with i from 1 to count of NewInfoContents
				if i > 1 then write return to NewInfoRef
				write item i of NewInfoContents to NewInfoRef
			end repeat
			
			repeat with i in ExtList
				set AddExt to true
				repeat with j in RemoveExtList
					if (i as string) is (j as string) then
						set AddExt to false
						exit repeat
					end if
				end repeat
				if AddExt then write ((return & tab & tab & tab & tab & "<string>" & i as text) & "</string>") as text to NewInfoRef
			end repeat
			
			repeat with i in EndInfoContents
				write return & i as text to NewInfoRef
			end repeat
			
			close access NewInfoRef
			
			set NewInfoFile to NewInfoFileName as alias
			
			set Scpt to "cp " & quoted form of POSIX path of InfoPlistFile & space & quoted form of (POSIX path of InfoPlistFile & ".bak")
			
			try
				do shell script Scpt with administrator privileges
				
				set Scpt to "mv " & quoted form of POSIX path of NewInfoFile & space & quoted form of POSIX path of InfoPlistFile
				
				do shell script Scpt with administrator privileges
				
				set Scpt to "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f " & quoted form of POSIX path of AppName
				do shell script Scpt
			on error
				display alert "Failed to modify Info.plist file" as critical
				return 0
			end try
			
		end if
	end if
end ModifyTypeExtensions

on parseLine(theLine, delimiter)
	-- This came from Nigel Garvey
	
	set astid to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {delimiter}
	set theTextItems to theLine's text items
	set AppleScript's text item delimiters to astid
	
	repeat with i from 1 to (count theTextItems)
		if (item i of theTextItems is "") then set item i of theTextItems to missing value
	end repeat
	
	return theTextItems's every text
end parseLine

Script 3: Script that goes into an Applescript action in Automator. Points and passes input to a handler in a script file. (this script is for calling Script 1 above)

on run {input, parameters}
	set theScript to load script file ((path to scripts folder from user domain as text) & "Applications:Finder:Services:Add to Open With Menu.scpt")
	tell theScript
		AddDefaultApplications(input)
	end tell
end run

*edit: Modified my ghetto check if a folder is in the input. Turns out that there is no “open with” menu for .app files anyways… oh well…

You really, really shouldn’t do that. You’re going to break lots of applications, the more so with recent versions of the OS. And depending on what apps do when they open files, you could cause corruption of documents.

Breaking my computer is a pastime of mine…

Thanks for the warning, but if this practice is inadvisable then there must be some way to get an application on the “open with” list without setting it as the default application?

That’s fine – I do the same. But you’re posting code here that can cause other users a lot of grief. They should be warned.

You might be able to do it via launch services. But ultimately the developer of the application should know what document types it can and can’t open, and it’s up to them to control what’s in the Info.plist file.

What if, hypothetically, my script always modifies the info.plist file correctly, only adding the proper strings to the list of file types. Would this still result in potential dangerous behavior and file corruption?

This is one of those love/hate topics for me, having moved from Windows 7 years ago and finding that there’s no good way to accomplish this. It’s absurd that, if I’d like to sometimes open a file with a non-default application, I should have to browse to the application itself or set a new default application. I can’t believe it’s not a problem for more people.

What I’m doing ” modding an application’s info.plist file, then forcing launch services to reload so that the change will go into effect ” is doing it via launch services right? If I were to go messing around with launch services directly, instead of just changing the files it reads to generate file associations, couldn’t that be even more dangerous? I’d be tampering with core OS files at that point right?

It hadn’t occurred to me that the reason you can’t find a good solution for this is because it’s dangerous. I guess it makes sense, but you think it’d be documented somewhere.

In my case I’m adding filetypes that the makers of gedit have probably never even heard of. Given that it’s just a text editor and so can open thousands of filetypes, I can’t blame them for having an incomplete”while very long”list of filetypes already in their info.plist.

That is hypothetical, because you’re not modifying it safely. You’re treating it as a text file, which it may not be, and you’re ignoring the CFBundleTypeRole entry, which means you have no idea whether you’re making an app capable of reading a particular file type or modifying it. The latter combined with either autosaving or some automatic action on opening is a recipe for serious problems.

You could cause similar problems, yes. But you wouldn’t risk screwing up the applications as well. The launch services database is pretty easily rebuilt.

I can’t point you to it, but the general admonition not to change files within an application bundle is not new, and it’s obviously a requirement of serious code-signing.

They can add a wildcard entry if they can handle them safely.

Hello.

I use the RC Default Apps preference pane to adjust this behaviour, as to what app opens which extension. I believe I still set the default app via Finder. RC default apps, is probably a much slower way to do this, but it has never made any mistakes on its own. :slight_smile: The source code for it is available out there, if you want to study how they are doing it, which I believe isn’t that far away from how you are doing it. I also think that sometimes, this would be nice to do with scripts, as RC Defaults isn’t that good at the whole picture (you can’t edit the “open with list”, or you can but that is a pain in the ass to do with RC Default Apps, because it will load and reload the whole defaults system on a per app basis, (maybe I am not entirely correct as to what happens, but the time consumptions is definately there!)).

I am not endorsing anything here, just saying that to me this would be useful if it works. RC Default App has publically available source code by the way.

Hi,

As a workaround, you could create your own app and enter it in the ‘open with’ list. It could be just an AppleScript app with an open handler. You could set its properties for certain files with AppleScript which routes the file to open with certain applications. :slight_smile:

Edited: actually, I don’t know what you’re doing, but am thinking about this idea. :smiley:

gl,
kel

My approach was very naive, and wholly based on automating a workaround I found on some obscure blog :slight_smile:

Great! They know better than me, to be sure, and while it’s not as fast as being able to simply select some files and then some applications and have all the filetypes assigned, it’s very much preferable to having to browse to the application every time I want to open using a non-default application.

If I did this, it would look something like this right?

property AppList : {{name:"Gedit",ExtList:{"log","out","job"}},{name:"Some other application",ExtList:{"ext1","ext2"}}}

on OpenIt(TheFile)
  tell application "Finder" to set Ext to name extension of TheFile
  repeat with AnApp in AppList
    if ExtList of AnApp contains Ext then
      tell application name of AnApp
        activate
        open TheFile
      end tell
      return 0
    end if
  end repeat
end OpenIt

But what about non-scriptable applications?

Thanks for the help, and especially to Shane because I, as usual, didn’t realize I was playing with fire. Wish someone told me that before I went and replaced a bunch of commands and programs in /usr/bin in order to “reassign” basic commands like gcc… Live, learn, reinstall your OS and all programs to recover. That’s life for me.

I’ll go with RC Default Apps until there exists a better way. Maybe Apple will implement an easier way to do this. Right after they allow file system access on iOS…

As a sidenote, can a thread be renamed? The title no longer reflects the thread’s contents, unless someone want’s to answer the more general question in my first paragraph of post 1.

Hi,

I don’t think an application needs to be scriptable in order for it to open a file. I think you can just use Finder to open using some app.

Anyway, it worked! I added the AppleScript app with the open handler to the open with list. To tell you the truth I’m not sure what did it. Need to try it again. :slight_smile: Hope I don’t crash it.

Have a good day,
kel

Aren’t you passed aliases?

Hello kel.

For some reason I couldn’t make the open command of Finder to work, so I wrote a little snippet that I have stuffed into my applet that shows a dialog from the Finder toolbar (Q.app). :slight_smile:

set thisApp to "BBEdit"

tell application "Finder"
	set theFiles to selection 
	repeat with aFile in theFiles
		tell application thisApp to open (aFile as alias)
	end repeat
end tell

Hi McUsr,

I just got back home. Couldn’t wait to test this router thing. Are you running Mavericks or the new one? I forgot the name. Have been trying to clean up before upgrading. :slight_smile: I don’t have BBEdit But will try other apps. I’ve been wanting to open rtf in HexEdit for some reason. Oh yeah, it’s not scriptable. I’ll try yours also.

Edited: oh yeah, TextWrangler is the little sibling of BBEdit, so I’ll try that also.

Edited: it works with TextWrangler from AppleScript Editor:

set f to choose file
tell application "Finder"
	open f using application file id "com.barebones.textwrangler"
end tell

Although, it didn’t open the rtf as like TextEdit would (a text editor), but as would the ‘read’ command. Maybe with the parameter.

Alright, something interesting to do on Sunday! :smiley:

Edited: it worked with HexEdit:

set f to choose file
tell application "Finder"
	open f using application file id "com.ideasfromthedeep.hexedit"
end tell

Later,
kel

Apparently not :slight_smile:

I asked that because I know that .app file is really just a container that, when double-clicked, opens a program rather than entering the container. This was the first time I wanted to treat an application as anything other than a container using AS, so I wondered if you seasoned guys had any experience treating applications as files in one case and folders in another, and, specifically, if there were any interesting lessons to be had there.

You can open files via the Finder and a given application


property appList : {{identifier:"com.barebones.bbedit", extensionList:{"log", "out", "job"}}, {identifier:"com.company.appname", extensionList:{"ext1", "ext2"}}}

on openIt(theFile)
	tell application "System Events" to set fileExtension to name extension of theFile
	repeat with anApp in appList
		if extensionList of anApp contains fileExtension then
			tell application "Finder" to open theFile using application file id anApp's identifier
			return
		end if
	end repeat
end openIt

I like that as well. I’ll play with this method and compare it to just having RC Default Apps do its magic.

Also, check this out, as I assume you’re all Simpsons/Futurama fans anyways:
Homer’s Last Theorem http://boingboing.net/2014/10/17/homers-last-theorem.html

Hi Stephan,

Thanks for answering the question. Just got back and testing.

Edited: btw scriptim. You can get the id with:

tell application "Finder"
	id of application "HexEdit"
end tell

gl,

Edited: this didn’t work with HexEdit because HexEdit opens with an Open dialog:

set f to choose file
tell application id "com.ideasfromthedeep.hexedit"
	launch
	activate
	open f
end tell

I need to find another non-scriptable app that can open rtf.

Edited: LibreOffice opened the rtf with:

set f to choose file
tell application id "org.libreoffice.script"
	launch
	activate
	open f
end tell

Edited: just ran text wrangler and opened an rtf. It opens showing bytes. So, that’s the default. I don’t know how to open files with application properties (couldn’t get it to work). But, that’s the default operation.

I think the droplet should work after adding it to the open with list.

Hi scriptim. I think it’ll work, but I’d rather use lists than records. To me, lists are easier to work with. It might take more memory, though.

Edited: just thought of this. I’d rather work with numbers than with words. :smiley:

Thanks again,
kel

Well what is in your FileList variable? You don’t make it clear where that comes from.

Hello.

Just for the record, what I referred to as a pain in the ass, is to edit to open file with “list” of Finder. I have many apps listed that I never want to use to open a pdf document with for instance. So I have to look downward the list for the one I want to use. I really would like to just be able to edit that list on a per extension basis.

I have not tried to set the apps I want to use as default openers via Finder, one by one for the hope that Finder then puts the apps I obviously prefers to use on the top of the list.

And: When you have the app you want to open the file with, already open, then you can really just drag that file onto the launchbar icon of the app, or its icon on the Dock as a shortcut to make it open with that app.

Sorry, I misread that as “Aren’t you past aliases?”, like I should be using a better method of dealing with files. “passed” makes much more sense!

Yes, the script is passed aliases, but the alias appears as a container”with a terminating “:”. This was the first time I’d ever wanted to deal with an application as a file inside AS, and I don’t know of any other object in OS X that behaves as a container and a file at the same time.

It seems like there should be some way of modifying that list directly from where you see it”in the context menu from right-clicking a file. RC Default Apps seems to verify that modifying the types a program is associated with is something to avoid, because you are able to add extensions to an application, but you cannot remove extensions from an application’s list.