/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
1
#!/usr/bin/python
2
# -*- mode: python; coding: utf-8 -*-
3
4
from __future__ import division, absolute_import, with_statement
5
6
import sys
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
7
import os
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
8
import signal
9
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
10
import datetime
11
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
12
import urwid.curses_display
13
import urwid
14
15
from dbus.mainloop.glib import DBusGMainLoop
16
import gobject
17
18
import dbus
19
20
import UserList
21
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
22
import locale
23
24
locale.setlocale(locale.LC_ALL, u'')
25
24.2.2 by teddy at bsnet
* mandos: Use logging.getLogger() as in the documentation.
26
import logging
27
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
28
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
29
# Some useful constants
30
domain = 'se.bsnet.fukt'
31
server_interface = domain + '.Mandos'
32
client_interface = domain + '.Mandos.Client'
24.2.2 by teddy at bsnet
* mandos: Use logging.getLogger() as in the documentation.
33
version = "1.0.15"
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
34
35
# Always run in monochrome mode
36
urwid.curses_display.curses.has_colors = lambda : False
37
38
# Urwid doesn't support blinking, but we want it.  Since we have no
39
# use for underline on its own, we make underline also always blink.
40
urwid.curses_display.curses.A_UNDERLINE |= (
41
    urwid.curses_display.curses.A_BLINK)
42
43
class MandosClientPropertyCache(object):
44
    """This wraps a Mandos Client D-Bus proxy object, caches the
45
    properties and calls a hook function when any of them are
46
    changed.
47
    """
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
48
    def __init__(self, proxy_object=None, *args, **kwargs):
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
49
        self.proxy = proxy_object # Mandos Client proxy object
50
        
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
51
        self.properties = dict()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
52
        self.proxy.connect_to_signal(u"PropertyChanged",
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
53
                                     self.property_changed,
54
                                     client_interface,
55
                                     byte_arrays=True)
24.2.2 by teddy at bsnet
* mandos: Use logging.getLogger() as in the documentation.
56
        
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
57
        self.properties.update(
58
            self.proxy.GetAll(client_interface,
59
                              dbus_interface = dbus.PROPERTIES_IFACE))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
60
        super(MandosClientPropertyCache, self).__init__(
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
61
            proxy_object=proxy_object, *args, **kwargs)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
62
    
63
    def property_changed(self, property=None, value=None):
64
        """This is called whenever we get a PropertyChanged signal
65
        It updates the changed property in the "properties" dict.
66
        """
67
        # Update properties dict with new value
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
68
        self.properties[property] = value
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
69
70
71
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
72
    """A Mandos Client which is visible on the screen.
73
    """
74
    
75
    def __init__(self, server_proxy_object=None, update_hook=None,
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
76
                 delete_hook=None, logger=None, *args, **kwargs):
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
77
        # Called on update
78
        self.update_hook = update_hook
79
        # Called on delete
80
        self.delete_hook = delete_hook
81
        # Mandos Server proxy object
82
        self.server_proxy_object = server_proxy_object
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
83
        # Logger
84
        self.logger = logger
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
85
        
86
        # The widget shown normally
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
87
        self._text_widget = urwid.Text(u"")
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
88
        # The widget shown when we have focus
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
89
        self._focus_text_widget = urwid.Text(u"")
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
90
        super(MandosClientWidget, self).__init__(
91
            update_hook=update_hook, delete_hook=delete_hook,
92
            *args, **kwargs)
93
        self.update()
94
        self.opened = False
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
95
        self.proxy.connect_to_signal(u"CheckerCompleted",
96
                                     self.checker_completed,
97
                                     client_interface,
98
                                     byte_arrays=True)
99
        self.proxy.connect_to_signal(u"CheckerStarted",
100
                                     self.checker_started,
101
                                     client_interface,
102
                                     byte_arrays=True)
103
        self.proxy.connect_to_signal(u"GotSecret",
104
                                     self.got_secret,
105
                                     client_interface,
106
                                     byte_arrays=True)
24.2.3 by teddy at bsnet
* mandos-monitor (MandosClientWidget): Accept new signal
107
        self.proxy.connect_to_signal(u"NeedApproval",
108
                                     self.need_approval,
109
                                     client_interface,
110
                                     byte_arrays=True)
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
111
        self.proxy.connect_to_signal(u"Rejected",
112
                                     self.rejected,
113
                                     client_interface,
114
                                     byte_arrays=True)
115
    
116
    def checker_completed(self, exitstatus, condition, command):
117
        if exitstatus == 0:
