r947: Added new SOAP methods Copy, Move, Link, Mount and FileType (Stephen Watson).
[rox-filer.git] / ROX-Filer / src / remote.c
blob102a21f6933e844d20188b719e2bd9032ee14c9f
1 /*
2 * $Id$
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)
10 * any later version.
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
15 * more details.
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.
28 #include "config.h"
30 #include <string.h>
32 #include <gdk/gdkx.h>
33 #include <X11/X.h>
34 #include <X11/Xatom.h>
35 #include <gtk/gtk.h>
36 #include <gtk/gtkinvisible.h>
37 #include <parser.h>
39 #include "global.h"
41 #include "main.h"
42 #include "support.h"
43 #include "gui_support.h"
44 #include "run.h"
45 #include "remote.h"
46 #include "filer.h"
47 #include "pinboard.h"
48 #include "panel.h"
49 #include "action.h"
50 #include "type.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);
58 struct _SOAP_call {
59 SOAP_func func;
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,
72 gpointer data);
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)
99 guchar *unique_id;
100 GdkWindow *existing_ipc_window;
101 GtkWidget *ipc_window;
102 Window xwindow;
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);
131 g_free(unique_id);
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)
147 xmlChar *mem;
148 int size;
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);
156 g_free(mem);
158 soap_send(ipc_window, xsoap, existing_ipc_window);
160 return TRUE;
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
167 * is an IPC window.
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);
184 return FALSE;
187 /* Executes the RPC call(s) in the given SOAP message and returns
188 * the reply.
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)
200 number_of_windows++;
202 node = xmlDocGetRootElement(soap);
203 if (!node->ns || strcmp(node->ns->href, SOAP_ENV_NS) != 0)
204 goto bad_soap;
206 body = get_subnode(node, SOAP_ENV_NS, "Body");
207 if (!body)
208 goto bad_soap;
210 for (node = body->xmlChildrenNode; node; node = node->next)
212 if (node->type != XML_ELEMENT_NODE)
213 continue;
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)");
220 continue;
223 reply = soap_invoke(node);
225 if (reply)
227 if (!rep_doc)
228 rep_doc = soap_new(&rep_body);
229 xmlAddChild(rep_body, reply);
233 goto out;
235 bad_soap:
236 g_warning("Bad SOAP message received!");
238 out:
239 number_of_windows--;
241 return rep_doc;
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)
257 SOAP_call *call;
259 call = g_new(SOAP_call, 1);
260 call->func = func;
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
268 * is one.
270 static GdkWindow *get_existing_ipc_window(void)
272 Window xid, xid_confirm;
273 GdkWindow *window;
275 if (!get_ipc_property(GDK_ROOT_PARENT(), &xid))
276 return NULL;
278 if (gdk_window_lookup(xid))
279 return NULL; /* Stale handle which we now own */
281 window = gdk_window_foreign_new(xid);
282 if (!window)
283 return NULL;
285 if (!get_ipc_property(window, &xid_confirm) || xid_confirm != xid)
286 return NULL;
288 return window;
291 /* Returns the 'rox_atom' property of 'window' */
292 static gboolean get_ipc_property(GdkWindow *window, Window *r_xid)
294 guchar *data;
295 gint format, length;
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)
304 retval = TRUE;
305 *r_xid = *((Window *) data);
307 g_free(data);
310 return retval;
313 static char *read_property(GdkWindow *window, GdkAtom prop, gint *out_length)
315 gint grab_len = 4096;
316 gint length;
317 guchar *data;
319 while (1)
321 if (!(gdk_property_get(window, prop,
322 gdk_x11_xatom_to_atom(XA_STRING), 0, grab_len,
323 FALSE, NULL, NULL,
324 &length, &data) && data))
325 return NULL; /* Error? */
327 if (length >= grab_len)
329 /* Didn't get all of it - try again */
330 grab_len <<= 1;
331 g_free(data);
332 continue;
335 data = g_realloc(data, length + 1);
336 data[length] = '\0'; /* libxml seems to need this */
337 *out_length = length;
339 return data;
343 static gboolean client_event(GtkWidget *window,
344 GdkEventClient *event,
345 gpointer user_data)
347 GdkWindow *src_window;
348 GdkAtom prop;
349 xmlDocPtr reply;
350 guchar *data;
351 gint length;
352 xmlDocPtr doc;
354 if (event->message_type != xsoap)
355 return FALSE;
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);
362 if (!data)
363 return TRUE;
365 doc = xmlParseMemory(g_strndup(data, length), length);
366 g_free(data);
368 reply = run_soap(doc);
369 if (number_of_windows == 0)
370 gtk_main_quit();
372 xmlFreeDoc(doc);
374 if (reply)
376 /* Send reply back... */
377 xmlChar *mem;
378 int size;
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);
386 g_free(mem);
388 else
389 gdk_property_delete(src_window, prop);
391 return TRUE;
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)
399 int answer;
400 char *optval;
402 if (!arg)
403 return -1;
405 optval = xmlNodeGetContent(arg);
406 answer = text_to_boolean(optval, -1); /* XXX: Report error? */
407 g_free(optval);
409 return answer;
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)
418 char *retval;
420 if (!arg)
421 return NULL;
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)
433 GList *list = NULL;
434 xmlNode *node;
436 for (node = arg->xmlChildrenNode; node; node = node->next)
438 if (node->type != XML_ELEMENT_NODE)
439 continue;
441 list = g_list_append(list, string_value(node));
444 return list;
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)
456 char *path;
458 path = string_value(ARG(0));
459 filer_opendir(path, NULL);
460 g_free(path);
462 return NULL;
465 static xmlNodePtr rpc_Run(GList *args)
467 char *path;
469 path = string_value(ARG(0));
470 run_by_path(path);
471 g_free(path);
473 return NULL;
476 static xmlNodePtr rpc_CloseDir(GList *args)
478 char *path;
480 path = string_value(ARG(0));
481 filer_close_recursive(path);
482 g_free(path);
484 return NULL;
487 static xmlNodePtr rpc_Examine(GList *args)
489 char *path;
491 path = string_value(ARG(0));
492 examine(path);
493 g_free(path);
495 return NULL;
498 static xmlNodePtr rpc_Show(GList *args)
500 char *dir, *leaf;
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);
508 g_free(dir);
509 g_free(leaf);
511 return NULL;
514 static xmlNodePtr rpc_Pinboard(GList *args)
516 char *name = NULL;
518 name = string_value(ARG(0));
519 pinboard_activate(name);
520 g_free(name);
522 return NULL;
525 /* args = Side, [Name] */
526 static xmlNodePtr rpc_Panel(GList *args)
528 char *name, *side;
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);
541 else
542 g_warning("Unknown panel side '%s'", side);
544 g_free(side);
545 g_free(name);
547 return NULL;
550 static xmlNodePtr rpc_Copy(GList *args)
552 GList *from;
553 char *to;
554 char *leaf;
555 int quiet;
557 from = list_value(ARG(0));
558 to = string_value(ARG(1));
559 leaf = string_value(ARG(2));
560 quiet = bool_value(ARG(3));
562 if (from)
563 action_copy(from, to, leaf, quiet);
565 g_list_foreach(from, (GFunc) g_free, NULL);
566 g_list_free(from);
567 g_free(to);
568 g_free(leaf);
570 return NULL;
573 static xmlNodePtr rpc_Move(GList *args)
575 GList *from;
576 char *to;
577 char *leaf;
578 int quiet;
580 from = list_value(ARG(0));
581 to = string_value(ARG(1));
582 leaf = string_value(ARG(2));
583 quiet = bool_value(ARG(3));
585 if (from)
586 action_move(from, to, leaf, quiet);
588 g_list_foreach(from, (GFunc) g_free, NULL);
589 g_list_free(from);
590 g_free(to);
591 g_free(leaf);
593 return NULL;
596 static xmlNodePtr rpc_Link(GList *args)
598 GList *from;
599 char *to;
600 char *leaf;
602 from = list_value(ARG(0));
603 to = string_value(ARG(1));
604 leaf = string_value(ARG(2));
606 if (from)
607 action_link(from, to, leaf);
609 g_list_foreach(from, (GFunc) g_free, NULL);
610 g_list_free(from);
611 g_free(to);
612 g_free(leaf);
614 return NULL;
617 static xmlNodePtr rpc_FileType(GList *args)
619 MIME_type *type;
620 char *path, *tname;
621 xmlNodePtr reply;
623 path = string_value(ARG(0));
624 type = type_get_type(path);
625 g_free(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);
630 g_free(tname);
632 return reply;
635 static xmlNodePtr rpc_Mount(GList *args)
637 GList *paths;
638 int open_dir, quiet;
640 paths = list_value(ARG(0));
641 open_dir = bool_value(ARG(1));
642 quiet = bool_value(ARG(2));
644 if (open_dir == -1)
645 open_dir = TRUE;
647 if (paths)
648 action_mount(paths, open_dir, quiet);
650 g_list_foreach(paths, (GFunc) g_free, NULL);
651 g_list_free(paths);
653 return NULL;
656 static void soap_done(GtkWidget *widget, GdkEventProperty *event, gpointer data)
658 GdkAtom prop = (GdkAtom) data;
660 if (prop != event->atom)
661 return;
663 /* If we got a reply, display it here */
664 if (event->state == GDK_PROPERTY_NEW_VALUE)
666 gint length;
668 data = read_property(event->window, event->atom, &length);
670 if (data)
671 puts(data);
674 gtk_main_quit();
677 gboolean too_slow(gpointer data)
679 g_warning("Existing ROX-Filer process is not responding! Try with -n");
680 gtk_main_quit();
682 return 0;
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);
705 gtk_main();
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)
714 GList *args = NULL;
715 SOAP_call *call;
716 gchar **arg;
717 xmlNodePtr retval = NULL;
719 call = g_hash_table_lookup(rpc_calls, method->name);
720 if (!call)
722 g_warning("Unknown method '%s'", method->name);
723 return NULL;
726 if (call->required_args)
728 for (arg = call->required_args; *arg; arg++)
730 xmlNode *node;
732 node = get_subnode(method, ROX_NS, *arg);
733 if (!node)
735 g_warning("Missing required argument '%s' "
736 "in call to method '%s'", *arg,
737 method->name);
738 goto out;
741 args = g_list_append(args, node);
745 if (call->optional_args)
747 for (arg = call->optional_args; *arg; arg++)
749 xmlNode *node;
751 node = get_subnode(method, ROX_NS, *arg);
753 args = g_list_append(args, node);
757 retval = call->func(args);
758 out:
759 g_list_free(args);
761 return retval;