2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2019 Teddy Hogeborn
7
# Copyright © 2009-2019 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"_" or key == u"ctrl k":
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
447
self.log = urwid.SimpleListWalker([])
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"bold",
254
u"Mandos Monitor version " + version))
255
self.log_message((u"bold",
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, key_id, address):
471
self.log_message("Client with address {} and key ID {} could"
472
" not be found".format(address, key_id))
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]
316
self.logbox.set_focus(len(self.logbox.body.contents),
317
coming_from=u"above")
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)-1,
319
509
def toggle_log_display(self):
320
510
"""Toggle visibility of the log buffer."""
321
511
self.log_visible = not self.log_visible
323
self.log_message(u"Log visibility changed to: "
324
+ unicode(self.log_visible))
513
self.log_message("Log visibility changed to: {}"
514
.format(self.log_visible), level=0)
326
516
def change_log_display(self):
327
517
"""Change type of log display.
328
518
Currently, this toggles wrapping of text lines."""
329
if self.log_wrap == u"clip":
330
self.log_wrap = u"any"
519
if self.log_wrap == "clip":
520
self.log_wrap = "any"
332
self.log_wrap = u"clip"
522
self.log_wrap = "clip"
333
523
for textwidget in self.log:
334
524
textwidget.set_wrap_mode(self.log_wrap)
335
self.log_message(u"Wrap mode: " + self.log_wrap)
337
def find_and_remove_client(self, path, name):
338
"""Find an client from its object path and remove it.
340
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
341
532
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
343
537
client = self.clients_dict[path]
347
self.remove_client(client, path)
349
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
350
554
client_proxy_object = self.bus.get_object(self.busname, path)
351
self.add_client(MandosClientWidget(server_proxy_object
354
=client_proxy_object,
355
properties=properties,
359
=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])),
362
564
def add_client(self, client, path=None):
363
565
self.clients.append(client)
365
567
path = client.proxy.object_path
366
568
self.clients_dict[path] = client
367
self.clients.sort(None, lambda c: c.properties[u"name"])
569
self.clients.sort(key=lambda c: c.properties["Name"])
370
572
def remove_client(self, client, path=None):
371
573
self.clients.remove(client)
373
575
path = client.proxy.object_path
374
576
del self.clients_dict[path]
375
if not self.clients_dict:
376
# Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
377
# is completely emptied, we need to recreate it.
378
self.clients = urwid.SimpleListWalker([])
382
579
def refresh(self):
383
580
"""Redraw the screen"""
384
581
canvas = self.topwidget.render(self.size, focus=True)
385
582
self.screen.draw_screen(self.size, canvas)
388
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),
390
self._input_callback_tag = (gobject.io_add_watch
630
self._input_callback_tag = (
632
GLib.IOChannel.unix_new(sys.stdin.fileno()),
633
GLib.PRIORITY_DEFAULT, GLib.IO_IN,
394
635
self.main_loop.run()
395
636
# Main loop has finished, we should close everything now
396
gobject.source_remove(self._input_callback_tag)
637
GLib.source_remove(self._input_callback_tag)
638
with warnings.catch_warnings():
639
warnings.simplefilter("ignore", BytesWarning)
400
643
self.main_loop.quit()
402
645
def process_input(self, source, condition):
403
646
keys = self.screen.get_input()
404
translations = { u"ctrl n": u"down", # Emacs
405
u"ctrl p": u"up", # Emacs
406
u"ctrl v": u"page down", # Emacs
407
u"meta v": u"page up", # Emacs
408
u" ": u"page down", # less
409
u"f": u"page down", # less
410
u"b": u"page up", # less
647
translations = {"ctrl n": "down", # Emacs
648
"ctrl p": "up", # Emacs
649
"ctrl v": "page down", # Emacs
650
"meta v": "page up", # Emacs
651
" ": "page down", # less
652
"f": "page down", # less
653
"b": "page up", # less
416
659
key = translations[key]
417
660
except KeyError: # :-)
420
if key == u"q" or key == u"Q":
663
if key == "q" or key == "Q":
423
elif key == u"window resize":
666
elif key == "window resize":
424
667
self.size = self.screen.get_cols_rows()
426
elif key == u"\f": # Ctrl-L
669
elif key == "ctrl l":
428
elif key == u"l" or key == u"D":
672
elif key == "l" or key == "D":
429
673
self.toggle_log_display()
431
elif key == u"w" or key == u"i":
675
elif key == "w" or key == "i":
432
676
self.change_log_display()
434
elif key == u"?" or key == u"f1" or key == u"esc":
678
elif key == "?" or key == "f1" or key == "esc":
435
679
if not self.log_visible:
436
680
self.log_visible = True
438
self.log_message((u"bold",
439
u" ".join((u"q: Quit",
441
u"l: Log window toggle",
442
u"TAB: Switch window",
444
self.log_message((u"bold",
445
u" ".join((u"Clients:",
449
u"s: Start new checker",
682
self.log_message_raw(("bold",
686
"l: Log window toggle",
687
"TAB: Switch window",
688
"w: Wrap (log lines)",
689
"v: Toggle verbose log",
691
self.log_message_raw(("bold",
697
"s: Start new checker",
454
704
if self.topwidget.get_focus() is self.logbox:
455
705
self.topwidget.set_focus(0)
457
707
self.topwidget.set_focus(self.logbox)
459
#elif (key == u"end" or key == u"meta >" or key == u"G"
461
# pass # xxx end-of-buffer
462
#elif (key == u"home" or key == u"meta <" or key == u"g"
464
# pass # xxx beginning-of-buffer
465
#elif key == u"ctrl e" or key == u"$":
466
# pass # xxx move-end-of-line
467
#elif key == u"ctrl a" or key == u"^":
468
# pass # xxx move-beginning-of-line
469
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
471
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
474
# pass # scroll up log
476
# pass # scroll down log
710
if self.log_level == 0:
712
self.log_message("Verbose mode: Off")
715
self.log_message("Verbose mode: On")
716
# elif (key == "end" or key == "meta >" or key == "G"
718
# pass # xxx end-of-buffer
719
# elif (key == "home" or key == "meta <" or key == "g"
721
# pass # xxx beginning-of-buffer
722
# elif key == "ctrl e" or key == "$":
723
# pass # xxx move-end-of-line
724
# elif key == "ctrl a" or key == "^":
725
# pass # xxx move-beginning-of-line
726
# elif key == "ctrl b" or key == "meta (" or key == "h":
728
# elif key == "ctrl f" or key == "meta )" or key == "l":
731
# pass # scroll up log
733
# pass # scroll down log
477
734
elif self.topwidget.selectable():
478
735
self.topwidget.keypress(self.size, key)
482
740
ui = UserInterface()
743
except KeyboardInterrupt:
745
except Exception as e:
746
ui.log_message(str(e))