4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, 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>
43 #include "gui_support.h"
52 static GdkAtom filer_atom
; /* _ROX_FILER_VERSION_HOST */
53 static GdkAtom xsoap
; /* _XSOAP */
55 typedef struct _SOAP_call SOAP_call
;
56 typedef xmlNodePtr (*SOAP_func
)(GList
*args
);
60 gchar
**required_args
;
61 gchar
**optional_args
;
64 static GHashTable
*rpc_calls
= NULL
; /* MethodName -> Function */
66 /* Static prototypes */
67 static GdkWindow
*get_existing_ipc_window();
68 static gboolean
get_ipc_property(GdkWindow
*window
, Window
*r_xid
);
69 static void soap_send(GtkWidget
*from
, GdkAtom prop
, GdkWindow
*dest
);
70 static gboolean
client_event(GtkWidget
*window
,
71 GdkEventClient
*event
,
73 static void soap_register(char *name
, SOAP_func func
, char *req
, char *opt
);
74 static xmlNodePtr
soap_invoke(xmlNode
*method
);
75 static xmlNodePtr
rpc_OpenDir(GList
*args
);
76 static xmlNodePtr
rpc_CloseDir(GList
*args
);
77 static xmlNodePtr
rpc_Examine(GList
*args
);
78 static xmlNodePtr
rpc_Show(GList
*args
);
79 static xmlNodePtr
rpc_Pinboard(GList
*args
);
80 static xmlNodePtr
rpc_Panel(GList
*args
);
81 static xmlNodePtr
rpc_Run(GList
*args
);
82 static xmlNodePtr
rpc_Copy(GList
*args
);
83 static xmlNodePtr
rpc_Move(GList
*args
);
84 static xmlNodePtr
rpc_Link(GList
*args
);
85 static xmlNodePtr
rpc_FileType(GList
*args
);
86 static xmlNodePtr
rpc_Mount(GList
*args
);
88 /****************************************************************
89 * EXTERNAL INTERFACE *
90 ****************************************************************/
93 /* Try to get an already-running filer to handle things (only if
94 * new_copy is FALSE); TRUE if we succeed.
95 * Create an IPC widget so that future filers can contact us.
97 gboolean
remote_init(xmlDocPtr rpc
, gboolean new_copy
)
100 GdkWindow
*existing_ipc_window
;
101 GtkWidget
*ipc_window
;
104 /* xmlDocDump(stdout, rpc); */
106 rpc_calls
= g_hash_table_new(g_str_hash
, g_str_equal
);
108 soap_register("Run", rpc_Run
, "Filename", NULL
);
109 soap_register("OpenDir", rpc_OpenDir
, "Filename", NULL
);
110 soap_register("CloseDir", rpc_CloseDir
, "Filename", NULL
);
111 soap_register("Examine", rpc_Examine
, "Filename", NULL
);
112 soap_register("Show", rpc_Show
, "Directory,Leafname", NULL
);
114 soap_register("Pinboard", rpc_Pinboard
, NULL
, "Name");
115 soap_register("Panel", rpc_Panel
, "Side", "Name");
117 soap_register("FileType", rpc_FileType
, "Filename", NULL
);
119 soap_register("Copy", rpc_Copy
, "From,To", "Leafname,Quiet");
120 soap_register("Move", rpc_Move
, "From,To", "Leafname,Quiet");
121 soap_register("Link", rpc_Link
, "From,To", "Leafname,Quiet");
122 soap_register("Mount", rpc_Mount
, "MountPoints", "OpenDir,Quiet");
124 /* Look for a property on the root window giving the IPC window
125 * of an already-running copy of this version of the filer, running
126 * on the same machine and with the same euid.
128 unique_id
= g_strdup_printf("_ROX_FILER_%d_%s_%s",
129 (int) euid
, VERSION
, our_host_name());
130 filer_atom
= gdk_atom_intern(unique_id
, FALSE
);
133 xsoap
= gdk_atom_intern("_XSOAP", FALSE
);
135 /* If we find a running copy, we'll need a window to put the
136 * SOAP message in before sending.
137 * If there's no running copy, we'll need a window to receive
138 * future SOAP message events.
139 * Either way, we'll need a window for it...
141 ipc_window
= gtk_invisible_new();
142 gtk_widget_realize(ipc_window
);
144 existing_ipc_window
= new_copy
? NULL
: get_existing_ipc_window();
145 if (existing_ipc_window
)
150 xmlDocDumpMemory(rpc
, &mem
, &size
);
151 g_return_val_if_fail(size
> 0, FALSE
);
153 gdk_property_change(ipc_window
->window
, xsoap
,
154 gdk_x11_xatom_to_atom(XA_STRING
), 8,
155 GDK_PROP_MODE_REPLACE
, mem
, size
);
158 soap_send(ipc_window
, xsoap
, existing_ipc_window
);
163 xwindow
= GDK_WINDOW_XWINDOW(ipc_window
->window
);
165 /* Make the IPC window contain a property pointing to
166 * itself - this can then be used to check that it really
169 gdk_property_change(ipc_window
->window
, filer_atom
,
170 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
171 GDK_PROP_MODE_REPLACE
,
172 (void *) &xwindow
, 1);
174 /* Get notified when we get a message */
175 gtk_signal_connect(GTK_OBJECT(ipc_window
), "client-event",
176 GTK_SIGNAL_FUNC(client_event
), NULL
);
178 /* Make the root window contain a pointer to the IPC window */
179 gdk_property_change(GDK_ROOT_PARENT(), filer_atom
,
180 gdk_x11_xatom_to_atom(XA_WINDOW
), 32,
181 GDK_PROP_MODE_REPLACE
,
182 (void *) &xwindow
, 1);
187 /* Executes the RPC call(s) in the given SOAP message and returns
190 xmlDocPtr
run_soap(xmlDocPtr soap
)
192 xmlNodePtr body
, node
, rep_body
, reply
;
193 xmlDocPtr rep_doc
= NULL
;
195 g_return_val_if_fail(soap
!= NULL
, NULL
);
197 /* Make sure we don't quit before doing the whole list
198 * (there's a command that closes windows)
202 node
= xmlDocGetRootElement(soap
);
203 if (!node
->ns
|| strcmp(node
->ns
->href
, SOAP_ENV_NS
) != 0)
206 body
= get_subnode(node
, SOAP_ENV_NS
, "Body");
210 for (node
= body
->xmlChildrenNode
; node
; node
= node
->next
)
212 if (node
->type
!= XML_ELEMENT_NODE
)
215 if (node
->ns
== NULL
|| strcmp(node
->ns
->href
, ROX_NS
) != 0)
217 g_warning("Unknown namespace %s",
218 node
->ns
? node
->ns
->href
219 : (xmlChar
*) "(none)");
223 reply
= soap_invoke(node
);
228 rep_doc
= soap_new(&rep_body
);
229 xmlAddChild(rep_body
, reply
);
236 g_warning("Bad SOAP message received!");
245 /****************************************************************
246 * INTERNAL FUNCTIONS *
247 ****************************************************************/
249 /* Register this function to handle SOAP calls to method 'name'.
250 * 'req' and 'opt' are comma separated lists of argument names, in the order
251 * that they are to be delivered to the function.
252 * NULL will be passed for an opt argument if not given.
253 * Otherwise, the parameter is the xmlNode from the call.
255 static void soap_register(char *name
, SOAP_func func
, char *req
, char *opt
)
259 call
= g_new(SOAP_call
, 1);
261 call
->required_args
= req
? g_strsplit(req
, ",", 0) : NULL
;
262 call
->optional_args
= opt
? g_strsplit(opt
, ",", 0) : NULL
;
264 g_hash_table_insert(rpc_calls
, g_strdup(name
), call
);
267 /* Get the remote IPC window of the already-running filer if there
270 static GdkWindow
*get_existing_ipc_window(void)
272 Window xid
, xid_confirm
;
275 if (!get_ipc_property(GDK_ROOT_PARENT(), &xid
))
278 if (gdk_window_lookup(xid
))
279 return NULL
; /* Stale handle which we now own */
281 window
= gdk_window_foreign_new(xid
);
285 if (!get_ipc_property(window
, &xid_confirm
) || xid_confirm
!= xid
)
291 /* Returns the 'rox_atom' property of 'window' */
292 static gboolean
get_ipc_property(GdkWindow
*window
, Window
*r_xid
)
296 gboolean retval
= FALSE
;
298 if (gdk_property_get(window
, filer_atom
,
299 gdk_x11_xatom_to_atom(XA_WINDOW
), 0, 4,
300 FALSE
, NULL
, &format
, &length
, &data
) && data
)
302 if (format
== 32 && length
== 4)
305 *r_xid
= *((Window
*) data
);
313 static char *read_property(GdkWindow
*window
, GdkAtom prop
, gint
*out_length
)
315 gint grab_len
= 4096;
321 if (!(gdk_property_get(window
, prop
,
322 gdk_x11_xatom_to_atom(XA_STRING
), 0, grab_len
,
324 &length
, &data
) && data
))
325 return NULL
; /* Error? */
327 if (length
>= grab_len
)
329 /* Didn't get all of it - try again */
335 data
= g_realloc(data
, length
+ 1);
336 data
[length
] = '\0'; /* libxml seems to need this */
337 *out_length
= length
;
343 static gboolean
client_event(GtkWidget
*window
,
344 GdkEventClient
*event
,
347 GdkWindow
*src_window
;
354 if (event
->message_type
!= xsoap
)
357 src_window
= gdk_window_foreign_new(event
->data
.l
[0]);
358 g_return_val_if_fail(src_window
!= NULL
, FALSE
);
359 prop
= gdk_x11_xatom_to_atom(event
->data
.l
[1]);
361 data
= read_property(src_window
, prop
, &length
);
365 doc
= xmlParseMemory(g_strndup(data
, length
), length
);
368 reply
= run_soap(doc
);
369 if (number_of_windows
== 0)
376 /* Send reply back... */
380 xmlDocDumpMemory(reply
, &mem
, &size
);
381 g_return_val_if_fail(size
> 0, TRUE
);
383 gdk_property_change(src_window
, prop
,
384 gdk_x11_xatom_to_atom(XA_STRING
), 8,
385 GDK_PROP_MODE_REPLACE
, mem
, size
);
389 gdk_property_delete(src_window
, prop
);
394 /* Some handy functions for processing SOAP RPC arguments... */
396 /* Returns TRUE, FALSE, or -1 (if argument is unspecified) */
397 static int bool_value(xmlNode
*arg
)
405 optval
= xmlNodeGetContent(arg
);
406 answer
= text_to_boolean(optval
, -1); /* XXX: Report error? */
412 /* Returns the text of this arg as a string, or NULL if the (optional)
413 * argument wasn't supplied. Returns "" if the arg is empty.
414 * g_free() the result.
416 static char *string_value(xmlNode
*arg
)
423 retval
= xmlNodeGetContent(arg
);
425 return retval
? retval
: g_strdup("");
428 /* Return a list of strings, one for each child node of arg.
429 * g_list_free the list, and g_free each string.
431 static GList
*list_value(xmlNode
*arg
)
436 for (node
= arg
->xmlChildrenNode
; node
; node
= node
->next
)
438 if (node
->type
!= XML_ELEMENT_NODE
)
441 list
= g_list_append(list
, string_value(node
));
447 #define ARG(n) ((xmlNode *) g_list_nth(args, n)->data)
449 /* The RPC handlers all work in the same way -- they get passed a list of
450 * xmlNode arguments from the RPC call and they return the result node, or
451 * NULL if there isn't a result.
454 static xmlNodePtr
rpc_OpenDir(GList
*args
)
458 path
= string_value(ARG(0));
459 filer_opendir(path
, NULL
);
465 static xmlNodePtr
rpc_Run(GList
*args
)
469 path
= string_value(ARG(0));
476 static xmlNodePtr
rpc_CloseDir(GList
*args
)
480 path
= string_value(ARG(0));
481 filer_close_recursive(path
);
487 static xmlNodePtr
rpc_Examine(GList
*args
)
491 path
= string_value(ARG(0));
498 static xmlNodePtr
rpc_Show(GList
*args
)
502 dir
= string_value(ARG(0));
503 leaf
= string_value(ARG(1));
505 /* XXX: Seems silly to join them only to split again later... */
506 open_to_show(make_path(dir
, leaf
)->str
);
514 static xmlNodePtr
rpc_Pinboard(GList
*args
)
518 name
= string_value(ARG(0));
519 pinboard_activate(name
);
525 /* args = Side, [Name] */
526 static xmlNodePtr
rpc_Panel(GList
*args
)
530 side
= string_value(ARG(0));
531 name
= string_value(ARG(1));
533 if (strcmp(side
, "Top") == 0)
534 panel_new(name
, PANEL_TOP
);
535 else if (strcmp(side
, "Bottom") == 0)
536 panel_new(name
, PANEL_BOTTOM
);
537 else if (strcmp(side
, "Left") == 0)
538 panel_new(name
, PANEL_LEFT
);
539 else if (strcmp(side
, "Right") == 0)
540 panel_new(name
, PANEL_RIGHT
);
542 g_warning("Unknown panel side '%s'", side
);
550 static xmlNodePtr
rpc_Copy(GList
*args
)
557 from
= list_value(ARG(0));
558 to
= string_value(ARG(1));
559 leaf
= string_value(ARG(2));
560 quiet
= bool_value(ARG(3));
563 action_copy(from
, to
, leaf
, quiet
);
565 g_list_foreach(from
, (GFunc
) g_free
, NULL
);
573 static xmlNodePtr
rpc_Move(GList
*args
)
580 from
= list_value(ARG(0));
581 to
= string_value(ARG(1));
582 leaf
= string_value(ARG(2));
583 quiet
= bool_value(ARG(3));
586 action_move(from
, to
, leaf
, quiet
);
588 g_list_foreach(from
, (GFunc
) g_free
, NULL
);
596 static xmlNodePtr
rpc_Link(GList
*args
)
602 from
= list_value(ARG(0));
603 to
= string_value(ARG(1));
604 leaf
= string_value(ARG(2));
607 action_link(from
, to
, leaf
);
609 g_list_foreach(from
, (GFunc
) g_free
, NULL
);
617 static xmlNodePtr
rpc_FileType(GList
*args
)
623 path
= string_value(ARG(0));
624 type
= type_get_type(path
);
627 reply
= xmlNewNode(ARG(0)->ns
, "FileTypeReply");
628 tname
= g_strconcat(type
->media_type
, "/", type
->subtype
, NULL
);
629 xmlNewChild(reply
, reply
->ns
, "MIMEType", tname
);
635 static xmlNodePtr
rpc_Mount(GList
*args
)
640 paths
= list_value(ARG(0));
641 open_dir
= bool_value(ARG(1));
642 quiet
= bool_value(ARG(2));
648 action_mount(paths
, open_dir
, quiet
);
650 g_list_foreach(paths
, (GFunc
) g_free
, NULL
);
656 static void soap_done(GtkWidget
*widget
, GdkEventProperty
*event
, gpointer data
)
658 GdkAtom prop
= (GdkAtom
) data
;
660 if (prop
!= event
->atom
)
663 /* If we got a reply, display it here */
664 if (event
->state
== GDK_PROPERTY_NEW_VALUE
)
668 data
= read_property(event
->window
, event
->atom
, &length
);
677 gboolean
too_slow(gpointer data
)
679 g_warning("Existing ROX-Filer process is not responding! Try with -n");
685 /* Send the SOAP message in property 'prop' on 'from' to 'dest' */
686 static void soap_send(GtkWidget
*from
, GdkAtom prop
, GdkWindow
*dest
)
688 GdkEventClient event
;
690 event
.data
.l
[0] = GDK_WINDOW_XWINDOW(from
->window
);
691 event
.data
.l
[1] = gdk_x11_atom_to_xatom(prop
);
692 event
.data_format
= 32;
693 event
.message_type
= xsoap
;
695 gtk_widget_add_events(from
, GDK_PROPERTY_CHANGE_MASK
);
696 gtk_signal_connect(GTK_OBJECT(from
), "property-notify-event",
697 GTK_SIGNAL_FUNC(soap_done
),
698 GINT_TO_POINTER(prop
));
700 gdk_event_send_client_message((GdkEvent
*) &event
,
701 GDK_WINDOW_XWINDOW(dest
));
703 gtk_timeout_add(10000, too_slow
, NULL
);
708 /* Lookup this method in rpc_calls and invoke it.
709 * Returns the SOAP reply or fault, or NULL if this method
710 * doesn't return anything.
712 static xmlNodePtr
soap_invoke(xmlNode
*method
)
717 xmlNodePtr retval
= NULL
;
719 call
= g_hash_table_lookup(rpc_calls
, method
->name
);
722 g_warning("Unknown method '%s'", method
->name
);
726 if (call
->required_args
)
728 for (arg
= call
->required_args
; *arg
; arg
++)
732 node
= get_subnode(method
, ROX_NS
, *arg
);
735 g_warning("Missing required argument '%s' "
736 "in call to method '%s'", *arg
,
741 args
= g_list_append(args
, node
);
745 if (call
->optional_args
)
747 for (arg
= call
->optional_args
; *arg
; arg
++)
751 node
= get_subnode(method
, ROX_NS
, *arg
);
753 args
= g_list_append(args
, node
);
757 retval
= call
->func(args
);