2
# -*- mode: python; coding: utf-8 -*-
4
from __future__ import division, absolute_import, with_statement
9
import urwid.curses_display
12
from dbus.mainloop.glib import DBusGMainLoop
19
# Some useful constants
20
domain = 'se.bsnet.fukt'
21
server_interface = domain + '.Mandos'
22
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)
33
class MandosClientPropertyCache(object):
34
"""This wraps a Mandos Client D-Bus proxy object, caches the
35
properties and calls a hook function when any of them are
38
def __init__(self, proxy_object=None, properties=None, *args,
40
# Type conversion mapping
42
dbus.ObjectPath: unicode,
44
dbus.Signature: unicode,
52
dbus.Dictionary: dict,
59
self.proxy = proxy_object # Mandos Client proxy object
61
if properties is None:
62
self.properties = dict()
64
self.properties = dict(self.convert_property(prop, val)
66
properties.iteritems())
67
self.proxy.connect_to_signal("PropertyChanged",
68
self.property_changed,
72
if properties is None:
73
self.properties.update(
74
self.convert_property(prop, val)
76
self.proxy.GetAll(client_interface,
78
dbus.PROPERTIES_IFACE).iteritems())
79
super(MandosClientPropertyCache, self).__init__(
80
proxy_object=proxy_object,
81
properties=properties, *args, **kwargs)
83
def convert_property(self, property, value):
84
"""This converts the arguments from a D-Bus signal, which are
85
D-Bus types, into normal Python types, using a conversion
86
function from "self.type_map".
88
property_name = unicode(property) # Always a dbus.String
89
if isinstance(value, dbus.UTF8String):
90
# Should not happen, but prepare for it anyway
91
value = dbus.String(str(value).decode("utf-8"))
93
convfunc = self.type_map[type(value)]
95
# Unknown type, return unmodified
96
return property_name, value
97
return property_name, convfunc(value)
98
def property_changed(self, property=None, value=None):
99
"""This is called whenever we get a PropertyChanged signal
100
It updates the changed property in the "properties" dict.
102
# Convert name and value
103
property_name, cvalue = self.convert_property(property, value)
104
# Update properties dict with new value
105
self.properties[property_name] = cvalue
108
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
109
"""A Mandos Client which is visible on the screen.
112
def __init__(self, server_proxy_object=None, update_hook=None,
113
delete_hook=None, *args, **kwargs):
115
self.update_hook = update_hook
117
self.delete_hook = delete_hook
118
# Mandos Server proxy object
119
self.server_proxy_object = server_proxy_object
121
# The widget shown normally
122
self._text_widget = urwid.Text("")
123
# The widget shown when we have focus
124
self._focus_text_widget = urwid.Text("")
125
super(MandosClientWidget, self).__init__(
126
update_hook=update_hook, delete_hook=delete_hook,
131
def selectable(self):
132
"""Make this a "selectable" widget.
133
This overrides the method from urwid.FlowWidget."""
136
def rows(self, (maxcol,), focus=False):
137
"""How many rows this widget will occupy might depend on
138
whether we have focus or not.
139
This overrides the method from urwid.FlowWidget"""
140
return self.current_widget(focus).rows((maxcol,), focus=focus)
142
def current_widget(self, focus=False):
143
if focus or self.opened:
144
return self._focus_widget
148
"Called when what is visible on the screen should be updated."
149
# How to add standout mode to a style
150
with_standout = { u"normal": u"standout",
151
u"bold": u"bold-standout",
153
u"underline-blink-standout",
154
u"bold-underline-blink":
155
u"bold-underline-blink-standout",
158
# Rebuild focus and non-focus widgets using current properties
159
self._text = (u'name="%(name)s", enabled=%(enabled)s'
161
if not urwid.supports_unicode():
162
self._text = self._text.encode("ascii", "replace")
163
textlist = [(u"normal", u"BLÄRGH: "), (u"bold", self._text)]
164
self._text_widget.set_text(textlist)
165
self._focus_text_widget.set_text([(with_standout[text[0]],
167
if isinstance(text, tuple)
169
for text in textlist])
170
self._widget = self._text_widget
171
self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
173
# Run update hook, if any
174
if self.update_hook is not None:
178
if self.delete_hook is not None:
179
self.delete_hook(self)
181
def render(self, (maxcol,), focus=False):
182
"""Render differently if we have focus.
183
This overrides the method from urwid.FlowWidget"""
184
return self.current_widget(focus).render((maxcol,),
187
def keypress(self, (maxcol,), key):
189
This overrides the method from urwid.FlowWidget"""
190
if key == u"e" or key == u"+":
192
elif key == u"d" or key == u"-":
194
elif key == u"r" or key == u"_":
195
self.server_proxy_object.RemoveClient(self.proxy
198
self.proxy.StartChecker()
200
self.proxy.StopChecker()
202
self.proxy.CheckedOK()
204
# elif key == u"p" or key == "=":
206
# elif key == u"u" or key == ":":
207
# self.proxy.unpause()
208
# elif key == u"RET":
213
def property_changed(self, property=None, value=None,
215
"""Call self.update() if old value is not new value.
216
This overrides the method from MandosClientPropertyCache"""
217
property_name = unicode(property)
218
old_value = self.properties.get(property_name)
219
super(MandosClientWidget, self).property_changed(
220
property=property, value=value, *args, **kwargs)
221
if self.properties.get(property_name) != old_value:
225
class UserInterface(object):
226
"""This is the entire user interface - the whole screen
227
with boxes, lists of client widgets, etc.
230
DBusGMainLoop(set_as_default=True )
232
self.screen = urwid.curses_display.Screen()
234
self.screen.register_palette((
236
u"default", u"default", None),
238
u"default", u"default", u"bold"),
240
u"default", u"default", u"underline"),
242
u"default", u"default", u"standout"),
243
(u"bold-underline-blink",
244
u"default", u"default", (u"bold", u"underline")),
246
u"default", u"default", (u"bold", u"standout")),
247
(u"underline-blink-standout",
248
u"default", u"default", (u"underline", u"standout")),
249
(u"bold-underline-blink-standout",
250
u"default", u"default", (u"bold", u"underline",
256
self.size = self.screen.get_cols_rows()
258
self.clients = urwid.SimpleListWalker([])
259
self.clients_dict = {}
260
self.topwidget = urwid.LineBox(urwid.ListBox(self.clients))
261
#self.topwidget = urwid.ListBox(clients)
263
self.busname = domain + '.Mandos'
264
self.main_loop = gobject.MainLoop()
265
self.bus = dbus.SystemBus()
266
mandos_dbus_objc = self.bus.get_object(
267
self.busname, u"/", follow_name_owner_changes=True)
268
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
272
mandos_clients = (self.mandos_serv
273
.GetAllClientsWithProperties())
274
except dbus.exceptions.DBusException:
275
mandos_clients = dbus.Dictionary()
278
.connect_to_signal("ClientRemoved",
279
self.find_and_remove_client,
280
dbus_interface=server_interface,
283
.connect_to_signal("ClientAdded",
285
dbus_interface=server_interface,
287
for path, client in (mandos_clients.iteritems()):
288
client_proxy_object = self.bus.get_object(self.busname,
290
self.add_client(MandosClientWidget(server_proxy_object
293
=client_proxy_object,
298
=self.remove_client),
301
def find_and_remove_client(self, path, name):
302
"""Find an client from its object path and remove it.
304
This is connected to the ClientRemoved signal from the
305
Mandos server object."""
307
client = self.clients_dict[path]
311
self.remove_client(client, path)
313
def add_new_client(self, path, properties):
314
client_proxy_object = self.bus.get_object(self.busname, path)
315
self.add_client(MandosClientWidget(server_proxy_object
318
=client_proxy_object,
319
properties=properties,
323
=self.remove_client),
326
def add_client(self, client, path=None):
327
self.clients.append(client)
329
path = client.proxy.object_path
330
self.clients_dict[path] = client
331
self.clients.sort(None, lambda c: c.properties[u"name"])
334
def remove_client(self, client, path=None):
335
self.clients.remove(client)
337
path = client.proxy.object_path
338
del self.clients_dict[path]
342
"""Redraw the screen"""
343
canvas = self.topwidget.render(self.size, focus=True)
344
self.screen.draw_screen(self.size, canvas)
347
"""Start the main loop and exit when it's done."""
349
self._input_callback_tag = (gobject.io_add_watch
354
# Main loop has finished, we should close everything now
355
gobject.source_remove(self._input_callback_tag)
359
self.main_loop.quit()
361
def process_input(self, source, condition):
362
keys = self.screen.get_input()
363
translations = { u"j": u"down",
368
key = translations[key]
369
except KeyError: # :-)
372
if key == u"q" or key == u"Q":
375
elif key == u"window resize":
376
self.size = self.screen.get_cols_rows()
380
elif self.topwidget.selectable():
381
self.topwidget.keypress(self.size, key)