1 /* $Id: xgserver.c,v 1.1 2000/11/06 06:42:20 sgt Exp $
2  * xgserver.c
3  * Copyright 2000 Steve Tell
4  * Copyright (C) 1998, 1999, 2000  Greg J. Badros and Maciej Stachowiak
5  *
6  * This file is based on fragments of scwm.c and events.c from
7  *  the SCWM window manager, by  Greg J. Badros and Maciej Stachowiak
8  *
9  * extracting only the scwmexec protocol, generalizing it, and adapting
10  * it to gdk was done by Steve Tell.
11  */
12 
13 #define XGNAME "GWAVE"
14 
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif
18 
19 #include <stdio.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/time.h>
23 #include <unistd.h>
24 #include <signal.h>
25 
26 #include <X11/Xlib.h>
27 #include <X11/Xatom.h>
28 #include <X11/Xutil.h>
29 #include <assert.h>
30 
31 #include <guile-gnome-gobject/gobject.h>
32 #include <gdk/gdk.h>
33 #include <gdk/gdkx.h>
34 
35 static GdkFilterReturn xg_gdk_filter(GdkXEvent *xevent,
36 			      GdkEvent *event,
37 			      gpointer data);
38 static void xg_cleanup();
39 extern char *remote_guile_eval(char *req, size_t *reslen,
40 			       char **outp, size_t *outlenp,
41 			       char **errp, size_t *errlenp);
42 
43 static void xg_handle_exec();
44 
45 
46 /**CONCEPT: SCWMEXEC Protocol
47   Scwm supports a protocol for other programs to send commands to the
48 window manager. Programs send ordinary configuration language
49 expressions and are returned a string representation of the return
50 value, and the output and error output generated, if any.
51 
52   For more information on how to make use of this protocol, see the
53 documentation for the scwmexec and scwmrepl programs, the scwm.el
54 emacs interaction mode, the libscwmexec library, and the details of
55 the SCWMEXEC protocol (as documented in
56 <filename>doc/scwmexec.proto</filename>).
57 FIXDOC: Link to file!
58 */
59 
60 /* w_for_scwmexec_response is the window that is used by scwmexec
61    protocol -- shutdown.c's Done function uses this too in case
62    scwmexec executes a quit, or causes a segfault (which cases
63    the HandleScwmExec function to not complete as it should) */
64 static Window w_for_exec_response;
65 
66 
67 static Atom XA_XGEXEC_LISTENER;
68 static Atom XA_XGEXEC_REQWIN;
69 static Atom XA_XGEXEC_REQUEST;
70 static Atom XA_XGEXEC_REPLY;
71 static Atom XA_XGEXEC_NOTIFY;
72 static Atom XA_XGEXEC_OUTPUT;
73 static Atom XA_XGEXEC_ERROR;
74 
75 static Display *Dpy;
76 static Window Root;
77 
78 /*
79 Reset the scwmexec protocol.\n\
80 This procedure removes the \"XA_SCWMEXEC_REQUEST\" property on the\n\
81 root window.  It should not be necessary but may be useful in case\n\
82 your X server goes awry (and otherwise you would have to restart your\n\
83 X server).  Use if scwmexec or scwmrepl are not returning (e.g.,\n\
84 if your Emacs hangs when you try evaluating a scwm expression).")
85 */
86 void
xg_reset_protocol()87 xg_reset_protocol()
88 {
89 	XDeleteProperty(Dpy, Root, XA_XGEXEC_REQUEST);
90 }
91 
92 void
xg_init(Display * display)93 xg_init(Display *display)
94 {
95 	XWindowAttributes xwa;
96 	int screen;
97 	GdkWindow *gdk_rootwindow;
98 	long old_event_mask;
99 
100 	if(display == NULL)
101 		Dpy = GDK_DISPLAY();
102 	else
103 		Dpy = display;
104 
105 	screen = XDefaultScreen(Dpy);
106 	Root = XRootWindow(Dpy, screen);
107 
108 	XA_XGEXEC_LISTENER=XInternAtom(Dpy, XGNAME "EXEC_LISTENER", False);
109 	XA_XGEXEC_REQWIN=XInternAtom(Dpy, XGNAME "EXEC_REQWIN", False);
110 	XA_XGEXEC_REQUEST=XInternAtom(Dpy, XGNAME "EXEC_REQUEST", False);
111 	XA_XGEXEC_REPLY=XInternAtom(Dpy,  XGNAME "EXEC_REPLY", False);
112 	XA_XGEXEC_NOTIFY=XInternAtom(Dpy, XGNAME "EXEC_NOTIFY", False);
113 	XA_XGEXEC_OUTPUT=XInternAtom(Dpy, XGNAME "EXEC_OUTPUT", False);
114 	XA_XGEXEC_ERROR=XInternAtom(Dpy, XGNAME "EXEC_ERROR", False);
115 
116 /* if the XA_XGEXEC_REQWIN window is already set at
117    startup, the first scwm-exec protocol request will cause
118    lots of X errors */
119 	XDeleteProperty(Dpy,Root,XA_XGEXEC_REQWIN);
120 
121 	g_atexit(xg_cleanup);
122 
123 	/* Initialize scwmexec response window (used in shutdown.c's Done,
124 	   as well as by HandleScwmExec) */
125 	w_for_exec_response = None;
126 	/* Announce support for scwmexec protocol. */
127 	XChangeProperty(Dpy, Root,
128 			XA_XGEXEC_LISTENER, XA_STRING,
129 			8, PropModeReplace, (unsigned char *) XGNAME "exec",
130 			4+strlen(XGNAME) );
131 
132 /*	printf("xg_init root=%d LISTENER atom=%d\n", Root, XA_XGEXEC_LISTENER);
133  */
134 
135 
136 	XGetWindowAttributes (gdk_display, Root, &xwa);
137 	old_event_mask = xwa.your_event_mask;
138 	XSelectInput (Dpy, Root, old_event_mask | PropertyChangeMask);
139 
140 	/* connect to gdk's XEvent handler using its event-filter mechanism */
141 	gdk_rootwindow = gdk_window_lookup(Root);
142 	gdk_window_add_filter(gdk_rootwindow, xg_gdk_filter, NULL);
143 }
144 
145 /* gdk event filter to hook into low-level gdk event handling
146  */
147 static GdkFilterReturn
xg_gdk_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)148 xg_gdk_filter(GdkXEvent *xevent, GdkEvent *event, gpointer data)
149 {
150 	XEvent *ev = (XEvent *)xevent;
151 /*	printf("in xg_gdk_filter data=%x type=%d\n", data, ev->type); */
152 
153 	if(ev->type == PropertyNotify
154 	   && ev->xproperty.atom == XA_XGEXEC_REQWIN) {
155 		xg_handle_exec();
156 		return GDK_FILTER_REMOVE;
157 	} else
158 		return GDK_FILTER_CONTINUE;
159 }
160 
161 static void
xg_cleanup()162 xg_cleanup()
163 {
164 	XDeleteProperty(Dpy, Root, XA_XGEXEC_LISTENER);
165 
166 	if (None != w_for_exec_response) {
167 		/* give a response to libscwmexec in case
168 		   we were in the middle of
169 		   an xgexec when we quit or segfaulted */
170 		XChangeProperty(Dpy, w_for_exec_response,
171 				XA_XGEXEC_OUTPUT, XA_STRING,
172 				8, PropModeReplace, "", 0);
173 		XChangeProperty(Dpy, w_for_exec_response,
174 				XA_XGEXEC_ERROR, XA_STRING,
175 				8, PropModeReplace, "", 0);
176 		XChangeProperty(Dpy, w_for_exec_response,
177 				XA_XGEXEC_REPLY, XA_STRING,
178 				8, PropModeReplace, "", 0);
179 	}
180 }
181 
182 /* implement the X-property-driven remote-exec protocol.
183  * gets called on PropertyNotify events.
184  */
185 static void
xg_handle_exec()186 xg_handle_exec()
187 {
188 	Window w;
189 	Window *pw;
190 	Atom type_ret;
191 	int form_ret;
192 	unsigned char *ret, *output, *error;
193 	size_t reslen, outlen, errlen;
194 
195 	unsigned long nitems;
196 	unsigned long bytes_after;
197 	unsigned char *req;
198 	unsigned long last_offset=0;
199 	unsigned long saved_bytes_after=0;
200 
201 	/* The XGEXEC_REQWIN property is treated as a queue of window IDs
202 	   from which the request will be read. There may be more than one
203 	   (or fewer than one in some cases) by the time we get here. We
204 	   will loop and keep reading until we have snarfed the whole
205 	   property, to make sure we can safely delete it.
206 
207 	   See also the doc/scwmexec.proto file for a high-level
208 	   description of this protocol.
209 	*/
210 	do {
211 		/* Read a single request window from the queue. */
212 		if (XGetWindowProperty(Dpy, Root, XA_XGEXEC_REQWIN,
213 				       last_offset, 1, True, AnyPropertyType,
214 				       &type_ret, &form_ret, &nitems, &bytes_after,
215 				       (unsigned char **) &pw)==Success && pw != NULL) {
216 			/* This is the window we want to look at: */
217 			w = *pw;
218 			XFree(pw);
219 			/* Increment the offset at which to read within the property. It
220 			   will not get deleted until we read the very last bytes at the
221 			   end. */
222 			last_offset += nitems * (form_ret/8);
223 			/* Save an indication of whether we need to read more or not. */
224 			saved_bytes_after=bytes_after;
225 
226 /*      DBUG((DBG,FUNC_NAME,"Trying to get request from %ld",w)); */
227 
228 			/* Get and delete its XGEXEC_REQUEST property. We do
229 			   XGetWindowProperty twice, once to get the length, and again
230 			   to read the whole length's worth. */
231 			if (XGetWindowProperty(Dpy, w,
232 					       XA_XGEXEC_REQUEST,
233 					       0, 0, False, XA_STRING,
234 					       &type_ret, &form_ret, &nitems, &bytes_after,
235 					       &req)==Success &&
236 			    XGetWindowProperty(Dpy, w,
237 					       XA_XGEXEC_REQUEST,
238 					       0, (bytes_after / 4) +
239 					       (bytes_after % 4 ? 1 : 0), True, XA_STRING,
240 					       &type_ret, &form_ret, &nitems, &bytes_after,
241 					       &req)==Success) {
242 
243 				/* before we eval the request, record the window to respond
244 				   in a global, so Done can respond if necessary (in case
245 				   the eval-d expression calls `quit' or seg faults, etc.)
246 				   TODO: implement this scwm behavior here.
247 				*/
248 				w_for_exec_response = w;
249 
250 				ret = remote_guile_eval(req,  &reslen,
251 							(char **)&output, &outlen,
252 							(char **)&error, &errlen);
253 				XFree(req);
254 
255 				/* Set the output, error and reply properties appropriately. */
256 				XChangeProperty(Dpy, w_for_exec_response,
257 						XA_XGEXEC_OUTPUT, XA_STRING,
258 						8, PropModeReplace, output,
259 						(long)outlen);
260 				XChangeProperty(Dpy, w_for_exec_response,
261 						XA_XGEXEC_ERROR, XA_STRING,
262 						8, PropModeReplace, error,
263 						(long)errlen);
264 				XChangeProperty(Dpy, w_for_exec_response,
265 						XA_XGEXEC_REPLY, XA_STRING,
266 						8, PropModeReplace, ret,
267 						(long)reslen);
268 
269 				/* Since we successfully reset the reply properties,
270 				   shutdown.c's Done no longer needs to, so reset
271 				   the global */
272 				w_for_exec_response = None;
273 
274 				free(ret);
275 				free(output);
276 				free(error);
277 			} else {
278 				fprintf(stderr, "Cannot get XA_%sEXEC_REQUEST atom from window %ld",
279 					XGNAME, w_for_exec_response);
280 			}
281 		} else {
282 			/* XGetWindowProperty returned False */
283 /*      DBUG((WARN,FUNC_NAME,"Done with last window in list of scwmexec requests"));*/
284 			saved_bytes_after = 0;
285 			last_offset = 0;
286 		}
287 	} while (saved_bytes_after != 0);
288 	/* Repeat until we get a saved_bytes_after of 0 on reading XGEXEC_REQWIN,
289 	   indicating that we read it all and it was deleted. It may well have
290 	   been re-created before we exit, but that doesn't matter because we'll
291 	   get a PropertyNotify and re-enter, but the offset to use will correctly
292 	   be 0. */
293 
294 	return;
295 }
296 
297 
298