Erik Grinaker is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

erikg / Revelation

Revelation is a password manager for the GNOME desktop, released under the GNU GPL license. It stores all your accounts and passwords in a single, secure place, and gives you access to it through a user-friendly graphical interface.

Clone this repository (size: 2.1 MB): HTTPS / SSH
hg clone https://bitbucket.org/erikg/revelation
hg clone ssh://hg@bitbucket.org/erikg/revelation

Revelation / src / revelation-applet.in

commit
75a53bdb5d20
parent
cf608f40f65b
parent
8a2b7cfa374a
branch
default

Automated merge with https://bitbucket.org/urmas/revelation

1
e5681e842641
#!/usr/bin/env python
2
e5681e842641
3
e5681e842641
#
4
78fb3436ec03
# Revelation - a password manager for GNOME 2
5
e5681e842641
# http://oss.codepoet.no/revelation/
6
e5681e842641
# $Id$
7
e5681e842641
#
8
e5681e842641
# Applet for account lookup
9
e5681e842641
#
10
e5681e842641
#
11
d230b54e5239
# Copyright (c) 2003-2006 Erik Grinaker
12
e5681e842641
#
13
e5681e842641
# This program is free software; you can redistribute it and/or
14
e5681e842641
# modify it under the terms of the GNU General Public License
15
e5681e842641
# as published by the Free Software Foundation; either version 2
16
e5681e842641
# of the License, or (at your option) any later version.
17
e5681e842641
#
18
e5681e842641
# This program is distributed in the hope that it will be useful,
19
e5681e842641
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
e5681e842641
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
e5681e842641
# GNU General Public License for more details.
22
e5681e842641
#
23
e5681e842641
# You should have received a copy of the GNU General Public License
24
e5681e842641
# along with this program; if not, write to the Free Software
25
e5681e842641
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26
e5681e842641
#
27
e5681e842641
28
e1117203c473
import gettext, gnome, gnomeapplet, gobject, gtk, os, sys
29
e5681e842641
30
9c0451b7071b
if "@pyexecdir@" not in sys.path:
31
9c0451b7071b
	sys.path.insert(0, "@pyexecdir@")
32
e5681e842641
33
57eaae684a7f
from revelation import config, data, datahandler, dialog, entry, io, ui, util
34
e5681e842641
35
e1117203c473
_ = gettext.gettext
36
e5681e842641
37
bd0aa2a05f0f
38
e5681e842641
class RevelationApplet(object):
39
e5681e842641
	"Revelation applet"
40
e5681e842641
41
e5681e842641
	def __init__(self, applet, iid):
42
e5681e842641
		self.applet	= applet
43
e5681e842641
		self.iid	= iid
44
e5681e842641
45
e5681e842641
		sys.excepthook	= self.__cb_exception
46
e5681e842641
47
e1117203c473
		gettext.bindtextdomain(config.PACKAGE, config.DIR_LOCALE)
48
e1117203c473
		gettext.bind_textdomain_codeset(config.PACKAGE, "UTF-8")
49
e1117203c473
		gettext.textdomain(config.PACKAGE)
50
e1117203c473
51
e5681e842641
		try:
52
3083465f003a
			self.__init_config()
53
e5681e842641
			self.__init_facilities()
54
e5681e842641
			self.__init_ui()
55
e5681e842641
			self.__init_states()
56
e5681e842641
57
e5681e842641
		except config.ConfigError:
58
e1117203c473
			dialog.Error(None, _('Missing configuration data'), _('The applet could not find its configuration data, please reinstall Revelation.')).run()
59
e5681e842641
			sys.exit(1)
60
e5681e842641
61
e5681e842641
62
3083465f003a
	def __init_config(self):
63
3083465f003a
		"Sets up configuration"
64
e5681e842641
65
e5681e842641
		self.applet.add_preferences("/schemas/apps/revelation-applet/prefs")
66
3083465f003a
		self.config = config.Config(self.applet.get_preferences_key())
67
e5681e842641
68
3083465f003a
		# set up defaults
69
e5681e842641
		# TODO this shouldn't really be necessary, the schema should
70
3083465f003a
		# be used for defaults - is this even possible with the current
71
e5681e842641
		# applet api?
72
e5681e842641
		defaults = {
73
e5681e842641
			"autolock"		: True,
74
f48ca1b4146d
			"autolock_timeout"	: 10,
75
e5681e842641
			"chain_username"	: False,
76
e5681e842641
			"file"			: "",
77
e5681e842641
			"menuaction"		: "show",
78
693abd3fbd04
			"show_passwords"	: True,
79
693abd3fbd04
			"show_searchentry"	: True
80
e5681e842641
		}
81
e5681e842641
82
e5681e842641
		for key, value in defaults.items():
83
e5681e842641
			try:
84
e5681e842641
				self.config.get(key)
85
e5681e842641
86
e5681e842641
			except config.ConfigError:
87
e5681e842641
				self.config.set_force(key, value)
88
e5681e842641
89
e5681e842641
		# make sure the launchers have been set up, otherwise
90
e5681e842641
		# install the Revelation schema
91
e5681e842641
		def check_launchers():
92
e5681e842641
			try:
93
e62dcf5e978b
				for entrytype in [ et() for et in entry.ENTRYLIST if et != entry.FolderEntry ]:
94
e5681e842641
					self.config.get("/apps/revelation/launcher/%s" % entrytype.id)
95
e5681e842641
96
e5681e842641
			except config.ConfigError:
97
e5681e842641
				return False
98
e5681e842641
99
3083465f003a
			else:
100
3083465f003a
				return True
101
3083465f003a
102
3083465f003a
103
e5681e842641
		if check_launchers() == False:
104
e5681e842641
			if config.install_schema("%s/revelation.schemas" % config.DIR_GCONFSCHEMAS) == False:
105
e5681e842641
				raise config.ConfigError
106
e5681e842641
107
e5681e842641
			if check_launchers() == False:
108
e5681e842641
				raise config.ConfigError
109
e5681e842641
110
e5681e842641
111
3083465f003a
	def __init_facilities(self):
112
3083465f003a
		"Sets up facilities"
113
3083465f003a
114
3083465f003a
		self.clipboard		= data.Clipboard()
115
3083465f003a
		self.datafile		= io.DataFile(datahandler.Revelation)
116
3083465f003a
		self.entrystore		= data.EntryStore()
117
3083465f003a
		self.entrysearch	= data.EntrySearch(self.entrystore)
