1 | #include <gio/gio.h> |
2 | #include <stdlib.h> |
3 | |
4 | #ifdef G_OS_UNIX |
5 | #include <gio/gunixfdlist.h> |
6 | /* For STDOUT_FILENO */ |
7 | #include <unistd.h> |
8 | #endif |
9 | |
10 | /* ---------------------------------------------------------------------------------------------------- */ |
11 | |
12 | static GDBusNodeInfo *introspection_data = NULL; |
13 | |
14 | /* Introspection data for the service we are exporting */ |
15 | static const gchar introspection_xml[] = |
16 | "<node>" |
17 | " <interface name='org.gtk.GDBus.TestInterface'>" |
18 | " <annotation name='org.gtk.GDBus.Annotation' value='OnInterface'/>" |
19 | " <annotation name='org.gtk.GDBus.Annotation' value='AlsoOnInterface'/>" |
20 | " <method name='HelloWorld'>" |
21 | " <annotation name='org.gtk.GDBus.Annotation' value='OnMethod'/>" |
22 | " <arg type='s' name='greeting' direction='in'/>" |
23 | " <arg type='s' name='response' direction='out'/>" |
24 | " </method>" |
25 | " <method name='EmitSignal'>" |
26 | " <arg type='d' name='speed_in_mph' direction='in'>" |
27 | " <annotation name='org.gtk.GDBus.Annotation' value='OnArg'/>" |
28 | " </arg>" |
29 | " </method>" |
30 | " <method name='GimmeStdout'/>" |
31 | " <signal name='VelocityChanged'>" |
32 | " <annotation name='org.gtk.GDBus.Annotation' value='Onsignal'/>" |
33 | " <arg type='d' name='speed_in_mph'/>" |
34 | " <arg type='s' name='speed_as_string'>" |
35 | " <annotation name='org.gtk.GDBus.Annotation' value='OnArg_NonFirst'/>" |
36 | " </arg>" |
37 | " </signal>" |
38 | " <property type='s' name='FluxCapicitorName' access='read'>" |
39 | " <annotation name='org.gtk.GDBus.Annotation' value='OnProperty'>" |
40 | " <annotation name='org.gtk.GDBus.Annotation' value='OnAnnotation_YesThisIsCrazy'/>" |
41 | " </annotation>" |
42 | " </property>" |
43 | " <property type='s' name='Title' access='readwrite'/>" |
44 | " <property type='s' name='ReadingAlwaysThrowsError' access='read'/>" |
45 | " <property type='s' name='WritingAlwaysThrowsError' access='readwrite'/>" |
46 | " <property type='s' name='OnlyWritable' access='write'/>" |
47 | " <property type='s' name='Foo' access='read'/>" |
48 | " <property type='s' name='Bar' access='read'/>" |
49 | " </interface>" |
50 | "</node>" ; |
51 | |
52 | /* ---------------------------------------------------------------------------------------------------- */ |
53 | |
54 | static void |
55 | handle_method_call (GDBusConnection *connection, |
56 | const gchar *sender, |
57 | const gchar *object_path, |
58 | const gchar *interface_name, |
59 | const gchar *method_name, |
60 | GVariant *parameters, |
61 | GDBusMethodInvocation *invocation, |
62 | gpointer user_data) |
63 | { |
64 | if (g_strcmp0 (str1: method_name, str2: "HelloWorld" ) == 0) |
65 | { |
66 | const gchar *greeting; |
67 | |
68 | g_variant_get (value: parameters, format_string: "(&s)" , &greeting); |
69 | |
70 | if (g_strcmp0 (str1: greeting, str2: "Return Unregistered" ) == 0) |
71 | { |
72 | g_dbus_method_invocation_return_error (invocation, |
73 | G_IO_ERROR, |
74 | code: G_IO_ERROR_FAILED_HANDLED, |
75 | format: "As requested, here's a GError not registered (G_IO_ERROR_FAILED_HANDLED)" ); |
76 | } |
77 | else if (g_strcmp0 (str1: greeting, str2: "Return Registered" ) == 0) |
78 | { |
79 | g_dbus_method_invocation_return_error (invocation, |
80 | G_DBUS_ERROR, |
81 | code: G_DBUS_ERROR_MATCH_RULE_NOT_FOUND, |
82 | format: "As requested, here's a GError that is registered (G_DBUS_ERROR_MATCH_RULE_NOT_FOUND)" ); |
83 | } |
84 | else if (g_strcmp0 (str1: greeting, str2: "Return Raw" ) == 0) |
85 | { |
86 | g_dbus_method_invocation_return_dbus_error (invocation, |
87 | error_name: "org.gtk.GDBus.SomeErrorName" , |
88 | error_message: "As requested, here's a raw D-Bus error" ); |
89 | } |
90 | else |
91 | { |
92 | gchar *response; |
93 | response = g_strdup_printf (format: "You greeted me with '%s'. Thanks!" , greeting); |
94 | g_dbus_method_invocation_return_value (invocation, |
95 | parameters: g_variant_new (format_string: "(s)" , response)); |
96 | g_free (mem: response); |
97 | } |
98 | } |
99 | else if (g_strcmp0 (str1: method_name, str2: "EmitSignal" ) == 0) |
100 | { |
101 | GError *local_error; |
102 | gdouble speed_in_mph; |
103 | gchar *speed_as_string; |
104 | |
105 | g_variant_get (value: parameters, format_string: "(d)" , &speed_in_mph); |
106 | speed_as_string = g_strdup_printf (format: "%g mph!" , speed_in_mph); |
107 | |
108 | local_error = NULL; |
109 | g_dbus_connection_emit_signal (connection, |
110 | NULL, |
111 | object_path, |
112 | interface_name, |
113 | signal_name: "VelocityChanged" , |
114 | parameters: g_variant_new (format_string: "(ds)" , |
115 | speed_in_mph, |
116 | speed_as_string), |
117 | error: &local_error); |
118 | g_assert_no_error (local_error); |
119 | g_free (mem: speed_as_string); |
120 | |
121 | g_dbus_method_invocation_return_value (invocation, NULL); |
122 | } |
123 | else if (g_strcmp0 (str1: method_name, str2: "GimmeStdout" ) == 0) |
124 | { |
125 | #ifdef G_OS_UNIX |
126 | if (g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) |
127 | { |
128 | GDBusMessage *reply; |
129 | GUnixFDList *fd_list; |
130 | GError *error; |
131 | |
132 | fd_list = g_unix_fd_list_new (); |
133 | error = NULL; |
134 | g_unix_fd_list_append (list: fd_list, STDOUT_FILENO, error: &error); |
135 | g_assert_no_error (error); |
136 | |
137 | reply = g_dbus_message_new_method_reply (method_call_message: g_dbus_method_invocation_get_message (invocation)); |
138 | g_dbus_message_set_unix_fd_list (message: reply, fd_list); |
139 | |
140 | error = NULL; |
141 | g_dbus_connection_send_message (connection, |
142 | message: reply, |
143 | flags: G_DBUS_SEND_MESSAGE_FLAGS_NONE, |
144 | NULL, /* out_serial */ |
145 | error: &error); |
146 | g_assert_no_error (error); |
147 | |
148 | g_object_unref (object: invocation); |
149 | g_object_unref (object: fd_list); |
150 | g_object_unref (object: reply); |
151 | } |
152 | else |
153 | { |
154 | g_dbus_method_invocation_return_dbus_error (invocation, |
155 | error_name: "org.gtk.GDBus.Failed" , |
156 | error_message: "Your message bus daemon does not support file descriptor passing (need D-Bus >= 1.3.0)" ); |
157 | } |
158 | #else |
159 | g_dbus_method_invocation_return_dbus_error (invocation, |
160 | "org.gtk.GDBus.NotOnUnix" , |
161 | "Your OS does not support file descriptor passing" ); |
162 | #endif |
163 | } |
164 | } |
165 | |
166 | static gchar *_global_title = NULL; |
167 | |
168 | static gboolean swap_a_and_b = FALSE; |
169 | |
170 | static GVariant * |
171 | handle_get_property (GDBusConnection *connection, |
172 | const gchar *sender, |
173 | const gchar *object_path, |
174 | const gchar *interface_name, |
175 | const gchar *property_name, |
176 | GError **error, |
177 | gpointer user_data) |
178 | { |
179 | GVariant *ret; |
180 | |
181 | ret = NULL; |
182 | if (g_strcmp0 (str1: property_name, str2: "FluxCapicitorName" ) == 0) |
183 | { |
184 | ret = g_variant_new_string (string: "DeLorean" ); |
185 | } |
186 | else if (g_strcmp0 (str1: property_name, str2: "Title" ) == 0) |
187 | { |
188 | if (_global_title == NULL) |
189 | _global_title = g_strdup (str: "Back To C!" ); |
190 | ret = g_variant_new_string (string: _global_title); |
191 | } |
192 | else if (g_strcmp0 (str1: property_name, str2: "ReadingAlwaysThrowsError" ) == 0) |
193 | { |
194 | g_set_error (err: error, |
195 | G_IO_ERROR, |
196 | code: G_IO_ERROR_FAILED, |
197 | format: "Hello %s. I thought I said reading this property " |
198 | "always results in an error. kthxbye" , |
199 | sender); |
200 | } |
201 | else if (g_strcmp0 (str1: property_name, str2: "WritingAlwaysThrowsError" ) == 0) |
202 | { |
203 | ret = g_variant_new_string (string: "There's no home like home" ); |
204 | } |
205 | else if (g_strcmp0 (str1: property_name, str2: "Foo" ) == 0) |
206 | { |
207 | ret = g_variant_new_string (string: swap_a_and_b ? "Tock" : "Tick" ); |
208 | } |
209 | else if (g_strcmp0 (str1: property_name, str2: "Bar" ) == 0) |
210 | { |
211 | ret = g_variant_new_string (string: swap_a_and_b ? "Tick" : "Tock" ); |
212 | } |
213 | |
214 | return ret; |
215 | } |
216 | |
217 | static gboolean |
218 | handle_set_property (GDBusConnection *connection, |
219 | const gchar *sender, |
220 | const gchar *object_path, |
221 | const gchar *interface_name, |
222 | const gchar *property_name, |
223 | GVariant *value, |
224 | GError **error, |
225 | gpointer user_data) |
226 | { |
227 | if (g_strcmp0 (str1: property_name, str2: "Title" ) == 0) |
228 | { |
229 | if (g_strcmp0 (str1: _global_title, str2: g_variant_get_string (value, NULL)) != 0) |
230 | { |
231 | GVariantBuilder *builder; |
232 | GError *local_error; |
233 | |
234 | g_free (mem: _global_title); |
235 | _global_title = g_variant_dup_string (value, NULL); |
236 | |
237 | local_error = NULL; |
238 | builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); |
239 | g_variant_builder_add (builder, |
240 | format_string: "{sv}" , |
241 | "Title" , |
242 | g_variant_new_string (string: _global_title)); |
243 | g_dbus_connection_emit_signal (connection, |
244 | NULL, |
245 | object_path, |
246 | interface_name: "org.freedesktop.DBus.Properties" , |
247 | signal_name: "PropertiesChanged" , |
248 | parameters: g_variant_new (format_string: "(sa{sv}as)" , |
249 | interface_name, |
250 | builder, |
251 | NULL), |
252 | error: &local_error); |
253 | g_assert_no_error (local_error); |
254 | } |
255 | } |
256 | else if (g_strcmp0 (str1: property_name, str2: "ReadingAlwaysThrowsError" ) == 0) |
257 | { |
258 | /* do nothing - they can't read it after all! */ |
259 | } |
260 | else if (g_strcmp0 (str1: property_name, str2: "WritingAlwaysThrowsError" ) == 0) |
261 | { |
262 | g_set_error (err: error, |
263 | G_IO_ERROR, |
264 | code: G_IO_ERROR_FAILED, |
265 | format: "Hello AGAIN %s. I thought I said writing this property " |
266 | "always results in an error. kthxbye" , |
267 | sender); |
268 | } |
269 | |
270 | return *error == NULL; |
271 | } |
272 | |
273 | |
274 | /* for now */ |
275 | static const GDBusInterfaceVTable interface_vtable = |
276 | { |
277 | handle_method_call, |
278 | handle_get_property, |
279 | handle_set_property |
280 | }; |
281 | |
282 | /* ---------------------------------------------------------------------------------------------------- */ |
283 | |
284 | static gboolean |
285 | on_timeout_cb (gpointer user_data) |
286 | { |
287 | GDBusConnection *connection = G_DBUS_CONNECTION (user_data); |
288 | GVariantBuilder *builder; |
289 | GVariantBuilder *invalidated_builder; |
290 | GError *error; |
291 | |
292 | swap_a_and_b = !swap_a_and_b; |
293 | |
294 | error = NULL; |
295 | builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); |
296 | invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as" )); |
297 | g_variant_builder_add (builder, |
298 | format_string: "{sv}" , |
299 | "Foo" , |
300 | g_variant_new_string (string: swap_a_and_b ? "Tock" : "Tick" )); |
301 | g_variant_builder_add (builder, |
302 | format_string: "{sv}" , |
303 | "Bar" , |
304 | g_variant_new_string (string: swap_a_and_b ? "Tick" : "Tock" )); |
305 | g_dbus_connection_emit_signal (connection, |
306 | NULL, |
307 | object_path: "/org/gtk/GDBus/TestObject" , |
308 | interface_name: "org.freedesktop.DBus.Properties" , |
309 | signal_name: "PropertiesChanged" , |
310 | parameters: g_variant_new (format_string: "(sa{sv}as)" , |
311 | "org.gtk.GDBus.TestInterface" , |
312 | builder, |
313 | invalidated_builder), |
314 | error: &error); |
315 | g_assert_no_error (error); |
316 | |
317 | |
318 | return G_SOURCE_CONTINUE; |
319 | } |
320 | |
321 | /* ---------------------------------------------------------------------------------------------------- */ |
322 | |
323 | static void |
324 | on_bus_acquired (GDBusConnection *connection, |
325 | const gchar *name, |
326 | gpointer user_data) |
327 | { |
328 | guint registration_id; |
329 | |
330 | registration_id = g_dbus_connection_register_object (connection, |
331 | object_path: "/org/gtk/GDBus/TestObject" , |
332 | interface_info: introspection_data->interfaces[0], |
333 | vtable: &interface_vtable, |
334 | NULL, /* user_data */ |
335 | NULL, /* user_data_free_func */ |
336 | NULL); /* GError** */ |
337 | g_assert (registration_id > 0); |
338 | |
339 | /* swap value of properties Foo and Bar every two seconds */ |
340 | g_timeout_add_seconds (interval: 2, |
341 | function: on_timeout_cb, |
342 | data: connection); |
343 | } |
344 | |
345 | static void |
346 | on_name_acquired (GDBusConnection *connection, |
347 | const gchar *name, |
348 | gpointer user_data) |
349 | { |
350 | } |
351 | |
352 | static void |
353 | on_name_lost (GDBusConnection *connection, |
354 | const gchar *name, |
355 | gpointer user_data) |
356 | { |
357 | exit (status: 1); |
358 | } |
359 | |
360 | int |
361 | main (int argc, char *argv[]) |
362 | { |
363 | guint owner_id; |
364 | GMainLoop *loop; |
365 | |
366 | /* We are lazy here - we don't want to manually provide |
367 | * the introspection data structures - so we just build |
368 | * them from XML. |
369 | */ |
370 | introspection_data = g_dbus_node_info_new_for_xml (xml_data: introspection_xml, NULL); |
371 | g_assert (introspection_data != NULL); |
372 | |
373 | owner_id = g_bus_own_name (bus_type: G_BUS_TYPE_SESSION, |
374 | name: "org.gtk.GDBus.TestServer" , |
375 | flags: G_BUS_NAME_OWNER_FLAGS_NONE, |
376 | bus_acquired_handler: on_bus_acquired, |
377 | name_acquired_handler: on_name_acquired, |
378 | name_lost_handler: on_name_lost, |
379 | NULL, |
380 | NULL); |
381 | |
382 | loop = g_main_loop_new (NULL, FALSE); |
383 | g_main_loop_run (loop); |
384 | |
385 | g_bus_unown_name (owner_id); |
386 | |
387 | g_dbus_node_info_unref (info: introspection_data); |
388 | |
389 | return 0; |
390 | } |
391 | |