2 #from logging import warn Not in Python2.2
4 from rox
import g
, tasks
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):
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
30 self
.ipc_window
.window
.property_change(self
.service
,
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
:
57 if event
.state
== g
.gdk
.PROPERTY_NEW_VALUE
:
58 val
= self
.ipc_window
.window
.property_get(
59 _message_id_prop
, 'XA_WINDOW', True)
61 self
.process_requests(val
[2])
63 def process_requests(self
, requests
):
65 foreign
= g
.gdk
.window_foreign_new(long(xid
))
66 xml
= foreign
.property_get(
67 _message_prop
, 'XA_STRING', False)
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
)
75 print >>sys
.stderr
, "No '%s' property on window %x" % (
78 def invoke(self
, method
, *params
):
80 raise Exception('No object path in message')
83 obj
= self
.objects
[obpath
]
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
)
91 method
= getattr(obj
, method
)
92 retval
= method(*params
[1:])
94 # XML-RPC doesn't allow returning None
100 #traceback.print_exc(file = sys.stderr)
101 return xmlrpclib
.Fault(ex
.__class
__.__name
__,
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)
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 \
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
130 def __getattr__(self
, method
):
131 if method
.startswith('_'):
132 raise AttributeError("No attribute '" + method
+ "'")
134 call
= ClientCall(self
.service
, method
, tuple([self
.path
] + list(params
)))
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!"
145 class ClientCall(tasks
.Blocker
):
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
:
161 if event
.state
== g
.gdk
.PROPERTY_NEW_VALUE
:
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
,
173 g
.gdk
.PROP_MODE_REPLACE
,
176 self
.invisible
.xmlrpc_response
= None
178 # Tell the service about it
179 self
.service
.remote
.property_change(_message_id_prop
,
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
190 val
= self
.invisible
.window
.property_get(
191 _message_prop
, 'XA_STRING', True)
192 self
.invisible
.destroy()
194 raise Exception('No response to XML-RPC call')
196 self
.invisible
.xmlrpc_response
= val
[2]
197 assert self
.invisible
.xmlrpc_response
is not None, `val`
202 def get_response(self
):
203 if self
.invisible
.xmlrpc_response
is None:
209 assert self
.invisible
.xmlrpc_response
is not None
210 retval
, method
= xmlrpclib
.loads(self
.invisible
.xmlrpc_response
)
211 assert len(retval
) == 1