118
3083465f003a
		self.items		= ui.ItemFactory(self.applet)
119
3083465f003a
		self.locktimer		= data.Timer()
120
3083465f003a
121
3083465f003a
		self.config.monitor("autolock_timeout", lambda k,v,d: self.locktimer.start(v * 60))
122
3083465f003a
		self.config.monitor("file", self.__cb_config_file)
123
3083465f003a
124
3083465f003a
		self.datafile.connect("changed", self.__cb_file_changed)
125
3083465f003a
		self.datafile.connect("content-changed", self.__cb_file_content_changed)
126
3083465f003a
		self.locktimer.connect("ring", self.__cb_file_autolock)
127
3083465f003a
128
3083465f003a
		self.entrysearch.folders = False
129
3083465f003a
130
3083465f003a
131
3083465f003a
	def __init_states(self):
132
3083465f003a
		"Sets up the initial states"
133
3083465f003a
134
3083465f003a
		self.datafile.emit("changed", self.datafile.get_file())
135
3083465f003a
		os.chdir(os.path.expanduser("~/"))
136
3083465f003a
137
693abd3fbd04
		self.config.monitor("show_searchentry", self.__cb_config_show_searchentry)
138
693abd3fbd04
139
3083465f003a
140
3083465f003a
	def __init_ui(self):
141
3083465f003a
		"Sets up the main ui"
142
3083465f003a
143
253760f7a205
		gtk.about_dialog_set_url_hook(lambda d,l: gnome.url_show(l))
144
253760f7a205
		gtk.about_dialog_set_email_hook(lambda d,l: gnome.url_show("mailto:" + l))
145
253760f7a205
146
3083465f003a
		# set up applet
147
0f8c7965fdf3
		self.applet.set_flags(gnomeapplet.EXPAND_MINOR)
148
3083465f003a
149
3083465f003a
		# set up window icons
150
c70c18df2134
		pixbufs = [ self.items.get_pixbuf("revelation", size) for size in ( 48, 32, 24, 16) ]
151
3083465f003a
		pixbufs = [ pixbuf for pixbuf in pixbufs if pixbuf != None ]
152
3083465f003a
153
3083465f003a
		if len(pixbufs) > 0:
154
3083465f003a
			gtk.window_set_default_icon_list(*pixbufs)
155
3083465f003a
156
3083465f003a
		# set up popup menu
157
3083465f003a
		self.applet.setup_menu("""
158
3083465f003a
			<popup name="button3">
159
e1117203c473
				<menuitem name="file-unlock"	verb="file-unlock"	label=\"""" + _('Unlock File') + """\"		pixtype="stock" pixname="revelation-unlock" />
160
e1117203c473
				<menuitem name="file-lock"	verb="file-lock"	label=\"""" + _('Lock File') + """\"		pixtype="stock" pixname="revelation-lock" />
161
e1117203c473
				<menuitem name="file-reload"	verb="file-reload"	label=\"""" + _('Reload File') + """\"		pixtype="stock" pixname="revelation-reload" />
162
3083465f003a
				<separator />
163
e1117203c473
				<menuitem name="revelation"	verb="revelation"	label=\"""" + _('Start Revelation') + """\"	pixtype="stock" pixname="revelation-revelation" />
164
e1117203c473
				<menuitem name="prefs"		verb="prefs"		label=\"""" + _('Preferences') + """\"		pixtype="stock"	pixname="gtk-properties" />
165
e1117203c473
				<menuitem name="about"		verb="about"		label=\"""" + _('About') + """\"		pixtype="stock"	pixname="gnome-stock-about" />
166
3083465f003a
			</popup>
167
3083465f003a
		""", (
168
3083465f003a
			( "about",		lambda w,d=None: self.about() ),
169
3083465f003a
			( "file-lock",		lambda w,d=None: self.file_close() ),
170
3083465f003a
			( "file-reload",	lambda w,d=None: self.file_reload() ),
171
6994b7f39b79
			( "file-unlock",	lambda w,d=None: self.file_open(self.config.get("file")) ),
172
3083465f003a
			( "prefs",		lambda w,d=None: self.prefs() ),
173
e1e69f9f5cf7
			( "revelation",		lambda w,d=None: util.execute_child("@bindir@/revelation") ),
174
3083465f003a
		), None)
175
3083465f003a
176
3083465f003a
		# set up ui items
177
3083465f003a
		self.entry = ui.Entry()
178
064a0c169fe8
		self.entry.set_width_chars(14)
179
3083465f003a
		self.entry.connect("activate", self.__cb_entry_activate)
180
3083465f003a
		self.entry.connect("button_press_event", self.__cb_entry_buttonpress)
181
3083465f003a
		self.entry.connect("key_press_event", lambda w,d=None: self.locktimer.reset())
182
3083465f003a
183
96a507350f4e
		self.icon = ui.Image()
184
3083465f003a
		self.eventbox = ui.EventBox(self.icon)
185
3083465f003a
		self.eventbox.connect("button_press_event", self.__cb_icon_buttonpress)
186
3083465f003a
187
3083465f003a
		self.hbox = ui.HBox(self.eventbox, self.entry)
188
3083465f003a
		self.applet.add(self.hbox)
189
e76f78009b7e
		
190
e76f78009b7e
		# handle Gnome Panel background
191
e76f78009b7e
		self.applet.connect("change-background",self.panel_bg)
192
3083465f003a
193
693abd3fbd04
		self.applet.show_all()
194
693abd3fbd04
195
6994b7f39b79
		# set up various ui element holders
196
6994b7f39b79
		self.popup_entryview	= None
197
6994b7f39b79
		self.popup_entrylist	= None
198
6994b7f39b79
199
3083465f003a
		self.entrymenu		= None
200
3083465f003a
201
3083465f003a
202
3083465f003a
203
6994b7f39b79
	##### CALLBACKS #####
204
3083465f003a
205
3083465f003a
	def __cb_config_file(self, key, value, data):
206
6994b7f39b79
		"Config callback for file key changes"
207
3083465f003a
208
3083465f003a
		self.file_close()
209
6994b7f39b79
		self.applet.get_popup_component().set_prop("/commands/file-unlock", "sensitive", self.config.get("file") != "" and "1" or "0")
210
3083465f003a
211
3083465f003a
212
693abd3fbd04
	def __cb_config_show_searchentry(self, key, value, data):
213
693abd3fbd04
		"Config callback for show searchentry setting"
214
693abd3fbd04
215
693abd3fbd04
		if value == True:
216
693abd3fbd04
				self.entry.show()
