2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2018 Teddy Hogeborn
7
# Copyright © 2009-2018 Björn Påhlsson
9
# This file is part of Mandos.
11
# Mandos is free software: you can redistribute it and/or modify it
12
# under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, either version 3 of the License, or
14
# (at your option) any later version.
16
# Mandos is distributed in the hope that it will be useful, but
17
# WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
# GNU General Public License for more details.
21
# You should have received a copy of the GNU General Public License
22
# along with Mandos. If not, see <http://www.gnu.org/licenses/>.
24
# Contact the authors at <mandos@recompile.se>.
4
from __future__ import division, absolute_import, with_statement
27
from __future__ import (division, absolute_import, print_function,
30
from future_builtins import *
9
39
import urwid.curses_display
12
42
from dbus.mainloop.glib import DBusGMainLoop
43
from gi.repository import GLib
51
if sys.version_info.major == 2:
54
locale.setlocale(locale.LC_ALL, '')
56
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
19
58
# Some useful constants
20
domain = 'se.bsnet.fukt'
59
domain = 'se.recompile'
21
60
server_interface = domain + '.Mandos'
22
61
client_interface = domain + '.Mandos.Client'
25
# Always run in monochrome mode
26
urwid.curses_display.curses.has_colors = lambda : False
28
# Urwid doesn't support blinking, but we want it. Since we have no
29
# use for underline on its own, we make underline also always blink.
30
urwid.curses_display.curses.A_UNDERLINE |= (
31
urwid.curses_display.curses.A_BLINK)
65
dbus.OBJECT_MANAGER_IFACE
66
except AttributeError:
67
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
70
def isoformat_to_datetime(iso):
71
"Parse an ISO 8601 date string to a datetime.datetime()"
74
d, t = iso.split("T", 1)
75
year, month, day = d.split("-", 2)
76
hour, minute, second = t.split(":", 2)
77
second, fraction = divmod(float(second), 1)
78
return datetime.datetime(int(year),
83
int(second), # Whole seconds
84
int(fraction*1000000)) # Microseconds
33
87
class MandosClientPropertyCache(object):
34
88
"""This wraps a Mandos Client D-Bus proxy object, caches the
35
89
properties and calls a hook function when any of them are
38
def __init__(self, proxy_object=None, properties=None, *args,
40
self.proxy = proxy_object # Mandos Client proxy object
42
if properties is None:
43
self.properties = dict()
45
self.properties = properties
46
self.proxy.connect_to_signal("PropertyChanged",
47
self.property_changed,
51
if properties is None:
52
self.properties.update(self.proxy.GetAll(client_interface,
54
dbus.PROPERTIES_IFACE))
55
super(MandosClientPropertyCache, self).__init__(
56
proxy_object=proxy_object,
57
properties=properties, *args, **kwargs)
59
def property_changed(self, property=None, value=None):
60
"""This is called whenever we get a PropertyChanged signal
61
It updates the changed property in the "properties" dict.
92
def __init__(self, proxy_object=None, properties=None, **kwargs):
93
self.proxy = proxy_object # Mandos Client proxy object
94
self.properties = dict() if properties is None else properties
95
self.property_changed_match = (
96
self.proxy.connect_to_signal("PropertiesChanged",
97
self.properties_changed,
98
dbus.PROPERTIES_IFACE,
101
if properties is None:
102
self.properties.update(self.proxy.GetAll(
104
dbus_interface=dbus.PROPERTIES_IFACE))
106
super(MandosClientPropertyCache, self).__init__(**kwargs)
108
def properties_changed(self, interface, properties, invalidated):
109
"""This is called whenever we get a PropertiesChanged signal
110
It updates the changed properties in the "properties" dict.
63
112
# Update properties dict with new value
64
self.properties[property] = value
113
if interface == client_interface:
114
self.properties.update(properties)
117
self.property_changed_match.remove()
67
120
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
68
121
"""A Mandos Client which is visible on the screen.
71
124
def __init__(self, server_proxy_object=None, update_hook=None,
72
delete_hook=None, *args, **kwargs):
125
delete_hook=None, logger=None, **kwargs):
73
126
# Called on update
74
127
self.update_hook = update_hook
75
128
# Called on delete
76
129
self.delete_hook = delete_hook
77
130
# Mandos Server proxy object
78
131
self.server_proxy_object = server_proxy_object
135
self._update_timer_callback_tag = None
80
137
# The widget shown normally
81
138
self._text_widget = urwid.Text("")
82
139
# The widget shown when we have focus
83
140
self._focus_text_widget = urwid.Text("")
84
super(MandosClientWidget, self).__init__(
85
update_hook=update_hook, delete_hook=delete_hook,
141
super(MandosClientWidget, self).__init__(**kwargs)
88
143
self.opened = False
145
self.match_objects = (
146
self.proxy.connect_to_signal("CheckerCompleted",
147
self.checker_completed,
150
self.proxy.connect_to_signal("CheckerStarted",
151
self.checker_started,
154
self.proxy.connect_to_signal("GotSecret",
158
self.proxy.connect_to_signal("NeedApproval",
162
self.proxy.connect_to_signal("Rejected",
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
169
def using_timer(self, flag):
170
"""Call this method with True or False when timer should be
171
activated or deactivated.
173
if flag and self._update_timer_callback_tag is None:
174
# Will update the shown timer value every second
175
self._update_timer_callback_tag = (GLib.timeout_add
178
elif not (flag or self._update_timer_callback_tag is None):
179
GLib.source_remove(self._update_timer_callback_tag)
180
self._update_timer_callback_tag = None
182
def checker_completed(self, exitstatus, condition, command):
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
190
if os.WIFEXITED(condition):
191
self.logger('Checker for client {} (command "{}") failed'
193
.format(self.properties["Name"], command,
194
os.WEXITSTATUS(condition)))
195
elif os.WIFSIGNALED(condition):
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
198
.format(self.properties["Name"], command,
199
os.WTERMSIG(condition)))
202
def checker_started(self, command):
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
208
def got_secret(self):
209
self.logger('Client {} received its secret'
210
.format(self.properties["Name"]))
212
def need_approval(self, timeout, default):
214
message = 'Client {} needs approval within {} seconds'
216
message = 'Client {} will get its secret in {} seconds'
217
self.logger(message.format(self.properties["Name"],
220
def rejected(self, reason):
221
self.logger('Client {} was rejected; reason: {}'
222
.format(self.properties["Name"], reason))
90
224
def selectable(self):
91
225
"""Make this a "selectable" widget.
92
226
This overrides the method from urwid.FlowWidget."""
95
def rows(self, (maxcol,), focus=False):
229
def rows(self, maxcolrow, focus=False):
96
230
"""How many rows this widget will occupy might depend on
97
231
whether we have focus or not.
98
232
This overrides the method from urwid.FlowWidget"""
99
return self.current_widget(focus).rows((maxcol,), focus=focus)
233
return self.current_widget(focus).rows(maxcolrow, focus=focus)
101
235
def current_widget(self, focus=False):
102
236
if focus or self.opened:
103
237
return self._focus_widget
104
238
return self._widget
106
240
def update(self):
107
241
"Called when what is visible on the screen should be updated."
108
242
# How to add standout mode to a style
109
with_standout = { u"normal": u"standout",
110
u"bold": u"bold-standout",
112
u"underline-blink-standout",
113
u"bold-underline-blink":
114
u"bold-underline-blink-standout",
243
with_standout = {"normal": "standout",
244
"bold": "bold-standout",
246
"underline-blink-standout",
247
"bold-underline-blink":
248
"bold-underline-blink-standout",
117
251
# Rebuild focus and non-focus widgets using current properties
118
self._text = (u'name="%(name)s", enabled=%(enabled)s'
253
# Base part of a client. Name!
254
base = '{name}: '.format(name=self.properties["Name"])
255
if not self.properties["Enabled"]:
257
self.using_timer(False)
258
elif self.properties["ApprovalPending"]:
259
timeout = datetime.timedelta(
260
milliseconds=self.properties["ApprovalDelay"])
261
last_approval_request = isoformat_to_datetime(
262
self.properties["LastApprovalRequest"])
263
if last_approval_request is not None:
264
timer = max(timeout - (datetime.datetime.utcnow()
265
- last_approval_request),
266
datetime.timedelta())
268
timer = datetime.timedelta()
269
if self.properties["ApprovedByDefault"]:
270
message = "Approval in {}. (d)eny?"
272
message = "Denial in {}. (a)pprove?"
273
message = message.format(str(timer).rsplit(".", 1)[0])
274
self.using_timer(True)
275
elif self.properties["LastCheckerStatus"] != 0:
276
# When checker has failed, show timer until client expires
277
expires = self.properties["Expires"]
279
timer = datetime.timedelta(0)
281
expires = (datetime.datetime.strptime
282
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
283
timer = max(expires - datetime.datetime.utcnow(),
284
datetime.timedelta())
285
message = ('A checker has failed! Time until client'
287
.format(str(timer).rsplit(".", 1)[0]))
288
self.using_timer(True)
291
self.using_timer(False)
292
self._text = "{}{}".format(base, message)
120
294
if not urwid.supports_unicode():
121
295
self._text = self._text.encode("ascii", "replace")
122
textlist = [(u"normal", u"BLARGH: "), (u"bold", self._text)]
296
textlist = [("normal", self._text)]
123
297
self._text_widget.set_text(textlist)
124
298
self._focus_text_widget.set_text([(with_standout[text[0]],
132
306
# Run update hook, if any
133
307
if self.update_hook is not None:
134
308
self.update_hook()
310
def update_timer(self):
311
"""called by GLib. Will indefinitely loop until
312
GLib.source_remove() on tag is called
315
return True # Keep calling this
317
def delete(self, **kwargs):
318
if self._update_timer_callback_tag is not None:
319
GLib.source_remove(self._update_timer_callback_tag)
320
self._update_timer_callback_tag = None
321
for match in self.match_objects:
323
self.match_objects = ()
137
324
if self.delete_hook is not None:
138
325
self.delete_hook(self)
140
def render(self, (maxcol,), focus=False):
326
return super(MandosClientWidget, self).delete(**kwargs)
328
def render(self, maxcolrow, focus=False):
141
329
"""Render differently if we have focus.
142
330
This overrides the method from urwid.FlowWidget"""
143
return self.current_widget(focus).render((maxcol,),
331
return self.current_widget(focus).render(maxcolrow,
146
def keypress(self, (maxcol,), key):
334
def keypress(self, maxcolrow, key):
148
336
This overrides the method from urwid.FlowWidget"""
149
if key == u"e" or key == u"+":
151
elif key == u"d" or key == u"-":
153
elif key == u"r" or key == u"_":
338
self.proxy.Set(client_interface, "Enabled",
339
dbus.Boolean(True), ignore_reply=True,
340
dbus_interface=dbus.PROPERTIES_IFACE)
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface=dbus.PROPERTIES_IFACE)
346
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
dbus_interface=client_interface,
350
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
351
dbus_interface=client_interface,
353
elif key == "R" or key == "_" or key == "ctrl k":
154
354
self.server_proxy_object.RemoveClient(self.proxy
157
self.proxy.StartChecker()
159
self.proxy.StopChecker()
161
self.proxy.CheckedOK()
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply=True,
360
dbus_interface=dbus.PROPERTIES_IFACE)
362
self.proxy.Set(client_interface, "CheckerRunning",
363
dbus.Boolean(False), ignore_reply=True,
364
dbus_interface=dbus.PROPERTIES_IFACE)
366
self.proxy.CheckedOK(dbus_interface=client_interface,
163
# elif key == u"p" or key == "=":
369
# elif key == "p" or key == "=":
164
370
# self.proxy.pause()
165
# elif key == u"u" or key == ":":
371
# elif key == "u" or key == ":":
166
372
# self.proxy.unpause()
167
# elif key == u"RET":
172
def property_changed(self, property=None, value=None,
174
"""Call self.update() if old value is not new value.
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
175
380
This overrides the method from MandosClientPropertyCache"""
176
property_name = unicode(property)
177
old_value = self.properties.get(property_name)
178
super(MandosClientWidget, self).property_changed(
179
property=property, value=value, *args, **kwargs)
180
if self.properties.get(property_name) != old_value:
381
old_values = {key: self.properties.get(key)
382
for key in properties.keys()}
383
super(MandosClientWidget, self).properties_changed(
384
interface, properties, invalidated)
385
if any(old_values[key] != self.properties.get(key)
386
for key in old_values):
197
404
"""This is the entire user interface - the whole screen
198
405
with boxes, lists of client widgets, etc.
200
def __init__(self, max_log_length=1000):
407
def __init__(self, max_log_length=1000, log_level=1):
201
408
DBusGMainLoop(set_as_default=True)
203
410
self.screen = urwid.curses_display.Screen()
205
412
self.screen.register_palette((
207
u"default", u"default", None),
209
u"default", u"default", u"bold"),
211
u"default", u"default", u"underline"),
213
u"default", u"default", u"standout"),
214
(u"bold-underline-blink",
215
u"default", u"default", (u"bold", u"underline")),
217
u"default", u"default", (u"bold", u"standout")),
218
(u"underline-blink-standout",
219
u"default", u"default", (u"underline", u"standout")),
220
(u"bold-underline-blink-standout",
221
u"default", u"default", (u"bold", u"underline",
414
"default", "default", None),
416
"bold", "default", "bold"),
418
"underline,blink", "default", "underline,blink"),
420
"standout", "default", "standout"),
421
("bold-underline-blink",
422
"bold,underline,blink", "default",
423
"bold,underline,blink"),
425
"bold,standout", "default", "bold,standout"),
426
("underline-blink-standout",
427
"underline,blink,standout", "default",
428
"underline,blink,standout"),
429
("bold-underline-blink-standout",
430
"bold,underline,blink,standout", "default",
431
"bold,underline,blink,standout"),
225
434
if urwid.supports_unicode():
226
#self.divider = u"─" # \u2500
227
self.divider = u"━" # \u2501
435
self.divider = "─" # \u2500
229
#self.divider = u"-" # \u002d
230
self.divider = u"_" # \u005f
437
self.divider = "_" # \u005f
232
439
self.screen.start()
234
441
self.size = self.screen.get_cols_rows()
236
443
self.clients = urwid.SimpleListWalker([])
237
444
self.clients_dict = {}
239
446
# We will add Text widgets to this list
241
448
self.max_log_length = max_log_length
450
self.log_level = log_level
243
452
# We keep a reference to the log widget so we can remove it
244
453
# from the ListWalker without it getting destroyed
245
454
self.logbox = ConstrainedListBox(self.log)
247
456
# This keeps track of whether self.uilist currently has
248
457
# self.logbox in it or not
249
458
self.log_visible = True
250
self.log_wrap = u"any"
459
self.log_wrap = "any"
253
self.log_message(u"Message")
254
self.log_message(u"Message0 Message1 Message2 Message3 Message4 Message5 Message6 Message7 Message8 Message9")
255
self.log_message(u"Message10 Message11 Message12 Message13 Message14 Message15 Message16 Message17 Message18 Message19")
256
self.log_message(u"Message20 Message21 Message22 Message23 Message24 Message25 Message26 Message27 Message28 Message29")
462
self.log_message_raw(("bold",
463
"Mandos Monitor version " + version))
464
self.log_message_raw(("bold",
258
467
self.busname = domain + '.Mandos'
259
self.main_loop = gobject.MainLoop()
260
self.bus = dbus.SystemBus()
261
mandos_dbus_objc = self.bus.get_object(
262
self.busname, u"/", follow_name_owner_changes=True)
263
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
267
mandos_clients = (self.mandos_serv
268
.GetAllClientsWithProperties())
269
except dbus.exceptions.DBusException:
270
mandos_clients = dbus.Dictionary()
273
.connect_to_signal("ClientRemoved",
274
self.find_and_remove_client,
275
dbus_interface=server_interface,
278
.connect_to_signal("ClientAdded",
280
dbus_interface=server_interface,
282
for path, client in mandos_clients.iteritems():
283
client_proxy_object = self.bus.get_object(self.busname,
285
self.add_client(MandosClientWidget(server_proxy_object
288
=client_proxy_object,
293
=self.remove_client),
468
self.main_loop = GLib.MainLoop()
470
def client_not_found(self, fingerprint, address):
471
self.log_message("Client with address {} and fingerprint {}"
472
" could not be found"
473
.format(address, fingerprint))
296
475
def rebuild(self):
297
476
"""This rebuilds the User Interface.
298
477
Call this when the widget layout needs to change"""
300
#self.uilist.append(urwid.ListBox(self.clients))
301
self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
302
#header=urwid.Divider(),
479
# self.uilist.append(urwid.ListBox(self.clients))
480
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
482
# header=urwid.Divider(),
304
footer=urwid.Divider(div_char=self.divider)))
484
footer=urwid.Divider(
485
div_char=self.divider)))
305
486
if self.log_visible:
306
487
self.uilist.append(self.logbox)
308
488
self.topwidget = urwid.Pile(self.uilist)
310
def log_message(self, markup):
490
def log_message(self, message, level=1):
491
"""Log message formatted with timestamp"""
492
if level < self.log_level:
494
timestamp = datetime.datetime.now().isoformat()
495
self.log_message_raw("{}: {}".format(timestamp, message),
498
def log_message_raw(self, markup, level=1):
311
499
"""Add a log message to the log buffer."""
500
if level < self.log_level:
312
502
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
313
if (self.max_log_length
314
and len(self.log) > self.max_log_length):
315
del self.log[0:len(self.log)-self.max_log_length-1]
503
if self.max_log_length:
504
if len(self.log) > self.max_log_length:
505
del self.log[0:len(self.log)-self.max_log_length-1]
506
self.logbox.set_focus(len(self.logbox.body.contents),
317
510
def toggle_log_display(self):
318
511
"""Toggle visibility of the log buffer."""
319
512
self.log_visible = not self.log_visible
321
self.log_message(u"Log visibility changed to: "
322
+ unicode(self.log_visible))
514
self.log_message("Log visibility changed to: {}"
515
.format(self.log_visible), level=0)
324
517
def change_log_display(self):
325
518
"""Change type of log display.
326
519
Currently, this toggles wrapping of text lines."""
327
if self.log_wrap == u"clip":
328
self.log_wrap = u"any"
520
if self.log_wrap == "clip":
521
self.log_wrap = "any"
330
self.log_wrap = u"clip"
523
self.log_wrap = "clip"
331
524
for textwidget in self.log:
332
525
textwidget.set_wrap_mode(self.log_wrap)
333
self.log_message(u"Wrap mode: " + self.log_wrap)
335
def find_and_remove_client(self, path, name):
336
"""Find an client from its object path and remove it.
338
This is connected to the ClientRemoved signal from the
526
self.log_message("Wrap mode: {}".format(self.log_wrap),
529
def find_and_remove_client(self, path, interfaces):
530
"""Find a client by its object path and remove it.
532
This is connected to the InterfacesRemoved signal from the
339
533
Mandos server object."""
534
if client_interface not in interfaces:
535
# Not a Mandos client object; ignore
341
538
client = self.clients_dict[path]
345
self.remove_client(client, path)
347
def add_new_client(self, path, properties):
541
self.log_message("Unknown client {!r} removed"
546
def add_new_client(self, path, ifs_and_props):
547
"""Find a client by its object path and remove it.
549
This is connected to the InterfacesAdded signal from the
550
Mandos server object.
552
if client_interface not in ifs_and_props:
553
# Not a Mandos client object; ignore
348
555
client_proxy_object = self.bus.get_object(self.busname, path)
349
self.add_client(MandosClientWidget(server_proxy_object
352
=client_proxy_object,
353
properties=properties,
357
=self.remove_client),
556
self.add_client(MandosClientWidget(
557
server_proxy_object=self.mandos_serv,
558
proxy_object=client_proxy_object,
559
update_hook=self.refresh,
560
delete_hook=self.remove_client,
561
logger=self.log_message,
562
properties=dict(ifs_and_props[client_interface])),
360
565
def add_client(self, client, path=None):
361
566
self.clients.append(client)
363
568
path = client.proxy.object_path
364
569
self.clients_dict[path] = client
365
self.clients.sort(None, lambda c: c.properties[u"name"])
570
self.clients.sort(key=lambda c: c.properties["Name"])
368
573
def remove_client(self, client, path=None):
369
574
self.clients.remove(client)
371
576
path = client.proxy.object_path
372
577
del self.clients_dict[path]
373
if not self.clients_dict:
374
# Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
375
# is completely emptied, we need to recreate it.
376
self.clients = urwid.SimpleListWalker([])
380
580
def refresh(self):
381
581
"""Redraw the screen"""
382
582
canvas = self.topwidget.render(self.size, focus=True)
383
583
self.screen.draw_screen(self.size, canvas)
386
586
"""Start the main loop and exit when it's done."""
587
self.bus = dbus.SystemBus()
588
mandos_dbus_objc = self.bus.get_object(
589
self.busname, "/", follow_name_owner_changes=True)
590
self.mandos_serv = dbus.Interface(
591
mandos_dbus_objc, dbus_interface=server_interface)
593
mandos_clients = (self.mandos_serv
594
.GetAllClientsWithProperties())
595
if not mandos_clients:
596
self.log_message_raw(("bold",
597
"Note: Server has no clients."))
598
except dbus.exceptions.DBusException:
599
self.log_message_raw(("bold",
600
"Note: No Mandos server running."))
601
mandos_clients = dbus.Dictionary()
604
.connect_to_signal("InterfacesRemoved",
605
self.find_and_remove_client,
606
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
609
.connect_to_signal("InterfacesAdded",
611
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
614
.connect_to_signal("ClientNotFound",
615
self.client_not_found,
616
dbus_interface=server_interface,
618
for path, client in mandos_clients.items():
619
client_proxy_object = self.bus.get_object(self.busname,
621
self.add_client(MandosClientWidget(
622
server_proxy_object=self.mandos_serv,
623
proxy_object=client_proxy_object,
625
update_hook=self.refresh,
626
delete_hook=self.remove_client,
627
logger=self.log_message),
388
self._input_callback_tag = (gobject.io_add_watch
631
self._input_callback_tag = (GLib.io_add_watch
389
632
(sys.stdin.fileno(),
391
634
self.process_input))
392
635
self.main_loop.run()
393
636
# Main loop has finished, we should close everything now
394
gobject.source_remove(self._input_callback_tag)
637
GLib.source_remove(self._input_callback_tag)
395
638
self.screen.stop()
398
641
self.main_loop.quit()
400
643
def process_input(self, source, condition):
401
644
keys = self.screen.get_input()
402
translations = { u"ctrl n": u"down", # Emacs
403
u"ctrl p": u"up", # Emacs
404
u"ctrl v": u"page down", # Emacs
405
u"meta v": u"page up", # Emacs
406
u" ": u"page down", # less
407
u"f": u"page down", # less
408
u"b": u"page up", # less
645
translations = {"ctrl n": "down", # Emacs
646
"ctrl p": "up", # Emacs
647
"ctrl v": "page down", # Emacs
648
"meta v": "page up", # Emacs
649
" ": "page down", # less
650
"f": "page down", # less
651
"b": "page up", # less
414
657
key = translations[key]
415
658
except KeyError: # :-)
418
if key == u"q" or key == u"Q":
661
if key == "q" or key == "Q":
421
elif key == u"window resize":
664
elif key == "window resize":
422
665
self.size = self.screen.get_cols_rows()
424
elif key == u"\f": # Ctrl-L
667
elif key == "ctrl l":
426
elif key == u"l" or key == u"D":
670
elif key == "l" or key == "D":
427
671
self.toggle_log_display()
429
elif key == u"w" or key == u"i":
673
elif key == "w" or key == "i":
430
674
self.change_log_display()
432
elif key == u"?" or key == u"f1":
433
self.log_message(u"Help!")
676
elif key == "?" or key == "f1" or key == "esc":
677
if not self.log_visible:
678
self.log_visible = True
680
self.log_message_raw(("bold",
684
"l: Log window toggle",
685
"TAB: Switch window",
686
"w: Wrap (log lines)",
687
"v: Toggle verbose log",
689
self.log_message_raw(("bold",
695
"s: Start new checker",
436
702
if self.topwidget.get_focus() is self.logbox:
437
703
self.topwidget.set_focus(0)
439
705
self.topwidget.set_focus(self.logbox)
441
elif (key == u"end" or key == u"meta >" or key == u"G"
443
pass # xxx end-of-buffer
444
elif (key == u"home" or key == u"meta <" or key == u"g"
446
pass # xxx beginning-of-buffer
447
elif key == u"ctrl e" or key == u"$":
448
pass # xxx move-end-of-line
449
elif key == u"ctrl a" or key == u"^":
450
pass # xxx move-beginning-of-line
451
elif key == u"ctrl b" or key == u"meta (" or key == u"h":
453
elif key == u"ctrl f" or key == u"meta )" or key == u"l":
458
pass # scroll down log
708
if self.log_level == 0:
710
self.log_message("Verbose mode: Off")
713
self.log_message("Verbose mode: On")
714
# elif (key == "end" or key == "meta >" or key == "G"
716
# pass # xxx end-of-buffer
717
# elif (key == "home" or key == "meta <" or key == "g"
719
# pass # xxx beginning-of-buffer
720
# elif key == "ctrl e" or key == "$":
721
# pass # xxx move-end-of-line
722
# elif key == "ctrl a" or key == "^":
723
# pass # xxx move-beginning-of-line
724
# elif key == "ctrl b" or key == "meta (" or key == "h":
726
# elif key == "ctrl f" or key == "meta )" or key == "l":
729
# pass # scroll up log
731
# pass # scroll down log
459
732
elif self.topwidget.selectable():
460
733
self.topwidget.keypress(self.size, key)
464
738
ui = UserInterface()
741
except KeyboardInterrupt:
743
except Exception as e:
744
ui.log_message(str(e))