Added the ability to remove keys from the trusted list, using the Preferences
[zeroinstall.git] / zeroinstall / 0launch-gui / preferences.py
blobdbee355a2fe11dddb425c420e90a228904bd7c9e
1 import gtk
2 from logging import warn
3 import os, sys
4 import help_box
5 from gui import policy
6 from dialog import Dialog, MixedButton, frame
7 from zeroinstall.injector.model import network_levels
8 from zeroinstall.injector import trust, gpg
9 from freshness import freshness_levels, Freshness
10 from sets import Set
12 tips = gtk.Tooltips()
14 SHOW_CACHE = 0
16 class Preferences(Dialog):
17 def __init__(self):
18 Dialog.__init__(self)
19 self.set_title('Zero Install Preferences')
21 self.connect('destroy', lambda w: self.destroyed())
23 content = gtk.VBox(False, 2)
24 content.set_border_width(8)
25 self.vbox.pack_start(content, True, True, 0)
27 vbox = gtk.VBox(False, 0)
28 frame(content, 'Policy settings', vbox)
30 # Network use
31 hbox = gtk.HBox(False, 2)
32 vbox.pack_start(hbox, False, True, 0)
33 hbox.set_border_width(4)
35 eb = gtk.EventBox() # For the tooltip
36 network = gtk.combo_box_new_text()
37 eb.add(network)
38 for level in network_levels:
39 network.append_text(level.capitalize())
40 network.set_active(list(network_levels).index(policy.network_use))
41 hbox.pack_start(gtk.Label('Network use:'), False, True, 0)
42 hbox.pack_start(eb, True, True, 2)
43 def set_network_use(combo):
44 policy.network_use = network_levels[network.get_active()]
45 policy.save_config()
46 policy.recalculate()
47 network.connect('changed', set_network_use)
48 tips.set_tip(eb, _('This controls whether the injector will always try to '
49 'run the best version, downloading it if needed, or whether it will prefer '
50 'to run an older version that is already on your machine.'))
52 hbox.show_all()
54 # Freshness
55 hbox = gtk.HBox(False, 2)
56 vbox.pack_start(hbox, False, True, 0)
57 hbox.set_border_width(4)
59 times = [x.time for x in freshness_levels]
60 if policy.freshness not in times:
61 freshness_levels.append(Freshness(policy.freshness,
62 '%d seconds' % policy.freshness))
63 times.append(policy.freshness)
64 eb = gtk.EventBox() # For the tooltip
65 freshness = gtk.combo_box_new_text()
66 eb.add(freshness)
67 for level in freshness_levels:
68 freshness.append_text(str(level))
69 freshness.set_active(times.index(policy.freshness))
70 hbox.pack_start(gtk.Label('Freshness:'), False, True, 0)
71 hbox.pack_start(eb, True, True, 2)
72 def set_freshness(combo):
73 policy.freshness = freshness_levels[freshness.get_active()].time
74 policy.save_config()
75 policy.recalculate()
76 freshness.connect('changed', set_freshness)
77 tips.set_tip(eb, _('Sets how often the injector will check for new versions.'))
79 stable_toggle = gtk.CheckButton('Help test new versions')
80 vbox.pack_start(stable_toggle, False, True, 0)
81 tips.set_tip(stable_toggle,
82 "Try out new versions as soon as they are available, instead of "
83 "waiting for them to be marked as 'stable'. "
84 "This sets the default policy. Click on 'Interface Properties...' "
85 "to set the policy for an individual interface.")
86 stable_toggle.set_active(policy.help_with_testing)
87 def toggle_stability(toggle):
88 policy.help_with_testing = toggle.get_active()
89 policy.save_config()
90 policy.recalculate()
91 stable_toggle.connect('toggled', toggle_stability)
93 # Keys
94 if hasattr(gpg, 'Key'):
95 keys_area = KeyList()
96 else:
97 keys_area = gtk.Label('Sorry, this feature requires 0launch >= 0.27')
98 keys_area.set_alignment(0, 0)
99 frame(content, 'Security', keys_area, expand = True)
101 # Responses
103 self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
104 self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
106 self.set_default_response(gtk.RESPONSE_CLOSE)
107 self.default_widget.grab_focus()
109 def response(dialog, resp):
110 import download_box
111 if resp in (gtk.RESPONSE_CLOSE, gtk.RESPONSE_DELETE_EVENT):
112 self.destroy()
113 elif resp == gtk.RESPONSE_HELP:
114 gui_help.display()
115 self.connect('response', response)
117 self.set_default_size(-1, gtk.gdk.screen_height() / 3)
118 self.vbox.show_all()
120 def destroyed(self):
121 global preferences_box
122 preferences_box = None
124 class KeyList(gtk.VBox):
125 def __init__(self):
126 gtk.VBox.__init__(self, False, 0)
128 label = gtk.Label('')
129 label.set_markup('<i>You have said that you trust these keys to sign software updates.</i>')
130 label.set_padding(4, 4)
131 label.set_alignment(0, 0.5)
132 self.pack_start(label, False, True, 0)
134 trusted_keys = gtk.TreeStore(str, object)
135 tv = gtk.TreeView(trusted_keys)
136 tc = gtk.TreeViewColumn('Trusted keys', gtk.CellRendererText(), text = 0)
137 tv.append_column(tc)
138 swin = gtk.ScrolledWindow(None, None)
139 swin.set_shadow_type(gtk.SHADOW_IN)
140 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
141 swin.add(tv)
142 trust.trust_db.ensure_uptodate()
143 trust.trust_db.watchers.append(lambda: self.update_keys(trusted_keys))
144 self.pack_start(swin, True, True, 0)
145 self.update_keys(trusted_keys)
147 def remove_key(fingerprint, domain):
148 trust.trust_db.untrust_key(fingerprint, domain)
149 trust.trust_db.notify()
151 def trusted_keys_button_press(tv, bev):
152 if bev.type == gtk.gdk.BUTTON_PRESS and bev.button == 3:
153 pos = tv.get_path_at_pos(int(bev.x), int(bev.y))
154 if not pos:
155 return False
156 path, col, x, y = pos
157 if len(path) != 2:
158 return False
160 domain = trusted_keys[path[:-1]][0]
161 key = trusted_keys[path][1]
163 menu = gtk.Menu()
165 item = gtk.MenuItem('Remove key for "%s"' % key.get_short_name())
166 item.connect('activate',
167 lambda item, fp = key.fingerprint, d = domain: remove_key(fp, d))
168 item.show()
169 menu.append(item)
171 menu.popup(None, None, None, bev.button, bev.time)
172 return True
173 return False
174 tv.connect('button-press-event', trusted_keys_button_press)
176 def update_keys(self, trusted_keys):
177 trusted_keys.clear()
178 domains = {}
179 for fingerprint in trust.trust_db.keys:
180 key = gpg.load_key(fingerprint)
181 for domain in trust.trust_db.keys[fingerprint]:
182 if domain not in domains:
183 domains[domain] = Set()
184 domains[domain].add(key)
185 for domain in domains:
186 iter = trusted_keys.append(None, [domain, None])
187 for key in domains[domain]:
188 trusted_keys.append(iter, [key.name, key])
190 preferences_box = None
191 def show_preferences():
192 global preferences_box
193 if preferences_box is not None:
194 preferences_box.present()
195 else:
196 preferences_box = Preferences()
197 preferences_box.show()
199 gui_help = help_box.HelpBox("Zero Install Preferences Help",
200 ('Overview', """
202 There are three ways to control which implementations are chosen. You can adjust the \
203 network policy and the overall stability policy, which affect all interfaces, or you \
204 can edit the policy of individual interfaces."""),
206 ('Network use', """
207 The 'Network use' option controls how the injector uses the network. If off-line, \
208 the network is not used at all. If 'Minimal' is selected then the injector will use \
209 the network if needed, but only if it has no choice. It will run an out-of-date \
210 version rather than download a newer one. If 'Full' is selected, the injector won't \
211 worry about how much it downloads, but will always pick the version it thinks is best."""),
213 ('Freshness', """
214 The feed files, which provide the information about which versions are \
215 available, are also cached. To update them, click on 'Refresh all now'. You can also \
216 get the injector to check for new versions automatically from time to time using \
217 the Freshness setting."""),
219 ('Help test new versions', """
220 The overall stability policy can either be to prefer stable versions, or to help test \
221 new versions. Choose whichever suits you. Since different programmers have different \
222 ideas of what 'stable' means, you may wish to override this on a per-interface basis.
224 To set the policy for an interface individually, select it in the main window and \
225 click on 'Interface Properties'. See that dialog's help text for more information."""),
227 ('Security', """
228 This section lists all keys which you currently trust. When fetching a new program or \
229 updates for an existing one, the feed must be signed by one of these keys. If not, \
230 you will be prompted to confirm that you trust the new key, and it will then be added \
231 to this list. To remove a key, right-click on it and choose 'Remove' from the menu."""),