Import the WebKit version
[browser-dbus-bridge.git] / jscorebus / jscorebus-signal.c
1 /**
2  * Browser D-Bus Bridge, JavaScriptCore version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include <glib.h>
24 #include <dbus/dbus.h>
25 #include <JavaScriptCore/JavaScript.h>
26
27 #include "jscorebus-marshal.h"
28
29 /* Private data for signals */
30 typedef struct _SignalPrivate
31 {
32   char *interface;
33   char *signal_name;
34   char *sender;
35   char *object_path;
36
37   DBusConnection *connection;
38   char *match_rule;
39
40   JSGlobalContextRef context;
41   JSObjectRef this;
42   JSObjectRef onemit;
43   gboolean enabled;
44 } SignalPrivate;
45
46 /* Utility functions */
47 static
48 JSClassRef get_class ();
49
50 static
51 void add_match_and_handler(SignalPrivate *priv);
52 static
53 void remove_match_and_handler(SignalPrivate *priv);
54
55 /* JSCore methods */
56 static
57 void signal_finalize(JSObjectRef object);
58
59 static
60 bool signal_set_property(JSContextRef context,
61                          JSObjectRef object,
62                          JSStringRef propertyName,
63                          JSValueRef value,
64                          JSValueRef* exception);
65
66 /* We keep a hash table to track the signals callbacks */
67 static GHashTable *signal_hash;
68
69 /* The Signal Class */
70 static const
71 JSClassDefinition signal_jsclass_def =
72 {
73   0,
74   kJSClassAttributeNone,
75   "DBusSignal",
76   NULL,
77
78   NULL,
79   NULL,
80   
81   NULL, /* Initialize */
82   signal_finalize,
83   
84   NULL,
85   NULL,
86   signal_set_property, /* SetProperty */
87   NULL,
88   NULL,
89   
90   NULL, /* CallAsFunction */
91   NULL, /* Constructor */
92   NULL,
93   NULL
94 };
95
96 /*** JSCore methods */
97
98 static
99 void signal_finalize(JSObjectRef object)
100 {
101   SignalPrivate *priv = (SignalPrivate *)JSObjectGetPrivate(object);
102
103   if (priv != NULL)
104   {
105     remove_match_and_handler(priv);
106     
107     g_free(priv->object_path);
108     g_free(priv->signal_name);
109     g_free(priv->interface);
110     g_free(priv->sender);
111     g_free(priv->object_path);
112     g_free(priv->match_rule);
113     dbus_connection_unref(priv->connection);
114
115     priv->this = NULL;
116     priv->onemit = NULL;
117     priv->context = NULL;
118     
119     g_free(priv);
120     priv = NULL;
121   }
122 }
123
124 static
125 bool signal_set_property(JSContextRef context,
126                          JSObjectRef object,
127                          JSStringRef propertyName,
128                          JSValueRef value,
129                          JSValueRef* exception)
130 {
131   SignalPrivate *priv;
132
133   priv = (SignalPrivate *)JSObjectGetPrivate(object);
134   g_assert(priv != NULL);
135
136   if (JSStringIsEqualToUTF8CString(propertyName, "enabled"))
137   {
138     /* We don't enable unless we have a callback */
139     if (priv->onemit == NULL)
140     {
141       /* TODO: Throw exception */
142       return TRUE;
143     }
144     if (JSValueIsBoolean(context, value))
145     {
146       priv->enabled = JSValueToBoolean(context, value);
147       if (priv->enabled)
148       {
149         add_match_and_handler(priv);
150       }
151       return TRUE;
152     }
153     
154     /* TODO: set exception */
155     g_warning("Tried to set a non-boolean to 'enabled'");
156     return TRUE;
157   } else if (JSStringIsEqualToUTF8CString(propertyName, "onemit")) {
158     priv->onemit = function_from_jsvalue(context, value, exception);
159
160     /* If the callback function goes away, we need to disable the signal,
161       * remove match and remove our entry from the handlers
162       */
163     if (priv->onemit == NULL)
164     {
165       remove_match_and_handler(priv);
166     }
167     return TRUE;
168   }
169
170   return FALSE;
171 }
172
173 /*** Utility functions */
174
175 static
176 JSClassRef get_class ()
177 {
178   JSClassRef jsclass = (JSClassRef)jsclass_lookup(&signal_jsclass_def);
179   
180   if (G_LIKELY(jsclass != NULL))
181   {
182     return jsclass;
183   }
184   
185   jsclassdef_insert("DBusSignal", &signal_jsclass_def);
186   jsclass = (JSClassRef)jsclass_lookup(&signal_jsclass_def);
187
188   g_assert(jsclass != NULL);
189
190   return jsclass;
191 }
192
193 static
194 void add_match_and_handler(SignalPrivate *priv)
195 {
196   char *signal_str;
197   GPtrArray *handlers;
198
199   dbus_bus_add_match(priv->connection, priv->match_rule, NULL);
200
201   signal_str = g_strdup_printf("%s.%s", priv->interface, priv->signal_name);
202   handlers = g_hash_table_lookup(signal_hash, signal_str);
203
204   if (handlers == NULL)
205   {
206     handlers = g_ptr_array_new();
207     g_hash_table_insert(signal_hash, signal_str, handlers);
208   } else {
209     g_free(signal_str);
210   }
211
212   g_ptr_array_add(handlers, priv);
213 }
214
215 static
216 void remove_match_and_handler(SignalPrivate *priv)
217 {
218   char *signal_str;
219   GPtrArray *handlers;
220
221   signal_str = g_strdup_printf("%s.%s", priv->interface, priv->signal_name);
222
223   handlers = g_hash_table_lookup(signal_hash, signal_str);
224
225   if (handlers != NULL)
226   {
227     g_ptr_array_remove(handlers, priv);
228     if (handlers->len == 0)
229     {
230       g_hash_table_remove(signal_hash, signal_str);
231       g_ptr_array_free(handlers, TRUE);
232     }
233   }
234
235   g_free(signal_str);
236
237   dbus_bus_remove_match(priv->connection, priv->match_rule, NULL);
238
239 }
240
241 static
242 void call_onemit(SignalPrivate *priv,
243                  DBusMessage *message)
244 {
245
246   if (priv->enabled == FALSE
247    || priv->onemit == NULL)
248   {
249     return;
250   }
251
252   /* If the signal was created with a sender, we need to check for it
253    * and not deliver if it does not match
254    */
255   if (priv->sender != NULL)
256   {
257     if (!dbus_message_has_sender(message, priv->sender))
258     {
259       return;
260     }
261   }
262   /* Ditto for object paths */
263   if (priv->object_path != NULL)
264   {
265     if (!dbus_message_has_path(message, priv->object_path))
266     {
267       return;
268     }
269   }
270
271   call_function_with_message_args(priv->context, priv->this, priv->onemit, message);
272 }
273
274 static
275 DBusHandlerResult signal_filter(DBusConnection *connection,
276                                 DBusMessage *message,
277                                 void *user_data)
278 {
279   char *signal_str;
280   GPtrArray *handlers;
281
282   if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
283   {
284     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
285   }
286
287   signal_str = g_strdup_printf("%s.%s",
288                                dbus_message_get_interface(message),
289                                dbus_message_get_member(message));
290
291   handlers = g_hash_table_lookup(signal_hash, signal_str);
292   g_free(signal_str);
293   
294   if (handlers == NULL || handlers->len == 0)
295   {
296     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
297   }
298
299   g_ptr_array_foreach(handlers, (GFunc)call_onemit, message);
300
301   return DBUS_HANDLER_RESULT_HANDLED;
302 }
303
304 /*** Public API */
305
306 /* NOTE: Takes ownership of the arguments! */
307 JSObjectRef jscorebus_create_signal (JSGlobalContextRef context,
308                                      DBusConnection *connection,
309                                      char *interface,
310                                      char *signal_name,
311                                      char *sender,
312                                      char *object_path,
313                                      JSObjectRef thisObject,
314                                      JSValueRef* exception)
315 {
316   int i;
317   char **matchv;
318   char *signal_str;
319   GPtrArray *handlers;
320   SignalPrivate *priv = NULL;
321   static gboolean filter_added = FALSE;
322
323   g_return_val_if_fail(interface != NULL
324                        && signal_name != NULL,
325                        NULL);
326
327   priv = g_new0(SignalPrivate, 1);
328
329   priv->object_path = object_path;
330   priv->interface = interface;
331   priv->signal_name = signal_name;
332   priv->sender = sender;
333   priv->object_path = object_path;
334
335   priv->enabled = false;
336
337   priv->connection = dbus_connection_ref(connection);
338   
339   priv->context = context;
340   priv->this = thisObject;
341
342   /* If we don't already have a filter function for the signals, add one */
343   if (G_UNLIKELY(!filter_added))
344   {
345     signal_hash = g_hash_table_new(g_str_hash, g_str_equal);
346     dbus_connection_add_filter(connection, signal_filter, NULL, NULL);
347     filter_added = TRUE;
348   }
349
350   /* Add the match rule for the signal */
351   matchv = g_new0(char*, 4);
352   i = 0;
353   matchv[i++] = g_strdup_printf("type=signal,interface=%s,member=%s",
354                                 priv->interface, priv->signal_name);
355   if (priv->sender != NULL)
356   {
357     matchv[i++] = g_strdup_printf("sender=%s", priv->sender);
358   }
359   if (priv->object_path != NULL)
360   {
361     matchv[i++] = g_strdup_printf("object_path=%s", priv->object_path);
362   }
363
364   priv->match_rule = g_strjoinv(",", matchv);
365   g_strfreev(matchv);
366   
367   return JSObjectMake(context, get_class(), priv);
368 }
369