Use subprocess instead of deprecated popen2 module
[zeroinstall.git] / zeroinstall / gtkui / addbox.py
blobe235bea09346c7ab35deb21452895307e313dd08
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
9 from zeroinstall import SafeException
10 from zeroinstall.injector import model
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')
27 self.set_keep_above(True)
29 def set_uri_ok(uri):
30 text = uri.get_text()
31 self.window.set_response_sensitive(_RESPONSE_NEXT, bool(text))
33 uri = widgets.get_widget('interface_uri')
34 about = widgets.get_widget('about')
35 icon_widget = 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 if self._sanity_check(data):
56 uri.set_text(data)
57 drag_context.finish(True, False, timestamp)
58 self.window.response(_RESPONSE_NEXT)
59 return True
60 self.window.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_HIGHLIGHT,
61 [('text/uri-list', 0, _URI_LIST),
62 ('text/x-moz-url', 0, _UTF_16)],
63 gtk.gdk.ACTION_COPY)
64 self.window.connect('drag-data-received', uri_dropped)
66 nb = widgets.get_widget('notebook1')
68 def update_details_page():
69 iface = iface_cache.get_interface(model.canonical_iface_uri(uri.get_text()))
70 about.set_text('%s - %s' % (iface.get_name(), iface.summary))
71 icon_path = iface_cache.get_icon_path(iface)
72 from zeroinstall.gtkui import icon
73 icon_pixbuf = icon.load_icon(icon_path)
74 if icon_pixbuf:
75 icon_widget.set_from_pixbuf(icon_pixbuf)
77 feed_category = None
78 for meta in iface.get_metadata(XMLNS_IFACE, 'category'):
79 feed_category = meta.content
80 break
81 if feed_category:
82 i = 0
83 for row in category.get_model():
84 if row[0].lower() == feed_category.lower():
85 category.set_active(i)
86 break
87 i += 1
88 self.window.set_response_sensitive(_RESPONSE_PREV, True)
90 def finish():
91 import xdgutils
92 iface = iface_cache.get_interface(model.canonical_iface_uri(uri.get_text()))
94 try:
95 icon_path = iface_cache.get_icon_path(iface)
96 xdgutils.add_to_menu(iface, icon_path, category.get_active_text())
97 except SafeException, ex:
98 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, str(ex))
99 box.run()
100 box.destroy()
101 else:
102 self.window.destroy()
104 def response(box, resp):
105 if resp == _RESPONSE_NEXT:
106 iface = uri.get_text()
107 if not self._sanity_check(iface):
108 return
109 self.window.set_sensitive(False)
110 self.set_keep_above(False)
111 import subprocess
112 child = subprocess.Popen(['0launch',
113 '--gui', '--download-only',
114 '--', iface],
115 stdout = subprocess.PIPE,
116 stderr = subprocess.STDOUT)
117 errors = ['']
118 def output_ready(src, cond):
119 got = os.read(src.fileno(), 100)
120 if got:
121 errors[0] += got
122 else:
123 status = child.wait()
124 self.window.set_sensitive(True)
125 self.set_keep_above(True)
126 if status == 0:
127 update_details_page()
128 nb.next_page()
129 dialog_next.set_property('visible', False)
130 dialog_ok.set_property('visible', True)
131 dialog_ok.grab_focus()
132 else:
133 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
134 'Failed to run 0launch.\n' + errors[0])
135 box.run()
136 box.destroy()
137 return False
138 return True
139 gobject.io_add_watch(child.stdout,
140 gobject.IO_IN | gobject.IO_HUP,
141 output_ready)
142 elif resp == gtk.RESPONSE_OK:
143 finish()
144 elif resp == _RESPONSE_PREV:
145 dialog_next.set_property('visible', True)
146 dialog_ok.set_property('visible', False)
147 dialog_next.grab_focus()
148 nb.prev_page()
149 self.window.set_response_sensitive(_RESPONSE_PREV, False)
150 else:
151 box.destroy()
152 self.window.connect('response', response)
154 if len(sys.argv) > 1:
155 self.window.response(_RESPONSE_NEXT)
157 def set_keep_above(self, above):
158 if hasattr(self.window, 'set_keep_above'):
159 # This isn't very nice, but GNOME defaults to
160 # click-to-raise and in that mode drag-and-drop
161 # is useless without this...
162 self.window.set_keep_above(above)
164 def _sanity_check(self, uri):
165 if uri.endswith('.tar.bz2') or \
166 uri.endswith('.tar.gz') or \
167 uri.endswith('.exe') or \
168 uri.endswith('.rpm') or \
169 uri.endswith('.deb') or \
170 uri.endswith('.tgz'):
171 box = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
172 "This URI (%s) looks like an archive, not a Zero Install feed. Make sure you're using the feed link!" % uri)
173 box.run()
174 box.destroy()
175 return False
176 return True