Quicklook Mac OSX UTI Hell (or what Apple did omit in its documentation)

After some blogging abstinence I am back. And we are coming up with a really nice topic, which hasn’t been blogged about out there on the web yet ;)

It’s all about Apple’s UTI’s and what Apple omitted to say about them.

UTI’s are one of the most powerful features of MacOSX and most CoreServices like LaunchServices, Spotlight and Quicklook depend upon it.

However documentation about it is unclear about which strategy applies when UTI’s are missing.

So what are UTI’s anyway:

UTI stays for Uniform Type Identifier, which means they identify a file type, very similar to a file extension or a MIME type, but more generic and with builtin inheritance.

So for example an image file has the UTI public.image, which inherits from public.data.

(you can learn more about UTI’s here)

Ok. That’s great, but where does a file hold the information about its UTI? Extension information comes from the filename and MIME types come from HTTP header information, but UTI??

Well it turns out that UTI information is stored in the extended attributes of a file on HFS+. You can show this metadata by typing mdls “filename” in a Terminal window.

The fields kMDItemContentType and kMDItemContentTypeTree will save information about the filetype and its inheritance.

But what happens if a file has not been created by a Mac OSX application and therefore has no UTI information at all??? That’s actually an interesting question, cause it happens quite oft.

MacOS X will generate a dynamic UTI, but most importantly it will fall back to file extensions, which is what Apple calls “Idintifier Tags”, but lets look at the information lookup flow a bit.

Lets say you have this awesome new file extension called .markdown and want to preview it with Quicklook. 

The relevant UTI information looks like this:

In case of Readme.md the relevant information is:

kMDItemContentType             = “dyn.ah62d4rv4ge8043a”

kMDItemContentTypeTree         = (

    “dyn.ah62d4rv4ge8043a”,

    “public.data”,

    “public.item”

)

OSX has no clue about it. So what’s gonna happen? First off, OS X will generate a dynamic UTI, but that’s not of a big help here. (see above)

So Quicklook will lookup the file extension and choose the appropriate program, that can ‘handle’ such file.

How can Quicklook know about which program is mapped to a certain file extension?

Each application has an Info.plist file in it’s Contents folder. This Info.plist can contain a key called UTImportedTypeDeclarations or UTExportedTypeDeclarations, both of which do more or less the same. The first is more generic as it says, which UTI’s (but also file extensions) an application will handle and registers it in the system. (The second one, says, that the application is not only able to handle such type, but is the actual ‘owner’ of the type).

Within UTImportedTypeDeclarations the application can then give a list of UTI Types the file type also conforms to. So an UTI inheritance list gets generated. E.g. Markdown being also public.plain-text.

So what we have till here is: file extension -> application that says it can handle extension -> inherited UTI’s for file extension.

Now back to Quicklook. Based upon the file extension Quicklook will try to find a Quicklook generator (.qlgenerator), a small Plugin, that says how to render a file type. First in the Application, then in ~/Library and /Library. The Quicklook generator itself has an Info.plist, where it can communicate to the system, which UTI’s (or file extensions) it is able to handle.

As a side information: Quicklook generators always renderer ouput to PDF, Text or Image Buffers.

If for a certain UTI or file type no qlgenerator is found Quicklook will display a standard dialog, but no Preview :(

As with Markdown, there is no .qlgenerator installed per default (actually one exists at https://github.com/toland/qlmarkdown, but we won’t use this one now).

As Markdown actually is a text file, it would be sufficient to start of the classic public-plain-text UTI qlgenerator, but markdown has no inheritance list that includes plain-text.

However if nothing is found the next lower UTI communicated by the application in UTTypeConformsTo is taken. This is where we can hook in the magic.

So take an application of your choice (which one is not important) and paste in the following in the Info.plist.

<key>UTImportedTypeDeclarations</key>

<array>

 <dict>

<key>UTTypeConformsTo</key>

<array>

<string>public.plain-text</string>

</array>

<key>UTTypeDescription</key>

<string>Markdown document</string>

<key>UTTypeIconFile</key>

<string>public.text.icns</string>

<key>UTTypeIdentifier</key>

<string>net.daringfireball.markdown</string>

<key>UTTypeReferenceURL</key>

<string>http://daringfireball.net/projects/markdown/</string>

<key>UTTypeTagSpecification</key>

<dict>

<key>public.filename-extension</key>

<array>

<string>markdown</string>

<string>mdown</string>

<string>md</string>

<string>mdml</string>

<string>text</string>

<string>mdwn</string>

<string>mkd</string>

<string>mmd</string>

<string>rst</string>

</array>

</dict>

 </dict>

</array>

So what we have now is: file extension -> application that says it can handle extension -> inherited UTI’s for file extension under ConformsTo is plain-text-> inherited UTI qlgenerator for plain-text will be used if no qppropriate qlgenerator was found before.

In order to reload the UTI table for the application, delete the app from the Application folder, then go to Trash and put it back into Applications. (Comments on an easier way to reregister the UTI’s of an app are appreciated!)

and here we go! :) Quicklook for Markdown!

Please note, that it was not important at all, which UTI the file has or which application we choose, as we are (ab)using the UTTypeTagSpecification of the application, mapping an extension to a list of conforming UTI’s.

Final hint: We were talking about Quicklook to find the right qlgenerator for preview here, still this has nothing to do with the application, which will be opening the file. This depends also on UTI’s and File Extensions,  CFBundleDocumentTypes, Creator Codes etc. but each app registers for them seperately. There is a very nice Preference Pane, that shows all registered extensions for an application and it’s called: RCDefaultApp http://www.rubicode.com/Software/RCDefaultApp/

Go get it and play around with the mappings!