/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-monitor

  • Committer: Björn Påhlsson
  • Date: 2010-09-07 20:22:16 UTC
  • mto: (237.4.3 mandos-release)
  • mto: This revision was merged to the branch mainline in revision 421.
  • Revision ID: belorn@fukt.bsnet.se-20100907202216-vypcn3nsd02mqz4s
mandos-monitor: removed milisecondsseconds from countdown.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- mode: python; coding: utf-8 -*-
 
3
 
 
4
from __future__ import division, absolute_import, with_statement
 
5
 
 
6
import sys
 
7
import os
 
8
import signal
 
9
 
 
10
import datetime
 
11
 
 
12
import urwid.curses_display
 
13
import urwid
 
14
 
 
15
from dbus.mainloop.glib import DBusGMainLoop
 
16
import gobject
 
17
 
 
18
import dbus
 
19
 
 
20
import UserList
 
21
 
 
22
import locale
 
23
 
 
24
locale.setlocale(locale.LC_ALL, u'')
 
25
 
 
26
import logging
 
27
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
 
28
 
 
29
# Some useful constants
 
30
domain = 'se.bsnet.fukt'
 
31
server_interface = domain + '.Mandos'
 
32
client_interface = domain + '.Mandos.Client'
 
33
version = "1.0.15"
 
34
 
 
35
# Always run in monochrome mode
 
36
urwid.curses_display.curses.has_colors = lambda : False
 
37
 
 
38
# Urwid doesn't support blinking, but we want it.  Since we have no
 
39
# use for underline on its own, we make underline also always blink.
 
40
urwid.curses_display.curses.A_UNDERLINE |= (
 
41
    urwid.curses_display.curses.A_BLINK)
 
42
 
 
43
def isoformat_to_datetime(iso):
 
44
    "Parse an ISO 8601 date string to a datetime.datetime()"
 
45
    if not iso:
 
46
        return None
 
47
    d, t = iso.split(u"T", 1)
 
48
    year, month, day = d.split(u"-", 2)
 
49
    hour, minute, second = t.split(u":", 2)
 
50
    second, fraction = divmod(float(second), 1)
 
51
    return datetime.datetime(int(year),
 
52
                             int(month),
 
53
                             int(day),
 
54
                             int(hour),
 
55
                             int(minute),
 
56
                             int(second),           # Whole seconds
 
57
                             int(fraction*1000000)) # Microseconds
 
58
 
 
59
class MandosClientPropertyCache(object):
 
60
    """This wraps a Mandos Client D-Bus proxy object, caches the
 
61
    properties and calls a hook function when any of them are
 
62
    changed.
 
63
    """
 
64
    def __init__(self, proxy_object=None, *args, **kwargs):
 
65
        self.proxy = proxy_object # Mandos Client proxy object
 
66
        
 
67
        self.properties = dict()
 
68
        self.proxy.connect_to_signal(u"PropertyChanged",
 
69
                                     self.property_changed,
 
70
                                     client_interface,
 
71
                                     byte_arrays=True)
 
72
        
 
73
        self.properties.update(
 
74
            self.proxy.GetAll(client_interface,
 
75
                              dbus_interface = dbus.PROPERTIES_IFACE))
 
76
 
 
77
        #XXX This break good super behaviour!
 
78
#        super(MandosClientPropertyCache, self).__init__(
 
79
#            *args, **kwargs)
 
80
    
 
81
    def property_changed(self, property=None, value=None):
 
82
        """This is called whenever we get a PropertyChanged signal
 
83
        It updates the changed property in the "properties" dict.
 
84
        """
 
85
        # Update properties dict with new value
 
86
        self.properties[property] = value
 
87
 
 
88
 
 
89
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
 
90
    """A Mandos Client which is visible on the screen.
 
91
    """
 
92
    
 
93
    def __init__(self, server_proxy_object=None, update_hook=None,
 
94
                 delete_hook=None, logger=None, *args, **kwargs):
 
95
        # Called on update
 
96
        self.update_hook = update_hook
 
97
        # Called on delete
 
98
        self.delete_hook = delete_hook
 