217
693abd3fbd04
218
693abd3fbd04
		else:
219
693abd3fbd04
				self.entry.hide()
220
693abd3fbd04
221
693abd3fbd04
222
3083465f003a
	def __cb_exception(self, type, value, trace):
223
3083465f003a
		"Callback for unhandled exceptions"
224
3083465f003a
225
3083465f003a
		if type == KeyboardInterrupt:
226
3083465f003a
			sys.exit(1)
227
3083465f003a
228
3083465f003a
		traceback = util.trace_exception(type, value, trace)
229
3083465f003a
		sys.stderr.write(traceback)
230
3083465f003a
231
3083465f003a
		if dialog.Exception(None, traceback).run() == True:
232
3083465f003a
			gtk.main()
233
3083465f003a
234
3083465f003a
		else:
235
3083465f003a
			sys.exit(1)
236
3083465f003a
237
3083465f003a
238
3083465f003a
	def __cb_entry_activate(self, widget, data = None):
239
3083465f003a
		"Callback for entry activation (pressing enter etc)"
240
3083465f003a
241
55c3f554d446
		self.entry_search(self.entry.get_text(), True)
242
3083465f003a
243
3083465f003a
244
3083465f003a
	def __cb_entry_buttonpress(self, widget, data = None):
245
6994b7f39b79
		"Callback for entry button presses"
246
3083465f003a
247
3083465f003a
		self.locktimer.reset()
248
3083465f003a
249
c46bd5f2a327
		if data.button == 1:
250
c46bd5f2a327
			self.applet.request_focus(data.time)
251
3083465f003a
252
3083465f003a
253
3083465f003a
	def __cb_file_autolock(self, widget, data = None):
254
3083465f003a
		"Callback for autolocking the file"
255
3083465f003a
256
3083465f003a
		if self.config.get("autolock") == True:
257
3083465f003a
			self.file_close()
258
3083465f003a
259
3083465f003a
260
3083465f003a
	def __cb_file_content_changed(self, widget, data = None):
261
3083465f003a
		"Callback for changed file content"
262
3083465f003a
263
3083465f003a
		try:
264
db3c0183fd5c
			self.__file_load(self.datafile.get_file(), self.datafile.get_password())
265
db3c0183fd5c
266
db3c0183fd5c
		except dialog.CancelError:
267
db3c0183fd5c
			pass
268
3083465f003a
269
3083465f003a
		except datahandler.PasswordError:
270
3083465f003a
			self.file_close()
271
3083465f003a
272
db3c0183fd5c
		except datahandler.Error:
273
db3c0183fd5c
			pass
274
db3c0183fd5c
275
3083465f003a
276
3083465f003a
	def __cb_file_changed(self, widget, data = None):
277
3083465f003a
		"Callback for changed data file"
278
3083465f003a
279
3083465f003a
		popup = self.applet.get_popup_component()
280
3083465f003a
281
3083465f003a
		if self.datafile.get_file() == None:
282
3083465f003a
			self.entry.set_text("")
283
3083465f003a
284
6994b7f39b79
			popup.set_prop("/commands/file-unlock", "sensitive", self.config.get("file") != "" and "1" or "0")
285
3083465f003a
			popup.set_prop("/commands/file-lock", "sensitive", "0")
286
3083465f003a
			popup.set_prop("/commands/file-reload", "sensitive", "0")
287
3083465f003a
288
96a507350f4e
			self.icon.set_from_stock(ui.STOCK_REVELATION_LOCKED, ui.ICON_SIZE_APPLET)
289
96a507350f4e
290
3083465f003a
		else:
291
3083465f003a
			popup.set_prop("/commands/file-unlock", "sensitive", "0")
292
3083465f003a
			popup.set_prop("/commands/file-lock", "sensitive", "1")
293
3083465f003a
			popup.set_prop("/commands/file-reload", "sensitive", "1")
294
3083465f003a
295
96a507350f4e
			self.icon.set_from_stock(ui.STOCK_REVELATION, ui.ICON_SIZE_APPLET)
296
96a507350f4e
297
3083465f003a
298
6994b7f39b79
	def __cb_icon_buttonpress(self, widget, data = None):
299
6994b7f39b79
		"Callback for buttonpress on button"
300
6994b7f39b79
301
6994b7f39b79
		if data.button != 1:
302
6994b7f39b79
			return False
303
6994b7f39b79
304
6994b7f39b79
		self.entry_menu(data.time)
305
6994b7f39b79
306
6994b7f39b79
		return True
307
6994b7f39b79
308
6994b7f39b79
309
6994b7f39b79
	def __cb_popup_activate(self, widget, data = None):
310
3083465f003a
		"Takes appropriate action when a menu item is activated"
311
3083465f003a
312
3083465f003a
		self.locktimer.reset()
313
3083465f003a
314
3083465f003a
		action = self.config.get("menuaction")
315
3083465f003a
316
3083465f003a
		if action == "show":
317
3083465f003a
			self.entry_show(data)
318
3083465f003a
319
3083465f003a
		elif action == "copy":
320
3083465f003a
			self.entry_copychain(data)
321
3083465f003a
322
6994b7f39b79
		elif self.__launcher_valid(data):
323
3083465f003a
			self.entry_goto(data)
324
3083465f003a
325
3083465f003a
		else:
326
3083465f003a
			self.entry_show(data)
327
3083465f003a
328
e76f78009b7e
    # Handle Gnome Panel background
329
e76f78009b7e
	def panel_bg(self, applet, bg_type, color, pixmap):
330
e76f78009b7e
		# Reset styles
331
e76f78009b7e
		rc_style = gtk.RcStyle()
332
e76f78009b7e
		self.applet.set_style(None)
333
e76f78009b7e
		self.eventbox.set_style(None)
334
e76f78009b7e
		self.entry.set_style(None)
335
e76f78009b7e
		
336
e76f78009b7e
		self.applet.modify_style(rc_style)
337
e76f78009b7e
		self.eventbox.modify_style(rc_style)
338
e76f78009b7e
		self.entry.modify_style(rc_style)
339
e76f78009b7e
		
340
e76f78009b7e
		if bg_type == gnomeapplet.PIXMAP_BACKGROUND:
341
e76f78009b7e
			style = self.applet.get_style()
342
e76f78009b7e
			style.bg_pixmap[gtk.STATE_NORMAL] = pixmap
343
e76f78009b7e
			self.applet.set_style(style)
344
e76f78009b7e
			self.eventbox.set_style(style)
