/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos-monitor

  • Committer: Teddy Hogeborn
  • Date: 2008-09-21 12:04:02 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080921120402-mgd2jl8xo634jw18
* Makefile: Put the init script before avahi-daemon.

* debian/mandos.prerm: Bug fix: stop mandos, not ssh.

* debian/rules (install-indep): Put the init script before
                                avahi-daemon.

* init.d/mandos: Require "avahi-daemon".

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