99
        # Mandos Server proxy object
 
100
        self.server_proxy_object = server_proxy_object
 
101
        # Logger
 
102
        self.logger = logger
 
103
        
 
104
        self._update_timer_callback_tag = None
 
105
        self.last_checker_failed = False
 
106
        
 
107
        # The widget shown normally
 
108
        self._text_widget = urwid.Text(u"")
 
109
        # The widget shown when we have focus
 
110
        self._focus_text_widget = urwid.Text(u"")
 
111
        super(MandosClientWidget, self).__init__(
 
112
            update_hook=update_hook, delete_hook=delete_hook,
 
113
            *args, **kwargs)
 
114
        self.update()
 
115
        self.opened = False
 
116
        self.proxy.connect_to_signal(u"CheckerCompleted",
 
117
                                     self.checker_completed,
 
118
                                     client_interface,
 
119
                                     byte_arrays=True)
 
120
        self.proxy.connect_to_signal(u"CheckerStarted",
 
121
                                     self.checker_started,
 
122
                                     client_interface,
 
123
                                     byte_arrays=True)
 
124
        self.proxy.connect_to_signal(u"GotSecret",
 
125
                                     self.got_secret,
 
126
                                     client_interface,
 
127
                                     byte_arrays=True)
 
128
        self.proxy.connect_to_signal(u"NeedApproval",
 
129
                                     self.need_approval,
 
130
                                     client_interface,
 
131
                                     byte_arrays=True)
 
132
        self.proxy.connect_to_signal(u"Rejected",
 
133
                                     self.rejected,
 
134
                                     client_interface,
 
135
                                     byte_arrays=True)
 
136
        last_checked_ok = isoformat_to_datetime(self.properties
 
137
                                                ["last_checked_ok"])
 
138
        if last_checked_ok is None:
 
139
            self.last_checker_failed = True
 
140
        else:
 
141
            self.last_checker_failed = ((datetime.datetime.utcnow()
 
142
                                         - last_checked_ok)
 
143
                                        > datetime.timedelta
 
144
                                        (milliseconds=
 
145
                                         self.properties["interval"]))
 
146
        if self.last_checker_failed:
 
147
            self._update_timer_callback_tag = (gobject.timeout_add
 
148
                                               (1000,
 
149
                                                self.update_timer))
 
150
    
 
151
    def checker_completed(self, exitstatus, condition, command):
 
152
        if exitstatus == 0:
 
153
            if self.last_checker_failed:
 
154
                self.last_checker_failed = False
 
155
                gobject.source_remove(self._update_timer_callback_tag)
 
156
                self._update_timer_callback_tag = None
 
157
            self.logger(u'Checker for client %s (command "%s")'
 
158
                        u' was successful'
 
159
                        % (self.properties[u"name"], command))
 
160
            self.update()
 
161
            return
 
162
        # Checker failed
 
163
        if not self.last_checker_failed:
 
164
            self.last_checker_failed = True
 
165
            self._update_timer_callback_tag = (gobject.timeout_add
 
166
                                               (1000,
 
167
                                                self.update_timer))
 
168
        if os.WIFEXITED(condition):
 
169
            self.logger(u'Checker for client %s (command "%s")'
 
170
                        u' failed with exit code %s'
 
171
                        % (self.properties[u"name"], command,
 
172
                           os.WEXITSTATUS(condition)))
 
173
        elif os.WIFSIGNALED(condition):
 
174
            self.logger(u'Checker for client %s (command "%s")'
 
175
                        u' was killed by signal %s'
 
176
                        % (self.properties[u"name"], command,
 
177
                           os.WTERMSIG(condition)))
 
178
        elif os.WCOREDUMP(condition):
 
179
            self.logger(u'Checker for client %s (command "%s")'
 
180
                        u' dumped core'
 
181
                        % (self.properties[u"name"], command))
 
182
        else:
 
183
            self.logger(u'Checker for client %s completed mysteriously')
 
184
        self.update()
 
185
    
 
186
    def checker_started(self, command):
 
187
        #self.logger(u'Client %s started checker "%s"'
 