345
e76f78009b7e
			self.entry.set_style(style)
346
e76f78009b7e
		if bg_type == gnomeapplet.COLOR_BACKGROUND:
347
e76f78009b7e
			self.applet.modify_bg(gtk.STATE_NORMAL, color)
348
e76f78009b7e
			self.eventbox.modify_bg(gtk.STATE_NORMAL, color)
349
e76f78009b7e
			self.entry.modify_bg(gtk.STATE_NORMAL, color)
350
e76f78009b7e
351
3083465f003a
352
e5681e842641
353
6994b7f39b79
	##### PRIVATE METHODS #####
354
e5681e842641
355
6994b7f39b79
	def __close_popups(self):
356
6994b7f39b79
		"Closes any open popups"
357
e5681e842641
358
6994b7f39b79
		self.locktimer.reset()
359
e5681e842641
360
6994b7f39b79
		if hasattr(self, "popup_entryview") == True and self.popup_entryview != None:
361
6994b7f39b79
			self.popup_entryview.destroy()
362
e5681e842641
363
6994b7f39b79
		if hasattr(self, "popup_entrylist") == True and self.popup_entrylist != None:
364
6994b7f39b79
			self.popup_entrylist.destroy()
365
e5681e842641
366
6994b7f39b79
		if hasattr(self, "entrymenu") == True and self.entrymenu != None:
367
6994b7f39b79
			self.entrymenu.hide()
368
e5681e842641
369
e5681e842641
370
db3c0183fd5c
	def __file_load(self, file, password = None):
371
db3c0183fd5c
		"Loads a data file"
372
db3c0183fd5c
373
db3c0183fd5c
		if file in ( "", None ):
374
db3c0183fd5c
			return False
375
db3c0183fd5c
376
db3c0183fd5c
		if dialog.present_unique(dialog.PasswordOpen) == True:
377
db3c0183fd5c
			return False
378
db3c0183fd5c
379
db3c0183fd5c
		entrystore = self.datafile.load(file, password, lambda: dialog.run_unique(dialog.PasswordOpen, None, os.path.basename(file)))
380
db3c0183fd5c
381
db3c0183fd5c
		self.entrystore.clear()
382
db3c0183fd5c
		self.entrystore.import_entry(entrystore, None)
383
db3c0183fd5c
384
db3c0183fd5c
		self.entrymenu = self.__generate_entrymenu(self.entrystore)
385
db3c0183fd5c
		self.locktimer.start(self.config.get("autolock_timeout") * 60)
386
db3c0183fd5c
387
db3c0183fd5c
		self.__close_popups()
388
db3c0183fd5c
389
db3c0183fd5c
		return True
390
db3c0183fd5c
391
db3c0183fd5c
392
6994b7f39b79
	def __flash_entry(self, color = "#ffbaba", duration = 500):
393
6994b7f39b79
		"Flashes the entry with a color"
394
e5681e842641
395
6994b7f39b79
		color_normal	= ui.Entry().rc_get_style().base[gtk.STATE_NORMAL]
396
6994b7f39b79
		color_new	= gtk.gdk.color_parse(color)
397
e5681e842641
398
6994b7f39b79
		self.entry.modify_base(gtk.STATE_NORMAL, color_new)
399
6994b7f39b79
		gobject.timeout_add(duration, lambda: self.entry.modify_base(gtk.STATE_NORMAL, color_normal))
400
e5681e842641
401
e5681e842641
402
6994b7f39b79
	def __focus_entry(self):
403
6994b7f39b79
		"Gives focus to the entry"
404
e5681e842641
405
c46bd5f2a327
		self.applet.request_focus(long(0))
406
e5681e842641
407
e5681e842641
408
6994b7f39b79
	def __generate_entrymenu(self, entrystore, parent = None):
409
6994b7f39b79
		"Generates an entry menu tree"
410
e5681e842641
411
6994b7f39b79
		menu = gtk.Menu()
412
6994b7f39b79
413
6994b7f39b79
		for i in range(entrystore.iter_n_children(parent)):
414
6994b7f39b79
			iter = entrystore.iter_nth_child(parent, i)
415
6994b7f39b79
416
6994b7f39b79
			e = entrystore.get_entry(iter)
417
6994b7f39b79
			item = ui.ImageMenuItem(type(e) == entry.FolderEntry and ui.STOCK_FOLDER or e.icon, e.name)
418
6994b7f39b79
			item.connect("select", lambda w,d=None: self.locktimer.reset())
419
6994b7f39b79
420
6994b7f39b79
			if type(e) == entry.FolderEntry:
421
6994b7f39b79
				item.set_submenu(self.__generate_entrymenu(entrystore, iter))
422
6994b7f39b79
423
6994b7f39b79
			else:
424
6994b7f39b79
				item.connect("activate", self.__cb_popup_activate, e)
425
6994b7f39b79
426
6994b7f39b79
			menu.append(item)
427
6994b7f39b79
428
6994b7f39b79
		return menu
429
6994b7f39b79
430
6994b7f39b79
431
6994b7f39b79
	def __get_launcher(self, e):
432
e5681e842641
		"Returns a launcher command for an entry, if possible"
433
e5681e842641
434
e5681e842641
		command = self.config.get("/apps/revelation/launcher/%s" % e.id)
435
e5681e842641
436
e5681e842641
		if command in ( "", None ):
437
e5681e842641
			return None
438
e5681e842641
439
e5681e842641
		subst = {}
440
e5681e842641
		for field in e.fields:
441
e5681e842641
			subst[field.symbol] = field.value
442
e5681e842641
443
e5681e842641
		command = util.parse_subst(command, subst)
444
e5681e842641
445
e5681e842641
		return command
446
e5681e842641
447
e5681e842641
448
6994b7f39b79
	def __get_popup_offset(self, popup):
449
e5681e842641
		"Returns a tuple of x and y offset coords for popups"
450
e5681e842641
451
e5681e842641
		screen	= self.applet.get_screen()
452
e5681e842641
		a	= self.applet.get_allocation()
453
e5681e842641
		rw, rh	= popup.size_request()
454
e5681e842641
455
e5681e842641
		x, y	= self.applet.window.get_origin()
456
e5681e842641
		x	+= a.x
457
e5681e842641
		y	+= a.y
458
e5681e842641
459
e5681e842641
460
e5681e842641
		# TODO use constants ORIENT_UP etc here, if available
461
e5681e842641
		if self.applet.get_orient() in ( 0, 1 ):
