2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2016 Teddy Hogeborn
7
# Copyright © 2009-2016 Björn Påhlsson
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.
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.
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see
21
# <http://www.gnu.org/licenses/>.
23
# Contact the authors at <mandos@recompile.se>.
4
from __future__ import division, absolute_import, with_statement
26
from __future__ import (division, absolute_import, print_function,
29
from future_builtins import *
9
38
import urwid.curses_display
12
41
from dbus.mainloop.glib import DBusGMainLoop
42
from gi.repository import GLib
50
if sys.version_info.major == 2:
53
locale.setlocale(locale.LC_ALL, '')
55
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
19
57
# Some useful constants
20
domain = 'se.bsnet.fukt'
58
domain = 'se.recompile'
21
59
server_interface = domain + '.Mandos'
22
60
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)
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
33
86
class MandosClientPropertyCache(object):
34
87
"""This wraps a Mandos Client D-Bus proxy object, caches the
35
88
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.
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.
63
111
# Update properties dict with new value
64
self.properties[property] = value
112
if interface == client_interface:
113
self.properties.update(properties)
116
self.property_changed_match.remove()
67
119
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
68
120
"""A Mandos Client which is visible on the screen.
71
123
def __init__(self, server_proxy_object=None, update_hook=None,
72
delete_hook=None, *args, **kwargs):
124
delete_hook=None, logger=None, **kwargs):
73
125
# Called on update
74
126
self.update_hook = update_hook
75
127
# Called on delete
76
128
self.delete_hook = delete_hook
77
129
# Mandos Server proxy object
78
130
self.server_proxy_object = server_proxy_object
134
self._update_timer_callback_tag = None
80
136
# The widget shown normally
81
137
self._text_widget = urwid.Text("")
82
138
# The widget shown when we have focus
83
139
self._focus_text_widget = urwid.Text("")
84
super(MandosClientWidget, self).__init__(
85
update_hook=update_hook, delete_hook=delete_hook,
140
super(MandosClientWidget, self).__init__(**kwargs)
88
142
self.opened = False
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
181
def checker_completed(self, exitstatus, condition, command):
183
self.logger('Checker for client {} (command "{}")'
184
' succeeded'.format(self.properties["Name"],
189
if os.WIFEXITED(condition):
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)))
201
def checker_started(self, command):
202
"""Server signals that a checker started."""
203
self.logger('Client {} started checker "{}"'
204
.format(self.properties["Name"],
207
def got_secret(self):
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))
90
223
def selectable(self):
91
224
"""Make this a "selectable" widget.
92
225
This overrides the method from urwid.FlowWidget."""
95
def rows(self, (maxcol,), focus=False):
228
def rows(self, maxcolrow, focus=False):
96
229
"""How many rows this widget will occupy might depend on
97
230
whether we have focus or not.
98
231
This overrides the method from urwid.FlowWidget"""
99
return self.current_widget(focus).rows((maxcol,), focus=focus)
232
return self.current_widget(focus).rows(maxcolrow, focus=focus)
101
234
def current_widget(self, focus=False):
102
235
if focus or self.opened:
103
236
return self._focus_widget
104
237
return self._widget
106
239
def update(self):
107
240
"Called when what is visible on the screen should be updated."
108
241
# 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",
242
with_standout = {"normal": "standout",
243
"bold": "bold-standout",
245
"underline-blink-standout",
246
"bold-underline-blink":
247
"bold-underline-blink-standout",
117
250
# Rebuild focus and non-focus widgets using current properties
118
self._text = (u'name="%(name)s", enabled=%(enabled)s'
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)
120
293
if not urwid.supports_unicode():
121
294
self._text = self._text.encode("ascii", "replace")
122
textlist = [(u"normal", u"BLARGH: "), (u"bold", self._text)]
295
textlist = [("normal", self._text)]
123
296
self._text_widget.set_text(textlist)
124
297
self._focus_text_widget.set_text([(with_standout[text[0]],
132
305
# Run update hook, if any
133
306
if self.update_hook is not None:
134
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 = ()
137
323
if self.delete_hook is not None:
138
324
self.delete_hook(self)
140
def render(self, (maxcol,), focus=False):
325
return super(MandosClientWidget, self).delete(**kwargs)
327
def render(self, maxcolrow, focus=False):
141
328
"""Render differently if we have focus.
142
329
This overrides the method from urwid.FlowWidget"""
143
return self.current_widget(focus).render((maxcol,),
330
return self.current_widget(focus).render(maxcolrow,
146
def keypress(self, (maxcol,), key):
333
def keypress(self, maxcolrow, key):
148
335
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"_":
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":
154
353
self.server_proxy_object.RemoveClient(self.proxy
157
self.proxy.StartChecker()
159
self.proxy.StopChecker()
161
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,
163
# elif key == u"p" or key == "=":
368
# elif key == "p" or key == "=":
164
369
# self.proxy.pause()
165
# elif key == u"u" or key == ":":
370
# elif key == "u" or key == ":":
166
371
# 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.
377
def properties_changed(self, interface, properties, invalidated):
378
"""Call self.update() if any properties changed.
175
379
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:
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):
197
403
"""This is the entire user interface - the whole screen
198
404
with boxes, lists of client widgets, etc.
200
def __init__(self, max_log_length=1000):
406
def __init__(self, max_log_length=1000, log_level=1):
201
407
DBusGMainLoop(set_as_default=True)
203
409
self.screen = urwid.curses_display.Screen()
205
411
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",
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"),
225
433
if urwid.supports_unicode():
226
#self.divider = u"─" # \u2500
227
self.divider = u"━" # \u2501
434
self.divider = "─" # \u2500
229
#self.divider = u"-" # \u002d
230
self.divider = u"_" # \u005f
436
self.divider = "_" # \u005f
232
438
self.screen.start()
234
440
self.size = self.screen.get_cols_rows()
236
442
self.clients = urwid.SimpleListWalker([])
237
443
self.clients_dict = {}
239
445
# We will add Text widgets to this list
241
447
self.max_log_length = max_log_length
449
self.log_level = log_level
243
451
# We keep a reference to the log widget so we can remove it
244
452
# from the ListWalker without it getting destroyed
245
453
self.logbox = ConstrainedListBox(self.log)
247
455
# This keeps track of whether self.uilist currently has
248
456
# self.logbox in it or not
249
457
self.log_visible = True
250
self.log_wrap = u"any"
458
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")
461
self.log_message_raw(("bold",
462
"Mandos Monitor version " + version))
463
self.log_message_raw(("bold",
258
466
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),
467
self.main_loop = GLib.MainLoop()
469
def client_not_found(self, fingerprint, address):
470
self.log_message("Client with address {} and fingerprint {}"
471
" could not be found"
472
.format(address, fingerprint))
296
474
def rebuild(self):
297
475
"""This rebuilds the User Interface.
298
476
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(),
478
# self.uilist.append(urwid.ListBox(self.clients))
479
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
481
# header=urwid.Divider(),
304
footer=urwid.Divider(div_char=self.divider)))
483
footer=urwid.Divider(
484
div_char=self.divider)))
305
485
if self.log_visible:
306
486
self.uilist.append(self.logbox)
308
487
self.topwidget = urwid.Pile(self.uilist)
310
def log_message(self, markup):
489
def log_message(self, message, level=1):
490
"""Log message formatted with timestamp"""
491
if level < self.log_level:
493
timestamp = datetime.datetime.now().isoformat()
494
self.log_message_raw("{}: {}".format(timestamp, message),
497
def log_message_raw(self, markup, level=1):
311
498
"""Add a log message to the log buffer."""
499
if level < self.log_level:
312
501
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]
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]
505
self.logbox.set_focus(len(self.logbox.body.contents),
317
509
def toggle_log_display(self):
318
510
"""Toggle visibility of the log buffer."""
319
511
self.log_visible = not self.log_visible
321
self.log_message(u"Log visibility changed to: "
322
+ unicode(self.log_visible))
513
self.log_message("Log visibility changed to: {}"
514
.format(self.log_visible), level=0)
324
516
def change_log_display(self):
325
517
"""Change type of log display.
326
518
Currently, this toggles wrapping of text lines."""
327
if self.log_wrap == u"clip":
328
self.log_wrap = u"any"
519
if self.log_wrap == "clip":
520
self.log_wrap = "any"
330
self.log_wrap = u"clip"
522
self.log_wrap = "clip"
331
523
for textwidget in self.log:
332
524
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
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
339
532
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
341
537
client = self.clients_dict[path]
345
self.remove_client(client, path)
347
def add_new_client(self, path, properties):
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
348
554
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),
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])),
360
564
def add_client(self, client, path=None):
361
565
self.clients.append(client)
363
567
path = client.proxy.object_path
364
568
self.clients_dict[path] = client
365
self.clients.sort(None, lambda c: c.properties[u"name"])
569
self.clients.sort(key=lambda c: c.properties["Name"])
368
572
def remove_client(self, client, path=None):
369
573
self.clients.remove(client)
371
575
path = client.proxy.object_path
372
576
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
579
def refresh(self):
381
580
"""Redraw the screen"""
382
581
canvas = self.topwidget.render(self.size, focus=True)
383
582
self.screen.draw_screen(self.size, canvas)
386
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),
388
self._input_callback_tag = (gobject.io_add_watch
630
self._input_callback_tag = (GLib.io_add_watch
389
631
(sys.stdin.fileno(),
391
633
self.process_input))
392
634
self.main_loop.run()
393
635
# Main loop has finished, we should close everything now
394
gobject.source_remove(self._input_callback_tag)
636
GLib.source_remove(self._input_callback_tag)
395
637
self.screen.stop()
398
640
self.main_loop.quit()
400
642
def process_input(self, source, condition):
401
643
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
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
414
656
key = translations[key]
415
657
except KeyError: # :-)
418
if key == u"q" or key == u"Q":
660
if key == "q" or key == "Q":
421
elif key == u"window resize":
663
elif key == "window resize":
422
664
self.size = self.screen.get_cols_rows()
424
elif key == u"\f": # Ctrl-L
666
elif key == "ctrl l":
426
elif key == u"l" or key == u"D":
669
elif key == "l" or key == "D":
427
670
self.toggle_log_display()
429
elif key == u"w" or key == u"i":
672
elif key == "w" or key == "i":
430
673
self.change_log_display()
432
elif key == u"?" or key == u"f1":
433
self.log_message(u"Help!")
675
elif key == "?" or key == "f1" or key == "esc":
676
if not self.log_visible:
677
self.log_visible = True
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",
436
701
if self.topwidget.get_focus() is self.logbox:
437
702
self.topwidget.set_focus(0)
439
704
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
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
459
731
elif self.topwidget.selectable():
460
732
self.topwidget.keypress(self.size, key)