Development/Architecture/KDE3/MIME Types

From KDE TechBase

KDE Architecture - MIME type handling

What are MIME types?

MIME types are used to describe the content type of files or data chunks. Originally they were introduced in order to allow sending around image or sound files etc. by e-mail (MIME stands for "Multipurpose Internet Mail Extensions"). Later this system was also used by web browsers to determine how to present data sent by a web server to the user. For example, an HTML page has a MIME type "text/html", a postscript file "application/postscript". In KDE, this concept is used at a variety of places:

  • In Konqueror's icon view, files are represented by icons. Each MIME type has a certain associated icon shown here.
  • When you click onto a file icon or a file name in Konqueror, either the file is shown in an embedded view, or an application associated with the file type is opened.
  • When you drag and drop some data from one application to another (or within the same application), the drop target may choose to accept only certain data types. Furthermore, it will handle image data different from textual data.
  • Clipboard data has a MIME type. Traditionally, X programs only handle pixmaps or texts, but with Qt, there are no restrictions on the data type.

From the above examples, it is clear that MIME handling is a complex issue. First, it is necessary to establish a mapping from file names to MIME types. KDE goes one step further in allowing even file contents to be mapped to MIME types, for cases in which the file name is not available. Second, it is necessary to map MIME types to applications or libraries which can view or edit a file with a certain type, or create a thumbnail picture for it.

There is a variety of APIs to figure out the MIME type of data or files. In general, there is a certain speed/reliability trade-off you have to make. You can find out the type of a file by examining only its file name (i.e. in most cases the file name extension). For example, a file foo.jpg is normally "image/jpeg". In cases where the extension is stripped off this is not safe, and you actually have to look at the contents of the file. This is of course slower, in particular for files that have to be downloaded via HTTP first. The content-based method is based on the file KDEDIR/share/mimelnk/magic and therefore difficult to extend. But in general, MIME type information can easily be made available to the system by installing a .desktop file, and it is efficiently and conveniently available through the KDE libraries.

Defining MIME types

Let us define a type "application/vnd.kde.foo" for our new foobar program. To this end, you have to write a file vnd.kde.foo.desktop and install it into KDEDIR/share/mimelnk/application. (This is the usual location, which may differ between distributions). This can be done by adding this to the Makefile.am:

mimedir = $(kde_mimedir)/application
mime_DATA = vnd.kde.foo.desktop
EXTRA_DIST = $(mime_DATA)

The file foo.desktop should look as follows:

[Desktop Entry]
Type=MimeType
MimeType=application/vnd.kde.foo
Icon=fooicon
Patterns=*.foo;
DefaultApp=foobar
Comment=Foo Data File
Comment[de]=Foo Datei

The "Comment" is entry is supposed to be translated. Since the .desktop file specifies an icon, you should also install an icon fooicon.png, which represents the file e.g. in Konqueror.

In the KDE libraries, such a type definition is mapped to an instance of the class KMimeType. Use this like in the following example:

KMimeType::Ptr type = KMimeType::mimeType("application/vnd.kde.foo");
kdDebug() << "Type:    " << type->name() << endl;
kdDebug() << "Icon:    " << type->icon() << endl;
kdDebug() << "Comment: " << type->comment() << endl;
QStringList patterns = type->patterns();
QStringList::ConstIterator it;
for (it = patterns.begin(); it != patterns.end(); ++it)
  kdDebug() << "Pattern: " << (*it) << endl;

Last not least you should register your new MIME type with IANA. Please contact Marc Mutz for help on this.

Finding out the MIME type of data

The fast method for finding out the type of a file is KMimeType::findByURL(). This looks for the url string and in most cases determines the type from the extension. For certain protocols (e.g. http, man, info), this mechanism is not used. For example, CGI scripts on web servers written in Perl often have the extension .pl, which would indicate a "text/x-perl" type. However, we file delivered by the server is the output of this script, which is normally HTML. For such a case, KMimeType::findByURL() returns the MIME type "application/octet-stream" (available through KMimeType::defaultMimeType()), which indicates a failure to find out the type.

KMimeType::Ptr type = KMimeType::findByURL("/home/user/foobar.jpg");
if (type->name() == KMimeType::defaultMimeType())
  kdDebug() << "Could not find out type" << endl;
else
  kdDebug() << "Type: " << type->name() << endl;

(this method has some more arguments, but these are undocumented, so simply forget about them.)

