forked from rox-desktop/ROX-Session
/
xxmlrpc.py
212 lines (177 loc) · 5.78 KB
/
xxmlrpc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
"""XML-RPC over X."""
import sys
from rox import g
import xmlrpclib
import gobject
_message_prop = g.gdk.atom_intern('_XXMLRPC_MESSAGE', False)
_message_id_prop = g.gdk.atom_intern('_XXMLRPC_ID', False)
# Communication via properies is broken in GTK+ 2.18
working = g.gtk_version < (2, 17, 0) #or g.gtk_version >= (2, 20, 0)
class NoSuchService(Exception):
pass
class XXMLRPCServer:
def __init__(self, service):
self.service = service
self.objects = {} # Path -> Object
# Can be used whether sending or receiving...
self.ipc_window = g.Invisible()
self.ipc_window.add_events(g.gdk.PROPERTY_NOTIFY)
self.ipc_window.realize()
# Append our window to the list for this service
# Make the IPC window contain a property pointing to
# itself - this can then be used to check that it really
# is an IPC window.
#
self.ipc_window.window.property_change(self.service,
'XA_WINDOW', 32,
g.gdk.PROP_MODE_REPLACE,
[self.ipc_window.window.xid])
self.ipc_window.connect('property-notify-event',
self.property_changed)
# Make the root window contain a pointer to the IPC window
g.gdk.get_default_root_window().property_change(
self.service, 'XA_WINDOW', 32,
g.gdk.PROP_MODE_REPLACE,
[self.ipc_window.window.xid])
def add_object(self, path, obj):
if path in self.objects:
raise Exception("An object with the path '%s' is already registered!" % path)
assert isinstance(path, str)
self.objects[path] = obj
def remove_object(self, path):
del self.objects[path]
def property_changed(self, win, event):
if event.atom != _message_id_prop:
return
if event.state == g.gdk.PROPERTY_NEW_VALUE:
val = self.ipc_window.window.property_get(
_message_id_prop, 'XA_WINDOW', True)
if val is not None:
self.process_requests(val[2])
def process_requests(self, requests):
for xid in requests:
foreign = g.gdk.window_foreign_new(long(xid))
if foreign is None:
print >>sys.stderr, "XMLRPC window %x no longer exists" % xid
continue
xml = foreign.property_get(
_message_prop, 'XA_STRING', False)
if xml:
params, method = xmlrpclib.loads(xml[2])
retval = self.invoke(method, *params)
retxml = xmlrpclib.dumps(retval, methodresponse = True)
foreign.property_change(_message_prop, 'XA_STRING', 8,
g.gdk.PROP_MODE_REPLACE, retxml)
else:
print >>sys.stderr, "No '%s' property on window %x" % (
_message_prop, xid)
def invoke(self, method, *params):
if len(params) == 0:
raise Exception('No object path in message')
obpath = params[0]
try:
obj = self.objects[obpath]
except KeyError:
return xmlrpclib.Fault("UnknownObject",
"Unknown object '%s'" % obpath)
if method not in obj.allowed_methods:
return xmlrpclib.Fault('NoSuchMethod',
"Method '%s' not a public method (check 'allowed_methods')" % method)
try:
method = getattr(obj, method)
retval = method(*params[1:])
if retval is None:
# XML-RPC doesn't allow returning None
return (True,)
else:
return (retval,)
except Exception, ex:
#import traceback
#traceback.print_exc(file = sys.stderr)
return xmlrpclib.Fault(ex.__class__.__name__,
str(ex))
class XXMLProxy:
def __init__(self, service):
self.service = service
xid = g.gdk.get_default_root_window().property_get(
self.service, 'XA_WINDOW', False)
if not xid:
raise NoSuchService("No such service '%s'" % service)
# Note: xid[0] might be str or Atom
if str(xid[0]) != 'XA_WINDOW' or \
xid[1] != 32 or \
len(xid[2]) != 1:
raise Exception("Root property '%s' not a service!" % service)
self.remote = g.gdk.window_foreign_new(long(xid[2][0]))
if self.remote is None:
raise NoSuchService("Service '%s' is no longer running" % service)
def get_object(self, path):
return XXMLObjectProxy(self, path)
class XXMLObjectProxy:
def __init__(self, service, path):
self.service = service
self.path = path
def __getattr__(self, method):
if method.startswith('_'):
raise AttributeError("No attribute '" + method + "'")
def invoke(*params):
call = ClientCall(self.service, method, tuple([self.path] + list(params)))
return call
return invoke
class ClientCall(g.Invisible):
waiting = False
def __init__(self, service, method, params):
g.Invisible.__init__(self)
self.service = service
self.method_name = method
self.add_events(g.gdk.PROPERTY_NOTIFY)
self.realize()
self.connect('property-notify-event',
self.property_changed)
# Store the message on our window
self.ignore_next_change = True
xml = xmlrpclib.dumps(params, method)
self.window.property_change(_message_prop,
'XA_STRING', 8,
g.gdk.PROP_MODE_REPLACE,
xml)
self.response = None
# Tell the service about it
self.service.remote.property_change(_message_id_prop,
'XA_WINDOW', 32,
g.gdk.PROP_MODE_APPEND,
[self.window.xid])
def property_changed(self, win, event):
if event.atom != _message_prop:
return
if event.state == g.gdk.PROPERTY_NEW_VALUE:
if self.ignore_next_change:
# This is just us sending the request
self.ignore_next_change = False
return
val = self.window.property_get(
_message_prop, 'XA_STRING', True)
if val is None:
raise Exception('No response to XML-RPC call')
else:
self.response = val[2]
if self.waiting:
g.main_quit()
def get_response(self):
if self.response is None:
self.waiting = True
tag=gobject.timeout_add(60*1000, self.timed_out)
try:
g.main()
finally:
self.waiting = False
gobject.source_remove(tag)
assert self.response is not None
retval, method = xmlrpclib.loads(self.response)
assert len(retval) == 1
return retval[0]
def timed_out(self):
print >> sys.stderr, 'Timed out waiting for response to', self.method_name
if self.waiting:
g.main_quit()
raise Exception('No response to XML-RPC call')