aboutsummaryrefslogtreecommitdiffstats
path: root/dex
diff options
context:
space:
mode:
Diffstat (limited to 'dex')
-rwxr-xr-xdex781
1 files changed, 0 insertions, 781 deletions
diff --git a/dex b/dex
deleted file mode 100755
index 651a2aa..0000000
--- a/dex
+++ /dev/null
@@ -1,781 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# vi: ft=python:tw=0:sw=4:ts=4:noet
-# Author: Jan Christoph Ebersbach <jceb@e-jc.de>
-# Last Modified: Thu 12. Jul 2012 20:16:13 +0200 CEST
-
-# dex
-# DesktopEntry Execution, is a program to generate and execute DesktopEntry
-# files of the type Application
-#
-# Depends: None
-#
-# Copyright (C) 2010, 2011, 2012, 2013 Jan Christoph Ebersbach
-#
-# http://www.e-jc.de/
-#
-# All rights reserved.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-import glob
-import os
-import subprocess
-import sys
-
-__version__ = "0.7"
-
-
-# DesktopEntry exceptions
-class DesktopEntryTypeException(Exception):
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return repr(self.value)
-
-
-class ApplicationExecException(Exception):
- def __init__(self, value):
- self.value = value
- Exception.__init__(self, value)
-
- def __str__(self):
- return repr(self.value)
-
-
-# DesktopEntry class definitions
-class DesktopEntry(object):
- """
- Implements some parts of Desktop Entry specification:
- http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html
- """
- def __init__(self, filename=None):
- """
- @param filename Desktop Entry File
- """
- if filename is not None and os.path.islink(filename) and \
- os.readlink(filename) == os.path.sep + os.path.join('dev', 'null'):
- # ignore links to /dev/null
- pass
- elif filename is None or not os.path.isfile(filename):
- raise IOError('File does not exist: %s' % filename)
- self._filename = filename
- self.groups = {}
-
- def __str__(self):
- if self.Name:
- return self.Name
- elif self.filename:
- return self.filename
- return repr(self)
-
- def __lt__(self, y):
- return self.filename < y.filename
-
- @property
- def filename(self):
- """
- The absolute filename
- """
- return self._filename
-
- @classmethod
- def fromfile(cls, filename):
- """Create DesktopEntry for file
-
- @params filename Create a DesktopEntry object for file and determine
- the type automatically
- """
-
- de = cls(filename=filename)
-
- # determine filetype
- de_type = 'Link'
- if os.path.exists(filename):
- if os.path.isdir(filename):
- de_type = 'Directory'
- # TODO fix the value for directories
- de.set_value('??', filename)
- else:
- de_type = 'Application'
- de.set_value('Exec', filename)
- de.set_value('Name', os.path.basename(filename))
- if os.name == 'posix':
- whatis = subprocess.Popen(['whatis', filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- stdout, stderr = whatis.communicate()
- res = stdout.decode(sys.stdin.encoding).split('- ', 1)
- if len(res) == 2:
- de.set_value('Comment', res[1].split(os.linesep, 1)[0])
- else:
- # type Link
- de.set_value('URL', filename)
-
- de.set_value('Type', de_type)
-
- return de
-
- def load(self):
- """Load or reload contents of desktop entry file"""
- self.groups = {} # clear settings
- grp_desktopentry = 'Desktop Entry'
-
- _f = open(self.filename, 'r')
- current_group = None
- try:
- for l in _f.readlines():
- l = l.strip('\n')
- # handle comments and empty lines
- if l.startswith('#') or l.strip() == '':
- continue
-
- # handle groups
- if l.startswith('['):
- if not l.endswith(']'):
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because of line '%s'." % (self.filename, l))
- group = l[1:-1]
- if self.groups.get(group, None):
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because group '%s' is specified multiple times." % (self.filename, group))
- current_group = group
- continue
-
- # handle all the other lines
- if not current_group:
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because line '%s' does not belong to a group." % (self.filename, l))
- kv = l.split('=', 1)
- if len(kv) != 2 or kv[0] == '':
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because line '%s' is not a valid key=value pair." % (self.filename, l))
- k = kv[0]
- v = kv[1]
- # TODO: parse k for locale specific settings
- # TODO: parse v for multivalue fields
- self.set_value(k, v, current_group)
- except Exception as ex:
- _f.close()
- raise ex
- finally:
- _f.close()
-
- if grp_desktopentry not in self.groups:
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry group is missing." % (self.filename, ))
- if not (self.Type and self.Name):
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because Type or Name keys are missing." % (self.filename, ))
- _type = self.Type
- if _type == 'Application':
- if not self.Exec:
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry of type '%s' because Exec is missing." % (self.filename, _type))
- elif _type == 'Link':
- if not self.URL:
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry of type '%s' because URL is missing." % (self.filename, _type))
- elif _type == 'Directory':
- pass
- else:
- raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because Type '%s' is unkown." % (self.filename, self.Type))
-
- # another name for load
- reload = load
-
- def write(self, fileobject):
- """Write DesktopEntry to a file
-
- @param fileobject DesktopEntry is written to file
- """
- for group in self.groups:
- fileobject.write('[%s]\n' % (group, ))
- for key in self.groups[group]:
- fileobject.write('%s=%s\n' % (key, self.groups[group][key]))
-
- def set_value(self, key, value, group='Desktop Entry'):
- """ Set a key, value pair in group
-
- @param key Key
- @param value Value
- @param group The group key and value are set in. Default: Desktop Entry
- """
- if group not in self.groups:
- self.groups[group] = {}
- self.groups[group][key] = str(value)
- return value
-
- def _get_value(self, key, group='Desktop Entry', default=None):
- if not self.groups:
- self.load()
- if group not in self.groups:
- raise KeyError("Group '%s' not found." % group)
- grp = self.groups[group]
- if key not in grp:
- return default
- return grp[key]
-
- def get_boolean(self, key, group='Desktop Entry', default=False):
- val = self._get_value(key, group=group, default=default)
- if type(val) == bool:
- return val
- if val in ['true', 'True']:
- return True
- if val in ['false', 'False']:
- return False
- raise ValueError("'%s's value '%s' in group '%s' is not a boolean value." % (key, val, group))
-
- def get_list(self, key, group='Desktop Entry', default=None):
- list_of_strings = []
- res = self.get_string(key, group=group, default=default)
- if type(res) == str:
- list_of_strings = [x for x in res.split(';') if x]
- return list_of_strings
-
- def get_string(self, key, group='Desktop Entry', default=''):
- return self._get_value(key, group=group, default=default)
-
- def get_strings(self, key, group='Desktop Entry', default=''):
- raise Exception("Not implemented yet.")
-
- def get_localestring(self, key, group='Desktop Entry', default=''):
- raise Exception("Not implemented yet.")
-
- def get_numeric(self, key, group='Desktop Entry', default=0.0):
- val = self._get_value(key, group=group, default=default)
- if type(val) == float:
- return val
- return float(val)
-
- @property
- def Type(self):
- return self.get_string('Type')
-
- @property
- def Version(self):
- return self.get_string('Version')
-
- @property
- def Name(self):
- # SHOULD be localestring!
- return self.get_string('Name')
-
- @property
- def GenericName(self):
- return self.get_localestring('GenericName')
-
- @property
- def NoDisplay(self):
- return self.get_boolean('NoDisplay')
-
- @property
- def Comment(self):
- return self.get_localestring('Comment')
-
- @property
- def Icon(self):
- return self.get_localestring('Icon')
-
- @property
- def Hidden(self):
- return self.get_boolean('Hidden')
-
- @property
- def OnlyShowIn(self):
- return self.get_list('OnlyShowIn')
-
- @property
- def NotShowIn(self):
- return self.get_list('NotShowIn')
-
- @property
- def TryExec(self):
- return self.get_string('TryExec')
-
- @property
- def Exec(self):
- return self.get_string('Exec')
-
- @property
- def Path(self):
- return self.get_string('Path')
-
- @property
- def Terminal(self):
- return self.get_boolean('Terminal')
-
- @property
- def MimeType(self):
- return self.get_strings('MimeType')
-
- @property
- def Categories(self):
- return self.get_strings('Categories')
-
- @property
- def StartupNotify(self):
- return self.get_boolean('StartupNotify')
-
- @property
- def StartupWMClass(self):
- return self.get_string('StartupWMClass')
-
- @property
- def URL(self):
- return self.get_string('URL')
-
-
-class Application(DesktopEntry):
- """
- Implements application files
- """
-
- def __init__(self, filename):
- """
- @param filename Absolute path to a Desktop Entry File
- """
- if not os.path.isabs(filename):
- filename = os.path.join(os.getcwd(), filename)
- super(Application, self).__init__(filename)
- self._basename = os.path.basename(filename)
- if self.Type != 'Application':
- raise DesktopEntryTypeException("'%s' is not of type 'Application'." % self.filename)
-
- def __cmp__(self, y):
- """
- @param y The object to compare the current object with - comparison is made on the property of basename
- """
- if isinstance(y, Application):
- return cmp(y.basename, self.basename)
- return -1
-
- def __eq__(self, y):
- """
- @param y The object to compare the current object with - comparison is made on the property of basename
- """
- if isinstance(y, Application):
- return y.basename == self.basename
- return False
-
- @property
- def basename(self):
- """
- The basename of file
- """
- return self._basename
-
- @classmethod
- def _build_cmd(cls, exec_string, needs_terminal=False):
- """
- # test single and multi argument commands
- >>> Application._build_cmd('gvim')
- ['gvim']
- >>> Application._build_cmd('gvim test')
- ['gvim', 'test']
-
- # test quotes
- >>> Application._build_cmd('"gvim" test')
- ['gvim', 'test']
- >>> Application._build_cmd('"gvim test"')
- ['gvim test']
-
- # test escape sequences
- >>> Application._build_cmd('"gvim test" test2 "test \\\\" 3"')
- ['gvim test', 'test2', 'test " 3']
- >>> Application._build_cmd(r'"test \\\\\\\\ \\" moin" test')
- ['test \\\\ " moin', 'test']
- >>> Application._build_cmd(r'"gvim \\\\\\\\ \\`test\\$"')
- ['gvim \\\\ `test$']
- >>> Application._build_cmd(r'vim ~/.vimrc', True)
- ['x-terminal-emulator', '-e', 'vim', '~/.vimrc']
- >>> Application._build_cmd('vim ~/.vimrc', False)
- ['vim', '~/.vimrc']
- >>> Application._build_cmd("vim '~/.vimrc test'", False)
- ['vim', '~/.vimrc test']
- >>> Application._build_cmd('vim \\'~/.vimrc " test\\'', False)
- ['vim', '~/.vimrc " test']
- >>> Application._build_cmd('sh -c \\'vim ~/.vimrc " test\\'', False)
- ['sh', '-c', 'vim ~/.vimrc " test']
- >>> Application._build_cmd("sh -c 'vim ~/.vimrc \\" test\\"'", False)
- ['sh', '-c', 'vim ~/.vimrc " test"']
-
- # expand field codes by removing them
- >>> Application._build_cmd("vim %u", False)
- ['vim']
- >>> Application._build_cmd("vim ~/.vimrc %u", False)
- ['vim', '~/.vimrc']
- >>> Application._build_cmd("vim '%u' ~/.vimrc", False)
- ['vim', '%u', '~/.vimrc']
- >>> Application._build_cmd("vim %u ~/.vimrc", False)
- ['vim', '~/.vimrc']
- >>> Application._build_cmd("vim /%u/.vimrc", False)
- ['vim', '//.vimrc']
- >>> Application._build_cmd("vim %u/.vimrc", False)
- ['vim', '/.vimrc']
- >>> Application._build_cmd("vim %U/.vimrc", False)
- ['vim', '/.vimrc']
- >>> Application._build_cmd("vim /%U/.vimrc", False)
- ['vim', '//.vimrc']
- >>> Application._build_cmd("vim %U .vimrc", False)
- ['vim', '.vimrc']
-
- # preserved escaped field codes
- >>> Application._build_cmd("vim \\\\%u ~/.vimrc", False)
- ['vim', '%u', '~/.vimrc']
-
- # test for non-valid field codes, they should be preserved
- >>> Application._build_cmd("vim %x .vimrc", False)
- ['vim', '%x', '.vimrc']
- >>> Application._build_cmd("vim %x/.vimrc", False)
- ['vim', '%x/.vimrc']
- """
- cmd = []
- if needs_terminal:
- cmd += ['x-terminal-emulator', '-e']
- _tmp = exec_string.replace('\\\\', '\\')
- _arg = ''
- in_esc = False
- in_quote = False
- in_singlequote = False
- in_fieldcode = False
-
- for c in _tmp:
- if in_esc:
- in_esc = False
- else:
- if in_fieldcode:
- in_fieldcode = False
- if c in ('u', 'U', 'f', 'F'):
- # TODO ignore field codes for the moment; at some point
- # field codes should be supported
- # strip %-char at the end of the argument
- _arg = _arg[:-1]
- continue
-
- if c == '"':
- if in_quote:
- in_quote = False
- cmd.append(_arg)
- _arg = ''
- continue
- if not in_singlequote:
- in_quote = True
- continue
-
- elif c == "'":
- if in_singlequote:
- in_singlequote = False
- cmd.append(_arg)
- _arg = ''
- continue
- if not in_quote:
- in_singlequote = True
- continue
-
- elif c == '\\':
- in_esc = True
- continue
-
- elif c == '%' and not (in_quote or in_singlequote):
- in_fieldcode = True
-
- elif c == ' ' and not (in_quote or in_singlequote):
- if not _arg:
- continue
- cmd.append(_arg)
- _arg = ''
- continue
-
- _arg += c
-
- if _arg and not (in_esc or in_quote or in_singlequote):
- cmd.append(_arg)
- elif _arg:
- raise ApplicationExecException('Exec value contains an unbalanced number of quote characters.')
-
- return cmd
-
- def execute(self, dryrun=False, verbose=False):
- """
- Execute application
- @return Return subprocess.Popen object
- """
- _exec = True
- _try = self.TryExec
- if _try and not (os.path.isabs(_try) and os.path.isfile(_try)) and not which(_try):
- _exec = False
-
- if _exec:
- path = self.Path
- cmd = self._build_cmd(self.Exec)
- if not cmd:
- raise ApplicationExecException('Failed to build command string.')
- if dryrun or verbose:
- if verbose:
- print('Autostart file: %s' % self.filename)
- if path:
- print('Changing directory to: ' + path)
- print('Executing command: ' + ' '.join(cmd))
- if dryrun:
- return None
- if path:
- return subprocess.Popen(cmd, cwd=path)
- return subprocess.Popen(cmd)
-
-
-class AutostartFile(Application):
- """
- Implements autostart files
- """
-
- def __init__(self, filename):
- """
- @param filename Absolute path to a Desktop Entry File
- """
- super(AutostartFile, self).__init__(filename)
-
-
-class EmptyAutostartFile(Application):
- """
- Workaround for empty autostart files that don't contain the necessary data
- """
-
- def __init__(self, filename):
- """
- @param filename Absolute path to a Desktop Entry File
- """
- try:
- super(EmptyAutostartFile, self).__init__(filename)
- except DesktopEntryTypeException:
- # ignore the missing type information
- pass
-
-
-# local methods
-def which(filename):
- path = os.environ.get('PATH', None)
- if path:
- for _p in path.split(os.pathsep):
- _f = os.path.join(_p, filename)
- if os.path.isfile(_f):
- return _f
-
-
-def get_autostart_directories():
- """
- Generate the list of autostart directories
- """
- autostart_directories = [] # autostart directories, ordered by preference
-
- if args.searchdir:
- autostart_directories.append(args.searchdir[0])
- else:
- # generate list of autostart directories
- if os.environ.get('XDG_CONFIG_HOME', None):
- autostart_directories.append(os.path.join(os.environ.get('XDG_CONFIG_HOME'), 'autostart'))
- else:
- autostart_directories.append(os.path.join(os.environ['HOME'], '.config', 'autostart'))
-
- if os.environ.get('XDG_CONFIG_DIRS', None):
- for d in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep):
- if not d:
- continue
- autostart_dir = os.path.join(d, 'autostart')
- if autostart_dir not in autostart_directories:
- autostart_directories.append(autostart_dir)
- else:
- autostart_directories.append(os.path.sep + os.path.join('etc', 'xdg', 'autostart'))
-
- return autostart_directories
-
-
-def get_autostart_files(args, verbose=False):
- """
- Generate a list of autostart files according to autostart-spec 0.5
-
- TODO: do filetype recognition according to spec
- """
- environment = args.environment[0].lower() if args.environment else ''
-
- autostart_files = [] # autostart files, excluding files marked as hidden
- non_autostart_files = []
-
- for d in get_autostart_directories():
- for _f in glob.glob1(d, '*.desktop'):
- _f = os.path.join(d, _f)
- af = None
-
- if os.path.isfile(_f) or os.path.islink(_f):
- try:
- af = AutostartFile(_f)
- except DesktopEntryTypeException as ex:
- af = EmptyAutostartFile(_f)
- if af not in autostart_files and not af in non_autostart_files:
- non_autostart_files.append(af)
- continue
- except ValueError as ex:
- if verbose:
- print(ex, file=sys.stderr)
- continue
- except IOError as ex:
- if verbose:
- print(ex, file=sys.stderr)
- continue
- else:
- if verbose:
- print('Ignoring unknown file: %s' % _f, file=sys.stderr)
- continue
-
- if verbose:
- if af.NotShowIn:
- print('Not show in environments %s: %s' % (', '.join(af.NotShowIn), af.filename), file=sys.stderr)
- if af.OnlyShowIn:
- print('Only show in environments %s: %s' % (', '.join(af.OnlyShowIn), af.filename), file=sys.stderr)
- if af in autostart_files or af in non_autostart_files:
- if verbose:
- print('Ignoring file, overridden by other autostart file: %s' % af.filename, file=sys.stderr)
- continue
- elif af.Hidden:
- if verbose:
- print('Ignoring file, hidden attribute is set: %s' % af.filename, file=sys.stderr)
- non_autostart_files.append(af)
- continue
- elif environment:
- if environment in [x.lower() for x in af.NotShowIn]:
- if verbose:
- print('Ignoring file, it must not start in specific environments (%s): %s' % (', '.join(af.NotShowIn), af.filename), file=sys.stderr)
- non_autostart_files.append(af)
- continue
- elif af.OnlyShowIn and environment not in [x.lower() for x in af.OnlyShowIn]:
- if verbose:
- print('Ignoring file, it must only start in specific environments (%s): %s' % (', '.join(af.OnlyShowIn), af.filename), file=sys.stderr)
- non_autostart_files.append(af)
- continue
- autostart_files.append(af)
- if verbose:
- for i in non_autostart_files:
- print('Ignoring empty file: %s' % i.filename, file=sys.stderr)
- return sorted(autostart_files)
-
-
-def _test(args):
- """
- run tests
- """
- import doctest
- doctest.testmod()
-
-
-def _autostart(args):
- """
- perform autostart
- """
- if args.dryrun and args.verbose:
- print('Dry run, nothing is executed.', file=sys.stderr)
-
- exit_value = 0
- for app in get_autostart_files(args, verbose=args.verbose):
- try:
- app.execute(dryrun=args.dryrun, verbose=args.verbose)
- except Exception as ex:
- exit_value = 1
- print("Execution faild: %s%s%s" % (app.filename, os.linesep, ex), file=sys.stderr)
-
-
-def _run(args):
- """
- execute specified DesktopEntry files
- """
- if args.dryrun and args.verbose:
- print('Dry run, nothing is executed.', file=sys.stderr)
-
- exit_value = 0
- if not args.files:
- print("Nothing to execute, no DesktopEntry files specified!", file=sys.stderr)
- parser.print_help()
- exit_value = 1
- else:
- for f in args.files:
- try:
- app = Application(f)
- app.execute(dryrun=args.dryrun, verbose=args.verbose)
- except ValueError as ex:
- print(ex, file=sys.stderr)
- except IOError as ex:
- print(ex, file=sys.stderr)
- except Exception as ex:
- exit_value = 1
- print("Execution faild: %s%s%s" % (f, os.linesep, ex), file=sys.stderr)
- return exit_value
-
-
-def _create(args):
- """
- create a new DesktopEntry file from the given argument
- """
- target = args.create[0]
- if args.verbose:
- print('Creating DesktopEntry for file %s.' % target)
-
- de = DesktopEntry.fromfile(target)
- if args.verbose:
- print('Type: %s' % de.Type)
-
- # determine output file
- output = '.'.join((os.path.basename(target), 'directory' if
- de.Type == 'Directory' else 'desktop'))
- if args.targetdir:
- output = os.path.join(args.targetdir[0], output)
- elif len(args.create) > 1:
- output = args.create[1]
-
- if args.verbose:
- print('Output: %s' % output)
- targetfile = sys.stdout if output == '-' else open(output, 'w')
- de.write(targetfile)
-
- if args.targetdir and len(args.create) > 1:
- args.create = args.create[1:]
- return _create(args)
- return 0
-
-
-# start execution
-if __name__ == '__main__':
- from argparse import ArgumentParser
- parser = ArgumentParser(usage='%(prog)s [options] [DesktopEntryFile [DesktopEntryFile ...]]',
- description='dex, DesktopEntry Execution, is a program to generate and execute DesktopEntry files of the type Application', epilog='Example usage: list autostart programs: dex -ad')
- parser.add_argument("--test", action="store_true", dest="test", help="perform a self-test")
- parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", help="verbose output")
- parser.add_argument("-V", "--version", action="store_true", dest="version", help="display version information")
- parser.add_argument('files', nargs='*', help="DesktopEntry files")
-
- run = parser.add_argument_group('run')
- run.add_argument("-a", "--autostart", action="store_true", dest="autostart", help="autostart programs")
- run.add_argument("-d", "--dry-run", action="store_true", dest="dryrun", help="dry run, don't execute any command")
- run.add_argument("-e", "--environment", nargs=1, dest="environment", help="specify the Desktop Environment an autostart should be performed for; works only in combination with --autostart")
- run.add_argument("-s", "--search-directory", nargs=1, dest="searchdir", help="specify the directory to search for desktop files in, overriding the default search list")
-
- create = parser.add_argument_group('create')
- create.add_argument("-c", "--create", nargs='+', dest="create", help="create a DesktopEntry file for the given program. If a second argument is provided it's taken as output filename or written to stdout (filname: -). By default a new file with the postfix .desktop is created")
- create.add_argument("-t", "--target-directory", nargs=1, dest="targetdir", help="create files in target directory")
-
- parser.set_defaults(func=_run, dryrun=False, test=False, autostart=False, verbose=False)
-
- args = parser.parse_args()
- if args.autostart:
- args.func = _autostart
- elif args.create:
- args.func = _create
- elif args.test:
- args.func = _test
-
- # display version information
- if args.version:
- print("dex %s" % __version__)
- else:
- sys.exit(args.func(args))