462
e5681e842641
			x = min(x, screen.get_width() - rw)
463
e5681e842641
464
e5681e842641
			if (y > screen.get_height() / 2):
465
e5681e842641
				y -= rh
466
e5681e842641
467
e5681e842641
			else:
468
e5681e842641
				y += a.height
469
e5681e842641
470
e5681e842641
		else:
471
e5681e842641
			y = min(y, screen.get_height() - rh)
472
e5681e842641
473
e5681e842641
			if (x > screen.get_width() / 2):
474
e5681e842641
				x -= rw
475
e5681e842641
476
e5681e842641
			else:
477
e5681e842641
				x += a.width
478
e5681e842641
479
e5681e842641
		return x, y
480
e5681e842641
481
e5681e842641
482
6994b7f39b79
	def __launcher_valid(self, e):
483
6994b7f39b79
		"Checks if a launcher is valid"
484
e5681e842641
485
6994b7f39b79
		try:
486
6994b7f39b79
			command = self.__get_launcher(e)
487
6994b7f39b79
488
6994b7f39b79
			return command != None
489
6994b7f39b79
490
6994b7f39b79
		except ( util.SubstFormatError ):
491
6994b7f39b79
			return True
492
6994b7f39b79
493
6994b7f39b79
		except ( util.SubstValueError, config.ConfigError ):
494
6994b7f39b79
			return False
495
6994b7f39b79
496
6994b7f39b79
497
6994b7f39b79
	def __require_file(self):
498
6994b7f39b79
		"Checks if a datafile is loaded, or alerts the user"
499
6994b7f39b79
500
6994b7f39b79
		if self.datafile.get_file() != None:
501
6994b7f39b79
			return True
502
6994b7f39b79
503
6994b7f39b79
		if self.config.get("file") != "":
504
6994b7f39b79
			return self.file_open(self.config.get("file"))
505
6994b7f39b79
506
6994b7f39b79
		d = dialog.Info(
507
e1117203c473
			None, _('File not selected'),
508
e1117203c473
			_('You must select a Revelation data file to use - this can be done in the applet preferences.'),
509
6994b7f39b79
			( ( gtk.STOCK_PREFERENCES, gtk.RESPONSE_ACCEPT ), ( gtk.STOCK_OK, gtk.RESPONSE_OK ) )
510
6994b7f39b79
		)
511
6994b7f39b79
512
6994b7f39b79
513
6994b7f39b79
		if d.run() == gtk.RESPONSE_ACCEPT:
514
6994b7f39b79
			self.prefs()
515
6994b7f39b79
516
6994b7f39b79
		return False
517
6994b7f39b79
518
6994b7f39b79
519
6994b7f39b79
	##### PUBLIC METHODS #####
520
6994b7f39b79
521
6994b7f39b79
	def about(self):
522
6994b7f39b79
		"Displays an about dialog"
523
6994b7f39b79
524
dce7200431f0
		dialog.run_unique(About, self.applet)
525
6994b7f39b79
526
6994b7f39b79
527
6994b7f39b79
	def entry_copychain(self, e, launcher = ""):
528
6994b7f39b79
		"Copies all passwords from an entry as a chain"
529
6994b7f39b79
530
6994b7f39b79
		if e == None:
531
6994b7f39b79
			return
532
6994b7f39b79
533
6994b7f39b79
		secrets = [ field.value for field in e.fields if field.datatype == entry.DATATYPE_PASSWORD and field.value != "" ]
534
6994b7f39b79
535
6994b7f39b79
		if self.config.get("chain_username") == True and len(secrets) > 0:
536
6994b7f39b79
			if e.has_field(entry.UsernameField) and e[entry.UsernameField] != "":
537
6994b7f39b79
				if "%" + entry.UsernameField.symbol not in launcher:
538
6994b7f39b79
					secrets.insert(0, e[entry.UsernameField])
539
6994b7f39b79
540
6994b7f39b79
		self.clipboard.set(secrets)
541
6994b7f39b79
542
6994b7f39b79
543
6994b7f39b79
	def entry_goto(self, e):
544
6994b7f39b79
		"Goes to an entry"
545
6994b7f39b79
546
6994b7f39b79
		try:
547
6994b7f39b79
			command = self.__get_launcher(e)
548
6994b7f39b79
549
6994b7f39b79
			if command == None:
550
6994b7f39b79
				return
551
6994b7f39b79
552
6994b7f39b79
			self.entry_copychain(e)
553
6994b7f39b79
554
6994b7f39b79
			util.execute_child(command)
555
6994b7f39b79
556
6994b7f39b79
		except ( util.SubstFormatError, config.ConfigError ):
557
e1117203c473
			dialog.Error(None, _('Invalid goto command format'), _('The goto command for '" + e.typename + "' entries is invalid, please correct this in the preferences.')).run()
558
6994b7f39b79
559
6994b7f39b79
		except util.SubstValueError:
560
6994b7f39b79
			self.entry_show(e)
561
6994b7f39b79
562
6994b7f39b79
563
6994b7f39b79
	def entry_menu(self, time = None):
564
6994b7f39b79
		"Displays the entry menu"
565
6994b7f39b79
566
6994b7f39b79
		self.__close_popups()
567
6994b7f39b79
568
6994b7f39b79
		if self.__require_file() == False:
569
6994b7f39b79
			return
570
6994b7f39b79
571
6994b7f39b79
		if self.entrymenu == None:
572
6994b7f39b79
			return
573
6994b7f39b79
574
6994b7f39b79
		x, y = self.__get_popup_offset(self.entrymenu)
575
6994b7f39b79
576
6994b7f39b79
		self.entrymenu.show_all()
577
6994b7f39b79
		self.entrymenu.popup(None, None, lambda d: (x, y, False), 1, time)
578
6994b7f39b79
579
6994b7f39b79
580
55c3f554d446
	def entry_search(self, term, focusafter = False):
581
6994b7f39b79
		"Searches for an entry"
582
6994b7f39b79
583
6994b7f39b79
		self.__close_popups()
584
6994b7f39b79
585
6994b7f39b79
		if term.strip() == "":
586
6994b7f39b79
			return
587
6994b7f39b79
588
6994b7f39b79
		if self.__require_file() == False:
589
6994b7f39b79
			return
590
6994b7f39b79
591
6994b7f39b79
592
6994b7f39b79
		matches = [ self.entrystore.get_entry(iter) for iter in self.entrysearch.find_all(term) ]
593
6994b7f39b79
594
6994b7f39b79
		if len(matches) == 0:
