/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: Teddy Hogeborn
  • Date: 2011-03-08 11:08:35 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110308110835-3sh7b314tf7q3o1i
* plugins.d/password-prompt.c (conflict_detection): Check for both
                                                    "plymouth" and
                                                    "plymouthd".

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
 
3
 
4
# Mandos Monitor - Control and monitor the Mandos server
 
5
 
6
# Copyright © 2009-2011 Teddy Hogeborn
 
7
# Copyright © 2009-2011 Björn Påhlsson
 
8
 
9
# This program is free software: you can redistribute it and/or modify
 
10
# it under the terms of the GNU General Public License as published by
 
11
# the Free Software Foundation, either version 3 of the License, or
 
12
# (at your option) any later version.
 
13
#
 
14
#     This program is distributed in the hope that it will be useful,
 
15
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
#     GNU General Public License for more details.
 
18
 
19
# You should have received a copy of the GNU General Public License
 
20
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
21
 
22
# Contact the authors at <mandos@fukt.bsnet.se>.
 
23
3
24
 
4
 
from __future__ import division, absolute_import, with_statement
 
25
from __future__ import division, absolute_import, print_function, unicode_literals
5
26
 
6
27
import sys
7
28
import os
21
42
 
22
43
import locale
23
44
 
24
 
locale.setlocale(locale.LC_ALL, u'')
 
45
locale.setlocale(locale.LC_ALL, '')
25
46
 
26
47
import logging
27
48
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
30
51
domain = 'se.bsnet.fukt'
31
52
server_interface = domain + '.Mandos'
32
53
client_interface = domain + '.Mandos.Client'
33
 
version = "1.0.15"
 
54
version = "1.2.3"
34
55
 
35
56
# Always run in monochrome mode
36
57
urwid.curses_display.curses.has_colors = lambda : False
44
65
    "Parse an ISO 8601 date string to a datetime.datetime()"
45
66
    if not iso:
46
67
        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)
 
68
    d, t = iso.split("T", 1)
 
69
    year, month, day = d.split("-", 2)
 
70
    hour, minute, second = t.split(":", 2)
50
71
    second, fraction = divmod(float(second), 1)
51
72
    return datetime.datetime(int(year),
52
73
                             int(month),
65
86
        self.proxy = proxy_object # Mandos Client proxy object
66
87
        
67
88
        self.properties = dict()
68
 
        self.proxy.connect_to_signal(u"PropertyChanged",
 
89
        self.proxy.connect_to_signal("PropertyChanged",
69
90
                                     self.property_changed,
70
91
                                     client_interface,
71
92
                                     byte_arrays=True)
106
127
        self.last_checker_failed = False
107
128
        
108
129
        # The widget shown normally
109
 
        self._text_widget = urwid.Text(u"")
 
130
        self._text_widget = urwid.Text("")
110
131
        # The widget shown when we have focus
111
 
        self._focus_text_widget = urwid.Text(u"")
 
132
        self._focus_text_widget = urwid.Text("")
112
133
        super(MandosClientWidget, self).__init__(
113
134
            update_hook=update_hook, delete_hook=delete_hook,
114
135
            *args, **kwargs)
116
137
        self.opened = False
117
138
        
118
139
        last_checked_ok = isoformat_to_datetime(self.properties
119
 
                                                [u"LastCheckedOK"])
 
140
                                                ["LastCheckedOK"])
120
141
        if last_checked_ok is None:
121
142
            self.last_checker_failed = True
122
143
        else:
125
146
                                        > datetime.timedelta
126
147
                                        (milliseconds=
127
148
                                         self.properties
128
 
                                         [u"Interval"]))
 
149
                                         ["Interval"]))
129
150
        
130
151
        if self.last_checker_failed:
131
152
            self.using_timer(True)
133
154
        if self.need_approval:
134
155
            self.using_timer(True)
135
156
        