188
        #            % (self.properties[u"name"], unicode(command)))
 
189
        pass
 
190
    
 
191
    def got_secret(self):
 
192
        self.logger(u'Client %s received its secret'
 
193
                    % self.properties[u"name"])
 
194
    
 
195
    def need_approval(self, timeout, default):
 
196
        if not default:
 
197
            message = u'Client %s needs approval within %s seconds'
 
198
        else:
 
199
            message = u'Client %s will get its secret in %s seconds'
 
200
        self.logger(message
 
201
                    % (self.properties[u"name"], timeout/1000))
 
202
    
 
203
    def rejected(self, reason):
 
204
        self.logger(u'Client %s was rejected; reason: %s'
 
205
                    % (self.properties[u"name"], reason))
 
206
    
 
207
    def selectable(self):
 
208
        """Make this a "selectable" widget.
 
209
        This overrides the method from urwid.FlowWidget."""
 
210
        return True
 
211
    
 
212
    def rows(self, (maxcol,), focus=False):
 
213
        """How many rows this widget will occupy might depend on
 
214
        whether we have focus or not.
 
215
        This overrides the method from urwid.FlowWidget"""
 
216
        return self.current_widget(focus).rows((maxcol,), focus=focus)
 
217
    
 
218
    def current_widget(self, focus=False):
 
219
        if focus or self.opened:
 
220
            return self._focus_widget
 
221
        return self._widget
 
222
    
 
223
    def update(self):
 
224
        "Called when what is visible on the screen should be updated."
 
225
        # How to add standout mode to a style
 
226
        with_standout = { u"normal": u"standout",
 
227
                          u"bold": u"bold-standout",
 
228
                          u"underline-blink":
 
229
                              u"underline-blink-standout",
 
230
                          u"bold-underline-blink":
 
231
                              u"bold-underline-blink-standout",
 
232
                          }
 
233
 
 
234
        # Rebuild focus and non-focus widgets using current properties
 
235
 
 
236
        # Base part of a client. Name!
 
237
        base = (u'%(name)s: '
 
238
                      % {u"name": self.properties[u"name"]})
 
239
        if not self.properties[u"enabled"]:
 
240
            message = u"DISABLED"
 
241
        elif self.last_checker_failed:
 
242
            timeout = datetime.timedelta(milliseconds
 
243
                                         = self.properties[u"timeout"])
 
244
            last_ok = isoformat_to_datetime(
 
245
                max((self.properties["last_checked_ok"]
 
246
                     or self.properties["created"]),
 
247
                    self.properties[u"last_enabled"]))
 
248
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
 
249
            message = (u'A checker has failed! Time until client gets diabled: %s'
 
250
                           % unicode(timer).rsplit(".", 1)[0])
 
251
        elif self.properties[u"approved_pending"]:
 
252
            if self.properties[u"approved_by_default"]:
 
253
                message = u"Connection established to client. (d)eny?"
 
254
            else:
 
255
                message = u"Seeks approval to send secret. (a)pprove?"
 
256
        else:
 
257
            message = u"enabled"
 
258
        self._text = "%s%s" % (base, message)
 
259
            
 
260
        if not urwid.supports_unicode():
 
261
            self._text = self._text.encode("ascii", "replace")
 
262
        textlist = [(u"normal", self._text)]
 
263
        self._text_widget.set_text(textlist)
 
264
        self._focus_text_widget.set_text([(with_standout[text[0]],
 
265
                                           text[1])
 
266
                                          if isinstance(text, tuple)
 
267
                                          else text
 
268
                                          for text in textlist])
 
269
        self._widget = self._text_widget
 
270
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
 
271
                                            "standout")
 
272
        # Run update hook, if any
 
273
        if self.update_hook is not None:
 
274
            self.update_hook()
 
275
    
 
276
    def update_timer(self):
 
277
        "called by gobject"
 
278
        self.update()
 
279
        return True             # Keep calling this
 
280
    
 
281
    def delete(self):
 
282
        if self._update_timer_callback_tag is not None:
 