595
13ec1163cf9f
			self.__focus_entry()
596
6994b7f39b79
			self.__flash_entry()
597
5a07753dbd3c
			self.entry.select_region(0, -1)
598
6994b7f39b79
599
6994b7f39b79
		elif len(matches) == 1:
600
55c3f554d446
			self.entry_show(matches[0], True)
601
6994b7f39b79
602
6994b7f39b79
		else:
603
6994b7f39b79
			self.popup_entrylist = EntryListPopup(matches)
604
55c3f554d446
			self.popup_entrylist.connect("entry-chosen", lambda w,e: self.entry_show(e, focusafter))
605
55c3f554d446
606
55c3f554d446
			if focusafter == True:
607
55c3f554d446
				self.popup_entrylist.connect("closed", lambda w: self.__focus_entry())
608
6994b7f39b79
609
6994b7f39b79
			self.popup_entrylist.realize()
610
6994b7f39b79
			x, y = self.__get_popup_offset(self.popup_entrylist)
611
6994b7f39b79
			self.popup_entrylist.show(x, y)
612
6994b7f39b79
613
6994b7f39b79
614
55c3f554d446
	def entry_show(self, e, focusafter = False):
615
6994b7f39b79
		"Shows an entry"
616
6994b7f39b79
617
6994b7f39b79
		self.__close_popups()
618
6994b7f39b79
619
6994b7f39b79
		self.popup_entryview = EntryViewPopup(e, self.config, self.clipboard)
620
55c3f554d446
621
55c3f554d446
		if focusafter == True:
622
55c3f554d446
			self.popup_entryview.connect("closed", lambda w: self.__focus_entry())
623
6994b7f39b79
624
6994b7f39b79
		def cb_goto(widget):
625
6994b7f39b79
			if self.__launcher_valid(e):
626
6994b7f39b79
				self.entry_goto(e)
627
6994b7f39b79
628
bd0aa2a05f0f
			self.popup_entryview.close()
629
6994b7f39b79
630
6994b7f39b79
		self.popup_entryview.button_goto.connect("clicked", cb_goto)
631
6994b7f39b79
		self.popup_entryview.button_goto.set_sensitive(self.__launcher_valid(e))
632
6994b7f39b79
633
6994b7f39b79
		self.popup_entryview.realize()
634
6994b7f39b79
		x, y = self.__get_popup_offset(self.popup_entryview)
635
6994b7f39b79
		self.popup_entryview.show(x, y)
636
6994b7f39b79
637
6994b7f39b79
638
6994b7f39b79
	def file_close(self):
639
6994b7f39b79
		"Closes the current data file"
640
6994b7f39b79
641
6994b7f39b79
		self.__close_popups()
642
6994b7f39b79
		self.locktimer.stop()
643
6994b7f39b79
644
6994b7f39b79
		self.datafile.close()
645
6994b7f39b79
		self.entrystore.clear()
646
6994b7f39b79
		self.entrymenu = None
647
6994b7f39b79
648
6994b7f39b79
649
6994b7f39b79
	def file_open(self, file, password = None):
650
6994b7f39b79
		"Opens a data file"
651
6994b7f39b79
652
6994b7f39b79
		try:
653
db3c0183fd5c
			return self.__file_load(file, password)
654
6994b7f39b79
655
6994b7f39b79
		except dialog.CancelError:
656
6994b7f39b79
			pass
657
6994b7f39b79
658
6994b7f39b79
		except datahandler.FormatError:
659
e1117203c473
			dialog.Error(None, _('Invalid file format'), _('The file \'%s\' contains invalid data.') % file).run()
660
6994b7f39b79
661
6994b7f39b79
		except ( datahandler.DataError, entry.EntryTypeError, entry.EntryFieldError ):
662
e1117203c473
			dialog.Error(None, _('Unknown data'), _('The file \'%s\' contains unknown data. It may have been created by a more recent version of Revelation.') % file).run()
663
6994b7f39b79
664
6994b7f39b79
		except datahandler.PasswordError:
665
e1117203c473
			dialog.Error(None, _('Incorrect password'), _('You entered an incorrect password for the file \'%s\', please try again.') % file).run()
666
6994b7f39b79
			self.file_open(file, None)
667
6994b7f39b79
668
6994b7f39b79
		except datahandler.VersionError:
669
e1117203c473
			dialog.Error(None, _('Unknown data version'), _('The file \'%s\' has a future version number, please upgrade Revelation to open it.') % file).run()
670
6994b7f39b79
671
6994b7f39b79
		except IOError:
672
e1117203c473
			dialog.Error(None, _('Unable to open file'), _('The file \'%s\' could not be opened. Make sure that the file exists, and that you have permissions to open it.') % file).run()
673
6994b7f39b79
674
6994b7f39b79
		return False
675
6994b7f39b79
676
6994b7f39b79
677
6994b7f39b79
	def file_reload(self):
678
6994b7f39b79
		"Reloads the current data file"
679
6994b7f39b79
680
6994b7f39b79
		if self.datafile.get_file() == None:
681
6994b7f39b79
			return
682
6994b7f39b79
683
6994b7f39b79
		self.file_open(self.datafile.get_file(), self.datafile.get_password())
684
e5681e842641
685
e5681e842641
686
e5681e842641
	def prefs(self):
687
e5681e842641
		"Displays the preference dialog"
688
e5681e842641
689
dce7200431f0
		dialog.run_unique(Preferences, None, self.config)
690
e5681e842641
691
e5681e842641
692
e5681e842641
693
253760f7a205
class About(dialog.About):
694
e5681e842641
	"About dialog"
695
e5681e842641
696
e5681e842641
	def __init__(self, parent):
697
253760f7a205
		dialog.About.__init__(self, parent)
698
e5681e842641
699
e1117203c473
		self.set_name(_('Revelation Account Search'))
700
e1117203c473
		self.set_comments(_('"%s"\n\nAn applet for searching and browsing a Revelation account database') % config.RELNAME)
701
e5681e842641
702
e5681e842641
703
e5681e842641
704
3083465f003a
class EntryListPopup(dialog.Popup):
705
3083465f003a
	"A popup for displaying a list of entries"
706
3083465f003a
707
3083465f003a
	def __init__(self, entries):
708
3083465f003a
		dialog.Popup.__init__(self)
709
167384ef2e8d
		self.set_default_size(225, 200)
710
3083465f003a
711
3083465f003a
		self.entrystore = data.EntryStore()
