#!/usr/bin/env pythonw # Copyright (C) 2005, Thomas Leonard # Copyright (C) 2010, Anders F Bjorklund # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os, sys from xml.dom import minidom import urllib2 import shutil, os, tempfile, subprocess from logging import info, warn from cStringIO import StringIO from zeroinstall.injector import qdom, selections from zeroinstall.injector import policy, namespaces, model from zeroinstall.injector.config import load_config from optparse import OptionParser parser = OptionParser(usage="usage: %prog [options] [URI [target]]") parser.add_option("--run-in-terminal", help="execute URI in a terminal emulator", action='store_true') parser.add_option("--show-help", help="open URI's help directory", action='store_true') parser.add_option("--no-download", help="don't actually download it", action='store_true') parser.disable_interspersed_args() (options, args) = parser.parse_args() ######################################################################## import Tkinter root = Tkinter.Tk() root.withdraw() def mac_alert(msg): import tkMessageBox tkMessageBox.showwarning('', msg) def mac_report_exception(): import tkMessageBox import traceback tkMessageBox.showerror('', traceback.format_exc()) def mac_chooser(): import tkSimpleDialog return tkSimpleDialog.askstring('', 'Enter the URI of a program available ' 'through the Zero Install Injector system.') def mac_savebox(path, name): import tkFileDialog return tkFileDialog.asksaveasfilename(initialdir=path, initialfile=name) ######################################################################## if os.getenv('JHBUILD_PREFIX'): injector = ['~/.local/bin/jhbuild', 'run', '0launch'] else: injector = ['0launch'] if os.path.exists('/opt/local/bin/png2icns'): png2icns = '/opt/local/bin/png2icns' else: png2icns = '/usr/local/bin/png2icns' ######################################################################## def try_interface(uri): if options.no_download: action = 'select' else: action = 'download' child = subprocess.Popen( [os.environ.get("ZEROINSTALL", "0install"), action, "--xml", "--", uri], stdout = subprocess.PIPE, stderr = subprocess.PIPE) stdout, stderr = child.communicate() code = child.wait() if code: mac_alert('0install download %s failed: error code = %d\n%s' % (uri, code, stderr)) return None return selections.Selections(qdom.parse(StringIO(stdout))) saveas = None if not args: assert not options.run_in_terminal assert not options.show_help while True: uri = mac_chooser() if not uri: sys.exit(1) sels = try_interface(uri) if sels: break else: uri = args[0] if options.run_in_terminal: import terminal terminal.run_in_terminal(args) sys.exit(1) if options.show_help: import help help.show_help(uri) sys.exit(0) sels = try_interface(uri) if not sels: sys.exit(1) assert sels ######################################################################## addapp_uri = "http://afb.users.sourceforge.net/zero-install/interfaces/AddApp.xml" class Policy(policy.Policy): pass class NoMain(Exception): pass class AppLauncher(): def __init__(self, iface_cache, stores, sels): self.uri = sels.interface self.iface_cache = iface_cache self.stores = stores self.icon = None self._icon_tmp = None self.impl = sels.selections[self.uri] if not self.impl: raise Exception("Failed to select an implementation of %s" % self.uri) def identifier(self, uri): """ Convert a feed uri to a suitable app identifier """ from urlparse import urlparse result = urlparse(uri) part1 = result.netloc.split('.') part1.reverse() part2 = os.path.splitext(result.path)[0].lstrip('/').split('/') part2 = [part2[-1]] return ('.'.join(part1 + part2)).lower() def save_to_file(self, path): if not path.endswith('.app'): path += '.app' assert "'" not in self.uri iface = self.iface_cache.get_interface(self.uri) _template = """#!/bin/sh exec %(0launch)s -- '%(iface)s' """ _plist_template = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> <string>%(executable)s</string> <key>CFBundleIconFile</key> <string>%(iconfile)s</string> <key>CFBundleIdentifier</key> <string>%(identifier)s</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>%(name)s</string> </dict> </plist> """ tmpdir = tempfile.mkdtemp(prefix = 'zero2desktop-') try: executable = iface.get_name().lower().replace(os.sep, '-').replace(' ', '') bundle_dir = os.path.join(tmpdir, "%s.app" % executable) os.mkdir(bundle_dir) contents = os.path.join(bundle_dir, 'Contents') os.mkdir(contents) plist_name = os.path.join(contents, 'Info.plist') plist = file(plist_name, 'w') plist.write(_plist_template % {'name': executable, 'executable': executable, 'iconfile': "%s.icns" % executable, 'comment': iface.summary, 'identifier': self.identifier(iface.uri)}) plist.close() os.mkdir(os.path.join(contents, 'MacOS')) script_name = os.path.join(contents, 'MacOS', executable) script = file(script_name, 'w') script.write(_template % {'0launch': " ".join(injector), 'iface': iface.uri }) script.close() os.chmod(script_name, 0755) icon_path = self.icon if icon_path: os.mkdir(os.path.join(contents, 'Resources')) icns_name = os.path.join(contents, 'Resources', "%s.icns" % executable) status = os.spawnlp(os.P_WAIT, png2icns, 'png2icns', icns_name, icon_path) if status: print 'Failed to convert icon (error code %d)' % status shutil.move(bundle_dir, path) finally: #shutil.rmtree(tmpdir) pass def get_appdir(self, impl): impl_path = impl.local_path or self.stores.lookup_any(impl.digests) main = impl.main if not main: return None assert not main.startswith('/'), main return os.path.dirname(os.path.join(impl_path, main)) def is_applet(self, iface): appdir = self.get_appdir(self.impl) if appdir: applet_run = os.path.join(appdir, 'AppletRun') return os.path.exists(applet_run) else: return False def get_icon(self): """Sets self.icon to a filename of an icon for the app (downloading it first), or None.""" iface = self.iface_cache.get_interface(self.uri) path = self.iface_cache.get_icon_path(iface) if path: self.icon = path return def delete_icon(self): """Remove the temporary icon file, if any""" self._icon_tmp = None ######################################################################## saveas = None if len(args) == 2: saveas = args[1] elif len(args) > 2: parser.print_help() sys.exit(1) try: config = load_config() launcher = AppLauncher(config.iface_cache, config.stores, sels) if saveas: if os.path.exists(saveas): raise Exception("'%s' already exists!" % saveas) launcher.get_icon() launcher.save_to_file(saveas) sys.exit(0) default_name = os.path.basename(sels.interface) if default_name.endswith('.xml'): default_name = default_name[:-4] saveas = mac_savebox(os.path.expanduser('~/Desktop'), default_name) if not saveas: sys.exit(1) launcher.get_icon() launcher.save_to_file(saveas) launcher.delete_icon() except NoMain, ex: mac_alert(str(ex)) sys.exit(1) except SystemExit: raise except: mac_report_exception() sys.exit(1)