35
42
properties and calls a hook function when any of them are
38
def __init__(self, proxy_object=None, properties=None, *args,
40
# Type conversion mapping
42
dbus.ObjectPath: unicode,
44
dbus.Signature: unicode,
52
dbus.Dictionary: dict,
45
def __init__(self, proxy_object=None, *args, **kwargs):
59
46
self.proxy = proxy_object # Mandos Client proxy object
61
if properties is None:
62
self.properties = dict()
64
self.properties = dict(self.convert_property(prop, val)
66
properties.iteritems())
67
self.proxy.connect_to_signal("PropertyChanged",
48
self.properties = dict()
49
self.proxy.connect_to_signal(u"PropertyChanged",
68
50
self.property_changed,
72
if properties is None:
73
self.properties.update(
74
self.convert_property(prop, val)
76
self.proxy.GetAll(client_interface,
78
dbus.PROPERTIES_IFACE).iteritems())
54
self.properties.update(
55
self.proxy.GetAll(client_interface,
56
dbus_interface = dbus.PROPERTIES_IFACE))
79
57
super(MandosClientPropertyCache, self).__init__(
80
proxy_object=proxy_object,
81
properties=properties, *args, **kwargs)
58
proxy_object=proxy_object, *args, **kwargs)
83
def convert_property(self, property, value):
84
"""This converts the arguments from a D-Bus signal, which are
85
D-Bus types, into normal Python types, using a conversion
86
function from "self.type_map".
88
property_name = unicode(property) # Always a dbus.String
89
if isinstance(value, dbus.UTF8String):
90
# Should not happen, but prepare for it anyway
91
value = dbus.String(str(value).decode("utf-8"))
93
convfunc = self.type_map[type(value)]
95
# Unknown type, return unmodified
96
return property_name, value
97
return property_name, convfunc(value)
98
60
def property_changed(self, property=None, value=None):
99
61
"""This is called whenever we get a PropertyChanged signal
100
62
It updates the changed property in the "properties" dict.
102
# Convert name and value
103
property_name, cvalue = self.convert_property(property, value)
104
64
# Update properties dict with new value
105
self.properties[property_name] = cvalue
65
self.properties[property] = value
108
68
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
112
72
def __init__(self, server_proxy_object=None, update_hook=None,
113
delete_hook=None, *args, **kwargs):
73
delete_hook=None, logger=None, *args, **kwargs):
114
74
# Called on update
115
75
self.update_hook = update_hook
116
76
# Called on delete
117
77
self.delete_hook = delete_hook
118
78
# Mandos Server proxy object
119
79
self.server_proxy_object = server_proxy_object
121
83
# The widget shown normally
122
self._text_widget = urwid.Text("")
84
self._text_widget = urwid.Text(u"")
123
85
# The widget shown when we have focus
124
self._focus_text_widget = urwid.Text("")
86
self._focus_text_widget = urwid.Text(u"")
125
87
super(MandosClientWidget, self).__init__(
126
88
update_hook=update_hook, delete_hook=delete_hook,
129
91
self.opened = False
92
self.proxy.connect_to_signal(u"CheckerCompleted",
93
self.checker_completed,
96
self.proxy.connect_to_signal(u"CheckerStarted",
100
self.proxy.connect_to_signal(u"GotSecret",
104
self.proxy.connect_to_signal(u"Rejected",
109
def checker_completed(self, exitstatus, condition, command):
111
self.logger(u'Checker for client %s (command "%s")'
113
% (self.properties[u"name"], command))
115
if os.WIFEXITED(condition):
116
self.logger(u'Checker for client %s (command "%s")'
117
u' failed with exit code %s'
118
% (self.properties[u"name"], command,
119
os.WEXITSTATUS(condition)))
121
if os.WIFSIGNALED(condition):
122
self.logger(u'Checker for client %s (command "%s")'
123
u' was killed by signal %s'
124
% (self.properties[u"name"], command,
125
os.WTERMSIG(condition)))
127
if os.WCOREDUMP(condition):
128
self.logger(u'Checker for client %s (command "%s")'
130
% (self.properties[u"name"], command))
131
self.logger(u'Checker for client %s completed mysteriously')
133
def checker_started(self, command):
134
self.logger(u'Client %s started checker "%s"'
135
% (self.properties[u"name"], unicode(command)))
137
def got_secret(self):
138
self.logger(u'Client %s received its secret'
139
% self.properties[u"name"])
142
self.logger(u'Client %s was rejected'
143
% self.properties[u"name"])
131
145
def selectable(self):
132
146
"""Make this a "selectable" widget.
158
172
# Rebuild focus and non-focus widgets using current properties
159
self._text = (u'name="%(name)s", enabled=%(enabled)s'
173
self._text = (u'%(name)s: %(enabled)s'
174
% { u"name": self.properties[u"name"],
177
if self.properties[u"enabled"]
161
179
if not urwid.supports_unicode():
162
180
self._text = self._text.encode("ascii", "replace")
163
textlist = [(u"normal", u"BLĆRGH: "), (u"bold", self._text)]
181
textlist = [(u"normal", self._text)]
164
182
self._text_widget.set_text(textlist)
165
183
self._focus_text_widget.set_text([(with_standout[text[0]],
243
class ConstrainedListBox(urwid.ListBox):
244
"""Like a normal urwid.ListBox, but will consume all "up" or
245
"down" key presses, thus not allowing any containing widgets to
246
use them as an excuse to shift focus away from this widget.
248
def keypress(self, (maxcol, maxrow), key):
249
ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
250
if ret in (u"up", u"down"):
225
255
class UserInterface(object):
226
256
"""This is the entire user interface - the whole screen
227
257
with boxes, lists of client widgets, etc.
230
DBusGMainLoop(set_as_default=True )
259
def __init__(self, max_log_length=1000):
260
DBusGMainLoop(set_as_default=True)
232
262
self.screen = urwid.curses_display.Screen()
284
if urwid.supports_unicode():
285
self.divider = u"ā" # \u2500
286
#self.divider = u"ā" # \u2501
288
#self.divider = u"-" # \u002d
289
self.divider = u"_" # \u005f
254
291
self.screen.start()
256
293
self.size = self.screen.get_cols_rows()
258
295
self.clients = urwid.SimpleListWalker([])
259
296
self.clients_dict = {}
260
self.topwidget = urwid.LineBox(urwid.ListBox(self.clients))
261
#self.topwidget = urwid.ListBox(clients)
298
# We will add Text widgets to this list
300
self.max_log_length = max_log_length
302
# We keep a reference to the log widget so we can remove it
303
# from the ListWalker without it getting destroyed
304
self.logbox = ConstrainedListBox(self.log)
306
# This keeps track of whether self.uilist currently has
307
# self.logbox in it or not
308
self.log_visible = True
309
self.log_wrap = u"any"
312
self.log_message_raw((u"bold",
313
u"Mandos Monitor version " + version))
314
self.log_message_raw((u"bold",
263
317
self.busname = domain + '.Mandos'
264
318
self.main_loop = gobject.MainLoop()
275
329
mandos_clients = dbus.Dictionary()
277
331
(self.mandos_serv
278
.connect_to_signal("ClientRemoved",
332
.connect_to_signal(u"ClientRemoved",
279
333
self.find_and_remove_client,
280
334
dbus_interface=server_interface,
281
335
byte_arrays=True))
282
336
(self.mandos_serv
283
.connect_to_signal("ClientAdded",
337
.connect_to_signal(u"ClientAdded",
284
338
self.add_new_client,
285
339
dbus_interface=server_interface,
286
340
byte_arrays=True))
287
for path, client in (mandos_clients.iteritems()):
342
.connect_to_signal(u"ClientNotFound",
343
self.client_not_found,
344
dbus_interface=server_interface,
346
for path, client in mandos_clients.iteritems():
288
347
client_proxy_object = self.bus.get_object(self.busname,
290
349
self.add_client(MandosClientWidget(server_proxy_object
298
=self.remove_client),
362
def client_not_found(self, fingerprint, address):
363
self.log_message((u"Client with address %s and fingerprint %s"
364
u" could not be found" % (address,
368
"""This rebuilds the User Interface.
369
Call this when the widget layout needs to change"""
371
#self.uilist.append(urwid.ListBox(self.clients))
372
self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
373
#header=urwid.Divider(),
375
footer=urwid.Divider(div_char=self.divider)))
377
self.uilist.append(self.logbox)
379
self.topwidget = urwid.Pile(self.uilist)
381
def log_message(self, message):
382
timestamp = datetime.datetime.now().isoformat()
383
self.log_message_raw(timestamp + u": " + message)
385
def log_message_raw(self, markup):
386
"""Add a log message to the log buffer."""
387
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
388
if (self.max_log_length
389
and len(self.log) > self.max_log_length):
390
del self.log[0:len(self.log)-self.max_log_length-1]
391
self.logbox.set_focus(len(self.logbox.body.contents),
392
coming_from=u"above")
395
def toggle_log_display(self):
396
"""Toggle visibility of the log buffer."""
397
self.log_visible = not self.log_visible
399
self.log_message(u"Log visibility changed to: "
400
+ unicode(self.log_visible))
402
def change_log_display(self):
403
"""Change type of log display.
404
Currently, this toggles wrapping of text lines."""
405
if self.log_wrap == u"clip":
406
self.log_wrap = u"any"
408
self.log_wrap = u"clip"
409
for textwidget in self.log:
410
textwidget.set_wrap_mode(self.log_wrap)
411
self.log_message(u"Wrap mode: " + self.log_wrap)
301
413
def find_and_remove_client(self, path, name):
302
414
"""Find an client from its object path and remove it.
311
423
self.remove_client(client, path)
313
def add_new_client(self, path, properties):
425
def add_new_client(self, path):
314
426
client_proxy_object = self.bus.get_object(self.busname, path)
315
427
self.add_client(MandosClientWidget(server_proxy_object
316
428
=self.mandos_serv,
318
430
=client_proxy_object,
319
properties=properties,
323
=self.remove_client),
326
439
def add_client(self, client, path=None):
361
479
def process_input(self, source, condition):
362
480
keys = self.screen.get_input()
363
translations = { u"j": u"down",
481
translations = { u"ctrl n": u"down", # Emacs
482
u"ctrl p": u"up", # Emacs
483
u"ctrl v": u"page down", # Emacs
484
u"meta v": u"page up", # Emacs
485
u" ": u"page down", # less
486
u"f": u"page down", # less
487
u"b": u"page up", # less
375
500
elif key == u"window resize":
376
501
self.size = self.screen.get_cols_rows()
503
elif key == u"\f": # Ctrl-L
505
elif key == u"l" or key == u"D":
506
self.toggle_log_display()
508
elif key == u"w" or key == u"i":
509
self.change_log_display()
511
elif key == u"?" or key == u"f1" or key == u"esc":
512
if not self.log_visible:
513
self.log_visible = True
515
self.log_message_raw((u"bold",
519
u"l: Log window toggle",
520
u"TAB: Switch window",
522
self.log_message_raw((u"bold",
528
u"s: Start new checker",
533
if self.topwidget.get_focus() is self.logbox:
534
self.topwidget.set_focus(0)
536
self.topwidget.set_focus(self.logbox)
538
#elif (key == u"end" or key == u"meta >" or key == u"G"
540
# pass # xxx end-of-buffer
541
#elif (key == u"home" or key == u"meta <" or key == u"g"
543
# pass # xxx beginning-of-buffer
544
#elif key == u"ctrl e" or key == u"$":
545
# pass # xxx move-end-of-line
546
#elif key == u"ctrl a" or key == u"^":
547
# pass # xxx move-beginning-of-line
548
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
550
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
553
# pass # scroll up log
555
# pass # scroll down log
380
556
elif self.topwidget.selectable():
381
557
self.topwidget.keypress(self.size, key)