Added --version option.
[Zero2Desktop.git] / zero2desktop
blobf9c2ba0b56ee0707704a4c983d0f101724450580
1 #!/usr/bin/env python
2 import os, sys, popen2, tempfile, shutil, optparse
3 import pygtk; pygtk.require('2.0')
4 import gtk, gobject
5 from StringIO import StringIO
6 import gtk.glade
8 from zeroinstall.injector.policy import Policy
9 from zeroinstall.injector.iface_cache import iface_cache
11 version = '0.1'
12 parser = optparse.OptionParser(usage="usage: %prog [options] [interface]")
13 parser.add_option("-V", "--version", help="display version information", action='store_true')
14 (options, args) = parser.parse_args()
16 if options.version:
17 print "0publish-gui (zero-install) " + version
18 print "Copyright (C) 2007 Thomas Leonard"
19 print "This program comes with ABSOLUTELY NO WARRANTY,"
20 print "to the extent permitted by law."
21 print "You may redistribute copies of this program"
22 print "under the terms of the GNU General Public License."
23 print "For more information about these matters, see the file named COPYING."
24 sys.exit(0)
26 zero2desktop_uri = "http://0install.net/2007/interfaces/Zero2Desktop.xml"
28 gladefile = os.path.join(os.path.dirname(__file__), 'zero2desktop.glade')
30 # XDG_UTILS should go at the end, so that the local copy is used first
31 os.environ['PATH'] += ':' + os.environ['XDG_UTILS']
33 template = """[Desktop Entry]
34 # This file was generated by zero2desktop.
35 # See the Zero Install project for details: http://0install.net
36 Type=Application
37 Version=1.0
38 Name=%s
39 Comment=%s
40 Icon=%s
41 Exec=0launch -- %s %%f
42 Categories=Application;%s
43 """
45 URI_LIST = 0
46 UTF_16 = 1
48 RESPONSE_NEXT = 1
50 class MainWindow:
51 def __init__(self):
52 tree = gtk.glade.XML(gladefile, 'main')
53 self.window = tree.get_widget('main')
55 def set_uri_ok(uri):
56 text = uri.get_text()
57 self.window.set_response_sensitive(RESPONSE_NEXT, bool(text))
59 drop_uri = tree.get_widget('drop_uri')
60 uri = tree.get_widget('interface_uri')
61 about = tree.get_widget('about')
62 icon = tree.get_widget('icon')
63 category = tree.get_widget('category')
64 dialog_next = tree.get_widget('dialog_next')
65 dialog_ok = tree.get_widget('dialog_ok')
67 if len(sys.argv) > 1:
68 uri.set_text(sys.argv[1])
70 uri.connect('changed', set_uri_ok)
71 set_uri_ok(uri)
73 category.set_active(11)
75 def uri_dropped(eb, drag_context, x, y, selection_data, info, timestamp):
76 if info == UTF_16:
77 import codecs
78 data = codecs.getdecoder('utf16')(selection_data.data)[0]
79 data = data.split('\n', 1)[0].strip()
80 else:
81 data = selection_data.data.split('\n', 1)[0].strip()
82 uri.set_text(data)
83 drag_context.finish(True, False, timestamp)
84 self.window.response(RESPONSE_NEXT)
85 return True
86 drop_uri.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_HIGHLIGHT,
87 [('text/uri-list', 0, URI_LIST),
88 ('text/x-moz-url', 0, UTF_16)],
89 gtk.gdk.ACTION_COPY)
90 drop_uri.connect('drag-data-received', uri_dropped)
92 nb = tree.get_widget('notebook1')
94 def update_details_page():
95 iface = iface_cache.get_interface(uri.get_text())
96 about.set_text('%s - %s' % (iface.get_name(), iface.summary))
97 icon_path = iface_cache.get_icon_path(iface)
98 if icon_path:
99 try:
100 # Icon format must be PNG (to avoid attacks)
101 loader = gtk.gdk.PixbufLoader('png')
102 try:
103 loader.write(file(icon_path).read())
104 finally:
105 loader.close()
106 icon_pixbuf = loader.get_pixbuf()
107 except Exception, ex:
108 print >>sys.stderr, "Failed to load cached PNG icon: %s" % ex
109 else:
110 icon.set_from_pixbuf(icon_pixbuf)
112 def finish():
113 iface = iface_cache.get_interface(uri.get_text())
114 tmpdir = tempfile.mkdtemp(prefix = 'zero2desktop-')
115 try:
116 desktop_name = os.path.join(tmpdir, 'zeroinstall-%s.desktop' % iface.get_name().lower())
117 desktop = file(desktop_name, 'w')
118 desktop.write(template % (iface.get_name(), iface.summary,
119 iface_cache.get_icon_path(iface),
120 iface.uri,
121 category.get_active_text()))
122 desktop.close()
123 status = os.spawnlp(os.P_WAIT, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name)
124 finally:
125 shutil.rmtree(tmpdir)
126 if status:
127 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
128 'Failed to run xdg-desktop-menu (error code %d)' % status)
129 box.run()
130 box.destroy()
131 else:
132 self.window.destroy()
134 def response(box, resp):
135 if resp == RESPONSE_NEXT:
136 iface = uri.get_text()
137 self.window.set_sensitive(False)
138 child = popen2.Popen4(['0launch',
139 '--gui', '--download-only',
140 '--', iface])
141 child.tochild.close()
142 errors = ['']
143 def output_ready(src, cond):
144 got = os.read(src.fileno(), 100)
145 if got:
146 errors[0] += got
147 else:
148 status = child.wait()
149 self.window.set_sensitive(True)
150 if status == 0:
151 update_details_page()
152 nb.next_page()
153 dialog_next.set_property('visible', False)
154 dialog_ok.set_property('visible', True)
155 dialog_ok.grab_focus()
156 else:
157 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
158 'Failed to run 0launch.\n' + errors[0])
159 box.run()
160 box.destroy()
161 return False
162 return True
163 gobject.io_add_watch(child.fromchild,
164 gobject.IO_IN | gobject.IO_HUP,
165 output_ready)
166 elif resp == gtk.RESPONSE_OK:
167 finish()
168 elif resp == 0:
169 nb.previous_page()
170 else:
171 box.destroy()
172 self.window.connect('response', response)
174 if len(sys.argv) > 1:
175 self.window.response(RESPONSE_NEXT)
177 main = MainWindow()
178 main.window.connect('destroy', lambda box: gtk.main_quit())
179 gtk.main()