Logo Search packages:      
Sourcecode: zim version File versions  Download package


# -*- coding: utf-8 -*-

# Copyright 2009 Jaap Karssenberg <pardus@cpan.org>

'''This module contains utilities to work with external applications
it is based on the Freedesktop.org (XDG) Desktop Entry specification
with some additional logic based on status quo on Gnome / XFCE.

The desktop entry class subclasses the Apllication class from zim.applications,
see there for methods to run or spawn applications.

import os
import logging
import gtk
import gobject

from zim.fs import *
from zim.config import data_dirs, XDG_DATA_HOME, XDG_DATA_DIRS, \
      ConfigDict, ConfigFile, json
from zim.parsing import split_quoted_strings
from zim.applications import Application, WebBrowser, StartFile
from zim.gui.widgets import Dialog, ErrorDialog

logger = logging.getLogger('zim.gui.applications')

def _application_dirs():
      # Generator for application directories, first check zim specific paths,
      # then general applications
      for dir in data_dirs('applications'):
            yield dir

      yield XDG_DATA_HOME.subdir('applications')

      for dir in XDG_DATA_DIRS:
            yield dir.subdir('applications')

def _application_file(path, dirs):
      # Some logic to chekc multiple options, e.g. a path of kde-foo.desktop
      # could also be stored as applications/kde/foo.desktop but not necessarily..
      paths = [path]
      if '-' in path:
            for i in range(1, path.count('-')+1):
                  paths.append(path.replace('-', '/', i))

      for dir in dirs:
            for p in paths:
                  file = dir.file(p)
                  if file.exists():
                        return file
            return None

def get_application(name):
      file = _application_file(name + '.desktop', _application_dirs())
      if file:
            return DesktopEntryFile(File(file))
      elif name == 'webbrowser':
            return WebBrowser()
      elif name == 'startfile':
            return StartFile()
            return None

def get_applications(mimetype):
      seen = set()
      entries = []
      key = '%s=' % mimetype
      for dir in _application_dirs():
            cache = dir.file('mimeinfo.cache')
            for line in cache.readlines():
                  if line.startswith(key):
                        for basename in line[len(key):].strip().split(';'):
                              if basename in seen:
                                    file = _application_file(basename, (dir,))
                                    if file:

      if mimetype == 'text/html':

      return entries

def get_default_application(mimetype):
      pass # TODO: get default from defaults.list

def set_default_application(mimetype, name):
      pass # TODO: set new value for mimetype in default.list

def get_helper_applications(type):
      '''Returns a list of known applications that can be used as a helper
      of a certain type.
      Type can e.g. be 'web_browser', 'file_browser' or 'email_client'.
      # Be aware that X-Zim-AppType can be a list of types
      seen = set()
      helpers = []
      for dir in data_dirs('applications'):
            for basename in [n for n in dir.list() if n.endswith('.desktop')]:
                  key = basename[:-8] # len('.desktop') == 8
                  if key in seen:
                  entry = DesktopEntryFile(dir.file(basename))
                  if entry.isvalid():
                        if ('X-Zim-AppType' in entry['Desktop Entry']
                        and type in entry['Desktop Entry']['X-Zim-AppType']):

      if type == 'web_browser':
            for entry in get_applications('text/html'):
                  if not entry.key in seen:
      if not 'startfile' in seen:
            helpers.append( get_application('startfile') )

      helpers = [helper for helper in helpers if helper.tryexec()]
      return helpers

def create_application(mimetype, Name, Exec, **param):
      '''Creates a desktop entry file for a new usercreated desktop entry
      which defines a custom command to handle a certain file type.
      Returns the DesktopEntryFile object with some
      sensible defaults for a user created application entry.
      To know the key to retrieve this application later look at the
      'key' property of the entry object.
      dir = XDG_DATA_HOME.subdir('applications')
      param['MimeType'] = mimetype
      file = _create_application(dir, Name, Exec, **param)
      set_default_application(mimetype, key)
      return file

def create_helper_application(type, Name, Exec, **param):
      '''Like create_mime_application() but defines a zim specific helper.
      Type can e.g. be 'web_browser', 'file_browser' or 'email_client'.
      dir = XDG_DATA_HOME.subdir('zim/applications')
      param['X-Zim-AppType'] = type
      return _create_application(dir, Name, Exec, **param)

