15
41
from dbus.mainloop.glib import DBusGMainLoop
42
from gi.repository import GLib
24
locale.setlocale(locale.LC_ALL, u'')
50
if sys.version_info.major == 2:
53
locale.setlocale(locale.LC_ALL, '')
55
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
26
57
# Some useful constants
27
domain = 'se.bsnet.fukt'
58
domain = 'se.recompile'
28
59
server_interface = domain + '.Mandos'
29
60
client_interface = domain + '.Mandos.Client'
32
# Always run in monochrome mode
33
urwid.curses_display.curses.has_colors = lambda : False
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)
64
dbus.OBJECT_MANAGER_IFACE
65
except AttributeError:
66
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
69
def isoformat_to_datetime(iso):
70
"Parse an ISO 8601 date string to a datetime.datetime()"
73
d, t = iso.split("T", 1)
74
year, month, day = d.split("-", 2)
75
hour, minute, second = t.split(":", 2)
76
second, fraction = divmod(float(second), 1)
77
return datetime.datetime(int(year),
82
int(second), # Whole seconds
83
int(fraction*1000000)) # Microseconds
40
86
class MandosClientPropertyCache(object):
41
87
"""This wraps a Mandos Client D-Bus proxy object, caches the
42
88
properties and calls a hook function when any of them are
45
def __init__(self, proxy_object=None, *args, **kwargs):
46
self.proxy = proxy_object # Mandos Client proxy object
48
self.properties = dict()
49
self.proxy.connect_to_signal(u"PropertyChanged",
50
self.property_changed,
54
self.properties.update(
55
self.proxy.GetAll(client_interface,
56
dbus_interface = dbus.PROPERTIES_IFACE))
57
super(MandosClientPropertyCache, self).__init__(
58
proxy_object=proxy_object, *args, **kwargs)
60
def property_changed(self, property=None, value=None):
61
"""This is called whenever we get a PropertyChanged signal
62
It updates the changed property in the "properties" dict.
91
def __init__(self, proxy_object=None, properties=None, **kwargs):
92
self.proxy = proxy_object # Mandos Client proxy object
93
self.properties = dict() if properties is None else properties
94
self.property_changed_match = (
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
100
if properties is None:
101
self.properties.update(self.proxy.GetAll(
103
dbus_interface=dbus.PROPERTIES_IFACE))
105
super(MandosClientPropertyCache, self).__init__(**kwargs)
107
def properties_changed(self, interface, properties, invalidated):
108
"""This is called whenever we get a PropertiesChanged signal
109
It updates the changed properties in the "properties" dict.
64
111
# Update properties dict with new value
65
self.properties[property] = value
112
if interface == client_interface:
113
self.properties.update(properties)
116
self.property_changed_match.remove()
68
119
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
69
120
"""A Mandos Client which is visible on the screen.
72
123
def __init__(self, server_proxy_object=None, update_hook=None,
73
delete_hook=None, logger=None, *args, **kwargs):
124
delete_hook=None, logger=None, **kwargs):
74
125
# Called on update
75
126
self.update_hook = update_hook
76
127
# Called on delete
79
130
self.server_proxy_object = server_proxy_object
81
132
self.logger = logger
134
self._update_timer_callback_tag = None
83
136
# The widget shown normally
84
self._text_widget = urwid.Text(u"")
137
self._text_widget = urwid.Text("")
85
138
# The widget shown when we have focus
86
self._focus_text_widget = urwid.Text(u"")
87
super(MandosClientWidget, self).__init__(
88
update_hook=update_hook, delete_hook=delete_hook,
139
self._focus_text_widget = urwid.Text("")
140
super(MandosClientWidget, self).__init__(**kwargs)
91
142
self.opened = False
92
self.proxy.connect_to_signal(u"CheckerCompleted",
93
self.checker_completed,
96
self.proxy.connect_to_signal(u"CheckerStarted",
100
self.proxy.connect_to_signal(u"GotSecret",
104
self.proxy.connect_to_signal(u"Rejected",
144
self.match_objects = (
145
self.proxy.connect_to_signal("CheckerCompleted",
146
self.checker_completed,
149
self.proxy.connect_to_signal("CheckerStarted",
150
self.checker_started,
153
self.proxy.connect_to_signal("GotSecret",
157
self.proxy.connect_to_signal("NeedApproval",
161
self.proxy.connect_to_signal("Rejected",
165
self.logger('Created client {}'
166
.format(self.properties["Name"]), level=0)
168
def using_timer(self, flag):
169
"""Call this method with True or False when timer should be
170
activated or deactivated.
172
if flag and self._update_timer_callback_tag is None:
173
# Will update the shown timer value every second
174
self._update_timer_callback_tag = (GLib.timeout_add
177
elif not (flag or self._update_timer_callback_tag is None):
178
GLib.source_remove(self._update_timer_callback_tag)
179
self._update_timer_callback_tag = None
109
181
def checker_completed(self, exitstatus, condition, command):
110
182
if exitstatus == 0:
111
self.logger(u'Checker for client %s (command "%s")'
113
% (self.properties[u"name"], command))
183
self.logger('Checker for client {} (command "{}")'
184
' succeeded'.format(self.properties["Name"],
115
189
if os.WIFEXITED(condition):
116
self.logger(u'Checker for client %s (command "%s")'
117
u' failed with exit code %s'
118
% (self.properties[u"name"], command,
119
os.WEXITSTATUS(condition)))
121
if os.WIFSIGNALED(condition):
122
self.logger(u'Checker for client %s (command "%s")'
123
u' was killed by signal %s'
124
% (self.properties[u"name"], command,
125
os.WTERMSIG(condition)))
127
if os.WCOREDUMP(condition):
128
self.logger(u'Checker for client %s (command "%s")'
130
% (self.properties[u"name"], command))
131
self.logger(u'Checker for client %s completed mysteriously')
190
self.logger('Checker for client {} (command "{}") failed'
192
.format(self.properties["Name"], command,
193
os.WEXITSTATUS(condition)))
194
elif os.WIFSIGNALED(condition):
195
self.logger('Checker for client {} (command "{}") was'
196
' killed by signal {}'
197
.format(self.properties["Name"], command,
198
os.WTERMSIG(condition)))
133
201
def checker_started(self, command):
134
self.logger(u'Client %s started checker "%s"'
135
% (self.properties[u"name"], unicode(command)))
202
"""Server signals that a checker started."""
203
self.logger('Client {} started checker "{}"'
204
.format(self.properties["Name"],
137
207
def got_secret(self):
138
self.logger(u'Client %s received its secret'
139
% self.properties[u"name"])
142
self.logger(u'Client %s was rejected'
143
% self.properties[u"name"])
208
self.logger('Client {} received its secret'
209
.format(self.properties["Name"]))
211
def need_approval(self, timeout, default):
213
message = 'Client {} needs approval within {} seconds'
215
message = 'Client {} will get its secret in {} seconds'
216
self.logger(message.format(self.properties["Name"],
219
def rejected(self, reason):
220
self.logger('Client {} was rejected; reason: {}'
221
.format(self.properties["Name"], reason))
145
223
def selectable(self):
146
224
"""Make this a "selectable" widget.
147
225
This overrides the method from urwid.FlowWidget."""
150
def rows(self, (maxcol,), focus=False):
228
def rows(self, maxcolrow, focus=False):
151
229
"""How many rows this widget will occupy might depend on
152
230
whether we have focus or not.
153
231
This overrides the method from urwid.FlowWidget"""
154
return self.current_widget(focus).rows((maxcol,), focus=focus)
232
return self.current_widget(focus).rows(maxcolrow, focus=focus)
156
234
def current_widget(self, focus=False):
157
235
if focus or self.opened:
158
236
return self._focus_widget
159
237
return self._widget
161
239
def update(self):
162
240
"Called when what is visible on the screen should be updated."
163
241
# How to add standout mode to a style
164
with_standout = { u"normal": u"standout",
165
u"bold": u"bold-standout",
167
u"underline-blink-standout",
168
u"bold-underline-blink":
169
u"bold-underline-blink-standout",
242
with_standout = {"normal": "standout",
243
"bold": "bold-standout",
245
"underline-blink-standout",
246
"bold-underline-blink":
247
"bold-underline-blink-standout",
172
250
# Rebuild focus and non-focus widgets using current properties
173
self._text = (u'%(name)s: %(enabled)s'
174
% { u"name": self.properties[u"name"],
177
if self.properties[u"enabled"]
252
# Base part of a client. Name!
253
base = '{name}: '.format(name=self.properties["Name"])
254
if not self.properties["Enabled"]:
256
self.using_timer(False)
257
elif self.properties["ApprovalPending"]:
258
timeout = datetime.timedelta(
259
milliseconds=self.properties["ApprovalDelay"])
260
last_approval_request = isoformat_to_datetime(
261
self.properties["LastApprovalRequest"])
262
if last_approval_request is not None:
263
timer = max(timeout - (datetime.datetime.utcnow()
264
- last_approval_request),
265
datetime.timedelta())
267
timer = datetime.timedelta()
268
if self.properties["ApprovedByDefault"]:
269
message = "Approval in {}. (d)eny?"
271
message = "Denial in {}. (a)pprove?"
272
message = message.format(str(timer).rsplit(".", 1)[0])
273
self.using_timer(True)
274
elif self.properties["LastCheckerStatus"] != 0:
275
# When checker has failed, show timer until client expires
276
expires = self.properties["Expires"]
278
timer = datetime.timedelta(0)
280
expires = (datetime.datetime.strptime
281
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
282
timer = max(expires - datetime.datetime.utcnow(),
283
datetime.timedelta())
284
message = ('A checker has failed! Time until client'
286
.format(str(timer).rsplit(".", 1)[0]))
287
self.using_timer(True)
290
self.using_timer(False)
291
self._text = "{}{}".format(base, message)
179
293
if not urwid.supports_unicode():
180
294
self._text = self._text.encode("ascii", "replace")
181
textlist = [(u"normal", self._text)]
295
textlist = [("normal", self._text)]
182
296
self._text_widget.set_text(textlist)
183
297
self._focus_text_widget.set_text([(with_standout[text[0]],
191
305
# Run update hook, if any
192
306
if self.update_hook is not None:
193
307
self.update_hook()
309
def update_timer(self):
310
"""called by GLib. Will indefinitely loop until
311
GLib.source_remove() on tag is called
314
return True # Keep calling this
316
def delete(self, **kwargs):
317
if self._update_timer_callback_tag is not None:
318
GLib.source_remove(self._update_timer_callback_tag)
319
self._update_timer_callback_tag = None
320
for match in self.match_objects:
322
self.match_objects = ()
196
323
if self.delete_hook is not None:
197
324
self.delete_hook(self)
199
def render(self, (maxcol,), focus=False):
325
return super(MandosClientWidget, self).delete(**kwargs)
327
def render(self, maxcolrow, focus=False):
200
328
"""Render differently if we have focus.
201
329
This overrides the method from urwid.FlowWidget"""
202
return self.current_widget(focus).render((maxcol,),
330
return self.current_widget(focus).render(maxcolrow,
205
def keypress(self, (maxcol,), key):
333
def keypress(self, maxcolrow, key):
207
335
This overrides the method from urwid.FlowWidget"""
208
if key == u"e" or key == u"+":
210
elif key == u"d" or key == u"-":
212
elif key == u"r" or key == u"_" or key == u"ctrl k":
337
self.proxy.Set(client_interface, "Enabled",
338
dbus.Boolean(True), ignore_reply=True,
339
dbus_interface=dbus.PROPERTIES_IFACE)
341
self.proxy.Set(client_interface, "Enabled", False,
343
dbus_interface=dbus.PROPERTIES_IFACE)
345
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
346
dbus_interface=client_interface,
349
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
350
dbus_interface=client_interface,
352
elif key == "R" or key == "_" or key == "ctrl k":
213
353
self.server_proxy_object.RemoveClient(self.proxy
216
self.proxy.StartChecker()
218
self.proxy.StopChecker()
220
self.proxy.CheckedOK()
357
self.proxy.Set(client_interface, "CheckerRunning",
358
dbus.Boolean(True), ignore_reply=True,
359
dbus_interface=dbus.PROPERTIES_IFACE)
361
self.proxy.Set(client_interface, "CheckerRunning",
362
dbus.Boolean(False), ignore_reply=True,
363
dbus_interface=dbus.PROPERTIES_IFACE)
365
self.proxy.CheckedOK(dbus_interface=client_interface,
222
# elif key == u"p" or key == "=":
368
# elif key == "p" or key == "=":
223
369
# self.proxy.pause()
224
# elif key == u"u" or key == ":":
370
# elif key == "u" or key == ":":
225
371
# self.proxy.unpause()
226
# elif key == u"RET":
231
def property_changed(self, property=None, value=None,
233
"""Call self.update() if old value is not new value.
377
def properties_changed(self, interface, properties, invalidated):
378
"""Call self.update() if any properties changed.
234
379
This overrides the method from MandosClientPropertyCache"""
235
property_name = unicode(property)
236
old_value = self.properties.get(property_name)
237
super(MandosClientWidget, self).property_changed(
238
property=property, value=value, *args, **kwargs)
239
if self.properties.get(property_name) != old_value:
380
old_values = {key: self.properties.get(key)
381
for key in properties.keys()}
382
super(MandosClientWidget, self).properties_changed(
383
interface, properties, invalidated)
384
if any(old_values[key] != self.properties.get(key)
385
for key in old_values):
256
403
"""This is the entire user interface - the whole screen
257
404
with boxes, lists of client widgets, etc.
259
def __init__(self, max_log_length=1000):
406
def __init__(self, max_log_length=1000, log_level=1):
260
407
DBusGMainLoop(set_as_default=True)
262
409
self.screen = urwid.curses_display.Screen()
264
411
self.screen.register_palette((
266
u"default", u"default", None),
268
u"default", u"default", u"bold"),
270
u"default", u"default", u"underline"),
272
u"default", u"default", u"standout"),
273
(u"bold-underline-blink",
274
u"default", u"default", (u"bold", u"underline")),
276
u"default", u"default", (u"bold", u"standout")),
277
(u"underline-blink-standout",
278
u"default", u"default", (u"underline", u"standout")),
279
(u"bold-underline-blink-standout",
280
u"default", u"default", (u"bold", u"underline",
413
"default", "default", None),
415
"bold", "default", "bold"),
417
"underline,blink", "default", "underline,blink"),
419
"standout", "default", "standout"),
420
("bold-underline-blink",
421
"bold,underline,blink", "default",
422
"bold,underline,blink"),
424
"bold,standout", "default", "bold,standout"),
425
("underline-blink-standout",
426
"underline,blink,standout", "default",
427
"underline,blink,standout"),
428
("bold-underline-blink-standout",
429
"bold,underline,blink,standout", "default",
430
"bold,underline,blink,standout"),
284
433
if urwid.supports_unicode():
285
self.divider = u"─" # \u2500
286
#self.divider = u"━" # \u2501
434
self.divider = "─" # \u2500
288
#self.divider = u"-" # \u002d
289
self.divider = u"_" # \u005f
436
self.divider = "_" # \u005f
291
438
self.screen.start()
293
440
self.size = self.screen.get_cols_rows()
295
442
self.clients = urwid.SimpleListWalker([])
296
443
self.clients_dict = {}
298
445
# We will add Text widgets to this list
300
447
self.max_log_length = max_log_length
449
self.log_level = log_level
302
451
# We keep a reference to the log widget so we can remove it
303
452
# from the ListWalker without it getting destroyed
304
453
self.logbox = ConstrainedListBox(self.log)
306
455
# This keeps track of whether self.uilist currently has
307
456
# self.logbox in it or not
308
457
self.log_visible = True
309
self.log_wrap = u"any"
458
self.log_wrap = "any"
312
self.log_message_raw((u"bold",
313
u"Mandos Monitor version " + version))
314
self.log_message_raw((u"bold",
461
self.log_message_raw(("bold",
462
"Mandos Monitor version " + version))
463
self.log_message_raw(("bold",
317
466
self.busname = domain + '.Mandos'
318
self.main_loop = gobject.MainLoop()
319
self.bus = dbus.SystemBus()
320
mandos_dbus_objc = self.bus.get_object(
321
self.busname, u"/", follow_name_owner_changes=True)
322
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
326
mandos_clients = (self.mandos_serv
327
.GetAllClientsWithProperties())
328
except dbus.exceptions.DBusException:
329
mandos_clients = dbus.Dictionary()
332
.connect_to_signal(u"ClientRemoved",
333
self.find_and_remove_client,
334
dbus_interface=server_interface,
337
.connect_to_signal(u"ClientAdded",
339
dbus_interface=server_interface,
342
.connect_to_signal(u"ClientNotFound",
343
self.client_not_found,
344
dbus_interface=server_interface,
346
for path, client in mandos_clients.iteritems():
347
client_proxy_object = self.bus.get_object(self.busname,
349
self.add_client(MandosClientWidget(server_proxy_object
352
=client_proxy_object,
467
self.main_loop = GLib.MainLoop()
362
469
def client_not_found(self, fingerprint, address):
363
self.log_message((u"Client with address %s and fingerprint %s"
364
u" could not be found" % (address,
470
self.log_message("Client with address {} and fingerprint {}"
471
" could not be found"
472
.format(address, fingerprint))
367
474
def rebuild(self):
368
475
"""This rebuilds the User Interface.
369
476
Call this when the widget layout needs to change"""
371
#self.uilist.append(urwid.ListBox(self.clients))
372
self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
373
#header=urwid.Divider(),
478
# self.uilist.append(urwid.ListBox(self.clients))
479
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
481
# header=urwid.Divider(),
375
footer=urwid.Divider(div_char=self.divider)))
483
footer=urwid.Divider(
484
div_char=self.divider)))
376
485
if self.log_visible:
377
486
self.uilist.append(self.logbox)
379
487
self.topwidget = urwid.Pile(self.uilist)
381
def log_message(self, message):
489
def log_message(self, message, level=1):
490
"""Log message formatted with timestamp"""
491
if level < self.log_level:
382
493
timestamp = datetime.datetime.now().isoformat()
383
self.log_message_raw(timestamp + u": " + message)
385
def log_message_raw(self, markup):
494
self.log_message_raw("{}: {}".format(timestamp, message),
497
def log_message_raw(self, markup, level=1):
386
498
"""Add a log message to the log buffer."""
499
if level < self.log_level:
387
501
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
388
if (self.max_log_length
389
and len(self.log) > self.max_log_length):
390
del self.log[0:len(self.log)-self.max_log_length-1]
502
if self.max_log_length:
503
if len(self.log) > self.max_log_length:
504
del self.log[0:len(self.log)-self.max_log_length-1]
391
505
self.logbox.set_focus(len(self.logbox.body.contents),
392
coming_from=u"above")
395
509
def toggle_log_display(self):
396
510
"""Toggle visibility of the log buffer."""
397
511
self.log_visible = not self.log_visible
399
self.log_message(u"Log visibility changed to: "
400
+ unicode(self.log_visible))
513
self.log_message("Log visibility changed to: {}"
514
.format(self.log_visible), level=0)
402
516
def change_log_display(self):
403
517
"""Change type of log display.
404
518
Currently, this toggles wrapping of text lines."""
405
if self.log_wrap == u"clip":
406
self.log_wrap = u"any"
519
if self.log_wrap == "clip":
520
self.log_wrap = "any"
408
self.log_wrap = u"clip"
522
self.log_wrap = "clip"
409
523
for textwidget in self.log:
410
524
textwidget.set_wrap_mode(self.log_wrap)
411
self.log_message(u"Wrap mode: " + self.log_wrap)
413
def find_and_remove_client(self, path, name):
414
"""Find an client from its object path and remove it.
416
This is connected to the ClientRemoved signal from the
525
self.log_message("Wrap mode: {}".format(self.log_wrap),
528
def find_and_remove_client(self, path, interfaces):
529
"""Find a client by its object path and remove it.
531
This is connected to the InterfacesRemoved signal from the
417
532
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
419
537
client = self.clients_dict[path]
423
self.remove_client(client, path)
425
def add_new_client(self, path):
540
self.log_message("Unknown client {!r} removed"
545
def add_new_client(self, path, ifs_and_props):
546
"""Find a client by its object path and remove it.
548
This is connected to the InterfacesAdded signal from the
549
Mandos server object.
551
if client_interface not in ifs_and_props:
552
# Not a Mandos client object; ignore
426
554
client_proxy_object = self.bus.get_object(self.busname, path)
427
self.add_client(MandosClientWidget(server_proxy_object
430
=client_proxy_object,
555
self.add_client(MandosClientWidget(
556
server_proxy_object=self.mandos_serv,
557
proxy_object=client_proxy_object,
558
update_hook=self.refresh,
559
delete_hook=self.remove_client,
560
logger=self.log_message,
561
properties=dict(ifs_and_props[client_interface])),
439
564
def add_client(self, client, path=None):
440
565
self.clients.append(client)
442
567
path = client.proxy.object_path
443
568
self.clients_dict[path] = client
444
self.clients.sort(None, lambda c: c.properties[u"name"])
569
self.clients.sort(key=lambda c: c.properties["Name"])
447
572
def remove_client(self, client, path=None):
448
573
self.clients.remove(client)
450
575
path = client.proxy.object_path
451
576
del self.clients_dict[path]
452
if not self.clients_dict:
453
# Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
454
# is completely emptied, we need to recreate it.
455
self.clients = urwid.SimpleListWalker([])
459
579
def refresh(self):
460
580
"""Redraw the screen"""
461
581
canvas = self.topwidget.render(self.size, focus=True)
462
582
self.screen.draw_screen(self.size, canvas)
465
585
"""Start the main loop and exit when it's done."""
586
self.bus = dbus.SystemBus()
587
mandos_dbus_objc = self.bus.get_object(
588
self.busname, "/", follow_name_owner_changes=True)
589
self.mandos_serv = dbus.Interface(
590
mandos_dbus_objc, dbus_interface=server_interface)
592
mandos_clients = (self.mandos_serv
593
.GetAllClientsWithProperties())
594
if not mandos_clients:
595
self.log_message_raw(("bold",
596
"Note: Server has no clients."))
597
except dbus.exceptions.DBusException:
598
self.log_message_raw(("bold",
599
"Note: No Mandos server running."))
600
mandos_clients = dbus.Dictionary()
603
.connect_to_signal("InterfacesRemoved",
604
self.find_and_remove_client,
605
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
608
.connect_to_signal("InterfacesAdded",
610
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
613
.connect_to_signal("ClientNotFound",
614
self.client_not_found,
615
dbus_interface=server_interface,
617
for path, client in mandos_clients.items():
618
client_proxy_object = self.bus.get_object(self.busname,
620
self.add_client(MandosClientWidget(
621
server_proxy_object=self.mandos_serv,
622
proxy_object=client_proxy_object,
624
update_hook=self.refresh,
625
delete_hook=self.remove_client,
626
logger=self.log_message),
467
self._input_callback_tag = (gobject.io_add_watch
630
self._input_callback_tag = (GLib.io_add_watch
468
631
(sys.stdin.fileno(),
470
633
self.process_input))
471
634
self.main_loop.run()
472
635
# Main loop has finished, we should close everything now
473
gobject.source_remove(self._input_callback_tag)
636
GLib.source_remove(self._input_callback_tag)
474
637
self.screen.stop()
477
640
self.main_loop.quit()
479
642
def process_input(self, source, condition):
480
643
keys = self.screen.get_input()
481
translations = { u"ctrl n": u"down", # Emacs
482
u"ctrl p": u"up", # Emacs
483
u"ctrl v": u"page down", # Emacs
484
u"meta v": u"page up", # Emacs
485
u" ": u"page down", # less
486
u"f": u"page down", # less
487
u"b": u"page up", # less
644
translations = {"ctrl n": "down", # Emacs
645
"ctrl p": "up", # Emacs
646
"ctrl v": "page down", # Emacs
647
"meta v": "page up", # Emacs
648
" ": "page down", # less
649
"f": "page down", # less
650
"b": "page up", # less
493
656
key = translations[key]
494
657
except KeyError: # :-)
497
if key == u"q" or key == u"Q":
660
if key == "q" or key == "Q":
500
elif key == u"window resize":
663
elif key == "window resize":
501
664
self.size = self.screen.get_cols_rows()
503
elif key == u"\f": # Ctrl-L
666
elif key == "ctrl l":
505
elif key == u"l" or key == u"D":
669
elif key == "l" or key == "D":
506
670
self.toggle_log_display()
508
elif key == u"w" or key == u"i":
672
elif key == "w" or key == "i":
509
673
self.change_log_display()
511
elif key == u"?" or key == u"f1" or key == u"esc":
675
elif key == "?" or key == "f1" or key == "esc":
512
676
if not self.log_visible:
513
677
self.log_visible = True
515
self.log_message_raw((u"bold",
519
u"l: Log window toggle",
520
u"TAB: Switch window",
522
self.log_message_raw((u"bold",
528
u"s: Start new checker",
679
self.log_message_raw(("bold",
683
"l: Log window toggle",
684
"TAB: Switch window",
685
"w: Wrap (log lines)",
686
"v: Toggle verbose log",
688
self.log_message_raw(("bold",
694
"s: Start new checker",
533
701
if self.topwidget.get_focus() is self.logbox:
534
702
self.topwidget.set_focus(0)
536
704
self.topwidget.set_focus(self.logbox)
538
#elif (key == u"end" or key == u"meta >" or key == u"G"
540
# pass # xxx end-of-buffer
541
#elif (key == u"home" or key == u"meta <" or key == u"g"
543
# pass # xxx beginning-of-buffer
544
#elif key == u"ctrl e" or key == u"$":
545
# pass # xxx move-end-of-line
546
#elif key == u"ctrl a" or key == u"^":
547
# pass # xxx move-beginning-of-line
548
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
550
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
553
# pass # scroll up log
555
# pass # scroll down log
707
if self.log_level == 0:
709
self.log_message("Verbose mode: Off")
712
self.log_message("Verbose mode: On")
713
# elif (key == "end" or key == "meta >" or key == "G"
715
# pass # xxx end-of-buffer
716
# elif (key == "home" or key == "meta <" or key == "g"
718
# pass # xxx beginning-of-buffer
719
# elif key == "ctrl e" or key == "$":
720
# pass # xxx move-end-of-line
721
# elif key == "ctrl a" or key == "^":
722
# pass # xxx move-beginning-of-line
723
# elif key == "ctrl b" or key == "meta (" or key == "h":
725
# elif key == "ctrl f" or key == "meta )" or key == "l":
728
# pass # scroll up log
730
# pass # scroll down log
556
731
elif self.topwidget.selectable():
557
732
self.topwidget.keypress(self.size, key)