Added 0desktop command for desktop integration.
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / gtkui / addbox.py
blobc09b7171a890bfdc8d0b4f0c41d280b22d6d92c6
1 """A GTK dialog which lets the user add a new application to their desktop."""
2 # Copyright (C) 2008, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 import os, sys
6 import gtk, gobject
7 import gtk.glade
8 import tempfile, shutil
10 from zeroinstall import SafeException
11 from zeroinstall.injector.namespaces import XMLNS_IFACE
12 from zeroinstall.injector.iface_cache import iface_cache
14 _URI_LIST = 0
15 _UTF_16 = 1
17 _RESPONSE_PREV = 0
18 _RESPONSE_NEXT = 1
20 class AddBox:
21 """A dialog box which prompts the user to choose the program to be added."""
22 def __init__(self, interface_uri = None):
23 gladefile = os.path.join(os.path.dirname(__file__), 'desktop.glade')
25 widgets = gtk.glade.XML(gladefile, 'main')
26 self.window = widgets.get_widget('main')
28 def set_uri_ok(uri):
29 text = uri.get_text()
30 self.window.set_response_sensitive(_RESPONSE_NEXT, bool(text))
32 drop_uri = widgets.get_widget('drop_uri')
33 uri = widgets.get_widget('interface_uri')
34 about = widgets.get_widget('about')
35 icon = widgets.get_widget('icon')
36 category = widgets.get_widget('category')
37 dialog_next = widgets.get_widget('dialog_next')
38 dialog_ok = widgets.get_widget('dialog_ok')
40 if interface_uri:
41 uri.set_text(interface_uri)
43 uri.connect('changed', set_uri_ok)
44 set_uri_ok(uri)
46 category.set_active(11)
48 def uri_dropped(eb, drag_context, x, y, selection_data, info, timestamp):
49 if info == _UTF_16:
50 import codecs
51 data = codecs.getdecoder('utf16')(selection_data.data)[0]
52 data = data.split('\n', 1)[0].strip()
53 else:
54 data = selection_data.data.split('\n', 1)[0].strip()
55 uri.set_text(data)
56 drag_context.finish(True, False, timestamp)
57 self.window.response(_RESPONSE_NEXT)
58 return True
59 self.window.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_HIGHLIGHT,
60 [('text/uri-list', 0, _URI_LIST),
61 ('text/x-moz-url', 0, _UTF_16)],
62 gtk.gdk.ACTION_COPY)
63 self.window.connect('drag-data-received', uri_dropped)
65 nb = widgets.get_widget('notebook1')
67 def update_details_page():
68 iface = iface_cache.get_interface(uri.get_text())
69 about.set_text('%s - %s' % (iface.get_name(), iface.summary))
70 icon_path = iface_cache.get_icon_path(iface)
71 if icon_path:
72 try:
73 # Icon format must be PNG (to avoid attacks)
74 loader = gtk.gdk.PixbufLoader('png')
75 try:
76 loader.write(file(icon_path).read())
77 finally:
78 loader.close()
79 icon_pixbuf = loader.get_pixbuf()
80 except Exception, ex:
81 print >>sys.stderr, "Failed to load cached PNG icon: %s" % ex
82 else:
83 icon.set_from_pixbuf(icon_pixbuf)
85 feed_category = None
86 for meta in iface.get_metadata(XMLNS_IFACE, 'category'):
87 feed_category = meta.content
88 break
89 if feed_category:
90 i = 0
91 for row in category.get_model():
92 if row[0].lower() == feed_category.lower():
93 category.set_active(i)
94 break
95 i += 1
96 self.window.set_response_sensitive(_RESPONSE_PREV, True)
98 def finish():
99 import xdgutils
100 iface = iface_cache.get_interface(uri.get_text())
102 try:
103 icon_path = iface_cache.get_icon_path(iface)
104 xdgutils.add_to_menu(iface, icon_path, category.get_active_text())
105 except SafeException, ex:
106 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, str(ex))
107 box.run()
108 box.destroy()
109 else:
110 self.window.destroy()
112 def response(box, resp):
113 if resp == _RESPONSE_NEXT:
114 iface = uri.get_text()
115 self.window.set_sensitive(False)
116 self.set_keep_above(False)
117 import popen2
118 child = popen2.Popen4(['0launch',
119 '--gui', '--download-only',
120 '--', iface])
121 child.tochild.close()
122 errors = ['']
123 def output_ready(src, cond):
124 got = os.read(src.fileno(), 100)
125 if got:
126 errors[0] += got
127 else:
128 status = child.wait()
129 self.window.set_sensitive(True)
130 self.set_keep_above(True)
131 if status == 0:
132 update_details_page()
133 nb.next_page()
134 dialog_next.set_property('visible', False)
135 dialog_ok.set_property('visible', True)
136 dialog_ok.grab_focus()
137 else:
138 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
139 'Failed to run 0launch.\n' + errors[0])
140 box.run()
141 box.destroy()
142 return False
143 return True
144 gobject.io_add_watch(child.fromchild,
145 gobject.IO_IN | gobject.IO_HUP,
146 output_ready)
147 elif resp == gtk.RESPONSE_OK:
148 finish()
149 elif resp == _RESPONSE_PREV:
150 dialog_next.set_property('visible', True)
151 dialog_ok.set_property('visible', False)
152 dialog_next.grab_focus()
153 nb.prev_page()
154 self.window.set_response_sensitive(_RESPONSE_PREV, False)
155 else:
156 box.destroy()
157 self.window.connect('response', response)
159 if len(sys.argv) > 1:
160 self.window.response(_RESPONSE_NEXT)
162 def set_keep_above(self, above):
163 if hasattr(self.window, 'set_keep_above'):
164 # This isn't very nice, but GNOME defaults to
165 # click-to-raise and in that mode drag-and-drop
166 # is useless without this...
167 self.window.set_keep_above(above)