You may want to find out a MIME from the contents of file instead of the file name. This is more reliable, but also slower, as it requires reading a part of the file. This is done with the KMimeMagic class, which has different error handling:

KMimeMagicResult *result =
    KMimeMagic::self()->findFileType("/home/user/foobar.jpg");
if (!result || !result->isValid())
  kdDebug() << "Could not find out type" << endl;
else
  kdDebug() << "Type: " << result->mimeType() << endl;

As a variant of this function, you can also determine the type of a memory chunk. This is e.g. used in KWrite in order to find out the highlighting mode:

QByteArray array;
//...
KMimeMagicResult *res = KMimeMagic::self()->findBufferType(array);
if (!res || !res->isValid())
  kdDebug() << "Could not find out type" << endl;
else
  kdDebug() << "Type: " << res->mimeType() << endl;

Of course, even KMimeMagic is only able to determine a file type from the contents of a local file. For remote files, there is a further possibility:

KURL url("http://developer.kde.org/favicon.ico");
QString type = KIO::NetAccess::mimetype(url);
if (type == KMimeType::defaultMimeType())
  kdDebug() << "Could not find out type" << endl;
else
  kdDebug() << "Type: " << type << endl;

This starts a KIO job to download a part of the file and check this. Note that this function is perhaps quite slow and blocks the program. Normally you will only want to use this if KMimeType::findByURL() has returned "application/octet-stream".

On the other hand, if you do not want to block your application, you can also explicitly start the KIO job and connect to some of its signals:

void FooClass::findType()
{
  KURL url("http://developer.kde.org/favicon.ico");
  KIO::MimetypeJob *job = KIO::mimetype(url);
  connect(job, SIGNAL(result(KIO::Job*)),
          this, SLOT(mimeResult(KIO::Job*)));
}

void FooClass::mimeResult(KIO::Job *job)
{
  if (job->error())
    job->showErrorDialog();
  else
    kdDebug() << "MIME type: "
              << ((KIO::MimetypeJob *)job)->mimetype() << endl;
}

Mapping a MIME type to an application or service

When an application is installed, it installs a .desktop file which contains a list of MIME types this application can load. Similarly, components like KParts make this information available by their service .desktop files. So in general, there are several programs and components which can process a given MIME type. You can obtain such a list from the class KServiceTypeProfile:

KService::OfferList offers =
    KServiceTypeProfile::offers("text/html", "Application");
KService::OfferList::ConstIterator it;
for (it = offers.begin(); it != offers.end(); ++it) {
  KService::Ptr service = (*it);
  kdDebug() << "Name: " << service->name() << endl;
}

The return value of this function is a list of service offers. A KServiceOffer packages a KService::Ptr together with a preference number. The list returned by KServiceTypeProfile::offers() is ordered by the user's preference. The user can change this by calling "keditfiletype text/html" or choosing "Edit File Type" on Konqueror's context menu on a HTML file.

In the above example, an offer list of the applications supporting text/html was requested. This will - among others - contain HTML editors like Quanta Plus. You can also replace the second argument "Application" by "KParts::ReadOnlyPart". In that case, you get a list of embedabble components for presenting HTML content, for example KHTML.

In most cases, you are not interested in the list of all service offers for a combination of MIME type and service type. There is a convenience function which gives you only the service offer with the highest preference:

KService::Ptr offer =
  KServiceTypeProfile::preferredService("text/html", "Application");
if (offer)
  kdDebug() << "Name: " << service->name() << endl;
else
  kdDebug() << "No appropriate service found" << endl;

For even more complex queries, there is a full-blown CORBA-like trader.

In order to run an application service with some urls, use KRun:

KURL::List urlList;
urlList << "http://www.ietf.org/rfc/rfc1341.txt?number=1341";
urlList << "http://www.ietf.org/rfc/rfc2046.txt?number=2046";
KRun::run(offer.service(), urlList);

Miscellaneous

In this section, we want to list some APIs which are loosely related to the previous discussion.

Getting an icon for a URL. This looks for the type of the URL and returns the associated icon.

KURL url("ftp://ftp.kde.org/pub/incoming/wibble.c");
QString icon = KMimeType::iconForURL(url);

Running a URL. This looks for the type of the URL and starts the user's preferred program associated with this type.

KURL url("http://dot.kde.org");
new KRun(url);


Initial Author: Bernd Gehrmann