Added an assertion to detect a mysterious error (Thomas Leonard; reported by
[rox-lib/lack.git] / ROX-Lib2 / python / rox / xxmlrpc.py
blob61918fb556bf1057e8f9f64ba8d33f4284b0062d
1 """XML-RPC over X."""
2 #from logging import warn Not in Python2.2
3 import sys, weakref
4 from rox import g, tasks
5 import xmlrpclib
7 _message_prop = g.gdk.atom_intern('_XXMLRPC_MESSAGE', False)
8 _message_id_prop = g.gdk.atom_intern('_XXMLRPC_ID', False)
10 class NoSuchService(Exception):
11 pass
13 class XXMLRPCServer:
14 def __init__(self, service):
15 self.service = service
16 self.objects = {} # Path -> Object
18 # Can be used whether sending or receiving...
19 self.ipc_window = g.Invisible()
20 self.ipc_window.add_events(g.gdk.PROPERTY_NOTIFY)
22 self.ipc_window.realize()
24 # Append our window to the list for this service
26 # Make the IPC window contain a property pointing to
27 # itself - this can then be used to check that it really
28 # is an IPC window.
30 self.ipc_window.window.property_change(self.service,
31 'XA_WINDOW', 32,
32 g.gdk.PROP_MODE_REPLACE,
33 [self.ipc_window.window.xid])
35 self.ipc_window.connect('property-notify-event',
36 self.property_changed)
38 # Make the root window contain a pointer to the IPC window
39 g.gdk.get_default_root_window().property_change(
40 self.service, 'XA_WINDOW', 32,
41 g.gdk.PROP_MODE_REPLACE,
42 [self.ipc_window.window.xid])
44 def add_object(self, path, obj):
45 if path in self.objects:
46 raise Exception("An object with the path '%s' is already registered!" % path)
47 assert isinstance(path, str)
48 self.objects[path] = obj
50 def remove_object(self, path):
51 del self.objects[path]
53 def property_changed(self, win, event):
54 if event.atom != _message_id_prop:
55 return
57 if event.state == g.gdk.PROPERTY_NEW_VALUE:
58 val = self.ipc_window.window.property_get(
59 _message_id_prop, 'XA_WINDOW', True)
60 if val is not None:
61 self.process_requests(val[2])
63 def process_requests(self, requests):
64 for xid in requests:
65 foreign = g.gdk.window_foreign_new(long(xid))
66 xml = foreign.property_get(
67 _message_prop, 'XA_STRING', False)
68 if xml:
69 params, method = xmlrpclib.loads(xml[2])
70 retval = self.invoke(method, *params)
71 retxml = xmlrpclib.dumps(retval, methodresponse = True)
72 foreign.property_change(_message_prop, 'XA_STRING', 8,
73 g.gdk.PROP_MODE_REPLACE, retxml)
74 else:
75 print >>sys.stderr, "No '%s' property on window %x" % (
76 _message_prop, xid)
78 def invoke(self, method, *params):
79 if len(params) == 0:
80 raise Exception('No object path in message')
81 obpath = params[0]
82 try:
83 obj = self.objects[obpath]
84 except KeyError:
85 return xmlrpclib.Fault("UnknownObject",
86 "Unknown object '%s'" % obpath)
87 if method not in obj.allowed_methods:
88 return xmlrpclib.Fault('NoSuchMethod',
89 "Method '%s' not a public method (check 'allowed_methods')" % method)
90 try:
91 method = getattr(obj, method)
92 retval = method(*params[1:])
93 if retval is None:
94 # XML-RPC doesn't allow returning None
95 return (True,)
96 else:
97 return (retval,)
98 except Exception, ex:
99 #import traceback
100 #traceback.print_exc(file = sys.stderr)
101 return xmlrpclib.Fault(ex.__class__.__name__,
102 str(ex))
104 class XXMLProxy:
105 def __init__(self, service):
106 self.service = service
107 xid = g.gdk.get_default_root_window().property_get(
108 self.service, 'XA_WINDOW', False)
110 if not xid:
111 raise NoSuchService("No such service '%s'" % service)
112 # Note: xid[0] might be str or Atom
113 if str(xid[0]) != 'XA_WINDOW' or \
114 xid[1] != 32 or \
115 len(xid[2]) != 1:
116 raise Exception("Root property '%s' not a service!" % service)
118 self.remote = g.gdk.window_foreign_new(long(xid[2][0]))
119 if self.remote is None:
120 raise NoSuchService("Service '%s' is no longer running" % service)
122 def get_object(self, path):
123 return XXMLObjectProxy(self, path)
125 class XXMLObjectProxy:
126 def __init__(self, service, path):
127 self.service = service
128 self.path = path
130 def __getattr__(self, method):
131 if method.startswith('_'):
132 raise AttributeError("No attribute '" + method + "'")
133 def invoke(*params):
134 call = ClientCall(self.service, method, tuple([self.path] + list(params)))
135 return call
136 return invoke
138 # It's easy to forget to read the response, which will cause the invisible window
139 # to hang around forever. Warn if we do that...
140 def _call_destroyed(invisible):
141 if invisible.xmlrpc_response is None:
142 print >>sys.stderr, "ClientCall object destroyed without waiting for response!"
143 invisible.destroy()
145 class ClientCall(tasks.Blocker):
146 waiting = False
147 invisible = None
149 def __init__(self, service, method, params):
150 tasks.Blocker.__init__(self)
151 self.service = service
153 self.invisible = g.Invisible()
154 self.invisible.realize()
155 self.invisible.add_events(g.gdk.PROPERTY_NOTIFY)
157 weakself = weakref.ref(self, lambda r,i=self.invisible: _call_destroyed(i))
158 def property_changed(win, event):
159 if event.atom != _message_prop:
160 return
161 if event.state == g.gdk.PROPERTY_NEW_VALUE:
162 call = weakself()
163 if call is not None:
164 call.message_property_changed()
165 self.invisible.connect('property-notify-event', property_changed)
167 # Store the message on our window
168 self.ignore_next_change = True
169 xml = xmlrpclib.dumps(params, method)
171 self.invisible.window.property_change(_message_prop,
172 'XA_STRING', 8,
173 g.gdk.PROP_MODE_REPLACE,
174 xml)
176 self.invisible.xmlrpc_response = None
178 # Tell the service about it
179 self.service.remote.property_change(_message_id_prop,
180 'XA_WINDOW', 32,
181 g.gdk.PROP_MODE_APPEND,
182 [self.invisible.window.xid])
184 def message_property_changed(self):
185 if self.ignore_next_change:
186 # This is just us sending the request
187 self.ignore_next_change = False
188 return
190 val = self.invisible.window.property_get(
191 _message_prop, 'XA_STRING', True)
192 self.invisible.destroy()
193 if val is None:
194 raise Exception('No response to XML-RPC call')
195 else:
196 self.invisible.xmlrpc_response = val[2]
197 assert self.invisible.xmlrpc_response is not None, `val`
198 self.trigger()
199 if self.waiting:
200 g.main_quit()
202 def get_response(self):
203 if self.invisible.xmlrpc_response is None:
204 self.waiting = True
205 try:
206 g.main()
207 finally:
208 self.waiting = False
209 assert self.invisible.xmlrpc_response is not None
210 retval, method = xmlrpclib.loads(self.invisible.xmlrpc_response)
211 assert len(retval) == 1
212 return retval[0]