2 import os
, sys
, popen2
, tempfile
, shutil
, optparse
3 import pygtk
; pygtk
.require('2.0')
7 from zeroinstall
.injector
.policy
import Policy
8 from zeroinstall
.injector
.iface_cache
import iface_cache
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()
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."
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
40 Exec=0launch -- %s %%f
41 Categories=Application;%s
52 tree
= gtk
.glade
.XML(gladefile
, 'main')
53 self
.window
= tree
.get_widget('main')
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')
68 uri
.set_text(sys
.argv
[1])
70 uri
.connect('changed', set_uri_ok
)
73 category
.set_active(11)
75 def uri_dropped(eb
, drag_context
, x
, y
, selection_data
, info
, timestamp
):
78 data
= codecs
.getdecoder('utf16')(selection_data
.data
)[0]
79 data
= data
.split('\n', 1)[0].strip()
81 data
= selection_data
.data
.split('\n', 1)[0].strip()
83 drag_context
.finish(True, False, timestamp
)
84 self
.window
.response(RESPONSE_NEXT
)
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
)],
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
)
100 # Icon format must be PNG (to avoid attacks)
101 loader
= gtk
.gdk
.PixbufLoader('png')
103 loader
.write(file(icon_path
).read())
106 icon_pixbuf
= loader
.get_pixbuf()
107 except Exception, ex
:
108 print >>sys
.stderr
, "Failed to load cached PNG icon: %s" % ex
110 icon
.set_from_pixbuf(icon_pixbuf
)
111 self
.window
.set_response_sensitive(RESPONSE_PREV
, True)
114 iface
= iface_cache
.get_interface(uri
.get_text())
115 tmpdir
= tempfile
.mkdtemp(prefix
= 'zero2desktop-')
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
),
122 category
.get_active_text()))
124 status
= os
.spawnlp(os
.P_WAIT
, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name
)
126 shutil
.rmtree(tmpdir
)
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
)
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',
142 child
.tochild
.close()
144 def output_ready(src
, cond
):
145 got
= os
.read(src
.fileno(), 100)
149 status
= child
.wait()
150 self
.window
.set_sensitive(True)
152 update_details_page()
154 dialog_next
.set_property('visible', False)
155 dialog_ok
.set_property('visible', True)
156 dialog_ok
.grab_focus()
158 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
,
159 'Failed to run 0launch.\n' + errors
[0])
164 gobject
.io_add_watch(child
.fromchild
,
165 gobject
.IO_IN | gobject
.IO_HUP
,
167 elif resp
== gtk
.RESPONSE_OK
:
169 elif resp
== RESPONSE_PREV
:
170 dialog_next
.set_property('visible', True)
171 dialog_ok
.set_property('visible', False)
172 dialog_next
.grab_focus()
174 self
.window
.set_response_sensitive(RESPONSE_PREV
, False)
177 self
.window
.connect('response', response
)
179 if len(sys
.argv
) > 1:
180 self
.window
.response(RESPONSE_NEXT
)
183 main
.window
.connect('destroy', lambda box
: gtk
.main_quit())