XXMLRPC call objects support the Blocker interface (so you can block
[rox-lib/lack.git] / python / rox / xxmlrpc.py
blob65b867fdc704752c1f42af4edbeef75977c5cbd5
1 """XML-RPC over X."""
2 #from logging import warn Not in Python2.2
3 import sys
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 class ClientCall(g.Invisible, tasks.Blocker):
139 waiting = False
141 def __init__(self, service, method, params):
142 g.Invisible.__init__(self)
143 tasks.Blocker.__init__(self)
144 self.service = service
145 self.add_events(g.gdk.PROPERTY_NOTIFY)
146 self.realize()
148 self.connect('property-notify-event',
149 self.property_changed)
151 # Store the message on our window
152 self.ignore_next_change = True
153 xml = xmlrpclib.dumps(params, method)
155 self.window.property_change(_message_prop,
156 'XA_STRING', 8,
157 g.gdk.PROP_MODE_REPLACE,
158 xml)
160 self.response = None
162 # Tell the service about it
163 self.service.remote.property_change(_message_id_prop,
164 'XA_WINDOW', 32,
165 g.gdk.PROP_MODE_APPEND,
166 [self.window.xid])
168 def property_changed(self, win, event):
169 if event.atom != _message_prop:
170 return
172 if event.state == g.gdk.PROPERTY_NEW_VALUE:
173 if self.ignore_next_change:
174 # This is just us sending the request
175 self.ignore_next_change = False
176 return
178 val = self.window.property_get(
179 _message_prop, 'XA_STRING', True)
180 if val is None:
181 raise Exception('No response to XML-RPC call')
182 else:
183 self.response = val[2]
184 self.trigger()
185 if self.waiting:
186 g.main_quit()
188 def get_response(self):
189 if self.response is None:
190 self.waiting = True
191 try:
192 g.main()
193 finally:
194 self.waiting = False
195 assert self.response is not None
196 retval, method = xmlrpclib.loads(self.response)
197 assert len(retval) == 1
198 return retval[0]