283
            gobject.source_remove(self._update_timer_callback_tag)
 
284
            self._update_timer_callback_tag = None
 
285
        if self.delete_hook is not None:
 
286
            self.delete_hook(self)
 
287
    
 
288
    def render(self, (maxcol,), focus=False):
 
289
        """Render differently if we have focus.
 
290
        This overrides the method from urwid.FlowWidget"""
 
291
        return self.current_widget(focus).render((maxcol,),
 
292
                                                 focus=focus)
 
293
    
 
294
    def keypress(self, (maxcol,), key):
 
295
        """Handle keys.
 
296
        This overrides the method from urwid.FlowWidget"""
 
297
        if key == u"+":
 
298
            self.proxy.Enable(dbus_interface = client_interface)
 
299
        elif key == u"-":
 
300
            self.proxy.Disable(dbus_interface = client_interface)
 
301
        elif key == u"a":
 
302
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
303
                               dbus_interface = client_interface)
 
304
        elif key == u"d":
 
305
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
 
306
                                  dbus_interface = client_interface)
 
307
        elif key == u"r" or key == u"_" or key == u"ctrl k":
 
308
            self.server_proxy_object.RemoveClient(self.proxy
 
309
                                                  .object_path)
 
310
        elif key == u"s":
 
311
            self.proxy.StartChecker(dbus_interface = client_interface)
 
312
        elif key == u"S":
 
313
            self.proxy.StopChecker(dbus_interface = client_interface)
 
314
        elif key == u"C":
 
315
            self.proxy.CheckedOK(dbus_interface = client_interface)
 
316
        # xxx
 
317
#         elif key == u"p" or key == "=":
 
318
#             self.proxy.pause()
 
319
#         elif key == u"u" or key == ":":
 
320
#             self.proxy.unpause()
 
321
#         elif key == u"RET":
 
322
#             self.open()
 
323
#        elif key == u"+":
 
324
#            self.proxy.Approve(True)
 
325
#        elif key == u"-":
 
326
#            self.proxy.Approve(False)
 
327
        else:
 
328
            return key
 
329
    
 
330
    def property_changed(self, property=None, value=None,
 
331
                         *args, **kwargs):
 
332
        """Call self.update() if old value is not new value.
 
333
        This overrides the method from MandosClientPropertyCache"""
 
334
        property_name = unicode(property)
 
335
        old_value = self.properties.get(property_name)
 
336
        super(MandosClientWidget, self).property_changed(
 
337
            property=property, value=value, *args, **kwargs)
 
338
        if self.properties.get(property_name) != old_value:
 
339
            self.update()
 
340
 
 
341
 
 
342
class ConstrainedListBox(urwid.ListBox):
 
343
    """Like a normal urwid.ListBox, but will consume all "up" or
 
344
    "down" key presses, thus not allowing any containing widgets to
 
345
    use them as an excuse to shift focus away from this widget.
 
346
    """
 
347
    def keypress(self, (maxcol, maxrow), key):
 
348
        ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
 
349
        if ret in (u"up", u"down"):
 
350
            return
 
351
        return ret
 
352
 
 
353
 
 
354
class UserInterface(object):
 
355
    """This is the entire user interface - the whole screen
 
356
    with boxes, lists of client widgets, etc.
 
357
    """
 
358
    def __init__(self, max_log_length=1000):
 
359
        DBusGMainLoop(set_as_default=True)
 
360
        
 
361
        self.screen = urwid.curses_display.Screen()
 
362
        
 
363
        self.screen.register_palette((
 
364
                (u"normal",
 
365
                 u"default", u"default", None),
 
366
                (u"bold",
 
367
                 u"default", u"default", u"bold"),
 
368
                (u"underline-blink",
 
369
                 u"default", u"default", u"underline"),
 
370
                (u"standout",
 
371
                 u"default", u"default", u"standout"),
 
372
                (u"bold-underline-blink",
 
373
                 u"default", u"default", (u"bold", u"underline")),
 
374
                (u"bold-standout",
 
375
                 u"default", u"default", (u"bold", u"standout")),
 
376
                (u"underline-blink-standout",
 
377
                 u"default", u"default", (u"underline", u"standout")),
 
378
                (u"bold-underline-blink-standout",
 
379
                 u"default", u"default", (u"bold", u"underline",
 
380
                                          u"standout")),
 
381
                ))
 
