Home | History | Annotate | Line # | Download | only in share
      1 #!/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 
      4 """Utility for opening a file using the default application in a cross-platform
      5 manner. Modified from http://code.activestate.com/recipes/511443/.
      6 """
      7 
      8 __version__ = '1.1x'
      9 __all__ = ['open']
     10 
     11 import os
     12 import sys
     13 import webbrowser
     14 import subprocess
     15 
     16 _controllers = {}
     17 _open = None
     18 
     19 
     20 class BaseController(object):
     21     '''Base class for open program controllers.'''
     22 
     23     def __init__(self, name):
     24         self.name = name
     25 
     26     def open(self, filename):
     27         raise NotImplementedError
     28 
     29 
     30 class Controller(BaseController):
     31     '''Controller for a generic open program.'''
     32 
     33     def __init__(self, *args):
     34         super(Controller, self).__init__(os.path.basename(args[0]))
     35         self.args = list(args)
     36 
     37     def _invoke(self, cmdline):
     38         if sys.platform[:3] == 'win':
     39             closefds = False
     40             startupinfo = subprocess.STARTUPINFO()
     41             startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
     42         else:
     43             closefds = True
     44             startupinfo = None
     45 
     46         if (os.environ.get('DISPLAY') or sys.platform[:3] == 'win' or
     47                                                     sys.platform == 'darwin'):
     48             inout = file(os.devnull, 'r+')
     49         else:
     50             # for TTY programs, we need stdin/out
     51             inout = None
     52 
     53         # if possible, put the child precess in separate process group,
     54         # so keyboard interrupts don't affect child precess as well as
     55         # Python
     56         setsid = getattr(os, 'setsid', None)
     57         if not setsid:
     58             setsid = getattr(os, 'setpgrp', None)
     59 
     60         pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout,
     61                                 stderr=inout, close_fds=closefds,
     62                                 preexec_fn=setsid, startupinfo=startupinfo)
     63 
     64         # It is assumed that this kind of tools (gnome-open, kfmclient,
     65         # exo-open, xdg-open and open for OSX) immediately exit after launching
     66         # the specific application
     67         returncode = pipe.wait()
     68         if hasattr(self, 'fixreturncode'):
     69             returncode = self.fixreturncode(returncode)
     70         return not returncode
     71 
     72     def open(self, filename):
     73         if isinstance(filename, basestring):
     74             cmdline = self.args + [filename]
     75         else:
     76             # assume it is a sequence
     77             cmdline = self.args + filename
     78         try:
     79             return self._invoke(cmdline)
     80         except OSError:
     81             return False
     82 
     83 
     84 # Platform support for Windows
     85 if sys.platform[:3] == 'win':
     86 
     87     class Start(BaseController):
     88         '''Controller for the win32 start program through os.startfile.'''
     89 
     90         def open(self, filename):
     91             try:
     92                 os.startfile(filename)
     93             except WindowsError:
     94                 # [Error 22] No application is associated with the specified
     95                 # file for this operation: '<URL>'
     96                 return False
     97             else:
     98                 return True
     99 
    100     _controllers['windows-default'] = Start('start')
    101     _open = _controllers['windows-default'].open
    102 
    103 
    104 # Platform support for MacOS
    105 elif sys.platform == 'darwin':
    106     _controllers['open']= Controller('open')
    107     _open = _controllers['open'].open
    108 
    109 
    110 # Platform support for Unix
    111 else:
    112 
    113     try:
    114         from commands import getoutput
    115     except ImportError:
    116         from subprocess import getoutput
    117 
    118     # @WARNING: use the private API of the webbrowser module
    119     from webbrowser import _iscommand
    120 
    121     class KfmClient(Controller):
    122         '''Controller for the KDE kfmclient program.'''
    123 
    124         def __init__(self, kfmclient='kfmclient'):
    125             super(KfmClient, self).__init__(kfmclient, 'exec')
    126             self.kde_version = self.detect_kde_version()
    127 
    128         def detect_kde_version(self):
    129             kde_version = None
    130             try:
    131                 info = getoutput('kde-config --version')
    132 
    133                 for line in info.splitlines():
    134                     if line.startswith('KDE'):
    135                         kde_version = line.split(':')[-1].strip()
    136                         break
    137             except (OSError, RuntimeError):
    138                 pass
    139 
    140             return kde_version
    141 
    142         def fixreturncode(self, returncode):
    143             if returncode is not None and self.kde_version > '3.5.4':
    144                 return returncode
    145             else:
    146                 return os.EX_OK
    147 
    148     def detect_desktop_environment():
    149         '''Checks for known desktop environments
    150 
    151         Return the desktop environments name, lowercase (kde, gnome, xfce)
    152         or "generic"
    153 
    154         '''
    155 
    156         desktop_environment = 'generic'
    157 
    158         if os.environ.get('KDE_FULL_SESSION') == 'true':
    159             desktop_environment = 'kde'
    160         elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
    161             desktop_environment = 'gnome'
    162         else:
    163             try:
    164                 info = getoutput('xprop -root _DT_SAVE_MODE')
    165                 if ' = "xfce4"' in info:
    166                     desktop_environment = 'xfce'
    167             except (OSError, RuntimeError):
    168                 pass
    169 
    170         return desktop_environment
    171 
    172 
    173     def register_X_controllers():
    174         if _iscommand('kfmclient'):
    175             _controllers['kde-open'] = KfmClient()
    176 
    177         for command in ('gnome-open', 'exo-open', 'xdg-open'):
    178             if _iscommand(command):
    179                 _controllers[command] = Controller(command)
    180 
    181     def get():
    182         controllers_map = {
    183             'gnome': 'gnome-open',
    184             'kde': 'kde-open',
    185             'xfce': 'exo-open',
    186         }
    187 
    188         desktop_environment = detect_desktop_environment()
    189 
    190         try:
    191             controller_name = controllers_map[desktop_environment]
    192             return _controllers[controller_name].open
    193 
    194         except KeyError:
    195             if 'xdg-open' in _controllers:
    196                 return _controllers['xdg-open'].open
    197             else:
    198                 return webbrowser.open
    199 
    200 
    201     if os.environ.get("DISPLAY"):
    202         register_X_controllers()
    203     _open = get()
    204 
    205 
    206 def open(filename):
    207     '''Open a file or a URL in the registered default application.'''
    208 
    209     return _open(filename)
    210