65
66
    "Parse an ISO 8601 date string to a datetime.datetime()"
 
68
 
    d, t = iso.split(u"T", 1)
 
69
 
    year, month, day = d.split(u"-", 2)
 
70
 
    hour, minute, second = t.split(u":", 2)
 
 
69
    d, t = iso.split("T", 1)
 
 
70
    year, month, day = d.split("-", 2)
 
 
71
    hour, minute, second = t.split(":", 2)
 
71
72
    second, fraction = divmod(float(second), 1)
 
72
73
    return datetime.datetime(int(year),
 
 
125
132
        self._update_timer_callback_tag = None
 
126
133
        self._update_timer_callback_lock = 0
 
127
 
        self.last_checker_failed = False
 
129
135
        # The widget shown normally
 
130
 
        self._text_widget = urwid.Text(u"")
 
 
136
        self._text_widget = urwid.Text("")
 
131
137
        # The widget shown when we have focus
 
132
 
        self._focus_text_widget = urwid.Text(u"")
 
 
138
        self._focus_text_widget = urwid.Text("")
 
133
139
        super(MandosClientWidget, self).__init__(
 
134
140
            update_hook=update_hook, delete_hook=delete_hook,
 
 
137
143
        self.opened = False
 
139
145
        last_checked_ok = isoformat_to_datetime(self.properties
 
141
 
        if last_checked_ok is None:
 
142
 
            self.last_checker_failed = True
 
144
 
            self.last_checker_failed = ((datetime.datetime.utcnow()
 
151
 
        if self.last_checker_failed:
 
 
148
        if self.properties ["LastCheckerStatus"] != 0:
 
152
149
            self.using_timer(True)
 
154
151
        if self.need_approval:
 
155
152
            self.using_timer(True)
 
157
 
        self.proxy.connect_to_signal(u"CheckerCompleted",
 
158
 
                                     self.checker_completed,
 
161
 
        self.proxy.connect_to_signal(u"CheckerStarted",
 
162
 
                                     self.checker_started,
 
165
 
        self.proxy.connect_to_signal(u"GotSecret",
 
169
 
        self.proxy.connect_to_signal(u"NeedApproval",
 
173
 
        self.proxy.connect_to_signal(u"Rejected",
 
 
154
        self.match_objects = (
 
 
155
            self.proxy.connect_to_signal("CheckerCompleted",
 
 
156
                                         self.checker_completed,
 
 
159
            self.proxy.connect_to_signal("CheckerStarted",
 
 
160
                                         self.checker_started,
 
 
163
            self.proxy.connect_to_signal("GotSecret",
 
 
167
            self.proxy.connect_to_signal("NeedApproval",
 
 
171
            self.proxy.connect_to_signal("Rejected",
 
 
175
        #self.logger('Created client %s' % (self.properties["Name"]))
 
178
177
    def property_changed(self, property=None, value=None):
 
179
178
        super(self, MandosClientWidget).property_changed(property,
 
181
 
        if property == u"ApprovalPending":
 
 
180
        if property == "ApprovalPending":
 
182
181
            using_timer(bool(value))
 
 
182
        if property == "LastCheckerStatus":
 
 
183
            using_timer(value != 0)
 
 
184
            #self.logger('Checker for client %s (command "%s")'
 
 
186
            #            % (self.properties["Name"], command))
 
184
188
    def using_timer(self, flag):
 
185
189
        """Call this method with True or False when timer should be
 
186
190
        activated or deactivated.
 
 
201
206
    def checker_completed(self, exitstatus, condition, command):
 
202
207
        if exitstatus == 0:
 
203
 
            if self.last_checker_failed:
 
204
 
                self.last_checker_failed = False
 
205
 
                self.using_timer(False)
 
206
 
            #self.logger(u'Checker for client %s (command "%s")'
 
208
 
            #            % (self.properties[u"Name"], command))
 
212
 
        if not self.last_checker_failed:
 
213
 
            self.last_checker_failed = True
 
214
 
            self.using_timer(True)
 
215
211
        if os.WIFEXITED(condition):
 
216
 
            self.logger(u'Checker for client %s (command "%s")'
 
217
 
                        u' failed with exit code %s'
 
218
 
                        % (self.properties[u"Name"], command,
 
 
212
            self.logger('Checker for client %s (command "%s")'
 
 
213
                        ' failed with exit code %s'
 
 
214
                        % (self.properties["Name"], command,
 
219
215
                           os.WEXITSTATUS(condition)))
 
220
216
        elif os.WIFSIGNALED(condition):
 
221
 
            self.logger(u'Checker for client %s (command "%s")'
 
222
 
                        u' was killed by signal %s'
 
223
 
                        % (self.properties[u"Name"], command,
 
 
217
            self.logger('Checker for client %s (command "%s")'
 
 
218
                        ' was killed by signal %s'
 
 
219
                        % (self.properties["Name"], command,
 
224
220
                           os.WTERMSIG(condition)))
 
225
221
        elif os.WCOREDUMP(condition):
 
226
 
            self.logger(u'Checker for client %s (command "%s")'
 
228
 
                        % (self.properties[u"Name"], command))
 
 
222
            self.logger('Checker for client %s (command "%s")'
 
 
224
                        % (self.properties["Name"], command))
 
230
 
            self.logger(u'Checker for client %s completed'
 
 
226
            self.logger('Checker for client %s completed'
 
234
230
    def checker_started(self, command):
 
235
 
        #self.logger(u'Client %s started checker "%s"'
 
236
 
        #            % (self.properties[u"Name"], unicode(command)))
 
 
231
        """Server signals that a checker started. This could be useful
 
 
232
           to log in the future. """
 
 
233
        #self.logger('Client %s started checker "%s"'
 
 
234
        #            % (self.properties["Name"], unicode(command)))
 
239
237
    def got_secret(self):
 
240
 
        self.last_checker_failed = False
 
241
 
        self.logger(u'Client %s received its secret'
 
242
 
                    % self.properties[u"Name"])
 
 
238
        self.logger('Client %s received its secret'
 
 
239
                    % self.properties["Name"])
 
244
241
    def need_approval(self, timeout, default):
 
246
 
            message = u'Client %s needs approval within %s seconds'
 
 
243
            message = 'Client %s needs approval within %s seconds'
 
248
 
            message = u'Client %s will get its secret in %s seconds'
 
 
245
            message = 'Client %s will get its secret in %s seconds'
 
249
246
        self.logger(message
 
250
 
                    % (self.properties[u"Name"], timeout/1000))
 
 
247
                    % (self.properties["Name"], timeout/1000))
 
251
248
        self.using_timer(True)
 
253
250
    def rejected(self, reason):
 
254
 
        self.logger(u'Client %s was rejected; reason: %s'
 
255
 
                    % (self.properties[u"Name"], reason))
 
 
251
        self.logger('Client %s was rejected; reason: %s'
 
 
252
                    % (self.properties["Name"], reason))
 
257
254
    def selectable(self):
 
258
255
        """Make this a "selectable" widget.
 
259
256
        This overrides the method from urwid.FlowWidget."""
 
262
 
    def rows(self, (maxcol,), focus=False):
 
 
259
    def rows(self, maxcolrow, focus=False):
 
263
260
        """How many rows this widget will occupy might depend on
 
264
261
        whether we have focus or not.
 
265
262
        This overrides the method from urwid.FlowWidget"""
 
266
 
        return self.current_widget(focus).rows((maxcol,), focus=focus)
 
 
263
        return self.current_widget(focus).rows(maxcolrow, focus=focus)
 
268
265
    def current_widget(self, focus=False):
 
269
266
        if focus or self.opened:
 
 
273
270
    def update(self):
 
274
271
        "Called when what is visible on the screen should be updated."
 
275
272
        # How to add standout mode to a style
 
276
 
        with_standout = { u"normal": u"standout",
 
277
 
                          u"bold": u"bold-standout",
 
279
 
                              u"underline-blink-standout",
 
280
 
                          u"bold-underline-blink":
 
281
 
                              u"bold-underline-blink-standout",
 
 
273
        with_standout = { "normal": "standout",
 
 
274
                          "bold": "bold-standout",
 
 
276
                              "underline-blink-standout",
 
 
277
                          "bold-underline-blink":
 
 
278
                              "bold-underline-blink-standout",
 
284
281
        # Rebuild focus and non-focus widgets using current properties
 
286
283
        # Base part of a client. Name!
 
287
 
        base = (u'%(name)s: '
 
288
 
                      % {u"name": self.properties[u"Name"]})
 
289
 
        if not self.properties[u"Enabled"]:
 
290
 
            message = u"DISABLED"
 
291
 
        elif self.properties[u"ApprovalPending"]:
 
 
285
                      % {"name": self.properties["Name"]})
 
 
286
        if not self.properties["Enabled"]:
 
 
288
        elif self.properties["ApprovalPending"]:
 
292
289
            timeout = datetime.timedelta(milliseconds
 
293
290
                                         = self.properties
 
295
292
            last_approval_request = isoformat_to_datetime(
 
296
 
                self.properties[u"LastApprovalRequest"])
 
 
293
                self.properties["LastApprovalRequest"])
 
297
294
            if last_approval_request is not None:
 
298
295
                timer = timeout - (datetime.datetime.utcnow()
 
299
296
                                   - last_approval_request)
 
301
298
                timer = datetime.timedelta()
 
302
 
            if self.properties[u"ApprovedByDefault"]:
 
303
 
                message = u"Approval in %s. (d)eny?"
 
 
299
            if self.properties["ApprovedByDefault"]:
 
 
300
                message = "Approval in %s. (d)eny?"
 
305
 
                message = u"Denial in %s. (a)pprove?"
 
 
302
                message = "Denial in %s. (a)pprove?"
 
306
303
            message = message % unicode(timer).rsplit(".", 1)[0]
 
307
 
        elif self.last_checker_failed:
 
308
 
            timeout = datetime.timedelta(milliseconds
 
311
 
            last_ok = isoformat_to_datetime(
 
312
 
                max((self.properties[u"LastCheckedOK"]
 
313
 
                     or self.properties[u"Created"]),
 
314
 
                    self.properties[u"LastEnabled"]))
 
315
 
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
 
316
 
            message = (u'A checker has failed! Time until client'
 
317
 
                       u' gets disabled: %s'
 
 
304
        elif self.properties["LastCheckerStatus"] != 0:
 
 
305
            # When checker has failed, print a timer until client expires
 
 
306
            expires = self.properties["Expires"]
 
 
308
                timer = datetime.timedelta(0)
 
 
310
                expires = datetime.datetime.strptime(expires,
 
 
311
                                                     '%Y-%m-%dT%H:%M:%S.%f')
 
 
312
                timer = expires - datetime.datetime.utcnow()
 
 
313
            message = ('A checker has failed! Time until client'
 
318
315
                           % unicode(timer).rsplit(".", 1)[0])
 
321
318
        self._text = "%s%s" % (base, message)
 
323
320
        if not urwid.supports_unicode():
 
324
321
            self._text = self._text.encode("ascii", "replace")
 
325
 
        textlist = [(u"normal", self._text)]
 
 
322
        textlist = [("normal", self._text)]
 
326
323
        self._text_widget.set_text(textlist)
 
327
324
        self._focus_text_widget.set_text([(with_standout[text[0]],
 
 
337
334
            self.update_hook()
 
339
336
    def update_timer(self):
 
 
337
        """called by gobject. Will indefinitely loop until
 
 
338
        gobject.source_remove() on tag is called"""
 
342
340
        return True             # Keep calling this
 
 
342
    def delete(self, *args, **kwargs):
 
345
343
        if self._update_timer_callback_tag is not None:
 
346
344
            gobject.source_remove(self._update_timer_callback_tag)
 
347
345
            self._update_timer_callback_tag = None
 
 
346
        for match in self.match_objects:
 
 
348
        self.match_objects = ()
 
348
349
        if self.delete_hook is not None:
 
349
350
            self.delete_hook(self)
 
 
351
        return super(MandosClientWidget, self).delete(*args, **kwargs)
 
351
 
    def render(self, (maxcol,), focus=False):
 
 
353
    def render(self, maxcolrow, focus=False):
 
352
354
        """Render differently if we have focus.
 
353
355
        This overrides the method from urwid.FlowWidget"""
 
354
 
        return self.current_widget(focus).render((maxcol,),
 
 
356
        return self.current_widget(focus).render(maxcolrow,
 
357
 
    def keypress(self, (maxcol,), key):
 
 
359
    def keypress(self, maxcolrow, key):
 
359
361
        This overrides the method from urwid.FlowWidget"""
 
361
 
            self.proxy.Enable(dbus_interface = client_interface)
 
363
 
            self.proxy.Disable(dbus_interface = client_interface)
 
 
363
            self.proxy.Enable(dbus_interface = client_interface,
 
 
366
            self.proxy.Disable(dbus_interface = client_interface,
 
365
369
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
366
 
                               dbus_interface = client_interface)
 
 
370
                               dbus_interface = client_interface,
 
368
373
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
 
369
 
                                  dbus_interface = client_interface)
 
370
 
        elif key == u"R" or key == u"_" or key == u"ctrl k":
 
 
374
                                  dbus_interface = client_interface,
 
 
376
        elif key == "R" or key == "_" or key == "ctrl k":
 
371
377
            self.server_proxy_object.RemoveClient(self.proxy
 
374
 
            self.proxy.StartChecker(dbus_interface = client_interface)
 
376
 
            self.proxy.StopChecker(dbus_interface = client_interface)
 
378
 
            self.proxy.CheckedOK(dbus_interface = client_interface)
 
 
381
            self.proxy.StartChecker(dbus_interface = client_interface,
 
 
384
            self.proxy.StopChecker(dbus_interface = client_interface,
 
 
387
            self.proxy.CheckedOK(dbus_interface = client_interface,
 
380
 
#         elif key == u"p" or key == "=":
 
 
390
#         elif key == "p" or key == "=":
 
381
391
#             self.proxy.pause()
 
382
 
#         elif key == u"u" or key == ":":
 
 
392
#         elif key == "u" or key == ":":
 
383
393
#             self.proxy.unpause()
 
384
 
#         elif key == u"RET":
 
 
403
413
    "down" key presses, thus not allowing any containing widgets to
 
404
414
    use them as an excuse to shift focus away from this widget.
 
406
 
    def keypress(self, (maxcol, maxrow), key):
 
407
 
        ret = super(ConstrainedListBox, self).keypress((maxcol,
 
409
 
        if ret in (u"up", u"down"):
 
 
416
    def keypress(self, maxcolrow, key):
 
 
417
        ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
 
 
418
        if ret in ("up", "down"):
 
 
421
430
        self.screen = urwid.curses_display.Screen()
 
423
432
        self.screen.register_palette((
 
425
 
                 u"default", u"default", None),
 
427
 
                 u"default", u"default", u"bold"),
 
429
 
                 u"default", u"default", u"underline"),
 
431
 
                 u"default", u"default", u"standout"),
 
432
 
                (u"bold-underline-blink",
 
433
 
                 u"default", u"default", (u"bold", u"underline")),
 
435
 
                 u"default", u"default", (u"bold", u"standout")),
 
436
 
                (u"underline-blink-standout",
 
437
 
                 u"default", u"default", (u"underline", u"standout")),
 
438
 
                (u"bold-underline-blink-standout",
 
439
 
                 u"default", u"default", (u"bold", u"underline",
 
 
434
                 "default", "default", None),
 
 
436
                 "default", "default", "bold"),
 
 
438
                 "default", "default", "underline"),
 
 
440
                 "default", "default", "standout"),
 
 
441
                ("bold-underline-blink",
 
 
442
                 "default", "default", ("bold", "underline")),
 
 
444
                 "default", "default", ("bold", "standout")),
 
 
445
                ("underline-blink-standout",
 
 
446
                 "default", "default", ("underline", "standout")),
 
 
447
                ("bold-underline-blink-standout",
 
 
448
                 "default", "default", ("bold", "underline",
 
443
452
        if urwid.supports_unicode():
 
444
 
            self.divider = u"─" # \u2500
 
445
 
            #self.divider = u"━" # \u2501
 
 
453
            self.divider = "─" # \u2500
 
 
454
            #self.divider = "━" # \u2501
 
447
 
            #self.divider = u"-" # \u002d
 
448
 
            self.divider = u"_" # \u005f
 
 
456
            #self.divider = "-" # \u002d
 
 
457
            self.divider = "_" # \u005f
 
450
459
        self.screen.start()
 
 
465
474
        # This keeps track of whether self.uilist currently has
 
466
475
        # self.logbox in it or not
 
467
476
        self.log_visible = True
 
468
 
        self.log_wrap = u"any"
 
 
477
        self.log_wrap = "any"
 
471
 
        self.log_message_raw((u"bold",
 
472
 
                              u"Mandos Monitor version " + version))
 
473
 
        self.log_message_raw((u"bold",
 
 
480
        self.log_message_raw(("bold",
 
 
481
                              "Mandos Monitor version " + version))
 
 
482
        self.log_message_raw(("bold",
 
476
485
        self.busname = domain + '.Mandos'
 
477
486
        self.main_loop = gobject.MainLoop()
 
478
 
        self.bus = dbus.SystemBus()
 
479
 
        mandos_dbus_objc = self.bus.get_object(
 
480
 
            self.busname, u"/", follow_name_owner_changes=True)
 
481
 
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
485
 
            mandos_clients = (self.mandos_serv
 
486
 
                              .GetAllClientsWithProperties())
 
487
 
        except dbus.exceptions.DBusException:
 
488
 
            mandos_clients = dbus.Dictionary()
 
491
 
         .connect_to_signal(u"ClientRemoved",
 
492
 
                            self.find_and_remove_client,
 
493
 
                            dbus_interface=server_interface,
 
496
 
         .connect_to_signal(u"ClientAdded",
 
498
 
                            dbus_interface=server_interface,
 
501
 
         .connect_to_signal(u"ClientNotFound",
 
502
 
                            self.client_not_found,
 
503
 
                            dbus_interface=server_interface,
 
505
 
        for path, client in mandos_clients.iteritems():
 
506
 
            client_proxy_object = self.bus.get_object(self.busname,
 
508
 
            self.add_client(MandosClientWidget(server_proxy_object
 
511
 
                                               =client_proxy_object,
 
521
488
    def client_not_found(self, fingerprint, address):
 
522
 
        self.log_message((u"Client with address %s and fingerprint %s"
 
523
 
                          u" could not be found" % (address,
 
 
489
        self.log_message(("Client with address %s and fingerprint %s"
 
 
490
                          " could not be found" % (address,
 
526
493
    def rebuild(self):
 
 
551
517
            and len(self.log) > self.max_log_length):
 
552
518
            del self.log[0:len(self.log)-self.max_log_length-1]
 
553
519
        self.logbox.set_focus(len(self.logbox.body.contents),
 
554
 
                              coming_from=u"above")
 
557
523
    def toggle_log_display(self):
 
558
524
        """Toggle visibility of the log buffer."""
 
559
525
        self.log_visible = not self.log_visible
 
561
 
        #self.log_message(u"Log visibility changed to: "
 
 
527
        #self.log_message("Log visibility changed to: "
 
562
528
        #                 + unicode(self.log_visible))
 
564
530
    def change_log_display(self):
 
565
531
        """Change type of log display.
 
566
532
        Currently, this toggles wrapping of text lines."""
 
567
 
        if self.log_wrap == u"clip":
 
568
 
            self.log_wrap = u"any"
 
 
533
        if self.log_wrap == "clip":
 
 
534
            self.log_wrap = "any"
 
570
 
            self.log_wrap = u"clip"
 
 
536
            self.log_wrap = "clip"
 
571
537
        for textwidget in self.log:
 
572
538
            textwidget.set_wrap_mode(self.log_wrap)
 
573
 
        #self.log_message(u"Wrap mode: " + self.log_wrap)
 
 
539
        #self.log_message("Wrap mode: " + self.log_wrap)
 
575
541
    def find_and_remove_client(self, path, name):
 
576
 
        """Find an client from its object path and remove it.
 
 
542
        """Find a client by its object path and remove it.
 
578
544
        This is connected to the ClientRemoved signal from the
 
579
545
        Mandos server object."""
 
 
627
595
        """Start the main loop and exit when it's done."""
 
 
596
        self.bus = dbus.SystemBus()
 
 
597
        mandos_dbus_objc = self.bus.get_object(
 
 
598
            self.busname, "/", follow_name_owner_changes=True)
 
 
599
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
 
603
            mandos_clients = (self.mandos_serv
 
 
604
                              .GetAllClientsWithProperties())
 
 
605
        except dbus.exceptions.DBusException:
 
 
606
            mandos_clients = dbus.Dictionary()
 
 
609
         .connect_to_signal("ClientRemoved",
 
 
610
                            self.find_and_remove_client,
 
 
611
                            dbus_interface=server_interface,
 
 
614
         .connect_to_signal("ClientAdded",
 
 
616
                            dbus_interface=server_interface,
 
 
619
         .connect_to_signal("ClientNotFound",
 
 
620
                            self.client_not_found,
 
 
621
                            dbus_interface=server_interface,
 
 
623
        for path, client in mandos_clients.iteritems():
 
 
624
            client_proxy_object = self.bus.get_object(self.busname,
 
 
626
            self.add_client(MandosClientWidget(server_proxy_object
 
 
629
                                               =client_proxy_object,
 
629
640
        self._input_callback_tag = (gobject.io_add_watch
 
630
641
                                    (sys.stdin.fileno(),
 
 
641
652
    def process_input(self, source, condition):
 
642
653
        keys = self.screen.get_input()
 
643
 
        translations = { u"ctrl n": u"down",      # Emacs
 
644
 
                         u"ctrl p": u"up",        # Emacs
 
645
 
                         u"ctrl v": u"page down", # Emacs
 
646
 
                         u"meta v": u"page up",   # Emacs
 
647
 
                         u" ": u"page down",      # less
 
648
 
                         u"f": u"page down",      # less
 
649
 
                         u"b": u"page up",        # less
 
 
654
        translations = { "ctrl n": "down",      # Emacs
 
 
655
                         "ctrl p": "up",        # Emacs
 
 
656
                         "ctrl v": "page down", # Emacs
 
 
657
                         "meta v": "page up",   # Emacs
 
 
658
                         " ": "page down",      # less
 
 
659
                         "f": "page down",      # less
 
 
660
                         "b": "page up",        # less
 
 
656
667
            except KeyError:    # :-)
 
659
 
            if key == u"q" or key == u"Q":
 
 
670
            if key == "q" or key == "Q":
 
662
 
            elif key == u"window resize":
 
 
673
            elif key == "window resize":
 
663
674
                self.size = self.screen.get_cols_rows()
 
665
 
            elif key == u"\f":  # Ctrl-L
 
 
676
            elif key == "\f":  # Ctrl-L
 
667
 
            elif key == u"l" or key == u"D":
 
 
678
            elif key == "l" or key == "D":
 
668
679
                self.toggle_log_display()
 
670
 
            elif key == u"w" or key == u"i":
 
 
681
            elif key == "w" or key == "i":
 
671
682
                self.change_log_display()
 
673
 
            elif key == u"?" or key == u"f1" or key == u"esc":
 
 
684
            elif key == "?" or key == "f1" or key == "esc":
 
674
685
                if not self.log_visible:
 
675
686
                    self.log_visible = True
 
677
 
                self.log_message_raw((u"bold",
 
681
 
                                            u"l: Log window toggle",
 
682
 
                                            u"TAB: Switch window",
 
684
 
                self.log_message_raw((u"bold",
 
690
 
                                             u"s: Start new checker",
 
 
688
                self.log_message_raw(("bold",
 
 
692
                                            "l: Log window toggle",
 
 
693
                                            "TAB: Switch window",
 
 
695
                self.log_message_raw(("bold",
 
 
701
                                             "s: Start new checker",
 
697
708
                if self.topwidget.get_focus() is self.logbox:
 
698
709
                    self.topwidget.set_focus(0)
 
700
711
                    self.topwidget.set_focus(self.logbox)
 
702
 
            #elif (key == u"end" or key == u"meta >" or key == u"G"
 
 
713
            #elif (key == "end" or key == "meta >" or key == "G"
 
704
715
            #    pass            # xxx end-of-buffer
 
705
 
            #elif (key == u"home" or key == u"meta <" or key == u"g"
 
 
716
            #elif (key == "home" or key == "meta <" or key == "g"
 
707
718
            #    pass            # xxx beginning-of-buffer
 
708
 
            #elif key == u"ctrl e" or key == u"$":
 
 
719
            #elif key == "ctrl e" or key == "$":
 
709
720
            #    pass            # xxx move-end-of-line
 
710
 
            #elif key == u"ctrl a" or key == u"^":
 
 
721
            #elif key == "ctrl a" or key == "^":
 
711
722
            #    pass            # xxx move-beginning-of-line
 
712
 
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
 
723
            #elif key == "ctrl b" or key == "meta (" or key == "h":
 
713
724
            #    pass            # xxx left
 
714
 
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
 
725
            #elif key == "ctrl f" or key == "meta )" or key == "l":
 
715
726
            #    pass            # xxx right
 
717
728
            #    pass            # scroll up log
 
719
730
            #    pass            # scroll down log
 
720
731
            elif self.topwidget.selectable():
 
721
732
                self.topwidget.keypress(self.size, key)