382
        
 
383
        if urwid.supports_unicode():
 
384
            self.divider = u"─" # \u2500
 
385
            #self.divider = u"━" # \u2501
 
386
        else:
 
387
            #self.divider = u"-" # \u002d
 
388
            self.divider = u"_" # \u005f
 
389
        
 
390
        self.screen.start()
 
391
        
 
392
        self.size = self.screen.get_cols_rows()
 
393
        
 
394
        self.clients = urwid.SimpleListWalker([])
 
395
        self.clients_dict = {}
 
396
        
 
397
        # We will add Text widgets to this list
 
398
        self.log = []
 
399
        self.max_log_length = max_log_length
 
400
        
 
401
        # We keep a reference to the log widget so we can remove it
 
402
        # from the ListWalker without it getting destroyed
 
403
        self.logbox = ConstrainedListBox(self.log)
 
404
        
 
405
        # This keeps track of whether self.uilist currently has
 
406
        # self.logbox in it or not
 
407
        self.log_visible = True
 
408
        self.log_wrap = u"any"
 
409
        
 
410
        self.rebuild()
 
411
        self.log_message_raw((u"bold",
 
412
                              u"Mandos Monitor version " + version))
 
413
        self.log_message_raw((u"bold",
 
414
                              u"q: Quit  ?: Help"))
 
415
        
 
416
        self.busname = domain + '.Mandos'
 
417
        self.main_loop = gobject.MainLoop()
 
418
        self.bus = dbus.SystemBus()
 
419
        mandos_dbus_objc = self.bus.get_object(
 
420
            self.busname, u"/", follow_name_owner_changes=True)
 
421
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
422
                                          dbus_interface
 
423
                                          = server_interface)
 
424
        try:
 
425
            mandos_clients = (self.mandos_serv
 
426
                              .GetAllClientsWithProperties())
 
427
        except dbus.exceptions.DBusException:
 
428
            mandos_clients = dbus.Dictionary()
 
429
        
 
430
        (self.mandos_serv
 
431
         .connect_to_signal(u"ClientRemoved",
 
432
                            self.find_and_remove_client,
 
433
                            dbus_interface=server_interface,
 
434
                            byte_arrays=True))
 
435
        (self.mandos_serv
 
436
         .connect_to_signal(u"ClientAdded",
 
437
                            self.add_new_client,
 
438
                            dbus_interface=server_interface,
 
439
                            byte_arrays=True))
 
440
        (self.mandos_serv
 
441
         .connect_to_signal(u"ClientNotFound",
 
442
                            self.client_not_found,
 
443
                            dbus_interface=server_interface,
 
444
                            byte_arrays=True))
 
445
        for path, client in mandos_clients.iteritems():
 
446
            client_proxy_object = self.bus.get_object(self.busname,
 
447
                                                      path)
 
448
            self.add_client(MandosClientWidget(server_proxy_object
 
449
                                               =self.mandos_serv,
 
450
                                               proxy_object
 
451
                                               =client_proxy_object,
 
452
                                               properties=client,
 
453
                                               update_hook
 
454
                                               =self.refresh,
 
455
                                               delete_hook
 
456
                                               =self.remove_client,
 
457
                                               logger
 
458
                                               =self.log_message),
 
459
                            path=path)
 
460
    
 
461
    def client_not_found(self, fingerprint, address):
 
462
        self.log_message((u"Client with address %s and fingerprint %s"
 
463
                          u" could not be found" % (address,
 
464
                                                    fingerprint)))
 
465
    
 
466
    def rebuild(self):
 
467
        """This rebuilds the User Interface.
 
468
        Call this when the widget layout needs to change"""
 
469
        self.uilist = []
 
470
        #self.uilist.append(urwid.ListBox(self.clients))
 
471
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
 
472
                                       #header=urwid.Divider(),
 
