4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* This code is used to communicate between two copies of the filer:
23 * If the filer is run and the same version of the filer is already
24 * running on the same machine then the new copy simply asks the old
25 * one deal with it and quits.
34 #include <X11/Xatom.h>
36 #include <gtk/gtkinvisible.h>
37 #include <libxml/parser.h>
43 #include "gui_support.h"
55 static GdkAtom filer_atom
; /* _ROX_FILER_EUID_VERSION_HOST */
56 static GdkAtom filer_atom_any
; /* _ROX_FILER_EUID_HOST */
57 static GdkAtom xsoap
; /* _XSOAP */
59 typedef struct _SOAP_call SOAP_call
;
60 typedef xmlNodePtr (*SOAP_func
)(GList
*args
);
64 gchar
**required_args
;
65 gchar
**optional_args
;
68 static GHashTable
*rpc_calls
= NULL
; /* MethodName -> Function */
70 /* Static prototypes */
71 static GdkWindow
*get_existing_ipc_window(void);
72 static gboolean
get_ipc_property(GdkWindow
*window
, Window
*r_xid
);
73 static void soap_send(GtkWidget
*from
, GdkAtom prop
, GdkWindow
*dest
);
74 static gboolean
client_event(GtkWidget
*window
,
75 GdkEventClient
*event
,
77 static void soap_done(GtkWidget
*widget
,
78 GdkEventProperty
*event
,
80 static void soap_register(char *name
, SOAP_func func
, char *req
, char *opt
);
81 static xmlNodePtr
soap_invoke(xmlNode
*method
);
83 static xmlNodePtr
rpc_Version(GList
*args
);
84 static xmlNodePtr
rpc_OpenDir(GList
*args
);
85 static xmlNodePtr
rpc_CloseDir(GList
*args
);
86 static xmlNodePtr
rpc_Examine(GList
*args
);
87 static xmlNodePtr
rpc_Show(GList
*args
);
88 static xmlNodePtr
rpc_Pinboard(GList
*args
);
89 static xmlNodePtr
rpc_Panel(GList
*args
);
90 static xmlNodePtr
rpc_Run(GList
*args
);
91 static xmlNodePtr
rpc_Copy(GList
*args
);
92 static xmlNodePtr
rpc_Move(GList
*args
);
93 static xmlNodePtr
rpc_Link(GList
*args
);
94 static xmlNodePtr
rpc_FileType(GList
*args
);
95 static xmlNodePtr
rpc_Mount(GList
*args
);
97 static xmlNodePtr
rpc_PanelAdd(GList
*args
);
98 static xmlNodePtr
rpc_PinboardAdd(GList
*args
);
99 static xmlNodePtr
rpc_SetBackdropApp(GList
*args
);
101 /****************************************************************
102 * EXTERNAL INTERFACE *
103 ****************************************************************/
106 /* Try to get an already-running filer to handle things (only if
107 * new_copy is FALSE); TRUE if we succeed.
108 * Create an IPC widget so that future filers can contact us.
110 gboolean
remote_init(xmlDocPtr rpc
, gboolean new_copy
)
113 GdkWindow
*existing_ipc_window
;
114 GtkWidget
*ipc_window
;
117 /* xmlDocDump(stdout, rpc); */
119 rpc_calls
= g_hash_table_new(g_str_hash
, g_str_equal
);
121 soap_register("Version", rpc_Version
, NULL
, NULL
);
123 soap_register("Run", rpc_Run
, "Filename", NULL
);
124 soap_register("OpenDir", rpc_OpenDir
, "Filename",
125 "Style,Details,Sort,Class");
126 soap_register("CloseDir", rpc_CloseDir
, "Filename", NULL
);
127 soap_register("Examine", rpc_Examine
, "Filename", NULL
);
128 soap_register("Show", rpc_Show
, "Directory,Leafname", NULL
);
130 soap_register("Pinboard", rpc_Pinboard
, NULL
, "Name");
131 soap_register("Panel", rpc_Panel
, "Side", "Name");
133 soap_register("FileType", rpc_FileType
, "Filename", NULL
);
135 soap_register("Copy", rpc_Copy
, "From,To", "Leafname,Quiet");
136 soap_register("Move", rpc_Move
, "From,To", "Leafname,Quiet");
137 soap_register("Link", rpc_Link
, "From,To", "Leafname");
138 soap_register("Mount", rpc_Mount
, "MountPoints", "OpenDir,Quiet");
140 soap_register("SetBackdropApp", rpc_SetBackdropApp
, "App", NULL
);
141 soap_register("PinboardAdd", rpc_PinboardAdd
, "Path,X,Y", "Label");
142 soap_register("PanelAdd", rpc_PanelAdd
, "Side,Path", "Label,After");
144 /* Look for a property on the root window giving the IPC window
145 * of an already-running copy of this version of the filer, running
146 * on the same machine and with the same euid.
148 unique_id
= g_strdup_printf("_ROX_FILER_%d_%s_%s",
149 (int) euid
, VERSION
, our_host_name());
150 filer_atom
= gdk_atom_intern(unique_id
, FALSE
);
153 xsoap
= gdk_atom_intern("_XSOAP", FALSE
);
155 /* If we find a running copy, we'll need a window to put the
156 * SOAP message in before sending.
157 * If there's no running copy, we'll need a window to receive
158 * future SOAP message events.
159 * Either way, we'll need a window for it...
161 ipc_window
= gtk_invisible_new();
162 gtk_widget_realize(ipc_window
);
164 XGrabServer(GDK_DISPLAY());
166 existing_ipc_window
= new_copy
? NULL
: get_existing_ipc_window();
167 if (existing_ipc_window
)
172 XUngrabServer(GDK_DISPLAY());
174 xmlDocDumpMemory(rpc
, &mem
, &size
);
175 g_return_val_if_fail(size
> 0, FALSE
);
177 /* Since Gtk might have selected this already, we'd
178 * better do it BEFORE changing the property.
180 gtk_widget_add_events(ipc_window
, GDK_PROPERTY_CHANGE_MASK
);
181 g_signal_connect(ipc_window
, "property-notify-event",
182 G_CALLBACK(soap_done
), GINT_TO_POINTER(xsoap
));
184 gdk_property_change(ipc_window
->window
, xsoap
,
185 gdk_x11_xatom_to_atom(XA_STRING
), 8,
186 GDK_PROP_MODE_REPLACE
, mem
, size
);
189 soap_send(ipc_window
, xsoap
, existing_ipc_window
);
194 xwindow
= GDK_WINDOW_XWINDOW(ipc_window
->window
);
196 /* Make the IPC window contain a property pointing to
197 * itself - this can then be used to check that it really
200 gdk_property_change(ipc_window
->window
, filer_atom
,
201 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
202 GDK_PROP_MODE_REPLACE
,
203 (void *) &xwindow
, 1);
205 /* Get notified when we get a message */
206 g_signal_connect(ipc_window
, "client-event",
207 G_CALLBACK(client_event
), NULL
);
209 /* Make the root window contain a pointer to the IPC window */
210 gdk_property_change(gdk_get_default_root_window(), filer_atom
,
211 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
212 GDK_PROP_MODE_REPLACE
,
213 (void *) &xwindow
, 1);
215 XUngrabServer(GDK_DISPLAY());
217 /* Also have a property without the version number, for programs
218 * that are happy to talk to any version of the filer.
220 unique_id
= g_strdup_printf("_ROX_FILER_%d_%s",
221 (int) euid
, our_host_name());
222 filer_atom_any
= gdk_atom_intern(unique_id
, FALSE
);
224 /* On the IPC window... */
225 gdk_property_change(ipc_window
->window
, filer_atom_any
,
226 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
227 GDK_PROP_MODE_REPLACE
,
228 (void *) &xwindow
, 1);
229 /* ... and on the root */
230 gdk_property_change(gdk_get_default_root_window(), filer_atom_any
,
231 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
232 GDK_PROP_MODE_REPLACE
,
233 (void *) &xwindow
, 1);
238 /* Executes the RPC call(s) in the given SOAP message and returns
241 xmlDocPtr
run_soap(xmlDocPtr soap
)
243 xmlNodePtr body
, node
, rep_body
, reply
;
244 xmlDocPtr rep_doc
= NULL
;
246 g_return_val_if_fail(soap
!= NULL
, NULL
);
248 /* Make sure we don't quit before doing the whole list
249 * (there's a command that closes windows)
253 node
= xmlDocGetRootElement(soap
);
257 if (strcmp(node
->ns
->href
, SOAP_ENV_NS
) != 0 &&
258 strcmp(node
->ns
->href
, SOAP_ENV_NS_OLD
) != 0)
261 body
= get_subnode(node
, SOAP_ENV_NS
, "Body");
263 body
= get_subnode(node
, SOAP_ENV_NS_OLD
, "Body");
267 for (node
= body
->xmlChildrenNode
; node
; node
= node
->next
)
269 if (node
->type
!= XML_ELEMENT_NODE
)
272 if (node
->ns
== NULL
|| strcmp(node
->ns
->href
, ROX_NS
) != 0)
274 g_warning("Unknown namespace %s",
275 node
->ns
? node
->ns
->href
276 : (xmlChar
*) "(none)");
280 reply
= soap_invoke(node
);
285 rep_doc
= soap_new(&rep_body
);
286 xmlAddChild(rep_body
, reply
);
293 g_warning("Bad SOAP message received!");
302 /****************************************************************
303 * INTERNAL FUNCTIONS *
304 ****************************************************************/
306 /* Register this function to handle SOAP calls to method 'name'.
307 * 'req' and 'opt' are comma separated lists of argument names, in the order
308 * that they are to be delivered to the function.
309 * NULL will be passed for an opt argument if not given.
310 * Otherwise, the parameter is the xmlNode from the call.
312 static void soap_register(char *name
, SOAP_func func
, char *req
, char *opt
)
316 call
= g_new(SOAP_call
, 1);
318 call
->required_args
= req
? g_strsplit(req
, ",", 0) : NULL
;
319 call
->optional_args
= opt
? g_strsplit(opt
, ",", 0) : NULL
;
321 g_hash_table_insert(rpc_calls
, g_strdup(name
), call
);
324 /* Get the remote IPC window of the already-running filer if there
327 static GdkWindow
*get_existing_ipc_window(void)
329 Window xid
, xid_confirm
;
332 if (!get_ipc_property(gdk_get_default_root_window(), &xid
))
335 if (gdk_window_lookup(xid
))
336 return NULL
; /* Stale handle which we now own */
338 window
= gdk_window_foreign_new(xid
);
342 if (!get_ipc_property(window
, &xid_confirm
) || xid_confirm
!= xid
)
348 /* Returns the 'rox_atom' property of 'window' */
349 static gboolean
get_ipc_property(GdkWindow
*window
, Window
*r_xid
)
353 gboolean retval
= FALSE
;
355 if (gdk_property_get(window
, filer_atom
,
356 gdk_x11_xatom_to_atom(XA_WINDOW
), 0, 4,
357 FALSE
, NULL
, &format
, &length
, &data
) && data
)
359 if (format
== 32 && length
== 4)
362 *r_xid
= *((Window
*) data
);
370 static char *read_property(GdkWindow
*window
, GdkAtom prop
, gint
*out_length
)
372 gint grab_len
= 4096;
375 guchar
*retval
= NULL
;
377 gdk_error_trap_push();
381 if (!(gdk_property_get(window
, prop
,
382 gdk_x11_xatom_to_atom(XA_STRING
), 0, grab_len
,
384 &length
, &data
) && data
))
385 goto out
; /* Error? */
387 if (length
>= grab_len
)
389 /* Didn't get all of it - try again */
395 data
= g_realloc(data
, length
+ 1);
396 data
[length
] = '\0'; /* libxml seems to need this */
397 *out_length
= length
;
403 if (gdk_error_trap_pop() == Success
)
406 g_warning("Error reading %s property!", gdk_atom_name(prop
));
412 static gboolean
client_event(GtkWidget
*window
,
413 GdkEventClient
*event
,
416 GdkWindow
*src_window
;
423 if (event
->message_type
!= xsoap
)
426 src_window
= gdk_window_foreign_new(event
->data
.l
[0]);
427 g_return_val_if_fail(src_window
!= NULL
, FALSE
);
428 prop
= gdk_x11_xatom_to_atom(event
->data
.l
[1]);
430 data
= read_property(src_window
, prop
, &length
);
434 doc
= xmlParseMemory(g_strndup(data
, length
), length
);
437 reply
= run_soap(doc
);
438 if (number_of_windows
== 0)
445 /* Send reply back... */
449 xmlDocDumpMemory(reply
, &mem
, &size
);
450 g_return_val_if_fail(size
> 0, TRUE
);
452 gdk_error_trap_push();
453 gdk_property_change(src_window
, prop
,
454 gdk_x11_xatom_to_atom(XA_STRING
), 8,
455 GDK_PROP_MODE_REPLACE
, mem
, size
);
458 if (gdk_error_trap_pop() != Success
)
459 g_warning("Failed to send SOAP reply!");
463 gdk_error_trap_push();
464 gdk_property_delete(src_window
, prop
);
466 if (gdk_error_trap_pop() != Success
)
467 g_warning("Failed to send SOAP reply!");
473 /* Some handy functions for processing SOAP RPC arguments... */
475 /* Returns TRUE, FALSE, or -1 (if argument is unspecified) */
476 static int bool_value(xmlNode
*arg
)
484 optval
= xmlNodeGetContent(arg
);
485 answer
= text_to_boolean(optval
, -1); /* XXX: Report error? */
491 /* Returns the text of this arg as a string, or NULL if the (optional)
492 * argument wasn't supplied. Returns "" if the arg is empty.
493 * g_free() the result.
495 static char *string_value(xmlNode
*arg
)
502 retval
= xmlNodeGetContent(arg
);
504 return retval
? retval
: g_strdup("");
507 /* Returns the text of this arg as an int, or the default value if not
508 * supplied or not an int.
510 static int int_value(xmlNode
*arg
, int def
)
518 str
= xmlNodeGetContent(arg
);
522 i
= (int) strtol(str
, &end
, 0);
524 return (end
> str
) ? i
: def
;
527 /* Return a list of strings, one for each child node of arg.
528 * g_list_free the list, and g_free each string.
530 static GList
*list_value(xmlNode
*arg
)
535 for (node
= arg
->xmlChildrenNode
; node
; node
= node
->next
)
537 if (node
->type
!= XML_ELEMENT_NODE
)
540 list
= g_list_append(list
, string_value(node
));
546 #define ARG(n) ((xmlNode *) g_list_nth(args, n)->data)
548 /* The RPC handlers all work in the same way -- they get passed a list of
549 * xmlNode arguments from the RPC call and they return the result node, or
550 * NULL if there isn't a result.
553 static xmlNodePtr
rpc_Version(GList
*args
)
557 reply
= xmlNewNode(NULL
, "rox:VersionResponse");
558 xmlNewNs(reply
, SOAP_RPC_NS
, "soap");
559 xmlNewChild(reply
, NULL
, "soap:result", VERSION
);
564 /* Args: Path, [Style, Details, Sort, Class] */
565 static xmlNodePtr
rpc_OpenDir(GList
*args
)
568 char *style
, *details
, *sort
, *class;
571 path
= string_value(ARG(0));
572 style
= string_value(ARG(1));
573 details
= string_value(ARG(2));
574 sort
= string_value(ARG(3));
575 class = string_value(ARG(4));
577 fwin
= filer_opendir(path
, NULL
, class);
585 ds
= !g_strcasecmp(style
, "Large") ? LARGE_ICONS
:
586 !g_strcasecmp(style
, "Small") ? SMALL_ICONS
:
587 !g_strcasecmp(style
, "Huge") ? HUGE_ICONS
:
588 !g_strcasecmp(style
, "Automatic") ? AUTO_SIZE_ICONS
:
590 if (ds
== UNKNOWN_STYLE
)
591 g_warning("Unknown style '%s'\n", style
);
593 display_set_layout(fwin
, ds
, fwin
->details_type
);
602 dt
= !g_strcasecmp(details
, "None") ? DETAILS_NONE
:
603 !g_strcasecmp(details
, "Summary") ? DETAILS_NONE
:
604 !g_strcasecmp(details
, "Size") ? DETAILS_SIZE
:
605 !g_strcasecmp(details
, "Type") ? DETAILS_TYPE
:
606 !g_strcasecmp(details
, "Times") ? DETAILS_TIMES
:
607 !g_strcasecmp(details
, "Permissions")
608 ? DETAILS_PERMISSIONS
:
611 if (dt
== DETAILS_UNKNOWN
)
612 g_warning("Unknown details type '%s'\n", details
);
614 display_set_layout(fwin
,
615 fwin
->display_style_wanted
, dt
);
622 int (*cmp
)(const void *, const void *);
624 cmp
= !g_strcasecmp(sort
, "Name") ? sort_by_name
:
625 !g_strcasecmp(sort
, "Type") ? sort_by_type
:
626 !g_strcasecmp(sort
, "Date") ? sort_by_date
:
627 !g_strcasecmp(sort
, "Size") ? sort_by_size
:
630 g_warning("Unknown sorting criteria '%s'\n", sort
);
632 display_set_sort_fn(fwin
, cmp
);
640 static xmlNodePtr
rpc_Run(GList
*args
)
644 path
= string_value(ARG(0));
651 static xmlNodePtr
rpc_CloseDir(GList
*args
)
655 path
= string_value(ARG(0));
656 filer_close_recursive(path
);
662 static xmlNodePtr
rpc_Examine(GList
*args
)
666 path
= string_value(ARG(0));
673 static xmlNodePtr
rpc_Show(GList
*args
)
677 dir
= string_value(ARG(0));
678 leaf
= string_value(ARG(1));
680 /* XXX: Seems silly to join them only to split again later... */
681 open_to_show(make_path(dir
, leaf
));
689 static xmlNodePtr
rpc_Pinboard(GList
*args
)
693 name
= string_value(ARG(0));
694 pinboard_activate(name
);
701 static xmlNodePtr
rpc_SetBackdropApp(GList
*args
)
705 app
= string_value(ARG(0));
707 pinboard_set_backdrop_app(app
);
714 /* args = Path, X, Y, [Label] */
715 static xmlNodePtr
rpc_PinboardAdd(GList
*args
)
721 path
= string_value(ARG(0));
722 x
= int_value(ARG(1), 0);
723 y
= int_value(ARG(2), 0);
724 name
= string_value(ARG(3));
726 pinboard_pin(path
, name
, x
, y
, NULL
);
733 /* Returns PANEL_NUMBER_OF_SIDES if name is invalid */
734 static PanelSide
panel_name_to_side(gchar
*side
)
736 if (strcmp(side
, "Top") == 0)
738 else if (strcmp(side
, "Bottom") == 0)
740 else if (strcmp(side
, "Left") == 0)
742 else if (strcmp(side
, "Right") == 0)
745 g_warning("Unknown panel side '%s'", side
);
746 return PANEL_NUMBER_OF_SIDES
;
749 /* args = Side, [Name] */
750 static xmlNodePtr
rpc_Panel(GList
*args
)
753 char *name
, *side_name
;
755 side_name
= string_value(ARG(0));
756 side
= panel_name_to_side(side_name
);
758 g_return_val_if_fail(side
!= PANEL_NUMBER_OF_SIDES
, NULL
);
760 name
= string_value(ARG(1));
762 panel_new(name
, side
);
769 /* args = Side, Path, [Label, After] */
770 static xmlNodePtr
rpc_PanelAdd(GList
*args
)
773 char *path
, *side_name
, *label
;
774 gboolean after
= FALSE
;
777 side_name
= string_value(ARG(0));
778 side
= panel_name_to_side(side_name
);
780 g_return_val_if_fail(side
!= PANEL_NUMBER_OF_SIDES
, NULL
);
782 path
= string_value(ARG(1));
783 label
= string_value(ARG(2));
785 tmp
= bool_value(ARG(3));
786 after
= (tmp
== -1) ? FALSE
: tmp
;
788 panel_add(side
, path
, label
, after
);
795 static xmlNodePtr
rpc_Copy(GList
*args
)
802 from
= list_value(ARG(0));
803 to
= string_value(ARG(1));
804 leaf
= string_value(ARG(2));
805 quiet
= bool_value(ARG(3));
808 action_copy(from
, to
, leaf
, quiet
);
810 destroy_glist(&from
);
817 static xmlNodePtr
rpc_Move(GList
*args
)
824 from
= list_value(ARG(0));
825 to
= string_value(ARG(1));
826 leaf
= string_value(ARG(2));
827 quiet
= bool_value(ARG(3));
830 action_move(from
, to
, leaf
, quiet
);
832 destroy_glist(&from
);
839 static xmlNodePtr
rpc_Link(GList
*args
)
845 from
= list_value(ARG(0));
846 to
= string_value(ARG(1));
847 leaf
= string_value(ARG(2));
850 action_link(from
, to
, leaf
);
852 destroy_glist(&from
);
859 static xmlNodePtr
rpc_FileType(GList
*args
)
865 path
= string_value(ARG(0));
866 type
= type_get_type(path
);
869 reply
= xmlNewNode(NULL
, "rox:FileTypeResponse");
870 tname
= g_strconcat(type
->media_type
, "/", type
->subtype
, NULL
);
872 xmlNewNs(reply
, SOAP_RPC_NS
, "soap");
873 xmlNewChild(reply
, NULL
, "soap:result", tname
);
879 static xmlNodePtr
rpc_Mount(GList
*args
)
884 paths
= list_value(ARG(0));
885 open_dir
= bool_value(ARG(1));
886 quiet
= bool_value(ARG(2));
892 action_mount(paths
, open_dir
, quiet
);
894 destroy_glist(&paths
);
899 /* The first time the property changes, do nothing (it's us setting the
900 * property). The second time, get the result.
901 * Don't call this function three times!
903 static void soap_done(GtkWidget
*widget
, GdkEventProperty
*event
, gpointer data
)
905 static int times_called
= 0;
906 GdkAtom prop
= (GdkAtom
) data
;
908 if (prop
!= event
->atom
)
912 g_return_if_fail(times_called
< 3);
914 if (times_called
== 1)
917 /* If we got a reply, display it here */
918 if (event
->state
== GDK_PROPERTY_NEW_VALUE
)
922 data
= read_property(event
->window
, event
->atom
, &length
);
931 static gboolean
too_slow(gpointer data
)
933 g_warning("Existing ROX-Filer process is not responding! Try with -n");
939 /* Send the SOAP message in property 'prop' on 'from' to 'dest' */
940 static void soap_send(GtkWidget
*from
, GdkAtom prop
, GdkWindow
*dest
)
942 GdkEventClient event
;
944 event
.data
.l
[0] = GDK_WINDOW_XWINDOW(from
->window
);
945 event
.data
.l
[1] = gdk_x11_atom_to_xatom(prop
);
946 event
.data_format
= 32;
947 event
.message_type
= xsoap
;
949 gdk_event_send_client_message((GdkEvent
*) &event
,
950 GDK_WINDOW_XWINDOW(dest
));
952 gtk_timeout_add(10000, too_slow
, NULL
);
957 /* Lookup this method in rpc_calls and invoke it.
958 * Returns the SOAP reply or fault, or NULL if this method
959 * doesn't return anything.
961 static xmlNodePtr
soap_invoke(xmlNode
*method
)
966 xmlNodePtr retval
= NULL
;
967 GHashTable
*name_to_node
;
970 call
= g_hash_table_lookup(rpc_calls
, method
->name
);
976 err
= g_strdup_printf(_("Attempt to invoke unknown SOAP "
977 "method '%s'"), method
->name
);
978 reply
= xmlNewNode(NULL
, "env:Fault");
979 xmlNewNs(reply
, SOAP_RPC_NS
, "rpc");
980 xmlNewNs(reply
, SOAP_ENV_NS
, "env");
981 xmlNewChild(reply
, NULL
, "faultcode",
982 "rpc:ProcedureNotPresent");
983 xmlNewChild(reply
, NULL
, "faultstring", err
);
988 name_to_node
= g_hash_table_new(g_str_hash
, g_str_equal
);
989 for (node
= method
->xmlChildrenNode
; node
; node
= node
->next
)
991 if (node
->type
!= XML_ELEMENT_NODE
)
994 if (node
->ns
== NULL
|| strcmp(node
->ns
->href
, ROX_NS
) != 0)
997 g_hash_table_insert(name_to_node
, (gchar
*) node
->name
, node
);
1000 if (call
->required_args
)
1002 for (arg
= call
->required_args
; *arg
; arg
++)
1004 node
= g_hash_table_lookup(name_to_node
, *arg
);
1007 g_warning("Missing required argument '%s' "
1008 "in call to method '%s'", *arg
,
1013 args
= g_list_append(args
, node
);
1017 if (call
->optional_args
)
1019 for (arg
= call
->optional_args
; *arg
; arg
++)
1020 args
= g_list_append(args
,
1021 g_hash_table_lookup(name_to_node
, *arg
));
1024 retval
= call
->func(args
);
1027 g_hash_table_destroy(name_to_node
);