pax_global_header00006660000000000000000000000064136712202470014516gustar00rootroot0000000000000052 comment=617bb29c0e6dfcb33fb7330a8b88e34461c2f905 dex-0.9.0/000077500000000000000000000000001367122024700123045ustar00rootroot00000000000000dex-0.9.0/CHANGELOG.md000066400000000000000000000015001367122024700141110ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. ## [0.8.0] - 2017-06-18 ### Added - Pass environment to sub processes - Add -s switch to specify the search paths (thanks to Johannes Löthberg) - Add support for KDE's proprietary Service type (#7 and #28, thanks to Sébastien Luttringer and Konfekt) ### Changed - Mark clean target PHONY - Switch to RST for the README and manpaeg - Ignore backslash in comments (#8, thanks to nanouck) - Ignore missing name for Type=Service entries (#28, thanks to Konfekt) ### Fixed - add force to clean target (#25, thanks to Johannes Löthberg) - Turn utf-8 string into Unicode string literal (#23, thanks to Johannes Löthberg) - Fix error converting man page - Print nice error message when target directory doesn't exist (#31, thanks to @lasers) dex-0.9.0/LICENSE000066400000000000000000000014121367122024700133070ustar00rootroot00000000000000dex (DesktopEntry Execution), is a program to generate and execute DesktopEntry files of the type Application Copyright (C) 2010,2011,2012 Jan Christoph Ebersbach 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 . dex-0.9.0/Makefile000066400000000000000000000020511367122024700137420ustar00rootroot00000000000000NAME = dex PREFIX = /usr/local DOCPREFIX = $(PREFIX)/share/doc/$(NAME) MANPREFIX = $(PREFIX)/man VERSION = $(shell git tag | tail -n 1) TAG = $(NAME)-$(VERSION) build: dex.1 dex.1: man/dex.rst @echo building the manpage in man/ @sphinx-build -b man -D version=$(TAG) -E man . $+ install: dex dex.1 README.rst LICENSE @echo installing executable file to $(DESTDIR)$(PREFIX)/bin @mkdir -p $(DESTDIR)$(PREFIX)/bin @install -m 0755 $< $(DESTDIR)$(PREFIX)/bin/$(NAME) @echo installing docs to $(DESTDIR)$(DOCPREFIX) @mkdir -p $(DESTDIR)$(DOCPREFIX) @install -m 0644 -t $(DESTDIR)$(DOCPREFIX)/ README.rst LICENSE @echo installing manual page to $(DESTDIR)$(MANPREFIX)/man1 @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 @install -m 0644 dex.1 $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 tgz: source source: dex dex.1 README.rst LICENSE Makefile CHANGELOG.md @echo "Creating source package: $(TAG).tar.gz" @mkdir $(TAG) @cp -t $(TAG) $+ @tar czf $(TAG).tar.gz $(TAG) @rm -rf $(TAG) clean: @rm -f $(TAG).tar.gz @rm -f dex.1 .PHONY: build install tgz source clean dex-0.9.0/README.rst000066400000000000000000000161401367122024700137750ustar00rootroot00000000000000dex === Synopsis -------- **dex** [*options*] [*DesktopEntryFile*]... Description ----------- ``dex``, DesktopEntry Execution, is a program to generate and execute DesktopEntry files of the Application type. Options ------- +------------------------------------+------------------------------------------------------------+ | Option | Description | +====================================+============================================================+ | -h, --help | Show a help message and exit | +------------------------------------+------------------------------------------------------------+ | -a, --autostart | Autostart programs | +------------------------------------+------------------------------------------------------------+ | -c, --create PATH | Create a DesktopEntry file for the program at the given | | | path. An optional second argument is used to specify the | | | filename of the created DesktopEntry file,or specify the | | | filename - to print the file to stdout. By default a new | | | file is createdwith the .desktop file extension. | +------------------------------------+------------------------------------------------------------+ | -d, --dry-run | Dry run, don't execute any command | +------------------------------------+------------------------------------------------------------+ | -e, --environment ENVIRONMENT | Specify the Desktop Environment an autostart should be | | | performed for; works only in combination with -a | +------------------------------------+------------------------------------------------------------+ | -s, --search-paths SEARCHPATHS | Colon separated list of paths to search for desktop files, | | | overriding the default search list | +------------------------------------+------------------------------------------------------------+ | -t, --target-directory ENVIRONMENT | Create files in target directory | +------------------------------------+------------------------------------------------------------+ | --term TERM | The terminal emulator that will be used to run the program | | | if Terminal=true is set in the desktop file, defaults to | | | x-terminal-emulator. | +------------------------------------+------------------------------------------------------------+ | -w, --wait | Block until the program exits. | +------------------------------------+------------------------------------------------------------+ | --test | Perform a self-test | +------------------------------------+------------------------------------------------------------+ | -v, --verbose | Verbose output | +------------------------------------+------------------------------------------------------------+ | -V, --version | Display version information | +------------------------------------+------------------------------------------------------------+ Examples -------- Perform an autostart/execute all programs in the autostart folders. ``dex -a`` Perform an autostart/execute all programs in the specified folders. ``dex -a -s /etc/xdg/autostart/:~/.config/autostart/`` Preview the programs would be executed in a regular autostart. ``dex -ad`` Preview the programs would be executed in a GNOME specific autostart. ``dex -ad -e GNOME`` Create a DesktopEntry for a program in the current directory. ``dex -c /usr/bin/skype`` Create a DesktopEntry for a programs in autostart directroy. ``dex -t ~/.config/autostart -c /usr/bin/skype /usr/bin/nm-applet`` Execute a single program from command line and enable verbose output. ``dex -v skype.desktop`` Execute a single program (with Terminal=true in the desktop file) in gnome-terminal. ``dex --term gnome-terminal nvim.desktop`` Execute a single program and block until it exits. ``dex --wait nvim.desktop`` Autostart Alternative --------------------- I consider ``systemd/user`` as a good alternative for ``dex``'s autostart functionality and switched to it recently. In particular, systemd solves the issue of ``dex`` losing control over the started processes which causes processes to live longer than the X session which could cause additional annoyances like reboots taking a lot of time because the system is waiting for the processes to terminate. The following steps will help you to get to a working ``systemd/user`` configuration: - Create the systemd user directory: ``mkdir -p ~/.config/systemd/user`` - Create an autostart target at ``~/.config/systemd/user/autostart.target`` with the following content:: [Unit] Description=Current graphical user session Documentation=man:systemd.special(7) RefuseManualStart=no StopWhenUnneeded=no - Create service files at ``~/.config/systemd/user/.service`` that service the same purpose as the ``.desktop`` files created by ``dex``. The service file should have at least the following content:: [Unit] Description= [Service] ExecStart= [] - Attention: for the service to work properly it mustn't fork. Systemd will take care of the service management but it can only do this when the service doesn't fork! If the services forks and terminates the main process, systemd will kill all the processes related to the service. The service will therefore not run at all! The man page of the service should list the required parameters that need to be provided to the service to avoid forking. - Register a service with systemd: ``systemctl --user add-wants autostart.target .service`` - Unregister a service: ``systemctl --user disable .service`` - List currently active services: ``systemctl --user list-units`` - Finally, start all services in the autostart target during startup by replacing the ``dex -a`` command with: ``systemctl --user start autostart.target`` - Reload all service configurations after making changes to a service file: ``systemctl --user daemon-reload`` - Start a service: ``systemctl --user start .service`` - Check the status of a service: ``systemctl --user status .service`` - Stop a service: ``systemctl --user stop .service`` dex-0.9.0/dex000077500000000000000000000562701367122024700130240ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # vi: ft=python:tw=0:sw=4:ts=4:noet # Author: Jan Christoph Ebersbach # 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 . import glob import os import subprocess import sys __version__ = "0.8.0" # 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): if self.Type != 'Service': # allow files with type Service and no Name raise DesktopEntryTypeException("'%s' is not a valid Desktop Entry because Type or Name keys are missing." % (self.filename, )) _type = self.Type if _type in ('Application', 'Service'): 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 unknown." % (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 not in ('Application', 'Service'): 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, term='x-terminal-emulator'): """ # 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 += [term, '-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 == '\\': if not in_quote: 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, term=None, wait=False, 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(exec_string=self.Exec, needs_terminal=self.Terminal, term=term) 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 _execute_fn = subprocess.Popen if wait: _execute_fn = subprocess.run if path: return _execute_fn(cmd, cwd=path, env=os.environ) return _execute_fn(cmd, env=os.environ) 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.searchpaths: for p in args.searchpaths[0].split(os.pathsep): path = os.path.expanduser(p) path = os.path.expandvars(path) autostart_directories += [path] 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(term=args.term, wait=args.wait, 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) try: targetfile = sys.stdout if output == '-' else open(output, 'w') except FileNotFoundError: print('Target directory does not exist: %s' % os.path.dirname(output)) return 1 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-paths", nargs=1, dest="searchpaths", help="colon separated list of paths to search for desktop files, overriding the default search list") run.add_argument("--term", dest="term", help="the terminal emulator that will be used to run the program if Terminal=true is set in the desktop file, defaults to x-terminal-emulator") run.add_argument("-w", "--wait", action="store_true", dest="wait", help="block until the program exits") 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 (filename: -). 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, term="x-terminal-emulator", wait=False, 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)) dex-0.9.0/man/000077500000000000000000000000001367122024700130575ustar00rootroot00000000000000dex-0.9.0/man/conf.py000066400000000000000000000005501367122024700143560ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import os project = 'dex' master_doc = 'dex' source_suffix = '.rst' # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('dex', 'dex', 'DesktopEntry Execution', ['Jan Christoph Ebersbach', u'Johannes Löthberg'], 1) ] dex-0.9.0/man/dex.rst000066400000000000000000000046661367122024700144050ustar00rootroot00000000000000dex === Synopsis -------- **dex** [*options*] [*DesktopEntryFile*]... Description ----------- :program:`dex`, DesktopEntry Execution, is a program to generate and execute DesktopEntry files of the Application type. Options ------- -h, --help Show this help message and exit -a, --autostart Autostart programs -c PATH, --create PATH Create a DesktopEntry file for the program at the given path. An optional second argument is used to specify the filename of the created DesktopEntry file, or specify the filename - to print the file to stdout. By default a new file is created with the .desktop file extension. -d, --dry-run Dry run, don't execute any command -e ENVIRONMENT, --environment ENVIRONMENT Specify the Desktop Environment an autostart should be performed for; works only in combination with --autostart -s SEARCHPATHS, --search-paths SEARCHPATHS Colon separated list of paths to search for desktop files, overriding the default search list -t DIRECTORY, --target-directory DIRECTORY Create files in target directory --term TERM The terminal emulator that will be used to run the program if Terminal=true is set in the desktop file, defaults to x-terminal-emulator -w, --wait Block until the program exits --test Perform a self-test -v, --verbose Verbose output -V, --version Display version information Examples -------- Perform an autostart/execute all programs in the autostart folders. :program:`dex -a` Perform an autostart/execute all programs in the specified folders. :program:`dex -a -s /etc/xdg/autostart/:~/.config/autostart/` Preview the programs would be executed in a regular autostart. :program:`dex -ad` Preview the programs would be executed in a GNOME specific autostart. :program:`dex -ad -e GNOME` Create a DesktopEntry for a program in the current directory. :program:`dex -c /usr/bin/skype` Create a DesktopEntry for a programs in autostart directory. :program:`dex -t ~/.config/autostart -c /usr/bin/skype /usr/bin/nm-applet` Execute a single program from command line and enable verbose output. :program:`dex -v skype.desktop` Execute a single program (with Terminal=true in the desktop file) in gnome-terminal. :program:`dex --term gnome-terminal nvim.desktop` Execute a single program and block until it exits. :program:`dex --wait nvim.desktop`