712
3083465f003a
		self.entrystore.set_sort_column_id(0, gtk.SORT_ASCENDING)
713
3083465f003a
714
3083465f003a
		for e in entries:
715
3083465f003a
			self.entrystore.add_entry(e)
716
3083465f003a
717
3083465f003a
		self.treeview = ui.EntryTree(self.entrystore)
718
3083465f003a
		self.treeview.set_cursor((0,))
719
6994b7f39b79
		self.treeview.connect("row-activated", self.__cb_row_activated)
720
3083465f003a
721
3083465f003a
		self.scrolledwindow = ui.ScrolledWindow(self.treeview)
722
167384ef2e8d
		self.scrolledwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
723
3083465f003a
		self.add(self.scrolledwindow)
724
3083465f003a
725
3083465f003a
726
6994b7f39b79
	def __cb_row_activated(self, widget, path, data = None):
727
6994b7f39b79
		"Callback for tree row activation"
728
6994b7f39b79
729
6994b7f39b79
		iter = self.entrystore.get_iter(path)
730
6994b7f39b79
		e = self.entrystore.get_entry(iter)
731
6994b7f39b79
732
6994b7f39b79
		self.emit("entry-chosen", e)
733
bd0aa2a05f0f
		self.close()
734
6994b7f39b79
735
167384ef2e8d
736
6994b7f39b79
gobject.signal_new("entry-chosen", EntryListPopup, gobject.SIGNAL_ACTION, gobject.TYPE_BOOLEAN, ( gobject.TYPE_PYOBJECT, ))
737
6994b7f39b79
738
6994b7f39b79
739
3083465f003a
740
3083465f003a
class EntryViewPopup(dialog.Popup):
741
3083465f003a
	"A popup for displaying an entry"
742
3083465f003a
743
3083465f003a
	def __init__(self, e, cfg = None, clipboard = None):
744
3083465f003a
		dialog.Popup.__init__(self)
745
3083465f003a
		self.set_title(e.name)
746
3083465f003a
747
3083465f003a
		self.entryview = ui.EntryView(cfg, clipboard)
748
3083465f003a
		self.entryview.set_border_width(0)
749
3083465f003a
		self.entryview.display_entry(e)
750
3083465f003a
751
bd0aa2a05f0f
		self.button_close = ui.Button(gtk.STOCK_CLOSE, lambda w: self.close())
752
3083465f003a
		self.button_goto = ui.Button(ui.STOCK_GOTO)
753
3083465f003a
		self.buttonbox = ui.HButtonBox(self.button_goto, self.button_close)
754
3083465f003a
755
3083465f003a
		self.vbox = ui.VBox(self.entryview, self.buttonbox)
756
3083465f003a
		self.vbox.set_border_width(12)
757
3083465f003a
		self.vbox.set_spacing(15)
758
3083465f003a
759
3083465f003a
		self.add(self.vbox)
760
3083465f003a
761
3083465f003a
		self.connect("show", lambda w: self.button_close.grab_focus())
762
3083465f003a
763
3083465f003a
764
3083465f003a
765
e5681e842641
class Preferences(dialog.Utility):
766
e5681e842641
	"A preference dialog"
767
e5681e842641
768
e5681e842641
	def __init__(self, parent, cfg):
769
e5681e842641
		dialog.Utility.__init__(self, parent, "Preferences")
770
e5681e842641
		self.config = cfg
771
e5681e842641
		self.set_modal(False)
772
e5681e842641
773
e5681e842641
		self.notebook = ui.Notebook()
774
e5681e842641
		self.vbox.pack_start(self.notebook)
775
e5681e842641
776
e1117203c473
		self.page_general = self.notebook.create_page(_('General'))
777
e5681e842641
		self.__init_section_file(self.page_general)
778
e5681e842641
		self.__init_section_menuaction(self.page_general)
779
e5681e842641
		self.__init_section_misc(self.page_general)
780
e5681e842641
781
e1117203c473
		self.page_goto = self.notebook.create_page(_('Goto Commands'))
782
e5681e842641
		self.__init_section_gotocmd(self.page_goto)
783
e5681e842641
784
e5681e842641
		self.connect("response", lambda w,d: self.destroy())
785
e5681e842641
786
e5681e842641
787
e5681e842641
	def __init_section_file(self, page):
788
e5681e842641
		"Sets up a file section in a page"
789
e5681e842641
790
e1117203c473
		self.section_file = page.add_section(_('File Handling'))
791
e5681e842641
792
e5681e842641
		# entry for file
793
e1117203c473
		self.button_file = ui.FileButton(_('Select File to Use'))
794
f3b5ac5a54f5
		ui.config_bind(self.config, "file", self.button_file)
795
167384ef2e8d
796
f3b5ac5a54f5
		eventbox = ui.EventBox(self.button_file)
797
e1117203c473
		self.tooltips.set_tip(eventbox, _('The data file to search for accounts in'))
798
e1117203c473
		self.section_file.append_widget(_('File to use'), eventbox)
799
e5681e842641
800
e5681e842641
		# check-button for autolock
801
e1117203c473
		self.check_autolock = ui.CheckButton(_('Lock file when inactive for'))
802
e5681e842641
		ui.config_bind(self.config, "autolock", self.check_autolock)
803
e5681e842641
		self.check_autolock.connect("toggled", lambda w: self.spin_autolock_timeout.set_sensitive(w.get_active()))
804
e1117203c473
		self.tooltips.set_tip(self.check_autolock, _('Automatically lock the file after a period of inactivity'))
805
e5681e842641
806
e5681e842641
		# spin-entry for autolock-timeout
807
e5681e842641
		self.spin_autolock_timeout = ui.SpinEntry()
808
e5681e842641
		self.spin_autolock_timeout.set_range(1, 120)
809
e5681e842641
		self.spin_autolock_timeout.set_sensitive(self.check_autolock.get_active())
810
e5681e842641
		ui.config_bind(self.config, "autolock_timeout", self.spin_autolock_timeout)
811
e1117203c473
		self.tooltips.set_tip(self.spin_autolock_timeout, _('The period of inactivity before locking the file, in minutes'))
812
e5681e842641
813
e5681e842641
		# container for autolock-widgets
814
e5681e842641
		hbox = ui.HBox()
815
e5681e842641
		hbox.set_spacing(3)
816
e5681e842641
		hbox.pack_start(self.check_autolock)
817
e5681e842641
		hbox.pack_start(self.spin_autolock_timeout)
