It doesn’t matter – an alias is an alias.
Apps can save their documents as packages, which are folders that behave like files. Common ones are .rtfd files, and of course .scptd files. Older iWorks files were also packages.
It doesn’t matter – an alias is an alias.
Apps can save their documents as packages, which are folders that behave like files. Common ones are .rtfd files, and of course .scptd files. Older iWorks files were also packages.
Well then… Maybe I shouldn’t say these things…
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.
That is correct, and having given this some more thought, it is really a no can do, due to the uri hierarchy. If an editor can edit xml, then it is supposed to be able to edit filetypes that are specialized versions of xml as well. In this construed example, I am thinking of xhtml/sdef. So, I can’t exclude the app from being able to read from xhtml/sdef, and use it for xml. I can’t remember seeing any “can’t edit/browse” extension in the info.plist files, so I doubt it is possible to exclude for instance a sub extension. (I also think I may break an app, by editing its info.plist file if it is codesigned.)
So, I have to investigate whether the list is organized, alphabetically, or by ranking, hopefully it is by ranking. A trick if it is arranged alphabetically, is of course to make an applet with some name starting with “A”, that just makes the real app open the file.
Edit
The open with app list is arranged alphabetically in Finder, so the easy solution may be to make an applet starting with “A” in its name.
Hi scriptim,
Fell asleep while sitting down and now I’m walking around like Frank. My neck is stiff. Enstein that is.
Anyway, if you’re interested, what I was thinking while I was sleeping was that you have two lists. The first list is a list of application IDs. The second list is a list of lists of extensions. The two lists map to each other by index.
So, when the user double clicks on a file that has been set to ‘open with’ the droplet, the droplet checks the extension, looks it up in the extension list, gets the index, finds the app to ‘open with’, and opens it with that app.
I think that using Finder’s open with is the best option. I’m still wondering if BBEdit opens files if Finder asks it to open a file.
gl,
kel
Anyway, if you’re interested, what I was thinking while I was sleeping was that you have two lists. The first list is a list of application IDs. The second list is a list of lists of extensions. The two lists map to each other by index.
So, when the user double clicks on a file that has been set to ‘open with’ the droplet, the droplet checks the extension, looks it up in the extension list, gets the index, finds the app to ‘open with’, and opens it with that app.
That’s what I was thinking. The applet would function as a “what I would like to open with” menu. In most cases there’s just the default application and one other that I use often enough to want it to appear in the “open with” menu. There are a few cases in which I have two non-default applications that I use frequently enough. For that reason I’ll probably have it display a list of applications if two or more are associated with an extension. I realize that now I’m just creating my own shoddy version of the “open with” menu, but it’s rare that there would be two or more applications for a given extension, so most of the time there would only be one application associated with the extension and it would open immediately.
It’s times like this I wish I had taken the time at some point to learn how to create cocoa applications, or maybe I should just start with swift instead. I’d love to have a solution like this be its own proper application so you could have a nice GUI for modifying the list of applications and extensions, and show application icons where necessary, and with useless animations and sound effects! On the other hand, that sounds like it would take more than an afternoon (-turned late night, probably) to accomplish…
Hi scriptim,
It’s quite easy making this droplet.
One other thing you can do is add a run handler to the droplet. If the user double clicks on the droplet then the preferences can be changed. The user could add new apps opening new extensions through dialog or whatever. Something like changing preferences.
Edited: btw, that’s exactly what I was thinking. That it’s like adding a sub-‘open with’ app to the open with menu. But, it might be good to learn how to add the initial extension, besides just using the extension, as Shane said. I still haven’t got that down either. I just use the extension to add the droplet to the ‘open with’ list. You have to change something else to something. I couldn’t get it from the developer docs. Tried all kinds of things, but the problem is that you don’t know when you’re doing the right thing. One of these days or months or years.
gl,
kel
One other thing you can do is add a run handler to the droplet. If the user double clicks on the droplet then the preferences can be changed. The user could add new apps opening new extensions through dialog or whatever. Something like changing preferences.
That’s a good idea. I’ll have a poorly implemented version in no time!
Yeah, and you could also delete existing preferences maybe in a choose from list dialog.
Hey Guys,
Thanks so much for your help and candor. I have a finished applet that does what I’d like. The only missing link is getting it to show up on the “open with” menu for all file types. I tried adding “*” to the CFBundleTypeExtensions in the info.plist for the finished applet and then rebuilding the launch services database, but it doesn’t show up.
One of you guys with OS X programming experience let me know how to achieve this?
(sorry, I know this particular question isn’t AS-related)
The applet is pretty simple to use. If you open a file with it for the first time then you choose an application to assign to that filetype, and subsequent opening will use result in the file being opened with the same application without any other user interaction. If you open the applet itself then you have options to add or remove file type associations, or to open files using it.
To use the applet, save the below code as an applet, and then do whatever is necessary to get it to show up on the “open with” menu for all file types (I don’t know what that is currently, but I hope to know soon!). I have mine saved as “¢ Open With” (where “¢” is a bullet [option-8 to use]) so that it appears alphabetically before letters and numbers. Pick a nice icon and you’re good to go.
property SampleList : {{path:"Applications:gedit.app", name:"gedit", BundleID:"org.gnome.gedit", TypeExtList:{"log", "script"}}}
property DefaultAppListFile : "DefaultApps.b"
property SummaryFile : "File Association Summary.txt"
property TheList : {}
on open theFiles
local FileExt, FileName, FoundAppList, OpenWithAppList, PickAppList
GetList()
repeat with aFile in theFiles
tell application "System Events"
set FileExt to name extension of aFile
set FileName to displayed name of aFile
end tell
set FoundAppList to {}
set OpenWithAppList to {}
repeat with AnApp in TheList
if TypeExtList of AnApp contains FileExt then set end of FoundAppList to AnApp
end repeat
if (count of FoundAppList) = 1 then
set OpenWithAppList to FoundAppList
else if (count of FoundAppList) > 1 then
set PickAppList to {}
repeat with AnApp in FoundAppList
set end of PickAppList to name of AnApp
end repeat
set PickAppList to choose from list PickAppList with title "Open " & FileName & " using:" default items {beginning of PickAppList} with multiple selections allowed without empty selection allowed
if PickAppList is false then return 0
repeat with PickedAppName in PickAppList
repeat with AnApp in TheList
if PickedAppName as string is (name of AnApp) as string then
set end of OpenWithAppList to AnApp
exit repeat
end if
end repeat
end repeat
else
set end of OpenWithAppList to SelectDefaultApp(missing value, {FileExt})
if end of OpenWithAppList is false then return 0
end if
repeat with AnApp in OpenWithAppList
if BundleID of AnApp is not missing value then
tell application "Finder" to open aFile using application file id (BundleID of AnApp)
else
tell application (path of AnApp)
activate
open aFile
end tell
end if
end repeat
end repeat
end open
on run
local ActionList, AddExt, RemExt, RemApp, OpenFiles, ViewSummary
local TmpList, TmpStr, TmpButton
set AddExt to "Add File Type Association"
set RemExt to "Remove File Type Association"
set RemApp to "Remove Application Association"
set OpenFiles to "Open File(s)"
set ViewSummary to "View File Type Association Summary"
repeat
GetList()
if (count of TheList) > 0 then
set ActionList to {AddExt, RemExt, RemApp, ViewSummary, OpenFiles}
else
set ActionList to {AddExt, OpenFiles}
end if
set TheAction to choose from list ActionList with prompt "What would you like to do?" with title "Open With..." default items {AddExt} without multiple selections allowed and empty selection allowed
if TheAction is false then return 0
set TheAction to beginning of TheAction
if TheAction is AddExt then
set {TmpStr, TmpButton} to {text returned, button returned} of (display dialog "Enter file extension(s) to add association for:" & return & return & "(separate multiple extensions with spaces)" default answer "" with icon note)
if TmpButton is "Cancel" then return 0
set TmpStr to parseLine(TmpStr, space)
set TmpList to GetTmpList(false)
set NewApp to "Select new application"
if (count of TmpList) > 0 then
set beginning of TmpList to NewApp
set TmpList to choose from list TmpList with prompt "Add file association(s) to which application(s)?" default items {beginning of TmpList} with multiple selections allowed and empty selection allowed
if TmpList is false then return 0
else
set TmpList to {NewApp}
end if
repeat with AnApp in TmpList
if AnApp as string is NewApp as string then
SelectDefaultApp(missing value, TmpStr)
else
SelectDefaultApp(AnApp, TmpStr)
end if
end repeat
else if TheAction is RemExt then
set TmpList to GetTmpList(true)
if (count of TmpList) > 1 then
set TmpStr to choose from list TmpList with prompt "Which file type extension would you like to remove?" default items {beginning of TmpList} without multiple selections allowed and empty selection allowed
if TmpStr is false then return 0
set TmpStr to beginning of TmpStr
else
set TmpStr to beginning of TmpList
end if
set TmpList to {}
repeat with AnApp in TheList
if TypeExtList of AnApp contains TmpStr then set end of TmpList to name of AnApp
end repeat
if (count of TmpList) > 1 then
sort(TmpList, 1, -1, {})
set TmpList to choose from list TmpList with prompt "Remove " & TmpStr & " association from which applications?" default items {beginning of TmpList} with multiple selections allowed without empty selection allowed
if TmpList is false then return 0
end if
repeat with ByeApp in TmpList
repeat with i from 1 to count of TheList
if name of (item i of TheList) as string is ByeApp as string then
if (count of TypeExtList of item i of TheList) > 1 then
set TypeExtList of item i of TheList to RemoveFromList(TypeExtList of item i of TheList, {TmpStr})
else
set TheList to RemoveFromList(TheList, {item i of TheList})
end if
exit repeat
end if
end repeat
end repeat
SaveList()
else if TheAction is RemApp then
set TmpList to GetTmpList(false)
if (count of TmpList) > 1 then
set TmpStr to choose from list TmpList with prompt "Which application would you like to remove association(s) for?" default items {beginning of TmpList} without multiple selections allowed and empty selection allowed
if TmpStr is false then return 0
set TmpStr to beginning of TmpStr
else
set TmpStr to beginning of TmpList
end if
repeat with i from 1 to count of TheList
if name of item i of TheList is TmpStr then
if (count of TypeExtList of item i of TheList) > 1 then
sort(TypeExtList of item i of TheList, 1, -1, {})
set TmpList to choose from list TypeExtList of item i of TheList with prompt "Remove which file type associations from " & name of item i of TheList & "?" default items {beginning of TmpList} with multiple selections allowed without empty selection allowed
if TmpList is false then return 0
if (count of TmpList) < (count of TypeExtList of item i of TheList) then
set TypeExtList of item i of TheList to RemoveFromList(TypeExtList of item i of TheList, TmpList)
else
set TheList to RemoveFromList(TheList, {item i of TheList})
end if
else
set TheList to RemoveFromList(TheList, {item i of TheList})
end if
exit repeat
end if
end repeat
SaveList()
else if TheAction is ViewSummary then
set TmpStr to (((path to me) as text) & "Contents:Resources:" & SummaryFile)
tell application "Finder"
activate
reveal TmpStr
end tell
return 0
else if TheAction is OpenFiles then
open (choose file with multiple selections allowed)
return 0
end if
end repeat
end run
on GetList()
local ListPath, ListFile
set ListPath to (((path to me) as text) & "Contents:Resources:" & DefaultAppListFile)
set ListFile to open for access ListPath -- with write permission
--set eof of ListFile to 0
--write SampleList to ListFile as list
set TheList to {}
try
set TheList to read ListFile from 0 as list
end try
close access ListFile
end GetList
on SaveList()
local ListPath, ListFile
set ListPath to (((path to me) as text) & "Contents:Resources:" & DefaultAppListFile)
set ListFile to open for access ListPath with write permission
set eof of ListFile to 0
write TheList to ListFile as list
close access ListFile
set ListPath to (((path to me) as text) & "Contents:Resources:" & SummaryFile)
set ListFile to open for access ListPath with write permission
set eof of ListFile to 0
if (count of TheList) > 0 then
repeat with AnApp in TheList
write (name of AnApp & " (" & name of AnApp & ", located at " & path of AnApp & ")" & return & "File types: ") as text to ListFile
repeat with i from 1 to count of TypeExtList of AnApp
write (item i of TypeExtList of AnApp) as text to ListFile
if i < (count of TypeExtList of AnApp) then write ", " as text to ListFile
end repeat
write (return & return) as text to ListFile
end repeat
else
write "There are no file associations in effect." to ListFile
end if
close access ListFile
end SaveList
on SelectDefaultApp(NewAppName, FileExts)
local SelectedApp, AppPath, AppName, AppBundleID, NewApp, ListModified
if NewAppName is missing value then
set SelectNewApp to true
set SelectedApp to choose file with prompt "Select application to add file association to" default location (path to applications folder) of type {"app"} without multiple selections allowed
if SelectedApp is false then return false
set AppPath to SelectedApp as text
if text item -1 of AppPath is ":" then repeat until text item -1 of AppPath is not ":"
set AppPath to (text items 1 through -2 of AppPath) as text
end repeat
else
set SelectNewApp to false
end if
repeat with AnApp in TheList
if (SelectNewApp and AppPath is path of AnApp) or (not SelectNewApp and NewAppName as string is name of AnApp as string) then
set ListModified to false
repeat with AnExt in FileExts
if TypeExtList of AnApp does not contain AnExt then
set end of TypeExtList of AnApp to AnExt
set ListModified to true
end if
end repeat
if ListModified then
SaveList()
return AnApp
end if
end if
end repeat
if SelectNewApp then
tell application "System Events" to set AppName to displayed name of SelectedApp
set AppBundleID to missing value
try
tell application "Finder" to set AppBundleID to id of application file AppPath
end try
set NewApp to {path:AppPath, name:AppName, BundleID:AppBundleID, TypeExtList:FileExts}
set end of TheList to NewApp
SaveList()
return NewApp
end if
end SelectDefaultApp
on RemoveFromList(OldList, RemItemList)
local NewList
set NewList to {}
if class of beginning of RemItemList is record then
repeat with i from 1 to count of OldList
set RemItem to false
repeat with j from 1 to count of RemItemList
if name of item i of OldList is name of item j of RemItemList then
set RemItem to true
exit repeat
end if
end repeat
if not RemItem then set end of NewList to item i of OldList
end repeat
else
repeat with i from 1 to count of OldList
if item i of OldList is not in RemItemList then set end of NewList to item i of OldList
end repeat
end if
return NewList
end RemoveFromList
on GetTmpList(MakeExtList)
local TmpList, Tmp
set TmpList to {}
if not MakeExtList then
repeat with AnApp in TheList
set end of TmpList to name of AnApp
end repeat
else
repeat with AnApp in TheList
repeat with AnExt in TypeExtList of AnApp
if TmpList does not contain AnExt as string then set end of TmpList to AnExt as string
end repeat
end repeat
end if
sort(TmpList, 1, -1, {})
return TmpList
end GetTmpList
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
on CustomMergeSort(ListToSort, l, r, customiser) -- Sort items l thru r of theList.
script o
property comparer : me
property slave : me
property lst : ListToSort
property rangeB : missing value
on msrt(l, r)
-- Get indices for the end of the left half of this range and the beginning of the right.
set m1 to (l + r) div 2
set m2 to m1 + 1
sortHalf(l, m1) -- Sort the left half.
sortHalf(m2, r) -- Sort the right half.
-- Extract a copy of the whole range.
set rangeB to items l thru r of my lst
slave's extract(l, r)
-- Merge the already sorted halves of the extract back into the original: for each slot
-- in the original, compare the lowest unassigned item from the left half of the extract
-- with that from the right half and assign the lower of the two to the slot.
-- Indices relative to the extract.
set i to 1 -- Beginning of left half.
set endLeft to m2 - l -- End of left half.
set j to endLeft + 1 -- Beginning of right half.
set endRight to r - l + 1 -- End of right half.
-- Get the first item from the left and right halves of the extract.
set lv to beginning of my rangeB
set rv to item j of my rangeB
repeat with k from l to r
if (comparer's isGreater(lv, rv)) then
-- The right value's less than the left. Assign it to this slot in the original list.
set item k of my lst to rv
slave's merge(j, k)
-- Get the next right value. If none, assign the remaining left values to the remaining slots and exit this repeat and recursion level.
if (j < endRight) then
set j to j + 1
set rv to item j of my rangeB
else
repeat with k from (k + 1) to r
set item k of my lst to item i of my rangeB
slave's merge(i, k)
set i to i + 1
end repeat
exit repeat
end if
else
-- The left value's less than or equal to the right. Assign it to this slot in the original list.
set item k of my lst to lv
slave's merge(i, k)
-- Get the next left value. If none, simply exit this repeat and recursion level, as the remaining right values are from the remaining slots anyway.
if (i < endLeft) then
set i to i + 1
set lv to item i of my rangeB
else
exit repeat
end if
end if
end repeat
end msrt
-- Sort the "half-range" l thru r.
on sortHalf(l, r)
-- If there are more than two items in this half, recurse further to merge-sort them.
-- If there are two, simply swap them (if necessary). If one, do nothing.
if (r - l > 1) then
msrt(l, r)
else if (r > l) then
set lv to item l of my lst
set rv to item r of my lst
if (comparer's isGreater(lv, rv)) then
set item l of my lst to rv
set item r of my lst to lv
slave's swap(l, r)
end if
end if
end sortHalf
-- Default comparison and slave handlers, for a normal, mergeSort-style sort.
on isGreater(a, b)
(a > b)
end isGreater
on extract(a, b)
end extract
on merge(a, b)
end merge
on swap(a, b)
end swap
end script
-- Process the input parmeters.
set listLen to (count ListToSort)
if (listLen > 1) then
-- Negative and/or transposed range indices.
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1
if (l > r) then set {l, r} to {r, l}
-- Supplied or default customisation scripts.
if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
-- Do the sort.
o's msrt(l, r)
end if
return -- nothing
end CustomMergeSort
property sort : CustomMergeSort
Hi scriptim,
I think the “*” is not enough. You need to add all the actual extensions for it to get entered in the list I think.
It turned into a huge script!
gl,
kel
I was afraid of that. It’ll be an even bigger info.plist file then!
My scripts have a way of blowing up like that. It would be a lot smaller but for the capability of modifying the file associations it manages. There’s a lot of logic that is really similar, but I rushed through it and didn’t bother to find a more clever way to represent things.
Hi scriptim,
Maybe you just need to add it to the Applications folder. I think that might have done it the first time I got it in the list.
gl,
kel
No, I think you need to enter every extension, then I just drop it into the Applications folder.
Maybe you can just copy the list of extensions from somewhere like other apps.
gl,
kel
Maybe you can just copy the list of extensions from somewhere like other apps.
Already have a script running: list of main folders + “ls -R” + hash table + merge sort, all in AS (except for the ls action)!!
output it all with the appropriate tags and I can just paste it into the info.plist file. then I can save my precious 10 seconds when I want to open things!
Hi scriptim,
You’re forgetting the priceless fun you’ve had!
Have a good day,
kel
Hello scriptim.
Nice applet you got there.
The * just won’t do. I think you’ll have to specify public.item (this will work with everything that can invoke the open with menu).
You can read about it here and here.
Edit
Actually, I recommend you copy everything from the key: CFBundleDocumentTypes and the full array below it from the info.plist of TextWrangler. Remove the things regarding icons and such, and try to specify public.item as the sole member of the LSItemContentTypes array. (Keep the CFBundleTypeRole as Editor, and specify CFBundleTypeName as “scriptims filetype”, I would keep the CFBundleTypeIconFile, but specifiy the applet icon (applet.icns), but copy it into the Resources folder of your applets Contents folder as well ).
HTH and good luck!
I recommend you copy everything from the key: CFBundleDocumentTypes and the full array below it from the info.plist of TextWrangler.
Good idea. it was actually enough to just include the mega-list of file extensions I got by parsing recursive ls commands in my main document folders. Then I re-registered my applet with launch services using
on run
set AppName to choose file with prompt "Choose application to re-register with launch services" of type {"app"} default location (path to applications folder)
set AppName to AppName as text
if text item -1 of AppName is ":" then repeat until text item -1 of AppName is not ":"
set AppName to (text items 1 through -2 of AppName) as text
end repeat
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
end run
And I was off to the races! (I never get a chance to say that…)
Now for every file on my system I see “¢ Open With” as the top item of the open with menu, and if there’re no applications associated with the filetype then it defaults to my applet!
Now for the next step:
In my readings (I had found what you linked to, McUsrII, in my own searching but it scared me so I asked here instead I found that you can also modify the icon that OS X uses for file types, and that the icon and filetypes are specified i the info.plist file in a similar way to what I’m doing already. Also, there’s no lack of example info.plist files to look at in one’s applications folder.
So my plan now is to have my applet maintain icon associations as well, so that filetypes that have an association through my applet (and only my applet, so this applies to previously unassociated filetypes) show the correct icon for whatever program they’re associated with. This should be as simple as adding some lines to the info.plist file that point to the icon of the association application for the particular file type. Or I’ll just get distracted and start doing something else…
Thanks again for your help guys! I wouldn’t have thought of this anytime soon on my own, and likely would have broken my system! Thanks Shane Stanley for the initial indication of my evil mis-doings!
Hi McUsr,
try to specify public.item as the sole member of the LSItemContentTypes array
What you’re saying is that if you do this, then you don’t need to enter every extension? That sounds right, because from the developer docs:
This key contains an array of strings. Each string contains a UTI defining a supported file type. The UTI string must be spelled out explicitly, as opposed to using one of the constants defined by Launch Services. For example, to support PNG files, you would include the string “public.png” in the array. When using this key, also add the NSExportableTypes key with the appropriate entries. In OS X v10.5 and later, this key (when present) takes precedence over these type-identifier keys: CFBundleTypeExtensions, CFBundleTypeMIMETypes, CFBundleTypeOSTypes.
But, would it add the application to the ‘Open with’ list?
Have a good day,
kel
Hello kel.
You’ll have to have a look at TextWrangler’s info.plist, then you’ll realize, that it is the LSItemContentTypes that makes textwrangler pop up as an app a file can be open with. My idea, which is untested, is that the public.item is the parent or containing public.png for instance.
Anyways, scriptim included the extensions he found in his folders, and filled out a huge array of CFBundleTypeExtensions I guess, so I’ll leave the question whether public.item work or not, out in the open.
@scriptim:
The key for the icon, is the CFBundleTypeIconFile, you’ll see its usage in the info.plist file of TextWrangler (for having your own app icon associated with the files).
Tomorrow.
Hi McUsr,
I’ll test it out and see if it works. From the developer docs, it seems that all you need to do is add the NSExportableTypes key with the same value public.item. Good find if it works. I haven’t seen this in my research.
Edited: the one thing that makes it a little bit harder is that you need to add it to the plist with text instead of with Xcode’s plist editor, because according to the docs theres no equivalent Xcode key.
Edited: oops, there is an Xcode equivalent.
Later,
kel