473
                                       header=None,
 
474
                                       footer=urwid.Divider(div_char=self.divider)))
 
475
        if self.log_visible:
 
476
            self.uilist.append(self.logbox)
 
477
            pass
 
478
        self.topwidget = urwid.Pile(self.uilist)
 
479
    
 
480
    def log_message(self, message):
 
481
        timestamp = datetime.datetime.now().isoformat()
 
482
        self.log_message_raw(timestamp + u": " + message)
 
483
    
 
484
    def log_message_raw(self, markup):
 
485
        """Add a log message to the log buffer."""
 
486
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
 
487
        if (self.max_log_length
 
488
            and len(self.log) > self.max_log_length):
 
489
            del self.log[0:len(self.log)-self.max_log_length-1]
 
490
        self.logbox.set_focus(len(self.logbox.body.contents),
 
491
                              coming_from=u"above")
 
492
        self.refresh()
 
493
    
 
494
    def toggle_log_display(self):
 
495
        """Toggle visibility of the log buffer."""
 
496
        self.log_visible = not self.log_visible
 
497
        self.rebuild()
 
498
        self.log_message(u"Log visibility changed to: "
 
499
                         + unicode(self.log_visible))
 
500
    
 
501
    def change_log_display(self):
 
502
        """Change type of log display.
 
503
        Currently, this toggles wrapping of text lines."""
 
504
        if self.log_wrap == u"clip":
 
505
            self.log_wrap = u"any"
 
506
        else:
 
507
            self.log_wrap = u"clip"
 
508
        for textwidget in self.log:
 
509
            textwidget.set_wrap_mode(self.log_wrap)
 
510
        self.log_message(u"Wrap mode: " + self.log_wrap)
 
511
    
 
512
    def find_and_remove_client(self, path, name):
 
513
        """Find an client from its object path and remove it.
 
514
        
 
515
        This is connected to the ClientRemoved signal from the
 
516
        Mandos server object."""
 
517
        try:
 
518
            client = self.clients_dict[path]
 
519
        except KeyError:
 
520
            # not found?
 
521
            return
 
522
        self.remove_client(client, path)
 
523
    
 
524
    def add_new_client(self, path):
 
525
        client_proxy_object = self.bus.get_object(self.busname, path)
 
526
        self.add_client(MandosClientWidget(server_proxy_object
 
527
                                           =self.mandos_serv,
 
528
                                           proxy_object
 
529
                                           =client_proxy_object,
 
530
                                           update_hook
 
531
                                           =self.refresh,
 
532
                                           delete_hook
 
533
                                           =self.remove_client,
 
534
                                           logger
 
535
                                           =self.log_message),
 
536
                        path=path)
 
537
    
 
538
    def add_client(self, client, path=None):
 
539
        self.clients.append(client)
 
540
        if path is None:
 
541
            path = client.proxy.object_path
 
542
        self.clients_dict[path] = client
 
543
        self.clients.sort(None, lambda c: c.properties[u"name"])
 
544
        self.refresh()
 
545
    
 
546
    def remove_client(self, client, path=None):
 
547
        self.clients.remove(client)
 
548
        if path is None:
 
549
            path = client.proxy.object_path
 
550
        del self.clients_dict[path]
 
551
        if not self.clients_dict:
 
552
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
 
553
            # is completely emptied, we need to recreate it.
 
554
            self.clients = urwid.SimpleListWalker([])
 
555
            self.rebuild()
 
556
        self.refresh()
 
557
    
 
558
    def refresh(self):
 
559
        """Redraw the screen"""
 
560
        canvas = self.topwidget.render(self.size, focus=True)
 
561
        self.screen.draw_screen(self.size, canvas)
 
562
    
 
563
    def run(self):
 
564
        """Start the main loop and exit when it's done."""
 
565
        self.refresh()
 
566
        self._input_callback_tag = (gobject.io_add_watch
 
567
                                    (sys.stdin.fileno(),
 
568
                                     gobject.IO_IN,
 
569
                                     self.process_input))
 
570
        self.main_loop.run()
 
