pax_global_header 0000666 0000000 0000000 00000000064 13671220247 0014516 g ustar 00root root 0000000 0000000 52 comment=617bb29c0e6dfcb33fb7330a8b88e34461c2f905
dex-0.9.0/ 0000775 0000000 0000000 00000000000 13671220247 0012304 5 ustar 00root root 0000000 0000000 dex-0.9.0/CHANGELOG.md 0000664 0000000 0000000 00000001500 13671220247 0014111 0 ustar 00root root 0000000 0000000 # 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/LICENSE 0000664 0000000 0000000 00000001412 13671220247 0013307 0 ustar 00root root 0000000 0000000 dex (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/Makefile 0000664 0000000 0000000 00000002051 13671220247 0013742 0 ustar 00root root 0000000 0000000 NAME = 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.rst 0000664 0000000 0000000 00000016140 13671220247 0013775 0 ustar 00root root 0000000 0000000 dex
===
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/dex 0000775 0000000 0000000 00000056270 13671220247 0013024 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 13671220247 0013057 5 ustar 00root root 0000000 0000000 dex-0.9.0/man/conf.py 0000664 0000000 0000000 00000000550 13671220247 0014356 0 ustar 00root root 0000000 0000000 #!/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.rst 0000664 0000000 0000000 00000004666 13671220247 0014405 0 ustar 00root root 0000000 0000000 dex
===
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`