818
e1117203c473
		hbox.pack_start(ui.Label(_('minutes')))
819
e5681e842641
		self.section_file.append_widget(None, hbox)
820
e5681e842641
821
e5681e842641
822
e5681e842641
	def __init_section_gotocmd(self, page):
823
e5681e842641
		"Sets up the goto command section"
824
e5681e842641
825
e1117203c473
		self.section_goto = page.add_section(_('Goto Commands'))
826
e5681e842641
827
e5681e842641
		for entrytype in entry.ENTRYLIST:
828
e5681e842641
			if entrytype == entry.FolderEntry:
829
e5681e842641
				continue
830
e5681e842641
831
e5681e842641
			e = entrytype()
832
e5681e842641
833
e5681e842641
			widget = ui.Entry()
834
e5681e842641
			ui.config_bind(self.config, "/apps/revelation/launcher/%s" % e.id, widget)
835
e5681e842641
836
e1117203c473
			tooltip = _('Goto command for %s accounts. The following expansion variables can be used:\n\n') % e.typename
837
e5681e842641
838
e5681e842641
			for field in e.fields:
839
e5681e842641
				tooltip += "%%%s: %s\n" % ( field.symbol, field.name )
840
e5681e842641
841
e5681e842641
			tooltip += "\n"
842
e1117203c473
			tooltip += _('%%: a % sign') + "\n"
843
e1117203c473
			tooltip += _('%?x: optional expansion variable') + "\n"
844
e1117203c473
			tooltip += _('%(...%): optional substring expansion')
845
e5681e842641
846
e5681e842641
			self.tooltips.set_tip(widget, tooltip)
847
e5681e842641
			self.section_goto.append_widget(e.typename, widget)
848
e5681e842641
849
e5681e842641
850
e5681e842641
	def __init_section_menuaction(self, page):
851
e5681e842641
		"Sets up a menuaction section in a page"
852
e5681e842641
853
e1117203c473
		self.section_menuaction = page.add_section(_('Menu Action'))
854
e5681e842641
855
e5681e842641
		# radio-button for show
856
e1117203c473
		self.radio_show = ui.RadioButton(None, _('Display account info'))
857
e5681e842641
		ui.config_bind(self.config, "menuaction", self.radio_show, "show")
858
e5681e842641
859
e1117203c473
		self.tooltips.set_tip(self.radio_show, _('Display the account information'))
860
e5681e842641
		self.section_menuaction.append_widget(None, self.radio_show)
861
e5681e842641
862
e5681e842641
		# radio-button for goto
863
e1117203c473
		self.radio_goto = ui.RadioButton(self.radio_show, _('Go to account, if possible'))
864
e5681e842641
		ui.config_bind(self.config, "menuaction", self.radio_goto, "goto")
865
e5681e842641
866
e1117203c473
		self.tooltips.set_tip(self.radio_goto, _('Open the account in an external application if possible, otherwise display it'))
867
e5681e842641
		self.section_menuaction.append_widget(None, self.radio_goto)
868
e5681e842641
869
e5681e842641
		# radio-button for copy username/password
870
e1117203c473
		self.radio_copy = ui.RadioButton(self.radio_show, _('Copy password to clipboard'))
871
e5681e842641
		ui.config_bind(self.config, "menuaction", self.radio_copy, "copy")
872
e5681e842641
873
e1117203c473
		self.tooltips.set_tip(self.radio_copy, _('Copy the account password to the clipboard'))
874
e5681e842641
		self.section_menuaction.append_widget(None, self.radio_copy)
875
e5681e842641
876
e5681e842641
877
e5681e842641
	def __init_section_misc(self, page):
878
e5681e842641
		"Sets up the misc section"
879
e5681e842641
880
e1117203c473
		self.section_misc = page.add_section(_('Miscellaneous'))
881
e5681e842641
882
693abd3fbd04
		# show searchentry checkbutton
883
e1117203c473
		self.check_show_searchentry = ui.CheckButton(_('Show search entry'))
884
693abd3fbd04
		ui.config_bind(self.config, "show_searchentry", self.check_show_searchentry)
885
693abd3fbd04
886
e1117203c473
		self.tooltips.set_tip(self.check_show_searchentry, _('Display an entry box in the applet for searching'))
887
693abd3fbd04
		self.section_misc.append_widget(None, self.check_show_searchentry)
888
693abd3fbd04
889
c6e069377f71
		# show passwords checkbutton
890
e1117203c473
		self.check_show_passwords = ui.CheckButton(_('Show passwords and other secrets'))
891
c6e069377f71
		ui.config_bind(self.config, "show_passwords", self.check_show_passwords)
892
c6e069377f71
893
e1117203c473
		self.tooltips.set_tip(self.check_show_passwords, _('Display passwords and other secrets, such as PIN codes (otherwise, hide with ******)'))
894
c6e069377f71
		self.section_misc.append_widget(None, self.check_show_passwords)
895
c6e069377f71
896
e5681e842641
		# check-button for username
897
e1117203c473
		self.check_chain_username = ui.CheckButton(_('Also copy username when copying password'))
898
e5681e842641
		ui.config_bind(self.config, "chain_username", self.check_chain_username)
899
e5681e842641
900
e1117203c473
		self.tooltips.set_tip(self.check_chain_username, _('When the password is copied to clipboard, put the username before the password as a clipboard "chain"'))
901
e5681e842641
		self.section_misc.append_widget(None, self.check_chain_username)
902
e5681e842641
903
e5681e842641
904
e5681e842641
	def run(self):
905
e5681e842641
		"Runs the dialog"
906
e5681e842641
907
e5681e842641
		self.show_all()
908
e5681e842641
909
e5681e842641
910
e5681e842641
911
e5681e842641
def factory(applet, iid):
912
e5681e842641
	"Applet factory function"
913
e5681e842641
914
e5681e842641
	RevelationApplet(applet, iid)
915
e5681e842641
916
e5681e842641
	return True
917
e5681e842641
918
e5681e842641
919
e5681e842641
920
e5681e842641
if __name__ == "__main__":
921
e5681e842641
	gnome.init(config.APPNAME, config.VERSION)
922
e5681e842641
923
0f8c7965fdf3
	gnomeapplet.bonobo_factory(
924
e5681e842641
		"OAFIID:GNOME_RevelationApplet_Factory",
925
0f8c7965fdf3
		gnomeapplet.Applet.__gtype__,
926
e5681e842641
		config.APPNAME, config.VERSION, factory
927
e5681e842641
	)
928
e5681e842641