571
        # Main loop has finished, we should close everything now
 
572
        gobject.source_remove(self._input_callback_tag)
 
573
        self.screen.stop()
 
574
    
 
575
    def stop(self):
 
576
        self.main_loop.quit()
 
577
    
 
578
    def process_input(self, source, condition):
 
579
        keys = self.screen.get_input()
 
580
        translations = { u"ctrl n": u"down",      # Emacs
 
581
                         u"ctrl p": u"up",        # Emacs
 
582
                         u"ctrl v": u"page down", # Emacs
 
583
                         u"meta v": u"page up",   # Emacs
 
584
                         u" ": u"page down",      # less
 
585
                         u"f": u"page down",      # less
 
586
                         u"b": u"page up",        # less
 
587
                         u"j": u"down",           # vi
 
588
                         u"k": u"up",             # vi
 
589
                         }
 
590
        for key in keys:
 
591
            try:
 
592
                key = translations[key]
 
593
            except KeyError:    # :-)
 
594
                pass
 
595
            
 
596
            if key == u"q" or key == u"Q":
 
597
                self.stop()
 
598
                break
 
599
            elif key == u"window resize":
 
600
                self.size = self.screen.get_cols_rows()
 
601
                self.refresh()
 
602
            elif key == u"\f":  # Ctrl-L
 
603
                self.refresh()
 
604
            elif key == u"l" or key == u"D":
 
605
                self.toggle_log_display()
 
606
                self.refresh()
 
607
            elif key == u"w" or key == u"i":
 
608
                self.change_log_display()
 
609
                self.refresh()
 
610
            elif key == u"?" or key == u"f1" or key == u"esc":
 
611
                if not self.log_visible:
 
612
                    self.log_visible = True
 
613
                    self.rebuild()
 
614
                self.log_message_raw((u"bold",
 
615
                                      u"  ".
 
616
                                      join((u"q: Quit",
 
617
                                            u"?: Help",
 
618
                                            u"l: Log window toggle",
 
619
                                            u"TAB: Switch window",
 
620
                                            u"w: Wrap (log)"))))
 
621
                self.log_message_raw((u"bold",
 
622
                                      u"  "
 
623
                                      .join((u"Clients:",
 
624
                                             u"+: Enable",
 
625
                                             u"-: Disable",
 
626
                                             u"r: Remove",
 
627
                                             u"s: Start new checker",
 
628
                                             u"S: Stop checker",
 
629
                                             u"C: Checker OK",
 
630
                                             u"a: Approve",
 
631
                                             u"d: Deny"))))
 
632
                self.refresh()
 
633
            elif key == u"tab":
 
634
                if self.topwidget.get_focus() is self.logbox:
 
635
                    self.topwidget.set_focus(0)
 
636
                else:
 
637
                    self.topwidget.set_focus(self.logbox)
 
638
                self.refresh()
 
639
            #elif (key == u"end" or key == u"meta >" or key == u"G"
 
640
            #      or key == u">"):
 
641
            #    pass            # xxx end-of-buffer
 
642
            #elif (key == u"home" or key == u"meta <" or key == u"g"
 
643
            #      or key == u"<"):
 
644
            #    pass            # xxx beginning-of-buffer
 
645
            #elif key == u"ctrl e" or key == u"$":
 
646
            #    pass            # xxx move-end-of-line
 
647
            #elif key == u"ctrl a" or key == u"^":
 
648
            #    pass            # xxx move-beginning-of-line
 
649
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
650
            #    pass            # xxx left
 
651
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
652
            #    pass            # xxx right
 
653
            #elif key == u"a":
 
654
            #    pass            # scroll up log
 
655
            #elif key == u"z":
 
656
            #    pass            # scroll down log
 
657
            elif self.topwidget.selectable():
 
658
                self.topwidget.keypress(self.size, key)
 
659
                self.refresh()
 
660
        return True
 
661
 
 
662
ui = UserInterface()
 
663
try:
 
664
    ui.run()
 
665
except Exception, e:
 
666
    ui.log_message(unicode(e))
 
667
    ui.screen.stop()
 
668
    raise