Added <feed-for>.
[Zero2Desktop.git] / zero2desktop
blob719de0bc12de2a0365491222e2735c7f9aaed760
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 import gtk.glade
7 from zeroinstall.injector.policy import Policy
8 from zeroinstall.injector.iface_cache import iface_cache
10 version = '0.1'
11 parser = optparse.OptionParser(usage="usage: %prog [options] [interface]")
12 parser.add_option("-V", "--version", help="display version information", action='store_true')
13 (options, args) = parser.parse_args()
15 if options.version:
16 print "Zero2Desktop (zero-install) " + version
17 print "Copyright (C) 2007 Thomas Leonard"
18 print "This program comes with ABSOLUTELY NO WARRANTY,"
19 print "to the extent permitted by law."
20 print "You may redistribute copies of this program"
21 print "under the terms of the GNU General Public License."
22 print "For more information about these matters, see the file named COPYING."
23 sys.exit(0)
25 zero2desktop_uri = "http://0install.net/2007/interfaces/Zero2Desktop.xml"
27 gladefile = os.path.join(os.path.dirname(__file__), 'zero2desktop.glade')
29 # XDG_UTILS should go at the end, so that the local copy is used first
30 os.environ['PATH'] += ':' + os.environ['XDG_UTILS']
32 template = """[Desktop Entry]
33 # This file was generated by zero2desktop.
34 # See the Zero Install project for details: http://0install.net
35 Type=Application
36 Version=1.0
37 Name=%s
38 Comment=%s
39 Icon=%s
40 Exec=0launch -- %s %%f
41 Categories=Application;%s
42 """
44 URI_LIST = 0
45 UTF_16 = 1
47 RESPONSE_PREV = 0
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)
111 self.window.set_response_sensitive(RESPONSE_PREV, True)
113 def finish():
114 iface = iface_cache.get_interface(uri.get_text())
115 tmpdir = tempfile.mkdtemp(prefix = 'zero2desktop-')
116 try:
117 desktop_name = os.path.join(tmpdir, 'zeroinstall-%s.desktop' % iface.get_name().lower())
118 desktop = file(desktop_name, 'w')
119 desktop.write(template % (iface.get_name(), iface.summary,
120 iface_cache.get_icon_path(iface),
121 iface.uri,
122 category.get_active_text()))
123 desktop.close()
124 status = os.spawnlp(os.P_WAIT, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name)
125 finally:
126 shutil.rmtree(tmpdir)
127 if status:
128 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
129 'Failed to run xdg-desktop-menu (error code %d)' % status)
130 box.run()
131 box.destroy()
132 else:
133 self.window.destroy()
135 def response(box, resp):
136 if resp == RESPONSE_NEXT:
137 iface = uri.get_text()
138 self.window.set_sensitive(False)
139 child = popen2.Popen4(['0launch',
140 '--gui', '--download-only',
141 '--', iface])
142 child.tochild.close()
143 errors = ['']
144 def output_ready(src, cond):
145 got = os.read(src.fileno(), 100)
146 if got:
147 errors[0] += got
148 else:
149 status = child.wait()
150 self.window.set_sensitive(True)
151 if status == 0:
152 update_details_page()
153 nb.next_page()
154 dialog_next.set_property('visible', False)
155 dialog_ok.set_property('visible', True)
156 dialog_ok.grab_focus()
157 else:
158 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
159 'Failed to run 0launch.\n' + errors[0])
160 box.run()
161 box.destroy()
162 return False
163 return True
164 gobject.io_add_watch(child.fromchild,
165 gobject.IO_IN | gobject.IO_HUP,
166 output_ready)
167 elif resp == gtk.RESPONSE_OK:
168 finish()
169 elif resp == RESPONSE_PREV:
170 dialog_next.set_property('visible', True)
171 dialog_ok.set_property('visible', False)
172 dialog_next.grab_focus()
173 nb.prev_page()
174 self.window.set_response_sensitive(RESPONSE_PREV, False)
175 else:
176 box.destroy()
177 self.window.connect('response', response)
179 if len(sys.argv) > 1:
180 self.window.response(RESPONSE_NEXT)
182 main = MainWindow()
183 main.window.connect('destroy', lambda box: gtk.main_quit())
184 gtk.main()