2 import os
, sys
, popen2
, tempfile
, shutil
, optparse
3 import pygtk
; pygtk
.require('2.0')
7 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
8 from zeroinstall
.injector
.policy
import Policy
9 from zeroinstall
.injector
.iface_cache
import iface_cache
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()
17 print "Zero2Desktop (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."
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
40 Exec=0launch -- %s %%f
41 Categories=Application;%s
44 icon_template
= """Icon=%s
55 tree
= gtk
.glade
.XML(gladefile
, 'main')
56 self
.window
= tree
.get_widget('main')
58 self
.set_keep_above(True)
62 self
.window
.set_response_sensitive(RESPONSE_NEXT
, bool(text
))
64 drop_uri
= tree
.get_widget('drop_uri')
65 uri
= tree
.get_widget('interface_uri')
66 about
= tree
.get_widget('about')
67 icon
= tree
.get_widget('icon')
68 category
= tree
.get_widget('category')
69 dialog_next
= tree
.get_widget('dialog_next')
70 dialog_ok
= tree
.get_widget('dialog_ok')
73 uri
.set_text(sys
.argv
[1])
75 uri
.connect('changed', set_uri_ok
)
78 category
.set_active(11)
80 def uri_dropped(eb
, drag_context
, x
, y
, selection_data
, info
, timestamp
):
83 data
= codecs
.getdecoder('utf16')(selection_data
.data
)[0]
84 data
= data
.split('\n', 1)[0].strip()
86 data
= selection_data
.data
.split('\n', 1)[0].strip()
88 drag_context
.finish(True, False, timestamp
)
89 self
.window
.response(RESPONSE_NEXT
)
91 drop_uri
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION | gtk
.DEST_DEFAULT_DROP | gtk
.DEST_DEFAULT_HIGHLIGHT
,
92 [('text/uri-list', 0, URI_LIST
),
93 ('text/x-moz-url', 0, UTF_16
)],
95 drop_uri
.connect('drag-data-received', uri_dropped
)
97 nb
= tree
.get_widget('notebook1')
99 def update_details_page():
100 iface
= iface_cache
.get_interface(uri
.get_text())
101 about
.set_text('%s - %s' % (iface
.get_name(), iface
.summary
))
102 icon_path
= iface_cache
.get_icon_path(iface
)
105 # Icon format must be PNG (to avoid attacks)
106 loader
= gtk
.gdk
.PixbufLoader('png')
108 loader
.write(file(icon_path
).read())
111 icon_pixbuf
= loader
.get_pixbuf()
112 except Exception, ex
:
113 print >>sys
.stderr
, "Failed to load cached PNG icon: %s" % ex
115 icon
.set_from_pixbuf(icon_pixbuf
)
118 for meta
in iface
.get_metadata(XMLNS_IFACE
, 'category'):
119 feed_category
= meta
.content
123 for row
in category
.get_model():
124 if row
[0].lower() == feed_category
.lower():
125 category
.set_active(i
)
128 self
.window
.set_response_sensitive(RESPONSE_PREV
, True)
131 iface
= iface_cache
.get_interface(uri
.get_text())
132 tmpdir
= tempfile
.mkdtemp(prefix
= 'zero2desktop-')
134 desktop_name
= os
.path
.join(tmpdir
, 'zeroinstall-%s.desktop' % iface
.get_name().lower())
135 desktop
= file(desktop_name
, 'w')
136 desktop
.write(template
% (iface
.get_name(), iface
.summary
,
138 category
.get_active_text()))
139 icon_path
= iface_cache
.get_icon_path(iface
)
141 desktop
.write(icon_template
% icon_path
)
143 status
= os
.spawnlp(os
.P_WAIT
, 'xdg-desktop-menu', 'xdg-desktop-menu', 'install', desktop_name
)
145 shutil
.rmtree(tmpdir
)
147 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
,
148 'Failed to run xdg-desktop-menu (error code %d)' % status
)
152 self
.window
.destroy()
154 def response(box
, resp
):
155 if resp
== RESPONSE_NEXT
:
156 iface
= uri
.get_text()
157 self
.window
.set_sensitive(False)
158 self
.set_keep_above(False)
159 child
= popen2
.Popen4(['0launch',
160 '--gui', '--download-only',
162 child
.tochild
.close()
164 def output_ready(src
, cond
):
165 got
= os
.read(src
.fileno(), 100)
169 status
= child
.wait()
170 self
.window
.set_sensitive(True)
171 self
.set_keep_above(True)
173 update_details_page()
175 dialog_next
.set_property('visible', False)
176 dialog_ok
.set_property('visible', True)
177 dialog_ok
.grab_focus()
179 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
,
180 'Failed to run 0launch.\n' + errors
[0])
185 gobject
.io_add_watch(child
.fromchild
,
186 gobject
.IO_IN | gobject
.IO_HUP
,
188 elif resp
== gtk
.RESPONSE_OK
:
190 elif resp
== RESPONSE_PREV
:
191 dialog_next
.set_property('visible', True)
192 dialog_ok
.set_property('visible', False)
193 dialog_next
.grab_focus()
195 self
.window
.set_response_sensitive(RESPONSE_PREV
, False)
198 self
.window
.connect('response', response
)
200 if len(sys
.argv
) > 1:
201 self
.window
.response(RESPONSE_NEXT
)
203 def set_keep_above(self
, above
):
204 if hasattr(self
.window
, 'set_keep_above'):
205 # This isn't very nice, but GNOME defaults to
206 # click-to-raise and in that mode drag-and-drop
207 # is useless without this...
208 self
.window
.set_keep_above(above
)
211 main
.window
.connect('destroy', lambda box
: gtk
.main_quit())