136
 
        self.proxy.connect_to_signal(u"CheckerCompleted",
 
157
        self.proxy.connect_to_signal("CheckerCompleted",
137
158
                                     self.checker_completed,
138
159
                                     client_interface,
139
160
                                     byte_arrays=True)
140
 
        self.proxy.connect_to_signal(u"CheckerStarted",
 
161
        self.proxy.connect_to_signal("CheckerStarted",
141
162
                                     self.checker_started,
142
163
                                     client_interface,
143
164
                                     byte_arrays=True)
144
 
        self.proxy.connect_to_signal(u"GotSecret",
 
165
        self.proxy.connect_to_signal("GotSecret",
145
166
                                     self.got_secret,
146
167
                                     client_interface,
147
168
                                     byte_arrays=True)
148
 
        self.proxy.connect_to_signal(u"NeedApproval",
 
169
        self.proxy.connect_to_signal("NeedApproval",
149
170
                                     self.need_approval,
150
171
                                     client_interface,
151
172
                                     byte_arrays=True)
152
 
        self.proxy.connect_to_signal(u"Rejected",
 
173
        self.proxy.connect_to_signal("Rejected",
153
174
                                     self.rejected,
154
175
                                     client_interface,
155
176
                                     byte_arrays=True)
157
178
    def property_changed(self, property=None, value=None):
158
179
        super(self, MandosClientWidget).property_changed(property,
159
180
                                                         value)
160
 
        if property == u"ApprovalPending":
 
181
        if property == "ApprovalPending":
161
182
            using_timer(bool(value))
162
183
        
163
184
    def using_timer(self, flag):
182
203
            if self.last_checker_failed:
183
204
                self.last_checker_failed = False
184
205
                self.using_timer(False)
185
 
            #self.logger(u'Checker for client %s (command "%s")'
186
 
            #            u' was successful'
187
 
            #            % (self.properties[u"Name"], command))
 
206
            #self.logger('Checker for client %s (command "%s")'
 
207
            #            ' was successful'
 
208
            #            % (self.properties["Name"], command))
188
209
            self.update()
189
210
            return
190
211
        # Checker failed
192
213
            self.last_checker_failed = True
193
214
            self.using_timer(True)
194
215
        if os.WIFEXITED(condition):
195
 
            self.logger(u'Checker for client %s (command "%s")'
196
 
                        u' failed with exit code %s'
197
 
                        % (self.properties[u"Name"], command,
 
216
            self.logger('Checker for client %s (command "%s")'
 
217
                        ' failed with exit code %s'
 
218
                        % (self.properties["Name"], command,
198
219
                           os.WEXITSTATUS(condition)))
199
220
        elif os.WIFSIGNALED(condition):
200
 
            self.logger(u'Checker for client %s (command "%s")'
201
 
                        u' was killed by signal %s'
202
 
                        % (self.properties[u"Name"], command,
 
221
            self.logger('Checker for client %s (command "%s")'
 
222
                        ' was killed by signal %s'
 
223
                        % (self.properties["Name"], command,
203
224
                           os.WTERMSIG(condition)))
204
225
        elif os.WCOREDUMP(condition):
205
 
            self.logger(u'Checker for client %s (command "%s")'
206
 
                        u' dumped core'
207
 
                        % (self.properties[u"Name"], command))
 
226
            self.logger('Checker for client %s (command "%s")'
 
227
                        ' dumped core'
 
228
                        % (self.properties["Name"], command))
208
229
        else:
209
 
            self.logger(u'Checker for client %s completed'
210
 
                        u' mysteriously')
 
230
            self.logger('Checker for client %s completed'
 
231
                        ' mysteriously')
211
232
        self.update()
212
233
    
213
234
    def checker_started(self, command):
214
 
        #self.logger(u'Client %s started checker "%s"'
215
 
        #            % (self.properties[u"Name"], unicode(command)))
 
235
        #self.logger('Client %s started checker "%s"'
 
236
        #            % (self.properties["Name"], unicode(command)))
216
237
        pass
217
238
    
218
239
    def got_secret(self):
219
240
        self.last_checker_failed = False
220
 
        self.logger(u'Client %s received its secret'
221
 
                    % self.properties[u"Name"])
 
241
        self.logger('Client %s received its secret'
 
242
                    % self.properties["Name"])
222
243
    
223
244
    def need_approval(self, timeout, default):
224
245
        if not default:
225
 
            message = u'Client %s needs approval within %s seconds'
 
246
            message = 'Client %s needs approval within %s seconds'
226
247
        else:
227
 
            message = u'Client %s will get its secret in %s seconds'
 
248
            message = 'Client %s will get its secret in %s seconds'
228
249
        self.logger(message
229
 
                    % (self.properties[u"Name"], timeout/1000))
 
250
                    % (self.properties["Name"], timeout/1000))
230
251
        self.using_timer(True)
231
252
    
232
253
    def rejected(self, reason):
233
 
        self.logger(u'Client %s was rejected; reason: %s'
234
 
                    % (self.properties[u"Name"], reason))
 
254
        self.logger('Client %s was rejected; reason: %s'
 
255
                    % (self.properties["Name"], reason))
235
256
    
236
257
    def selectable(self):
237
258
        """Make this a "selectable" widget.
238
259
        This overrides the method from urwid.FlowWidget."""
239
260
        return True
240
261
    
241
 
    def rows(self, (maxcol,), focus=False):
 
262
    def rows(self, maxcolrow, focus=False):
242
263
        """How many rows this widget will occupy might depend on
243
264
        whether we have focus or not.
244
265
        This overrides the method from urwid.FlowWidget"""
245
 
        return self.current_widget(focus).rows((maxcol,), focus=focus)
 
266
        return self.current_widget(focus).rows(maxcolrow, focus=focus)
246
267
    
247
268
    def current_widget(self, focus=False):
248
269
        if focus or self.opened:
252
273
    def update(self):
253
274
        "Called when what is visible on the screen should be updated."
254
275
        # How to add standout mode to a style
255
 
        with_standout = { u"normal": u"standout",
256
 
                          u"bold": u"bold-standout",
257
 
                          u"underline-blink":
258
 
                              u"underline-blink-standout",
259
 
                          u"bold-underline-blink":
260
 
                              u"bold-underline-blink-standout",
 
276
        with_standout = { "normal": "standout",
 
277
                          "bold": "bold-standout",
 
278
                          "underline-blink":
 
279
                              "underline-blink-standout",
 
280
                          "bold-underline-blink":
 
281
                              "bold-underline-blink-standout",
261
282
                          }
262
283
 
263
284
        # Rebuild focus and non-focus widgets using current properties
264
285
 
265
286
        # Base part of a client. Name!
266
 
        base = (u'%(name)s: '
267
 
                      % {u"name": self.properties[u"Name"]})
268
 
        if not self.properties[u"Enabled"]:
269
 
            message = u"DISABLED"
270
 
        elif self.properties[u"ApprovalPending"]:
 
287
        base = ('%(name)s: '
 
288
                      % {"name": self.properties["Name"]})
 
289
        if not self.properties["Enabled"]:
 
290
            message = "DISABLED"
 
291
        elif self.properties["ApprovalPending"]:
271
292
            timeout = datetime.timedelta(milliseconds
272
293
                                         = self.properties
273
 
                                         [u"ApprovalDelay"])
 
294
                                         ["ApprovalDelay"])
274
295
            last_approval_request = isoformat_to_datetime(
275
 
                self.properties[u"LastApprovalRequest"])
 
296
                self.properties["LastApprovalRequest"])
276
297
            if last_approval_request is not None:
277
298
                timer = timeout - (datetime.datetime.utcnow()
278
299
                                   - last_approval_request)
279
300
            else:
280
301
                timer = datetime.timedelta()
281
 
            if self.properties[u"ApprovedByDefault"]:
282
 
                message = u"Approval in %s. (d)eny?"
 
302
            if self.properties["ApprovedByDefault"]:
 
303
                message = "Approval in %s. (d)eny?"
283
304
            else:
284
 
                message = u"Denial in %s. (a)pprove?"
 
305
                message = "Denial in %s. (a)pprove?"
285
306
            message = message % unicode(timer).rsplit(".", 1)[0]
286
307
        elif self.last_checker_failed:
287
308
            timeout = datetime.timedelta(milliseconds
288
309
                                         = self.properties
289
 
                                         [u"Timeout"])
 
310
                                         ["Timeout"])
290
311
            last_ok = isoformat_to_datetime(
291
 
                max((self.properties[u"LastCheckedOK"]
292
 
                     or self.properties[u"Created"]),
293
 
                    self.properties[u"LastEnabled"]))
 
312
                max((self.properties["LastCheckedOK"]
 
313
                     or self.properties["Created"]),
 
314
                    self.properties["LastEnabled"]))
294
315
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
295
 
            message = (u'A checker has failed! Time until client'
296
 
                       u' gets disabled: %s'
 
316
            message = ('A checker has failed! Time until client'
 
317
                       ' gets disabled: %s'
297
318
                           % unicode(timer).rsplit(".", 1)[0])
298
319
        else:
299
 
            message = u"enabled"
 
320
            message = "enabled"
300
321
        self._text = "%s%s" % (base, message)
301
322
            
302
323
        if not urwid.supports_unicode():
303
324
            self._text = self._text.encode("ascii", "replace")
304
 
        textlist = [(u"normal", self._text)]
 
325
        textlist = [("normal", self._text)]
305
326
        self._text_widget.set_text(textlist)
306
327
        self._focus_text_widget.set_text([(with_standout[text[0]],
307
328
                                           text[1])
327
348
        if self.delete_hook is not None:
328
349
            self.delete_hook(self)
329
350
    
330
 
    def render(self, (maxcol,), focus=False):
 
351
    def render(self, maxcolrow, focus=False):
331
352
        """Render differently if we have focus.
332
353
        This overrides the method from urwid.FlowWidget"""
333
 
        return self.current_widget(focus).render((maxcol,),
 
354
        return self.current_widget(focus).render(maxcolrow,
334
355
                                                 focus=focus)
335
356
    
336
 
    def keypress(self, (maxcol,), key):
 
357
    def keypress(self, maxcolrow, key):
337
358
        """Handle keys.
338
359
        This overrides the method from urwid.FlowWidget"""
339
 
        if key == u"+":
 
360
        if key == "+":
340
361
            self.proxy.Enable(dbus_interface = client_interface)
341
 
        elif key == u"-":
 
362
        elif key == "-":
342
363
            self.proxy.Disable(dbus_interface = client_interface)
343
 
        elif key == u"a":
 
364
        elif key == "a":
344
365
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
345
366
                               dbus_interface = client_interface)
346
 
        elif key == u"d":
 
367
        elif key == "d":
347
368
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
348
369
                                  dbus_interface = client_interface)
349
 
        elif key == u"r" or key == u"_" or key == u"ctrl k":
 
370
        elif key == "R" or key == "_" or key == "ctrl k":
350
371
            self.server_proxy_object.RemoveClient(self.proxy
351
372
                                                  .object_path)
352
 
        elif key == u"s":
 
373
        elif key == "s":
353
374
            self.proxy.StartChecker(dbus_interface = client_interface)
354
 
        elif key == u"S":
 
375
        elif key == "S":
355
376
            self.proxy.StopChecker(dbus_interface = client_interface)
356
 
        elif key == u"C":
 
377
        elif key == "C":
357
378
            self.proxy.CheckedOK(dbus_interface = client_interface)
358
379
        # xxx
359
 
#         elif key == u"p" or key == "=":
 
380
#         elif key == "p" or key == "=":
360
381
#             self.proxy.pause()
361
 
#         elif key == u"u" or key == ":":
 
382
#         elif key == "u" or key == ":":
362
383
#             self.proxy.unpause()
363
 
#         elif key == u"RET":
 
384
#         elif key == "RET":
364
385
#             self.open()
365
386
        else:
366
387
            return key
382
403
    "down" key presses, thus not allowing any containing widgets to
383
404
    use them as an excuse to shift focus away from this widget.
384
405
    """
385
 
    def keypress(self, (maxcol, maxrow), key):
386
 
        ret = super(ConstrainedListBox, self).keypress((maxcol,
387
 
                                                        maxrow), key)
388
 
        if ret in (u"up", u"down"):
 
406
    def keypress(self, maxcolrow, key):
 
407
        ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
 
408
        if ret in ("up", "down"):
389
409
            return
390
410
        return ret
391
411
 
400
420
        self.screen = urwid.curses_display.Screen()
401
421
        
402
422
        self.screen.register_palette((
403
 
                (u"normal",
404
 
                 u"default", u"default", None),
405
 
                (u"bold",
406
 
                 u"default", u"default", u"bold"),
407
 
                (u"underline-blink",
408
 
                 u"default", u"default", u"underline"),
409
 
                (u"standout",
410
 
                 u"default", u"default", u"standout"),
411
 
                (u"bold-underline-blink",
412
 
                 u"default", u"default", (u"bold", u"underline")),
413
 
                (u"bold-standout",
414
 
                 u"default", u"default", (u"bold", u"standout")),
415
 
                (u"underline-blink-standout",
416
 
                 u"default", u"default", (u"underline", u"standout")),
417
 
                (u"bold-underline-blink-standout",
418
 
                 u"default", u"default", (u"bold", u"underline",
419
 
                                          u"standout")),
 
423
                ("normal",
 
424
                 "default", "default", None),
 
425
                ("bold",
 
426
                 "default", "default", "bold"),
 
427
                ("underline-blink",
 
428
                 "default", "default", "underline"),
 
429
                ("standout",
 
430
                 "default", "default", "standout"),
 
431
                ("bold-underline-blink",
 
432
                 "default", "default", ("bold", "underline")),
 
433
                ("bold-standout",
 
434
                 "default", "default", ("bold", "standout")),
 
435
                ("underline-blink-standout",
 
436
                 "default", "default", ("underline", "standout")),
 
437
                ("bold-underline-blink-standout",
 
438
                 "default", "default", ("bold", "underline",
 
439
                                          "standout")),
420
440
                ))
421
441
        
422
442
        if urwid.supports_unicode():
423
 
            self.divider = u"─" # \u2500
424
 
            #self.divider = u"━" # \u2501
 
443
            self.divider = "─" # \u2500
 
444
            #self.divider = "━" # \u2501
425
445
        else:
426
 
            #self.divider = u"-" # \u002d
427
 
            self.divider = u"_" # \u005f
 
446
            #self.divider = "-" # \u002d
 
447
            self.divider = "_" # \u005f
428
448
        
429
449
        self.screen.start()
430
450
        
444
464
        # This keeps track of whether self.uilist currently has
445
465
        # self.logbox in it or not
446
466
        self.log_visible = True
447
 
        self.log_wrap = u"any"
 
467
        self.log_wrap = "any"
448
468
        
449
469
        self.rebuild()
450
 
        self.log_message_raw((u"bold",
451
 
                              u"Mandos Monitor version " + version))
452
 
        self.log_message_raw((u"bold",
453
 
                              u"q: Quit  ?: Help"))
 
470
        self.log_message_raw(("bold",
 
471
                              "Mandos Monitor version " + version))
 
472
        self.log_message_raw(("bold",
 
473
                              "q: Quit  ?: Help"))
454
474
        
455
475
        self.busname = domain + '.Mandos'
456
476
        self.main_loop = gobject.MainLoop()
457
477
        self.bus = dbus.SystemBus()
458
478
        mandos_dbus_objc = self.bus.get_object(
459
 
            self.busname, u"/", follow_name_owner_changes=True)
 
479
            self.busname, "/", follow_name_owner_changes=True)
460
480
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
461
481
                                          dbus_interface
462
482
                                          = server_interface)
467
487
            mandos_clients = dbus.Dictionary()
468
488
        
469
489
        (self.mandos_serv
470
 
         .connect_to_signal(u"ClientRemoved",
 
490
         .connect_to_signal("ClientRemoved",
471
491
                            self.find_and_remove_client,
472
492
                            dbus_interface=server_interface,
473
493
                            byte_arrays=True))
474
494
        (self.mandos_serv
475
 
         .connect_to_signal(u"ClientAdded",
 
495
         .connect_to_signal("ClientAdded",
476
496
                            self.add_new_client,
477
497
                            dbus_interface=server_interface,
478
498
                            byte_arrays=True))
479
499
        (self.mandos_serv
480
 
         .connect_to_signal(u"ClientNotFound",
 
500
         .connect_to_signal("ClientNotFound",
481
501
                            self.client_not_found,
482
502
                            dbus_interface=server_interface,
483
503
                            byte_arrays=True))
498
518
                            path=path)
499
519
    
500
520
    def client_not_found(self, fingerprint, address):
501
 
        self.log_message((u"Client with address %s and fingerprint %s"
502
 
                          u" could not be found" % (address,
 
521
        self.log_message(("Client with address %s and fingerprint %s"
 
522
                          " could not be found" % (address,
503
523
                                                    fingerprint)))
504
524
    
505
525
    def rebuild(self):
521
541
    
522
542
    def log_message(self, message):
523
543
        timestamp = datetime.datetime.now().isoformat()
524
 
        self.log_message_raw(timestamp + u": " + message)
 
544
        self.log_message_raw(timestamp + ": " + message)
525
545
    
526
546
    def log_message_raw(self, markup):
527
547
        """Add a log message to the log buffer."""
530
550
            and len(self.log) > self.max_log_length):
531
551
            del self.log[0:len(self.log)-self.max_log_length-1]
532
552
        self.logbox.set_focus(len(self.logbox.body.contents),
533
 
                              coming_from=u"above")
 
553
                              coming_from="above")
534
554
        self.refresh()
535
555
    
536
556
    def toggle_log_display(self):
537
557
        """Toggle visibility of the log buffer."""
538
558
        self.log_visible = not self.log_visible
539
559
        self.rebuild()
540
 
        #self.log_message(u"Log visibility changed to: "
 
560
        #self.log_message("Log visibility changed to: "
541
561
        #                 + unicode(self.log_visible))
542
562
    
543
563
    def change_log_display(self):
544
564
        """Change type of log display.
545
565
        Currently, this toggles wrapping of text lines."""
546
 
        if self.log_wrap == u"clip":
547
 
            self.log_wrap = u"any"
 
566
        if self.log_wrap == "clip":
 
567
            self.log_wrap = "any"
548
568
        else:
549
 
            self.log_wrap = u"clip"
 
569
            self.log_wrap = "clip"
550
570
        for textwidget in self.log:
551
571
            textwidget.set_wrap_mode(self.log_wrap)
552
 
        #self.log_message(u"Wrap mode: " + self.log_wrap)
 
572
        #self.log_message("Wrap mode: " + self.log_wrap)
553
573
    
554
574
    def find_and_remove_client(self, path, name):
555
575
        """Find an client from its object path and remove it.
582
602
        if path is None:
583
603
            path = client.proxy.object_path
584
604
        self.clients_dict[path] = client
585
 
        self.clients.sort(None, lambda c: c.properties[u"Name"])
 
605
        self.clients.sort(None, lambda c: c.properties["Name"])
586
606
        self.refresh()
587
607
    
588
608
    def remove_client(self, client, path=None):
619
639
    
620
640
    def process_input(self, source, condition):
621
641
        keys = self.screen.get_input()
622
 
        translations = { u"ctrl n": u"down",      # Emacs
623
 
                         u"ctrl p": u"up",        # Emacs
624
 
                         u"ctrl v": u"page down", # Emacs
625
 
                         u"meta v": u"page up",   # Emacs
626
 
                         u" ": u"page down",      # less
627
 
                         u"f": u"page down",      # less
628
 
                         u"b": u"page up",        # less
629
 
                         u"j": u"down",           # vi
630
 
                         u"k": u"up",             # vi
 
642
        translations = { "ctrl n": "down",      # Emacs
 
643
                         "ctrl p": "up",        # Emacs
 
644
                         "ctrl v": "page down", # Emacs
 
645
                         "meta v": "page up",   # Emacs
 
646
                         " ": "page down",      # less
 
647
                         "f": "page down",      # less
 
648
                         "b": "page up",        # less
 
649
                         "j": "down",           # vi
 
650
                         "k": "up",             # vi
631
651
                         }
632
652
        for key in keys:
633
653
            try:
635
655
            except KeyError:    # :-)
636
656
                pass
637
657
            
638
 
            if key == u"q" or key == u"Q":
 
658
            if key == "q" or key == "Q":
639
659
                self.stop()
640
660
                break
641
 
            elif key == u"window resize":
 
661
            elif key == "window resize":
642
662
                self.size = self.screen.get_cols_rows()
643
663
                self.refresh()
644
 
            elif key == u"\f":  # Ctrl-L
 
664
            elif key == "\f":  # Ctrl-L
645
665
                self.refresh()
646
 
            elif key == u"l" or key == u"D":
 
666
            elif key == "l" or key == "D":
647
667
                self.toggle_log_display()
648
668
                self.refresh()
649
 
            elif key == u"w" or key == u"i":
 
669
            elif key == "w" or key == "i":
650
670
                self.change_log_display()
651
671
                self.refresh()
652
 
            elif key == u"?" or key == u"f1" or key == u"esc":
 
672
            elif key == "?" or key == "f1" or key == "esc":
653
673
                if not self.log_visible:
654
674
                    self.log_visible = True
655
675
                    self.rebuild()
656
 
                self.log_message_raw((u"bold",
657
 
                                      u"  ".
658
 
                                      join((u"q: Quit",
659
 
                                            u"?: Help",
660
 
                                            u"l: Log window toggle",
661
 
                                            u"TAB: Switch window",
662
 
                                            u"w: Wrap (log)"))))
663
 
                self.log_message_raw((u"bold",
664
 
                                      u"  "
665
 
                                      .join((u"Clients:",
666
 
                                             u"+: Enable",
667
 
                                             u"-: Disable",
668
 
                                             u"r: Remove",
669
 
                                             u"s: Start new checker",
670
 
                                             u"S: Stop checker",
671
 
                                             u"C: Checker OK",
672
 
                                             u"a: Approve",
673
 
                                             u"d: Deny"))))
 
676
                self.log_message_raw(("bold",
 
677
                                      "  ".
 
678
                                      join(("q: Quit",
 
679
                                            "?: Help",
 
680
                                            "l: Log window toggle",
 
681
                                            "TAB: Switch window",
 
682
                                            "w: Wrap (log)"))))
 
683
                self.log_message_raw(("bold",
 
684
                                      "  "
 
685
                                      .join(("Clients:",
 
686
                                             "+: Enable",
 
687
                                             "-: Disable",
 
688
                                             "R: Remove",
 
689
                                             "s: Start new checker",
 
690
                                             "S: Stop checker",
 
691
                                             "C: Checker OK",
 
692
                                             "a: Approve",
 
693
                                             "d: Deny"))))
674
694
                self.refresh()
675
 
            elif key == u"tab":
 
695
            elif key == "tab":
676
696
                if self.topwidget.get_focus() is self.logbox:
677
697
                    self.topwidget.set_focus(0)
678
698
                else:
679
699
                    self.topwidget.set_focus(self.logbox)
680
700
                self.refresh()
681
 
            #elif (key == u"end" or key == u"meta >" or key == u"G"
682
 
            #      or key == u">"):
 
701
            #elif (key == "end" or key == "meta >" or key == "G"
 
702
            #      or key == ">"):
683
703
            #    pass            # xxx end-of-buffer
684
 
            #elif (key == u"home" or key == u"meta <" or key == u"g"
685
 
            #      or key == u"<"):
 
704
            #elif (key == "home" or key == "meta <" or key == "g"
 
705
            #      or key == "<"):
686
706
            #    pass            # xxx beginning-of-buffer
687
 
            #elif key == u"ctrl e" or key == u"$":
 
707
            #elif key == "ctrl e" or key == "$":
688
708
            #    pass            # xxx move-end-of-line
689
 
            #elif key == u"ctrl a" or key == u"^":
 
709
            #elif key == "ctrl a" or key == "^":
690
710
            #    pass            # xxx move-beginning-of-line
691
 
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
711
            #elif key == "ctrl b" or key == "meta (" or key == "h":
692
712
            #    pass            # xxx left
693
 
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
713
            #elif key == "ctrl f" or key == "meta )" or key == "l":
694
714
            #    pass            # xxx right
695
 
            #elif key == u"a":
 
715
            #elif key == "a":
696
716
            #    pass            # scroll up log
697
 
            #elif key == u"z":
 
717
            #elif key == "z":
698
718
            #    pass            # scroll down log
699
719
            elif self.topwidget.selectable():
700
720
                self.topwidget.keypress(self.size, key)