/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

merge
new approve/deny functionallity in mandos-monitor

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