#!/bin/env python
# vim: set fileencoding=utf-8 :
# Copyright © 2010 Regents of the University of California.
# All Rights Reserved.
#
# Make X11 desktop menu, icon, and mime types with xdg-utils
#
# Usage:
#
# chimera --nogui --silent --script "xdg-setup.py [un]install"
#
from __future__ import with_statement
import sys, os, codecs
verbose = False
system_generated = False
# remember locale codes are frequently different than country codes
localized_chimera = {
'af': u'Chimera', # Afrikaans
'cs': u'Přízrak', # Czech
'da': u'Chiemra', # Danish
'de': u'Chimäre', # German
'el': u'Χίμαιρα', # Greek
'en': u'Chimera', # English
'es': u'Quimera', # Spanish
'fi': u'Kauhukuva', # Finish
'fr': u'Chimère', # French
'hr': u'Himera', # Croatian
#'hu': 'Mesebeli szörny', # Hungarian
'in': u'Angan-angan', # Indonesian
'it': u'Chimera', # Italian
'ja': u'キメラ', # Japanese
'ko': u'키메라', # Korean
'nl': u'Chimera', # Dutch
'no': u'Chimera', # Norwegian
'pl': u'Chimera', # Polish
'pt': u'Quimera', # Portuguese
'ro': u'Himeră', # Romainian
'ru': u'Химера', # Russian
'sr': u'Химера', # Serbian
'sk': u'Prízrak', # Slovak
'sv': u'Chimera', # Swedish
'th': u'ความเพ้อฝัน', # Thai
'tr': u'Kuruntu', # Turkish
'uk': u'Химера', # Ukrainian
'zh': u'嵌合體', # Chinese
}
"""
From Desktop Entry Specification 1.0:
The escape sequences \s, \n, \t, \r, and \\ are supported for values of type string and localestring, meaning ASCII space, newline, tab, carriage return, and backslash, respectively.
Some keys can have multiple values. In such a case, the value of the key is specified as a plural: for example, string(s). The multiple values should be separated by a semicolon. Those keys which have several values should have a semicolon as the trailing character. Semicolons in these values need to be escaped using \;.
"""
def str_quote(text):
result = ""
for ch in text:
if ch == '\n':
result += '\\n'
elif ch == '\t':
result += '\\t'
elif ch == '\r':
result += '\\r'
elif ch == '\\':
result += '\\\\'
elif ch == ';':
result += '\\;'
elif ord(ch) < 32:
continue
else:
result += ch
return result
"""
From Desktop Entry Specification 1.0:
Arguments may be quoted in whole. If an argument contains a reserved
character the argument must be quoted. The rules for quoting of arguments
is also applicable to the executable name or path of the executable
program as provided.
Quoting must be done by enclosing the argument between double quotes and
escaping the double quote character, backtick character ("`"), dollar
sign ("$") and backslash character ("\") by preceding it with an
additional backslash character. Implementations must undo quoting before
expanding field codes and before passing the argument to the executable
program. Reserved characters are space (" "), tab, newline, double quote,
single quote ("'"), backslash character ("\"), greater-than sign (">"),
less-than sign ("<"), tilde ("~"), vertical bar ("|"), ampersand ("&"),
semicolon (";"), dollar sign ("$"), asterisk ("*"), question mark ("?"),
hash mark ("#"), parenthesis ("(") and (")") and backtick character ("`").
"""
reserved_char = """ \t\n"'\\><~|&;$*?#()`"""
def arg_quote(arg):
has_reserved = any(True for ch in arg if ch in reserved_char)
if not has_reserved:
return arg
result = '"'
for ch in arg:
if ch in '"`$\\':
result += '\\'
result += ch
result += '"'
return result
"""
Shiny new file type
"""
class MimeInfo:
IDENT = ' '
class Nested:
def __init__(self, plist, tag, args=''):
self.plist = plist
self.tag = tag
self.args = args
def __enter__(self):
p = self.plist
p.output.write("%s<%s%s>\n" % (p.IDENT * p.level, self.tag, self.args))
p.level += 1
def __exit__(self, exc_type, exc_value, traceback):
p = self.plist
p.level -= 1
p.output.write("%s%s>\n" % (p.IDENT * p.level, self.tag))
def __init__(self, output=sys.stdout):
self.level = 0
self.output = output
def __enter__(self):
self.output.write(
"""
""")
self.level = 1
def __exit__(self, exc_type, exc_value, traceback):
self.output.write("\n")
self.output.close()
def xml_comment(self, text):
self.output.write("%s\n"
% (self.IDENT * self.level, text))
def comment(self, text):
if isinstance(text, unicode):
text = text.encode("utf-8")
self.output.write("%s%s\n"
% (self.IDENT * self.level, text))
def glob(self, pattern):
if isinstance(pattern, unicode):
pattern = pattern.encode("utf-8")
self.output.write('%s\n'
% (self.IDENT * self.level, pattern))
def type(self, mimetype):
#
return self.Nested(self, "mime-type", args=' type="%s"' % mimetype)
def desktop_comment(f, text):
f.write("# %s\n" % text)
def desktop_group(f, name):
# assert '[' not in name and ']' not in name
f.write("[%s]\n" % name)
def desktop_boolean(f, tag, value):
f.write("%s=%s\n" % (tag, "true" if value else "false"))
def desktop_numeric(f, tag, value, format="%f"):
f.write(("%s=" + format + "\n") % (tag, value))
def desktop_string(f, tag, value):
f.write("%s=%s\n" % (tag, str_quote(value)))
def desktop_stringlist(f, tag, values):
f.write("%s=%s;\n" % (tag, ';'.join(str_quote(v) for v in values)))
def make_desktop(name, version, mime_types):
if verbose:
print "generating", name
with codecs.open(name, mode='wt', encoding='utf-8') as f:
if version.endswith('s'):
version = version[0:-1] + " snapshot"
elif version.endswith('rc'):
version = version[0:-2] + " release candidate"
chimera_dir = os.getenv('CHIMERA')
desktop_group(f, "Desktop Entry")
year = chimera.version.version.split()[5].split('-')[0]
desktop_comment(f, u"Copyright \u00A9 %s Regents of the University of California. All Rights Reserved." % year)
desktop_string(f, "Type", "Application")
desktop_numeric(f, "Version", 1.0, "%.1f")
desktop_string(f, "Encoding", "UTF-8")
desktop_string(f, "Name", "UCSF Chimera %s" % version)
locales = list(localized_chimera.keys())
locales.sort()
for lo in locales:
desktop_string(f, "Name[%s]" % lo,
"UCSF %s %s" % (localized_chimera[lo], version))
desktop_string(f, "GenericName", "Molecular Visualization")
desktop_string(f, "Comment",
"A extensible molecular modeling system, http://www.cgl.ucsf.edu/chimera/")
desktop_string(f, "Icon", "UCSF-Chimera")
desktop_stringlist(f, "Categories", [
"Education", "Science", "Biology", "Chemistry",
"Graphics", "3DGraphics", "DataVisualization"])
desktop_stringlist(f, "MimeType", mime_types)
if '=' in chimera_dir:
print >> sys.stderr, "warning: '=' found in path to chimera"
else:
desktop_string(f, "Exec",
"%s -- %%F" % arg_quote(chimera_dir + "/bin/chimera"))
s = os.stat(name)
os.chmod(name, s.st_mode | 0o555) # make executable
def make_mimeinfo(name, file_info):
if verbose:
print "generating", name
mi = MimeInfo(codecs.open(name, mode='wt', encoding='utf-8'))
with mi:
year = chimera.version.version.split()[5].split('-')[0]
mi.xml_comment(u"Copyright \u00A9 %s Regents of the University of California. All Rights Reserved." % year)
for t in file_info.types():
extensions = file_info.extensions(t)
mimeTypes = file_info.mimeType(t)
if not extensions or not mimeTypes:
continue
for m in mimeTypes:
with mi.type(m):
mi.comment(file_info.category(t))
for e in extensions:
mi.glob(e)
def add_xdg_utils_to_path():
if verbose:
print "adding xdg scripts to end of path"
path = os.getenv("PATH")
path += ":%s/share/xdg-utils" % os.getenv("CHIMERA")
os.environ["PATH"] = path
import subprocess
def install_icons(mime_types):
if verbose:
print "installing icons"
image_dir = os.getenv("CHIMERA") + "/share/chimera/images"
cmd = [
'xdg-icon-resource', 'install',
'--context', 'apps',
'--size', '48',
'%s/chimera48.png' % image_dir,
'UCSF-Chimera'
]
subprocess.call(cmd)
if "chimera/x-pdb" in mime_types:
cmd = [
'xdg-icon-resource', 'install',
'--context', 'mimetypes',
'--size', '48',
'%s/pdb48.png' % image_dir,
'chemical-x-pdb'
]
subprocess.call(cmd)
if "chimera/x-mol2" in mime_types:
cmd = [
'xdg-icon-resource', 'install',
'--context', 'mimetypes',
'--size', '48',
'%s/pdb48.png' % image_dir,
'chemical-x-mol2'
]
subprocess.call(cmd)
def install_desktop_menu(desktop):
if verbose:
print "installing desktop menu"
cmd = [ 'xdg-desktop-menu', 'install', desktop ]
subprocess.call(cmd)
def install_desktop_icon(desktop):
if verbose:
print "installing desktop icon"
cmd = [ 'xdg-desktop-icon', 'install', desktop ]
subprocess.call(cmd)
def uninstall_desktop_menu(desktop):
if verbose:
print "uninstalling desktop menu"
cmd = [ 'xdg-desktop-menu', 'uninstall', desktop ]
subprocess.call(cmd)
def uninstall_desktop_icon(desktop):
if verbose:
print "uninstalling desktop icon"
cmd = [ 'xdg-desktop-icon', 'uninstall', desktop ]
subprocess.call(cmd)
def install_mimeinfo(mimetypes):
if verbose:
print "installing MIME info"
cmd = [ 'xdg-mime', 'install', mimetypes ]
subprocess.call(cmd)
def uninstall_mimeinfo(mimetypes):
if verbose:
print "uninstalling MIME info"
cmd = [ 'xdg-mime', 'uninstall', mimetypes ]
subprocess.call(cmd)
if __name__ == '__main__':
USAGE = "%s [-v] generate|install|reinstall|uninstall"
# temporary hack until we have a way to run chimera and non-chimera binaries
del os.environ["LD_LIBRARY_PATH"]
name = "UCSF-Chimera"
import platform
if sys.maxsize > 2 ** 32:
name += '64'
import chimera
version = chimera.version.releaseVersion()
import getopt
try:
opts, args = getopt.getopt(sys.argv[1:], "v", ["build"])
except getopt.error, message:
print >> sys.stderr, "%s: %s" % (sys.argv[0], message)
print >> sys.stderr, USAGE % sys.argv[0]
raise SystemExit, 2
for opt, arg in opts:
if opt == "-v":
verbose = True
elif opt == "--build":
version = 'build'
name += '-' + version
if len(args) != 1:
print >> sys.stderr, USAGE % sys.argv[0]
raise SystemExit, 2
command = args[0]
if os.getuid() == 0:
# make sure other users can read .desktop files
if os.umask(0o22) != 0o22 and verbose:
print "temporarily changed umask to 0o22"
dir = os.getenv("CHIMERA")
desktop = "%s/%s.desktop" % (dir, name)
mimeinfo = "%s/%s.xml" % (dir, name)
system_generated = os.path.exists(desktop) and os.path.exists(mimeinfo)
if not system_generated and os.getuid() != 0:
dir = os.getenv("HOME") + "/.chimera"
try:
os.mkdir(dir)
except OSError, e:
if e.errno != 17:
print >> sys.stderr, "Error: Unable to create", dir
raise SystemExit, 2
desktop = "%s/%s.desktop" % (dir, name)
mimeinfo = "%s/%s.xml" % (dir, name)
if not system_generated or command == "reinstall":
mime_types = []
for t in chimera.fileInfo.types():
mt = chimera.fileInfo.mimeType(t)
if isinstance(mt, (list, tuple)):
mime_types.extend(mt)
elif mt:
mime_types.append(mt)
mime_types.sort()
if command == 'generate':
if system_generated:
if verbose:
print "already generated"
else:
make_desktop(desktop, version, mime_types)
make_mimeinfo(mimeinfo, chimera.fileInfo)
elif command in ('install', 'reinstall'):
if not system_generated and command == 'install':
make_desktop(desktop, version, mime_types)
make_mimeinfo(mimeinfo, chimera.fileInfo)
add_xdg_utils_to_path()
if not system_generated or command == "reinstall":
install_mimeinfo(mimeinfo)
install_icons(mime_types)
install_desktop_menu(desktop)
if os.getuid() != 0:
install_desktop_icon(desktop)
elif command == 'uninstall':
add_xdg_utils_to_path()
if os.getuid() != 0:
uninstall_desktop_icon(desktop)
if os.getuid() == 0 or not system_generated:
uninstall_desktop_menu(desktop)
uninstall_mimeinfo(mimeinfo)
os.remove(desktop)
os.remove(mimeinfo)
else:
print >> sys.stderr, "Error: unknown command:", command
raise SystemExit, 2
raise SystemExit, 0