45
35
properties and calls a hook function when any of them are
48
def __init__(self, proxy_object=None, *args, **kwargs):
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,
49
59
self.proxy = proxy_object # Mandos Client proxy object
51
self.properties = dict()
52
self.proxy.connect_to_signal(u"PropertyChanged",
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",
53
68
self.property_changed,
57
self.properties.update(
58
self.proxy.GetAll(client_interface,
59
dbus_interface = dbus.PROPERTIES_IFACE))
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())
60
79
super(MandosClientPropertyCache, self).__init__(
61
proxy_object=proxy_object, *args, **kwargs)
80
proxy_object=proxy_object,
81
properties=properties, *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)
63
98
def property_changed(self, property=None, value=None):
64
99
"""This is called whenever we get a PropertyChanged signal
65
100
It updates the changed property in the "properties" dict.
102
# Convert name and value
103
property_name, cvalue = self.convert_property(property, value)
67
104
# Update properties dict with new value
68
self.properties[property] = value
105
self.properties[property_name] = cvalue
71
108
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
75
112
def __init__(self, server_proxy_object=None, update_hook=None,
76
delete_hook=None, logger=None, *args, **kwargs):
113
delete_hook=None, *args, **kwargs):
77
114
# Called on update
78
115
self.update_hook = update_hook
79
116
# Called on delete
80
117
self.delete_hook = delete_hook
81
118
# Mandos Server proxy object
82
119
self.server_proxy_object = server_proxy_object
86
121
# The widget shown normally
87
self._text_widget = urwid.Text(u"")
122
self._text_widget = urwid.Text("")
88
123
# The widget shown when we have focus
89
self._focus_text_widget = urwid.Text(u"")
124
self._focus_text_widget = urwid.Text("")
90
125
super(MandosClientWidget, self).__init__(
91
126
update_hook=update_hook, delete_hook=delete_hook,
94
129
self.opened = False
95
self.proxy.connect_to_signal(u"CheckerCompleted",
96
self.checker_completed,
99
self.proxy.connect_to_signal(u"CheckerStarted",
100
self.checker_started,
103
self.proxy.connect_to_signal(u"GotSecret",
107
self.proxy.connect_to_signal(u"NeedApproval",
111
self.proxy.connect_to_signal(u"Rejected",
116
def checker_completed(self, exitstatus, condition, command):
118
#self.logger(u'Checker for client %s (command "%s")'
120
# % (self.properties[u"name"], command))
122
if os.WIFEXITED(condition):
123
self.logger(u'Checker for client %s (command "%s")'
124
u' failed with exit code %s'
125
% (self.properties[u"name"], command,
126
os.WEXITSTATUS(condition)))
128
if os.WIFSIGNALED(condition):
129
self.logger(u'Checker for client %s (command "%s")'
130
u' was killed by signal %s'
131
% (self.properties[u"name"], command,
132
os.WTERMSIG(condition)))
134
if os.WCOREDUMP(condition):
135
self.logger(u'Checker for client %s (command "%s")'
137
% (self.properties[u"name"], command))
138
self.logger(u'Checker for client %s completed mysteriously')
140
def checker_started(self, command):
141
#self.logger(u'Client %s started checker "%s"'
142
# % (self.properties[u"name"], unicode(command)))
145
def got_secret(self):
146
self.logger(u'Client %s received its secret'
147
% self.properties[u"name"])
149
def need_approval(self, timeout, default):
151
message = u'Client %s needs approval within %s seconds'
153
message = u'Client %s will get its secret in %s seconds'
155
% (self.properties[u"name"], timeout/1000))
157
def rejected(self, reason):
158
self.logger(u'Client %s was rejected; reason: %s'
159
% (self.properties[u"name"], reason))
161
131
def selectable(self):
162
132
"""Make this a "selectable" widget.
188
158
# Rebuild focus and non-focus widgets using current properties
189
self._text = (u'%(name)s: %(enabled)s'
190
% { u"name": self.properties[u"name"],
193
if self.properties[u"enabled"]
159
self._text = (u'name="%(name)s", enabled=%(enabled)s'
195
161
if not urwid.supports_unicode():
196
162
self._text = self._text.encode("ascii", "replace")
197
textlist = [(u"normal", self._text)]
163
textlist = [(u"normal", u"BLÄRGH: "), (u"bold", self._text)]
198
164
self._text_widget.set_text(textlist)
199
165
self._focus_text_widget.set_text([(with_standout[text[0]],
263
class ConstrainedListBox(urwid.ListBox):
264
"""Like a normal urwid.ListBox, but will consume all "up" or
265
"down" key presses, thus not allowing any containing widgets to
266
use them as an excuse to shift focus away from this widget.
268
def keypress(self, (maxcol, maxrow), key):
269
ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
270
if ret in (u"up", u"down"):
275
225
class UserInterface(object):
276
226
"""This is the entire user interface - the whole screen
277
227
with boxes, lists of client widgets, etc.
279
def __init__(self, max_log_length=1000):
280
DBusGMainLoop(set_as_default=True)
230
DBusGMainLoop(set_as_default=True )
282
232
self.screen = urwid.curses_display.Screen()
304
if urwid.supports_unicode():
305
self.divider = u"─" # \u2500
306
#self.divider = u"━" # \u2501
308
#self.divider = u"-" # \u002d
309
self.divider = u"_" # \u005f
311
254
self.screen.start()
313
256
self.size = self.screen.get_cols_rows()
315
258
self.clients = urwid.SimpleListWalker([])
316
259
self.clients_dict = {}
318
# We will add Text widgets to this list
320
self.max_log_length = max_log_length
322
# We keep a reference to the log widget so we can remove it
323
# from the ListWalker without it getting destroyed
324
self.logbox = ConstrainedListBox(self.log)
326
# This keeps track of whether self.uilist currently has
327
# self.logbox in it or not
328
self.log_visible = True
329
self.log_wrap = u"any"
332
self.log_message_raw((u"bold",
333
u"Mandos Monitor version " + version))
334
self.log_message_raw((u"bold",
260
self.topwidget = urwid.LineBox(urwid.ListBox(self.clients))
261
#self.topwidget = urwid.ListBox(clients)
337
263
self.busname = domain + '.Mandos'
338
264
self.main_loop = gobject.MainLoop()
349
275
mandos_clients = dbus.Dictionary()
351
277
(self.mandos_serv
352
.connect_to_signal(u"ClientRemoved",
278
.connect_to_signal("ClientRemoved",
353
279
self.find_and_remove_client,
354
280
dbus_interface=server_interface,
355
281
byte_arrays=True))
356
282
(self.mandos_serv
357
.connect_to_signal(u"ClientAdded",
283
.connect_to_signal("ClientAdded",
358
284
self.add_new_client,
359
285
dbus_interface=server_interface,
360
286
byte_arrays=True))
362
.connect_to_signal(u"ClientNotFound",
363
self.client_not_found,
364
dbus_interface=server_interface,
366
for path, client in mandos_clients.iteritems():
287
for path, client in (mandos_clients.iteritems()):
367
288
client_proxy_object = self.bus.get_object(self.busname,
369
290
self.add_client(MandosClientWidget(server_proxy_object
298
=self.remove_client),
382
def client_not_found(self, fingerprint, address):
383
self.log_message((u"Client with address %s and fingerprint %s"
384
u" could not be found" % (address,
388
"""This rebuilds the User Interface.
389
Call this when the widget layout needs to change"""
391
#self.uilist.append(urwid.ListBox(self.clients))
392
self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
393
#header=urwid.Divider(),
395
footer=urwid.Divider(div_char=self.divider)))
397
self.uilist.append(self.logbox)
399
self.topwidget = urwid.Pile(self.uilist)
401
def log_message(self, message):
402
timestamp = datetime.datetime.now().isoformat()
403
self.log_message_raw(timestamp + u": " + message)
405
def log_message_raw(self, markup):
406
"""Add a log message to the log buffer."""
407
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
408
if (self.max_log_length
409
and len(self.log) > self.max_log_length):
410
del self.log[0:len(self.log)-self.max_log_length-1]
411
self.logbox.set_focus(len(self.logbox.body.contents),
412
coming_from=u"above")
415
def toggle_log_display(self):
416
"""Toggle visibility of the log buffer."""
417
self.log_visible = not self.log_visible
419
self.log_message(u"Log visibility changed to: "
420
+ unicode(self.log_visible))
422
def change_log_display(self):
423
"""Change type of log display.
424
Currently, this toggles wrapping of text lines."""
425
if self.log_wrap == u"clip":
426
self.log_wrap = u"any"
428
self.log_wrap = u"clip"
429
for textwidget in self.log:
430
textwidget.set_wrap_mode(self.log_wrap)
431
self.log_message(u"Wrap mode: " + self.log_wrap)
433
301
def find_and_remove_client(self, path, name):
434
302
"""Find an client from its object path and remove it.
499
361
def process_input(self, source, condition):
500
362
keys = self.screen.get_input()
501
translations = { u"ctrl n": u"down", # Emacs
502
u"ctrl p": u"up", # Emacs
503
u"ctrl v": u"page down", # Emacs
504
u"meta v": u"page up", # Emacs
505
u" ": u"page down", # less
506
u"f": u"page down", # less
507
u"b": u"page up", # less
363
translations = { u"j": u"down",
520
375
elif key == u"window resize":
521
376
self.size = self.screen.get_cols_rows()
523
elif key == u"\f": # Ctrl-L
525
elif key == u"l" or key == u"D":
526
self.toggle_log_display()
528
elif key == u"w" or key == u"i":
529
self.change_log_display()
531
elif key == u"?" or key == u"f1" or key == u"esc":
532
if not self.log_visible:
533
self.log_visible = True
535
self.log_message_raw((u"bold",
539
u"l: Log window toggle",
540
u"TAB: Switch window",
542
self.log_message_raw((u"bold",
548
u"s: Start new checker",
553
if self.topwidget.get_focus() is self.logbox:
554
self.topwidget.set_focus(0)
556
self.topwidget.set_focus(self.logbox)
558
#elif (key == u"end" or key == u"meta >" or key == u"G"
560
# pass # xxx end-of-buffer
561
#elif (key == u"home" or key == u"meta <" or key == u"g"
563
# pass # xxx beginning-of-buffer
564
#elif key == u"ctrl e" or key == u"$":
565
# pass # xxx move-end-of-line
566
#elif key == u"ctrl a" or key == u"^":
567
# pass # xxx move-beginning-of-line
568
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
570
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
573
# pass # scroll up log
575
# pass # scroll down log
576
380
elif self.topwidget.selectable():
577
381
self.topwidget.keypress(self.size, key)