24.2.3 by teddy at bsnet
* mandos-monitor (MandosClientWidget): Accept new signal
118
            #self.logger(u'Checker for client %s (command "%s")'
119
            #            u' was successful'
120
            #            % (self.properties[u"name"], command))
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
121
            return
122
        if os.WIFEXITED(condition):
123
            self.logger(u'Checker for client %s (command "%s")'
124
                        u' failed with exit code %s'
125
                        % (self.properties[u"name"], command,
126
                           os.WEXITSTATUS(condition)))
127
            return
128
        if os.WIFSIGNALED(condition):
129
            self.logger(u'Checker for client %s (command "%s")'
130
                        u' was killed by signal %s'
131
                        % (self.properties[u"name"], command,
132
                           os.WTERMSIG(condition)))
133
            return
134
        if os.WCOREDUMP(condition):
135
            self.logger(u'Checker for client %s (command "%s")'
136
                        u' dumped core'
137
                        % (self.properties[u"name"], command))
138
        self.logger(u'Checker for client %s completed mysteriously')
139
    
140
    def checker_started(self, command):
24.2.3 by teddy at bsnet
* mandos-monitor (MandosClientWidget): Accept new signal
141
        #self.logger(u'Client %s started checker "%s"'
142
        #            % (self.properties[u"name"], unicode(command)))
143
        pass
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
144
    
145
    def got_secret(self):
146
        self.logger(u'Client %s received its secret'
147
                    % self.properties[u"name"])
148
    
24.2.3 by teddy at bsnet
* mandos-monitor (MandosClientWidget): Accept new signal
149
    def need_approval(self, timeout, default):
150
        if not default:
151
            message = u'Client %s needs approval within %s seconds'
152
        else:
153
            message = u'Client %s will get its secret in %s seconds'
154
        self.logger(message
155
                    % (self.properties[u"name"], timeout/1000))
156
    
157
    def rejected(self, reason):
158
        self.logger(u'Client %s was rejected; reason: %s'
159
                    % (self.properties[u"name"], reason))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
160
    
161
    def selectable(self):
162
        """Make this a "selectable" widget.
163
        This overrides the method from urwid.FlowWidget."""
164
        return True
165
    
166
    def rows(self, (maxcol,), focus=False):
167
        """How many rows this widget will occupy might depend on
168
        whether we have focus or not.
169
        This overrides the method from urwid.FlowWidget"""
170
        return self.current_widget(focus).rows((maxcol,), focus=focus)
171
    
172
    def current_widget(self, focus=False):
173
        if focus or self.opened:
174
            return self._focus_widget
175
        return self._widget
176
    
177
    def update(self):
178
        "Called when what is visible on the screen should be updated."
179
        # How to add standout mode to a style
180
        with_standout = { u"normal": u"standout",
181
                          u"bold": u"bold-standout",
182
                          u"underline-blink":
183
                              u"underline-blink-standout",
184
                          u"bold-underline-blink":
185
                              u"bold-underline-blink-standout",
186
                          }
187
        
188
        # Rebuild focus and non-focus widgets using current properties
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
189
        self._text = (u'%(name)s: %(enabled)s'
190
                      % { u"name": self.properties[u"name"],
191
                          u"enabled":
192
                              (u"enabled"
193
                               if self.properties[u"enabled"]
194
                               else u"DISABLED")})
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
195
        if not urwid.supports_unicode():
196
            self._text = self._text.encode("ascii", "replace")
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
197
        textlist = [(u"normal", self._text)]
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
198
        self._text_widget.set_text(textlist)
199
        self._focus_text_widget.set_text([(with_standout[text[0]],
200
                                           text[1])
201
                                          if isinstance(text, tuple)
202
                                          else text
203
                                          for text in textlist])
204
        self._widget = self._text_widget
205
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
206
                                            "standout")
207
        # Run update hook, if any
208
        if self.update_hook is not None:
209
            self.update_hook()
210
    
211
    def delete(self):
212
        if self.delete_hook is not None:
213
            self.delete_hook(self)
214
    
215
    def render(self, (maxcol,), focus=False):
216
        """Render differently if we have focus.
217
        This overrides the method from urwid.FlowWidget"""
218
        return self.current_widget(focus).render((maxcol,),
219
                                                 focus=focus)
220
    
221
    def keypress(self, (maxcol,), key):
222
        """Handle keys.
223
        This overrides the method from urwid.FlowWidget"""
224
        if key == u"e" or key == u"+":
225
            self.proxy.Enable()
226
        elif key == u"d" or key == u"-":