def _create_application(dir, Name, Exec, **param):
      n = Name.lower() + '-usercreated'
      key = n
      file = dir.file('applications', key + '.desktop')
      i = 0
      while file.exists():
            assert i < 1000, 'BUG: Infinite loop ?'
            i += 1
            key = n + '-' + str(i)
            file = dir.file('applications', key + '.desktop')
      entry = DesktopEntryFile(file)
      entry['Desktop Entry'].update(
      assert entry.isvalid(), 'BUG: created invalid desktop entry'
      return entry

00183 class DesktopEntryDict(ConfigDict, Application):
      '''Base class for DesktopEntryFile. Defines all the logic to work with
      desktop entry files. A desktop entry files describes all you need to know
      about an external application.

00189       def isvalid(self):
            '''Validate all the required fields are set. Assumes we only
            use desktop files to describe applications. Returns boolean
            for success.
            entry = self['Desktop Entry']
                  assert 'Type' in entry and entry['Type'] == 'Application', '"Type" missing or invalid'
                  assert 'Name' in entry, '"Name" missing'
                  assert 'Exec' in entry, '"Exec" missing'
                  if 'Version' in entry:
                        assert entry['Version'] == 1.0, 'Version invalid'
            except AssertionError:
                  logger.exception('Invalid desktop entry:')
                  return False
                  return True

      def name(self):
            # TODO: localisation of application name
            return self['Desktop Entry']['Name']

      def comment(self):
            # TODO: localisation of application name
            return self['Desktop Entry']['Comment']

      def tryexeccmd(self):
            return self['Desktop Entry'].get('TryExec')

      def cmd(self):
            return split_quoted_strings(self['Desktop Entry']['Exec'])

      def get_pixbuf(self):
            if 'Icon' in self['Desktop Entry']:
                  icon = self['Desktop Entry']['Icon']
                  return None

            w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
            if '/' in icon:
                  if os.path.isfile(icon):
                        return gtk.gdk.pixbuf_new_from_file_at_size(icon, w, h)
                        return None
                  theme = gtk.icon_theme_get_default()
                        pixbuf = theme.load_icon(icon.encode('utf-8'), w, 0)
                  except Exception, error:
                        #~ logger.exception('Foo')
                        return None
                  return pixbuf

00246       def parse_exec(self, args=None):
            '''Returns a list of command and arguments that can be used to
            open this application. Args can be either File objects or urls.
            assert args is None or isinstance(args, (list, tuple))

            def uris(args):
                  uris = []
                  for arg in args:
                        if isinstance(arg, (File, Dir)):
                  return uris

            cmd = split_quoted_strings(self['Desktop Entry']['Exec'])
            if args is None or len(args) == 0:
                  if '%f' in cmd: cmd.remove('%f')
                  elif '%F' in cmd: cmd.remove('%F')
                  elif '%u' in cmd: cmd.remove('%u')
                  elif '%U' in cmd: cmd.remove('%U')
            elif '%f' in cmd:
                  assert len(args) == 1, 'application takes one file name'
                  i = cmd.index('%f')
                  cmd[i] = unicode(args[0])
            elif '%F' in cmd:
                  i = cmd.index('%F')
                  for arg in reversed(map(unicode, args)):
                        cmd.insert(i, unicode(arg))
            elif '%u' in cmd:
                  assert len(args) == 1, 'application takes one url'
                  i = cmd.index('%u')
                  cmd[i] = uris(args)[0]
            elif '%U' in cmd:
                  i = cmd.index('%U')
                  for arg in reversed(uris(args)):
                        cmd.insert(i, unicode(arg))
                  cmd.extend(map(unicode, args))

            if '%i' in cmd:
                  if 'Icon' in self['Desktop Entry']:
                        i = cmd.index('%i')
                        cmd[i] = self['Desktop Entry']['Icon']
                        cmd.insert(i, '--icon')

            if '%c' in cmd:
                  i = cmd.index('%c')
                  cmd[i] = self.name

            if '%k' in cmd:
                  i = cmd.index('%k')
                  if hasattr(self, 'file'):
                        cmd[i] = self.file.path
                        cmd[i] = ''

            return tuple(cmd)

      _cmd = parse_exec # To hook into Application.spawn and Application.run

      def _decode_desktop_value(self, value):
            if value == 'true': return True
            elif value == 'false': return False
                        value = float(value)
                        return value
                        return json.loads('"%s"' % value.replace('"', '\\"')) # force string

      def _encode_value(self, value):
            if value is True: return 'true'
            elif value is False: return 'false'
            elif isinstance(value, int) or isinstance(value, float):
                  value = value.__str__()
                  assert isinstance(value, basestring), 'Desktop files can not store complex data'
                  return json.dumps(value)[1:-1] # get rid of quotes

class DesktopEntryFile(ConfigFile, DesktopEntryDict):

      def key(self):
            return self.file.basename[:-8] # len('.desktop') is 8

class OpenWithMenu(gtk.Menu):

      def __init__(self, file, mimetype=None):
            self. file = file
            if mimetype is None:
                  mimetype = file.get_mimetype()

            for entry in get_applications(mimetype):
                  item = DesktopEntryMenuItem(entry)
                  item.connect('activate', self.on_activate)

      def on_activate(self, menuitem):
            entry = menuitem.entry

class DesktopEntryMenuItem(gtk.ImageMenuItem):

      def __init__(self, entry):
            text = _('Open with "%s"') % entry.name
                  # T: menu item to open a file with an application, %s is the app name
            gtk.ImageMenuItem.__init__(self, text)
            self.entry = entry

            if hasattr(entry, 'get_pixbuf'):
                  pixbuf = entry.get_pixbuf()
                  if pixbuf:

class CustomCommandDialog(Dialog):

      def __init__(self, ui, type):
            Dialog.__init__(self, ui, _('Custom Command')) # T: Dialog title
            assert type in ('file_browser', 'web_browser', 'email_client')
            self.type = type
                  ('name', 'string', _('Name'), ''), # T: Field in 'custom command' dialog
                  ('exec', 'string', _('Command'), ''), # T: Field in 'custom command' dialog

      def do_response_ok(self):
            fields = self.get_fields()
            file = create_helper_application(self.type, fields['name'], fields['exec'])
            self.result = file
            return True

Generated by  Doxygen 1.6.0   Back to index