227
            self.proxy.Disable()
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
228
        elif key == u"r" or key == u"_" or key == u"ctrl k":
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
229
            self.server_proxy_object.RemoveClient(self.proxy
230
                                                  .object_path)
231
        elif key == u"s":
232
            self.proxy.StartChecker()
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
233
        elif key == u"S":
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
234
            self.proxy.StopChecker()
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
235
        elif key == u"C":
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
236
            self.proxy.CheckedOK()
237
        # xxx
238
#         elif key == u"p" or key == "=":
239
#             self.proxy.pause()
240
#         elif key == u"u" or key == ":":
241
#             self.proxy.unpause()
242
#         elif key == u"RET":
243
#             self.open()
24.2.3 by teddy at bsnet
* mandos-monitor (MandosClientWidget): Accept new signal
244
        elif key == u"+":
245
            self.proxy.Approve(True)
246
        elif key == u"-":
247
            self.proxy.Approve(False)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
248
        else:
249
            return key
250
    
251
    def property_changed(self, property=None, value=None,
252
                         *args, **kwargs):
253
        """Call self.update() if old value is not new value.
254
        This overrides the method from MandosClientPropertyCache"""
255
        property_name = unicode(property)
256
        old_value = self.properties.get(property_name)
257
        super(MandosClientWidget, self).property_changed(
258
            property=property, value=value, *args, **kwargs)
259
        if self.properties.get(property_name) != old_value:
260
            self.update()
261
262
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
263
class ConstrainedListBox(urwid.ListBox):
264
    """Like a normal urwid.ListBox, but will consume all "up" or
265
    "down" key presses, thus not allowing any containing widgets to
266
    use them as an excuse to shift focus away from this widget.
267
    """
268
    def keypress(self, (maxcol, maxrow), key):
269
        ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
270
        if ret in (u"up", u"down"):
271
            return
272
        return ret
273
274
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
275
class UserInterface(object):
276
    """This is the entire user interface - the whole screen
277
    with boxes, lists of client widgets, etc.
278
    """
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
279
    def __init__(self, max_log_length=1000):
280
        DBusGMainLoop(set_as_default=True)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
281
        
282
        self.screen = urwid.curses_display.Screen()
283
        
284
        self.screen.register_palette((
285
                (u"normal",
286
                 u"default", u"default", None),
287
                (u"bold",
288
                 u"default", u"default", u"bold"),
289
                (u"underline-blink",
290
                 u"default", u"default", u"underline"),
291
                (u"standout",
292
                 u"default", u"default", u"standout"),
293
                (u"bold-underline-blink",
294
                 u"default", u"default", (u"bold", u"underline")),
295
                (u"bold-standout",
296
                 u"default", u"default", (u"bold", u"standout")),
297
                (u"underline-blink-standout",
298
                 u"default", u"default", (u"underline", u"standout")),
299
                (u"bold-underline-blink-standout",
300
                 u"default", u"default", (u"bold", u"underline",
301
                                          u"standout")),
302
                ))
303
        
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
304
        if urwid.supports_unicode():
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
305
            self.divider = u"─" # \u2500
306
            #self.divider = u"━" # \u2501
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
307
        else:
308
            #self.divider = u"-" # \u002d
309
            self.divider = u"_" # \u005f
310
        
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
311
        self.screen.start()
312
        
313
        self.size = self.screen.get_cols_rows()
314
        
315
        self.clients = urwid.SimpleListWalker([])
316
        self.clients_dict = {}
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
317
        
318
        # We will add Text widgets to this list
319
        self.log = []
320
        self.max_log_length = max_log_length
321
        
322
        # We keep a reference to the log widget so we can remove it
323
        # from the ListWalker without it getting destroyed
324
        self.logbox = ConstrainedListBox(self.log)
325
        
326
        # This keeps track of whether self.uilist currently has
327
        # self.logbox in it or not
328
        self.log_visible = True
329
        self.log_wrap = u"any"
330
        
331
        self.rebuild()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
332
        self.log_message_raw((u"bold",
333
                              u"Mandos Monitor version " + version))
334
        self.log_message_raw((u"bold",
335
                              u"q: Quit  ?: Help"))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
336
        
337
        self.busname = domain + '.Mandos'
338
        self.main_loop = gobject.MainLoop()
339
        self.bus = dbus.SystemBus()
340
        mandos_dbus_objc = self.bus.get_object(
341
            self.busname, u"/", follow_name_owner_changes=True)
342
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
343
                                          dbus_interface
344
                                          = server_interface)
345
        try:
346
            mandos_clients = (self.mandos_serv
347
                              .GetAllClientsWithProperties())
348
        except dbus.exceptions.DBusException:
349
            mandos_clients = dbus.Dictionary()
350
        
351
        (self.mandos_serv
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
352
         .connect_to_signal(u"ClientRemoved",
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
353
                            self.find_and_remove_client,
354
                            dbus_interface=server_interface,
355
                            byte_arrays=True))
356
        (self.mandos_serv
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
357
         .connect_to_signal(u"ClientAdded",
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
358
                            self.add_new_client,
359
                            dbus_interface=server_interface,
360
                            byte_arrays=True))
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
361
        (self.mandos_serv
362
         .connect_to_signal(u"ClientNotFound",
363
                            self.client_not_found,
364
                            dbus_interface=server_interface,
365
                            byte_arrays=True))
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
366
        for path, client in mandos_clients.iteritems():
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
367
            client_proxy_object = self.bus.get_object(self.busname,
368
                                                      path)
369
            self.add_client(MandosClientWidget(server_proxy_object
370
                                               =self.mandos_serv,
371
                                               proxy_object
372
                                               =client_proxy_object,
373
                                               properties=client,
374
                                               update_hook
375
                                               =self.refresh,
376
                                               delete_hook
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
377
                                               =self.remove_client,
378
                                               logger
379
                                               =self.log_message),
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
380
                            path=path)
381
    
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
382
    def client_not_found(self, fingerprint, address):
383
        self.log_message((u"Client with address %s and fingerprint %s"
384
                          u" could not be found" % (address,
385
                                                    fingerprint)))
386
    
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
387
    def rebuild(self):
388
        """This rebuilds the User Interface.
389
        Call this when the widget layout needs to change"""
390
        self.uilist = []
391
        #self.uilist.append(urwid.ListBox(self.clients))
392
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
393
                                       #header=urwid.Divider(),
394
                                       header=None,
395
                                       footer=urwid.Divider(div_char=self.divider)))
396
        if self.log_visible:
397
            self.uilist.append(self.logbox)
398
            pass
399
        self.topwidget = urwid.Pile(self.uilist)
400
    
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
401
    def log_message(self, message):
402
        timestamp = datetime.datetime.now().isoformat()
403
        self.log_message_raw(timestamp + u": " + message)
404
    
405
    def log_message_raw(self, markup):
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
406
        """Add a log message to the log buffer."""
407
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
408
        if (self.max_log_length
409
            and len(self.log) > self.max_log_length):
410
            del self.log[0:len(self.log)-self.max_log_length-1]
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
411
        self.logbox.set_focus(len(self.logbox.body.contents),
412
                              coming_from=u"above")
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
413
        self.refresh()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
414
    
415
    def toggle_log_display(self):
416
        """Toggle visibility of the log buffer."""
417
        self.log_visible = not self.log_visible
418
        self.rebuild()
419
        self.log_message(u"Log visibility changed to: "
420
                         + unicode(self.log_visible))
421
    
422
    def change_log_display(self):
423
        """Change type of log display.
424
        Currently, this toggles wrapping of text lines."""
425
        if self.log_wrap == u"clip":
426
            self.log_wrap = u"any"
427
        else:
428
            self.log_wrap = u"clip"
429
        for textwidget in self.log:
430
            textwidget.set_wrap_mode(self.log_wrap)
431
        self.log_message(u"Wrap mode: " + self.log_wrap)
432
    
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
433
    def find_and_remove_client(self, path, name):
434
        """Find an client from its object path and remove it.
435
        
436
        This is connected to the ClientRemoved signal from the
437
        Mandos server object."""
438
        try:
439
            client = self.clients_dict[path]
440
        except KeyError:
441
            # not found?
442
            return
443
        self.remove_client(client, path)
444
    
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
445
    def add_new_client(self, path):
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
446
        client_proxy_object = self.bus.get_object(self.busname, path)
447
        self.add_client(MandosClientWidget(server_proxy_object
448
                                           =self.mandos_serv,
449
                                           proxy_object
450
                                           =client_proxy_object,
451
                                           update_hook
452
                                           =self.refresh,
453
                                           delete_hook
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
454
                                           =self.remove_client,
455
                                           logger
456
                                           =self.log_message),
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
457
                        path=path)
458
    
459
    def add_client(self, client, path=None):
460
        self.clients.append(client)
461
        if path is None:
462
            path = client.proxy.object_path
463
        self.clients_dict[path] = client
464
        self.clients.sort(None, lambda c: c.properties[u"name"])
465
        self.refresh()
466
    
467
    def remove_client(self, client, path=None):
468
        self.clients.remove(client)
469
        if path is None:
470
            path = client.proxy.object_path
471
        del self.clients_dict[path]
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
472
        if not self.clients_dict:
473
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
474
            # is completely emptied, we need to recreate it.
475
            self.clients = urwid.SimpleListWalker([])
476
            self.rebuild()
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
477
        self.refresh()
478
    
479
    def refresh(self):
480
        """Redraw the screen"""
481
        canvas = self.topwidget.render(self.size, focus=True)
482
        self.screen.draw_screen(self.size, canvas)
483
    
484
    def run(self):
485
        """Start the main loop and exit when it's done."""
486
        self.refresh()
487
        self._input_callback_tag = (gobject.io_add_watch
488
                                    (sys.stdin.fileno(),
489
                                     gobject.IO_IN,
490
                                     self.process_input))
491
        self.main_loop.run()
492
        # Main loop has finished, we should close everything now
493
        gobject.source_remove(self._input_callback_tag)
494
        self.screen.stop()
495
    
496
    def stop(self):
497
        self.main_loop.quit()
498
    
499
    def process_input(self, source, condition):
500
        keys = self.screen.get_input()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
501
        translations = { u"ctrl n": u"down",      # Emacs
502
                         u"ctrl p": u"up",        # Emacs
503
                         u"ctrl v": u"page down", # Emacs
504
                         u"meta v": u"page up",   # Emacs
505
                         u" ": u"page down",      # less
506
                         u"f": u"page down",      # less
507
                         u"b": u"page up",        # less
508
                         u"j": u"down",           # vi
509
                         u"k": u"up",             # vi
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
510
                         }
511
        for key in keys:
512
            try:
513
                key = translations[key]
514
            except KeyError:    # :-)
515
                pass
516
            
517
            if key == u"q" or key == u"Q":
518
                self.stop()
519
                break
520
            elif key == u"window resize":
521
                self.size = self.screen.get_cols_rows()
522
                self.refresh()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
523
            elif key == u"\f":  # Ctrl-L
524
                self.refresh()
525
            elif key == u"l" or key == u"D":
526
                self.toggle_log_display()
527
                self.refresh()
528
            elif key == u"w" or key == u"i":
529
                self.change_log_display()
530
                self.refresh()
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
531
            elif key == u"?" or key == u"f1" or key == u"esc":
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
532
                if not self.log_visible:
533
                    self.log_visible = True
534
                    self.rebuild()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
535
                self.log_message_raw((u"bold",
536
                                      u"  ".
537
                                      join((u"q: Quit",
538
                                            u"?: Help",
539
                                            u"l: Log window toggle",
540
                                            u"TAB: Switch window",
541
                                            u"w: Wrap (log)"))))
542
                self.log_message_raw((u"bold",
543
                                      u"  "
544
                                      .join((u"Clients:",
545
                                             u"e: Enable",
546
                                             u"d: Disable",
547
                                             u"r: Remove",
548
                                             u"s: Start new checker",
549
                                             u"S: Stop checker",
550
                                             u"C: Checker OK"))))
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
551
                self.refresh()
552
            elif key == u"tab":
553
                if self.topwidget.get_focus() is self.logbox:
554
                    self.topwidget.set_focus(0)
555
                else:
556
                    self.topwidget.set_focus(self.logbox)
557
                self.refresh()
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
558
            #elif (key == u"end" or key == u"meta >" or key == u"G"
559
            #      or key == u">"):
560
            #    pass            # xxx end-of-buffer
561
            #elif (key == u"home" or key == u"meta <" or key == u"g"
562
            #      or key == u"<"):
563
            #    pass            # xxx beginning-of-buffer
564
            #elif key == u"ctrl e" or key == u"$":
565
            #    pass            # xxx move-end-of-line
566
            #elif key == u"ctrl a" or key == u"^":
567
            #    pass            # xxx move-beginning-of-line
568
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
569
            #    pass            # xxx left
570
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
571
            #    pass            # xxx right
572
            #elif key == u"a":
573
            #    pass            # scroll up log
574
            #elif key == u"z":
575
            #    pass            # scroll down log
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
576
            elif self.topwidget.selectable():
577
                self.topwidget.keypress(self.size, key)
578
                self.refresh()
579
        return True
580
581
ui = UserInterface()
582
try:
583
    ui.run()
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
584
except Exception, e:
585
    ui.log_message(unicode(e))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
586
    ui.screen.stop()
587
    raise