1/*The Panache Window Manager*/
2/*By George Peter Staplin*/
3/*Please read the LICENSE file included with the Panache distribution
4 *for usage restrictions.
5 */
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <signal.h>
10#ifndef __STDC__
11	#include <malloc.h>
12#endif
13#include <X11/Xlib.h>
14#include <X11/Xutil.h>
15#include <X11/Xos.h>
16#include <X11/Xatom.h>
17#include <X11/keysym.h>
18#include <X11/cursorfont.h>
19#include <tcl.h>
20#include "PanacheWindowList.h"
21
22/*Style
23I use if (returnFromFunc == 1) instead of if (returnFromFunc)
24I use if (returnFromFunc == 0) instead of if (!returnFromFunc)
25*/
26
27/*Automatic focus of new windows yes/no.*/
28/*Automatic focus of transient windows yes/no.*/
29
30#define PANACHE_DIRECTORY "Panache"
31#define CMD_ARGS (ClientData clientData, Tcl_Interp *interp, \
32int objc, Tcl_Obj *CONST objv[])
33
34Display *dis;
35XEvent report;
36Window root;
37Tcl_Interp *interp;
38int distance_from_edge = 0;
39Window mapped_window = None;
40int screen;
41Atom _XA_WM_STATE;
42Atom _XA_WM_PROTOCOLS;
43Atom _XA_WM_DELETE_WINDOW;
44Window workspace_manager;
45struct CList *keepAboveWindowList;
46unsigned long eventMask = (ResizeRedirectMask | PropertyChangeMask | \
47	EnterWindowMask | LeaveWindowMask | FocusChangeMask | KeyPressMask);
48
49#define winIdLength 14
50/*#define FORK_ON_START*/
51
52int PanacheGetWMState (Window win);
53void PanacheSelectInputForRootParented (Window win);
54void PanacheConfigureNormalWindow (Window win, unsigned long value_mask);
55
56char Panache_Init_script[] = {
57	"if {[file exists $prefix/$panacheDirectory/Panache.tcl] != 1} {\n"
58	"	puts stderr  {unable to open Panache.tcl  Did you run make install?}\n"
59	"	puts stderr \"I looked in $prefix/$panacheDirectory\"\n"
60	"	exit -1\n"
61	"}\n"
62	"proc sendToPipe str {\n"
63	"	set str [string map {\"\n\" \"\" \"\r\" \"\"} $str]\n"
64	"	puts $::pipe $str\n"
65	"	flush $::pipe\n"
66	"}\n"
67	"proc getFromPipe {} {\n"
68	"	gets $::pipe line\n"
69	"	if {$line != \"\"} {\n"
70	"		set cmd [lindex $line 0]\n"
71	"		if {[llength $line] == 2} {\n"
72	"			$cmd [lindex $line 1]\n"
73	"		} else {\n"
74	"			eval $line\n"
75	"		}\n"
76	"	}\n"
77	"}\n"
78	"set ::pipe [open \"|$wishInterpreter $prefix/$panacheDirectory/Panache.tcl\" w+]\n"
79	"fconfigure $::pipe -blocking 0\n"
80	"\n"};
81
82
83char *charMalloc (int size) {
84	char *mem = NULL;
85
86	mem = (char *)	malloc ((sizeof (char)) * size);
87
88	if (mem == NULL) {
89		fprintf (stderr, "malloc failed to allocate memory  This means that Panache \
90and other applications could have problems if they continue running.\n\n  \
91exiting Panache now!");
92		exit (-1);
93	}
94
95	return mem;
96}
97
98
99void sendConfigureNotify (Window win, unsigned long value_mask, XWindowChanges *winChanges) {
100	XEvent xe;
101	XWindowAttributes wattr;
102
103	if (XGetWindowAttributes (dis, win, &wattr) == 0) {
104		return;
105	}
106
107	xe.type = ConfigureNotify;
108	xe.xconfigure.type = ConfigureNotify;
109	xe.xconfigure.event = win;
110	xe.xconfigure.window = win;
111
112
113	xe.xconfigure.x = (value_mask & CWX) ? winChanges->x : wattr.x;
114	xe.xconfigure.y = (value_mask & CWY) ? winChanges->y : wattr.y;
115	xe.xconfigure.width = (value_mask & CWWidth) ? winChanges->width : wattr.width;
116	xe.xconfigure.height = (value_mask & CWHeight) ? winChanges->height : wattr.height;
117
118	xe.xconfigure.border_width = 0;
119	xe.xconfigure.above = None;
120	xe.xconfigure.override_redirect = 0;
121
122	XSendEvent (dis, win, 0, StructureNotifyMask, &xe);
123
124	XFlush (dis);
125}
126
127
128void sendMapNotify (Window win) {
129	XEvent mapNotify;
130
131	mapNotify.type = MapNotify;
132	mapNotify.xmap.type = MapNotify;
133	mapNotify.xmap.window = win;
134	mapNotify.xmap.display = dis;
135	mapNotify.xmap.event = win;
136	XSendEvent (dis, win, 0, StructureNotifyMask, &mapNotify);
137	XFlush (dis);
138}
139
140
141int PanacheAddAllWindowsCmd CMD_ARGS {
142	Window dummy;
143	Window *children = NULL;
144	unsigned int nchildren;
145	unsigned int i;
146	char *winId;
147	char *transientForWinId;
148	char str[] = "sendToPipe [list add [list $winTitle] $winId $winType $transientForWinId]";
149	Window twin;
150
151	XSync (dis, 0);
152	/*XGrabServer (dis);*/
153
154	if (XQueryTree (dis,
155			root,
156			&dummy,
157			&dummy,
158			&children,
159			&nchildren) == 0) {
160
161		fprintf (stderr, "Error querying the tree for the root window.\n");
162	}
163
164	for (i = 0; i < nchildren; i++) {
165		XTextProperty xtp;
166		XWMHints *wmHints = XGetWMHints (dis, children[i]);
167		XWindowAttributes wattr;
168
169		xtp.value = NULL;
170
171		if (wmHints == NULL) {
172			continue;
173		}
174
175		if (wmHints->flags & IconWindowHint) {
176			continue;
177		}
178
179		if (XGetWindowAttributes (dis, children[i], &wattr) == 0) {
180			continue;
181		}
182
183		if (wattr.override_redirect == 1) {
184			continue;
185		}
186
187		if (wmHints->flags & StateHint) {
188			if (wmHints->initial_state & WithdrawnState) {
189				continue;
190			} else if (wattr.map_state == 0 && PanacheGetWMState (children[i]) == 0) {
191				continue;
192			}
193		}
194
195		XFree (wmHints);
196
197		XGetWMName (dis, children[i], &xtp);
198
199		winId = charMalloc (winIdLength);
200
201		sprintf (winId, "%ld", children[i]);
202		Tcl_SetVar (interp, "winTitle", (char *) xtp.value, 0);
203		Tcl_SetVar (interp, "winId", winId, 0);
204
205		if (XGetTransientForHint (dis, children[i], &twin) == 1) {
206			Tcl_SetVar (interp, "winType", "transient", 0);
207
208			transientForWinId =  charMalloc (winIdLength);
209			sprintf (transientForWinId, "%ld", twin);
210			Tcl_SetVar (interp, "transientForWinId", transientForWinId, 0);
211			free (transientForWinId);
212
213			PanacheSelectInputForRootParented (children[i]);
214
215		} else {
216			Tcl_SetVar (interp, "winType", "normal", 0);
217			Tcl_SetVar (interp, "transientForWinId", "", 0);
218
219			/*Maybe I should compare the first char and then do strcmp?*/
220			if (xtp.value != NULL && strcmp ((char *)xtp.value, "___Panache_GUI") != 0) {
221				PanacheConfigureNormalWindow (children[i], CWX|CWY|CWWidth|CWHeight);
222				PanacheSelectInputForRootParented (children[i]);
223			}
224		}
225
226		XFree (xtp.value);
227		free (winId);
228
229		if (Tcl_Eval (interp, str) != TCL_OK) {
230			fprintf (stderr, "Error in PanacheAddAllWindowsCmd: %s\n", Tcl_GetStringResult (interp));
231		}
232	}
233
234	if (children != NULL) {
235		XFree (children);
236	}
237
238	/*XUngrabServer (dis);*/
239	XSync (dis, 0);
240
241	return TCL_OK;
242}
243
244
245void PanacheConfigureRequest (XConfigureRequestEvent *event) {
246	XWindowChanges wc;
247	Window twin;
248	int maxWidth;
249	int maxHeight;
250
251	if (event->parent != root) {
252		return;
253	}
254
255#ifdef DEBUG
256	fprintf (stderr, "ConfigureRequest win %ld\n", event->window);
257	fprintf (stderr, "CWSibling %d\n", (event->value_mask & CWSibling) == 1);
258	fprintf (stderr, "CWStackMode %d\n", (event->value_mask & CWStackMode) == 1);
259#endif
260
261	maxWidth = (DisplayWidth (dis, screen) - distance_from_edge - 4);
262	maxHeight = DisplayHeight (dis, screen);
263
264	wc.border_width = 0;
265	wc.sibling = None;
266	wc.stack_mode = Above;
267
268	if (event->window == workspace_manager) {
269		wc.width = distance_from_edge;
270		wc.height = maxHeight;
271		wc.x = 0;
272		wc.y = 0;
273		XConfigureWindow(dis, event->window, CWX|CWY|CWWidth|CWHeight, &wc);
274		sendConfigureNotify (event->window, CWX|CWY|CWWidth|CWHeight, &wc);
275		return;
276	} else {
277		PanacheSelectInputForRootParented (event->window);
278	}
279
280	if (XGetTransientForHint (dis, event->window, &twin) == 1) {
281		if (event->width > maxWidth) {
282			wc.width = maxWidth;
283		} else {
284			wc.width = event->width;
285		}
286
287		wc.height = event->height;
288
289		if (event->x < distance_from_edge) {
290			wc.x = distance_from_edge;
291		} else {
292			wc.x = event->x;
293		}
294
295		wc.y = event->y;
296		XConfigureWindow (dis, event->window, event->value_mask, &wc);
297		sendConfigureNotify (event->window, event->value_mask, &wc);
298	} else {
299		PanacheConfigureNormalWindow (event->window, event->value_mask);
300	}
301
302	XFlush (dis);
303}
304
305
306/*This configures the window and sends a ConfigureNotify event.
307 *It's designed for normal non-transient windows
308 */
309void PanacheConfigureNormalWindow (
310	Window win, unsigned long value_mask)
311{
312	XWindowChanges wc;
313	XSizeHints sizeHints;
314	long ljunk = 0;
315	int maxWidth = (DisplayWidth (dis, screen) - distance_from_edge - 4);
316	int maxHeight = DisplayHeight (dis, screen);
317
318	wc.border_width = 0;
319	wc.sibling = None;
320	wc.stack_mode = Above;
321
322	wc.x = distance_from_edge;
323	wc.y = 0;
324	wc.width = maxWidth;
325	wc.height = maxHeight;
326
327	if (XGetWMNormalHints (dis, win, &sizeHints, &ljunk)) {
328		if (sizeHints.flags & PMaxSize) {
329			wc.width = (sizeHints.max_width > maxWidth) ? maxWidth : sizeHints.max_width;
330			wc.height = (sizeHints.max_height > maxHeight) ? maxHeight : sizeHints.max_height;
331#ifdef DEBUG
332			fprintf (stderr, "MaxSize %d %d\n", sizeHints.max_width, sizeHints.max_height);
333#endif
334		}
335#ifdef DEBUG
336		if (sizeHints.flags & PResizeInc) {
337			fprintf (stderr, "PResizeInc\n");
338			fprintf (stderr, "incr %d %d\n", sizeHints.width_inc, sizeHints.height_inc);
339		}
340		if (sizeHints.flags & PAspect) {
341			fprintf (stderr, "PAspect x %d\n", sizeHints.min_aspect.x);
342		}
343#endif
344	}
345
346	XConfigureWindow (dis, win, value_mask, &wc);
347	sendConfigureNotify (win, value_mask, &wc);
348}
349
350
351/*This appends windows that are not to be managed by
352 *Panache to a list, and Panache will later on raise
353 *them above other windows.
354 */
355void PanacheCreateNotify (XCreateWindowEvent *event) {
356
357	if (event->override_redirect == 0 || event->parent != root) {
358		return;
359	}
360
361	CListAppend (keepAboveWindowList, event->window);
362}
363
364/*X has told Panache that a DestroyNotify event occured
365 *to a child of the root window, so Panache removes the
366 *window from the window list.
367 */
368void PanacheDestroyNotify (XDestroyWindowEvent *event) {
369	Window win;
370	char *winId;
371	char str[] = "sendToPipe [list remove $winId]";
372
373	win = event->window;
374
375	winId = charMalloc (winIdLength);
376	sprintf (winId, "%ld", win);
377
378	Tcl_SetVar (interp, "winId", winId, 0);
379	free (winId);
380
381#ifdef DEBUG
382	fprintf (stderr, "DestroyNotify\n");
383#endif
384
385	CListRemove (keepAboveWindowList, event->window);
386
387	/*Tell Panache_GUI to remove the window*/
388	if (Tcl_Eval (interp, str) != TCL_OK) {
389		fprintf (stderr, "Tcl_Eval error in PanacheDestroyNotify %s\n", Tcl_GetStringResult (interp));
390	}
391}
392
393
394/*Panache_GUI calls this to send WM_DELETE_WINDOW or
395 *invoke XKillClient (if the window doesn't support
396 *WM_DELETE_WINDOW).  We can't use XKillClient on all
397 *windows, because if the application has multiple
398 *toplevel windows sending XKillClient would destroy
399 *them all.
400 */
401int PanacheDestroyCmd CMD_ARGS {
402	XClientMessageEvent ev;
403	Window win;
404	Atom *wmProtocols = NULL;
405	Atom *protocol;
406	int i;
407	int numAtoms;
408	int handlesWM_DELETE_WINDOW = 0;
409
410
411	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
412
413	if (XGetWMProtocols (dis, win, &wmProtocols, &numAtoms) == 1) {
414		for (i = 0, protocol = wmProtocols; i < numAtoms; i++, protocol++) {
415			if (*protocol == (Atom)_XA_WM_DELETE_WINDOW) {
416				handlesWM_DELETE_WINDOW = 1;
417			}
418		}
419		if (wmProtocols) {
420			XFree (wmProtocols);
421		}
422	}
423
424	if (handlesWM_DELETE_WINDOW == 1) {
425		ev.type = ClientMessage;
426		ev.window = win;
427		ev.message_type = _XA_WM_PROTOCOLS;
428		ev.format = 32;
429		ev.data.l[0] = _XA_WM_DELETE_WINDOW;
430		ev.data.l[1] = CurrentTime;
431		XSendEvent (dis, win, 0, 0L, (XEvent *) &ev);
432	} else {
433		XKillClient (dis, win);
434	}
435
436	XFlush (dis);
437
438	return TCL_OK;
439}
440
441
442int PanacheDFECmd CMD_ARGS {
443	Tcl_GetIntFromObj (interp, objv[1], &distance_from_edge);
444	return TCL_OK;
445}
446
447
448/*Panache_GUI sends focus $winId to get here.*/
449int PanacheFocusCmd CMD_ARGS {
450	Window win;
451
452	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
453
454	if (XSetInputFocus (dis, win, RevertToParent, CurrentTime) != 1) {
455		fprintf (stderr, "XSetInputFocus failure within PanacheFocusCmd()");
456	}
457
458	XFlush (dis);
459
460	return TCL_OK;
461}
462
463
464int PanacheGetWMState (Window win) {
465	int returnValue = 0;
466	Atom type;
467	int ijunk;
468	unsigned long ljunk;
469	unsigned long *state = NULL;
470
471	XGetWindowProperty (
472		dis,
473		win,
474		_XA_WM_STATE,
475		0L,
476		1L,
477		0,
478		_XA_WM_STATE,
479		&type,
480		&ijunk,
481		&ljunk,
482		&ljunk,
483		(unsigned char **) &state
484	);
485
486	if (type == _XA_WM_STATE) {
487 		returnValue = (int) *state;
488	} else {
489		/*Don't know what to do*/
490		returnValue = 0;
491	}
492
493	if (state != NULL) {
494		XFree (state);
495	}
496
497	return returnValue;
498}
499
500/*A window to keep above has the override_redirect
501 *attribute set to 1.
502 */
503
504void PanacheRaiseKeepAboveWindows () {
505	Window win;
506
507	CListRewind (keepAboveWindowList);
508
509	while ((win = CListGet (keepAboveWindowList)) != NULL) {
510		XRaiseWindow (dis, win);
511	}
512
513	XFlush (dis);
514}
515
516
517void PanacheRecursivelyGrabKey (Window win, int keycode) {
518	Window dummy;
519	Window *children = NULL;
520	unsigned int nchildren;
521	int i;
522
523
524	if (XQueryTree (dis, win, &dummy, &dummy, &children, &nchildren) == 0) {
525		return;
526	}
527
528	for (i = 0; i < nchildren; i++) {
529		PanacheRecursivelyGrabKey (children[i], keycode);
530		XGrabKey (dis, keycode, Mod1Mask, win, 1, GrabModeAsync, GrabModeSync);
531	}
532
533	if (children != NULL) {
534		XFree (children);
535	}
536}
537
538
539int PanacheReparentCmd CMD_ARGS {
540	Window newParent;
541	Window win;
542
543	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
544	Tcl_GetLongFromObj (interp, objv[2], (long *) &newParent);
545
546	XReparentWindow (dis, win, newParent, 0, 20);
547
548	return TCL_OK;
549}
550
551
552void PanacheSelectInputForRootParented (Window win) {
553
554	XSelectInput (dis, win, eventMask);
555}
556
557
558void PanacheSetWMState (Window win, int state) {
559	unsigned long data[2];
560	data[0] = state;
561	data[1] = None;
562
563	XChangeProperty (dis, win, _XA_WM_STATE, _XA_WM_STATE, 32,
564		PropModeReplace, (unsigned char *) data, 2
565	);
566
567	XSync (dis, 0);
568}
569
570
571int PanacheTransientCmd CMD_ARGS {
572	Window parent;
573	Window win;
574
575	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
576	Tcl_GetLongFromObj (interp, objv[2], (long *) &parent);
577
578	XSetTransientForHint (dis, win, parent);
579
580	return TCL_OK;
581}
582
583/*This sends a string to Panache_GUI with info about the window,
584 *such as its title and window id.  This information is processed
585 *within Panache_GUI and if desired PanacheMapCmd will map the
586 *window.
587 */
588void PanacheMapRequest (XMapRequestEvent *event) {
589	char *winId;
590	char *transientForWinId;
591	XTextProperty xtp;
592	char str[] = "sendToPipe [list add [list $winTitle] $winId $winType $transientForWinId]";
593	Window twin;
594
595	if (event->window == NULL) {
596		return;
597	}
598
599	/*This makes the state iconic, so that if the user presses
600	 *restart before mapping the window, the window will show up.
601	 */
602	PanacheSetWMState (event->window, IconicState);
603
604	xtp.value = NULL;
605
606	XGetWMName (dis, event->window, &xtp);
607
608	winId = charMalloc (winIdLength);
609
610	sprintf (winId, "%ld", event->window);
611	PanacheSelectInputForRootParented (event->window);
612
613	Tcl_SetVar (interp, "winTitle", (char *) xtp.value, 0);
614	Tcl_SetVar (interp, "winId", winId, 0);
615
616	if (XGetTransientForHint (dis, event->window, &twin) == 1) {
617		Tcl_SetVar (interp, "winType", "transient", 0);
618		transientForWinId = charMalloc (winIdLength);
619		sprintf (transientForWinId, "%ld", twin);
620		Tcl_SetVar (interp, "transientForWinId", transientForWinId, 0);
621		free (transientForWinId);
622	} else {
623		Tcl_SetVar (interp, "winType", "normal", 0);
624		Tcl_SetVar (interp, "transientForWinId", "", 0);
625	}
626
627	XFree (xtp.value);
628	free (winId);
629
630	if (Tcl_Eval (interp, str) != TCL_OK) {
631		fprintf (stderr, "Error in PanacheMapRequest: %s\n", Tcl_GetStringResult (interp));
632	}
633}
634
635
636/*This maps a window.  It may be called after PanacheMapRequest by
637 *Panache_GUI.  This is also called when a window is over another
638 *window and the user selects the button for the window to display
639 *which causes this function to raise the window.
640 */
641int PanacheMapCmd CMD_ARGS {
642	Window win;
643	Window twin;
644	XWindowAttributes winAttrib;
645
646	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
647
648	PanacheSelectInputForRootParented (win);
649
650	/*XGrabKey (dis, XK_Tab, Mod1Mask, win, 1, GrabModeAsync, GrabModeAsync);*/
651	/*PanacheRecursivelyGrabKey (win, XK_Tab);*/
652
653	XGetWindowAttributes (dis, win, &winAttrib);
654
655	if (winAttrib.x < distance_from_edge) {
656		winAttrib.x = distance_from_edge;
657		if (winAttrib.y < 0) {
658			winAttrib.y = 0;
659		}
660		XMoveWindow (dis, win, winAttrib.x, winAttrib.y);
661	}
662
663	if (XGetTransientForHint (dis, win, &twin) == 1) {
664		PanacheSetWMState (win, NormalState);
665		XMapRaised (dis, win);
666		sendMapNotify (win);
667		mapped_window = win;
668		PanacheRaiseKeepAboveWindows ();
669
670		return TCL_OK;
671	}
672
673
674	if ((PanacheGetWMState (win)) == 1) {
675		XRaiseWindow (dis, win);
676		PanacheRaiseKeepAboveWindows ();
677
678		return TCL_OK;
679	}
680
681	/*If we are here the window hasn't had its size set, or
682	 *the WM_STATE was not 1.
683	 */
684
685	PanacheSetWMState (win, NormalState);
686
687	/*I've found that some applications get upset if you sent
688	 *a ConfigureNotify before the MapNotify, when they are
689	 *expecting the MapNotify to be eminent.
690	 */
691
692	XMapRaised (dis, win);
693	sendMapNotify (win);
694
695	PanacheConfigureNormalWindow (win, CWX|CWY|CWWidth|CWHeight);
696
697	mapped_window = win;
698	PanacheRaiseKeepAboveWindows ();
699
700	return TCL_OK;
701}
702
703
704int PanacheMapWorkspaceCmd CMD_ARGS {
705	XWindowChanges wc;
706	Window win;
707
708	Tcl_GetLongFromObj (interp, objv[1], (long *) &win);
709	workspace_manager = win;
710	PanacheSetWMState (win, NormalState);
711
712	wc.x = 0;
713	wc.y = 0;
714	wc.width = distance_from_edge;
715	wc.height = DisplayHeight (dis, screen);
716
717	XConfigureWindow(dis, win, CWX|CWY|CWWidth|CWHeight, &wc);
718	sendConfigureNotify (win, CWX|CWY|CWWidth|CWHeight, &wc);
719
720	XMapWindow (dis, win);
721	sendMapNotify (win);
722	mapped_window = win;
723	XFlush (dis);
724
725	return TCL_OK;
726}
727
728
729int PanacheMoveCmd CMD_ARGS {
730	XEvent event;
731	unsigned int buttonPressed;
732	Window wjunk;
733	int ijunk;
734	Cursor handCursor;
735	Window win;
736	int oldX;
737	int oldY;
738	int x;
739	int y;
740	int internalX;
741	int internalY;
742	unsigned int maskReturn;
743	int continueEventLoop = 1;
744	XWindowAttributes winAttr;
745
746	handCursor = XCreateFontCursor (dis, XC_hand2);
747
748	XGrabPointer (dis, root, 1,
749		ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | \
750			PointerMotionHintMask,
751		GrabModeAsync, GrabModeAsync,
752		None,
753		handCursor,
754		CurrentTime
755	);
756
757	/*Wait until the user has selected the window to move.*/
758	XMaskEvent (dis, ButtonPressMask, &event);
759
760	/*The button being held down while dragging the window.*/
761	buttonPressed = event.xbutton.button;
762
763	/*fprintf (stderr, "ButtonPressed %d\n", buttonPressed);*/
764
765	XQueryPointer (dis, root,
766		&wjunk, &win,
767		&oldX, &oldY,
768		&internalX, &internalY,
769		&maskReturn
770	);
771
772	if (win == workspace_manager) {
773		XUngrabPointer (dis, CurrentTime);
774		XFreeCursor (dis, handCursor);
775		XSync (dis, 0);
776
777		return TCL_OK;
778	}
779
780
781	XGetWindowAttributes (dis, win, &winAttr);
782
783	while (continueEventLoop == 1) {
784		XNextEvent (dis, &event);
785		switch (event.type) {
786			case ButtonRelease:
787			{
788				if (event.xbutton.button == buttonPressed) {
789					continueEventLoop = 0;
790				}
791			}
792			break;
793			case MotionNotify:
794			{
795				XWindowChanges wc;
796				int newX;
797				int newY;
798
799				while (XCheckTypedEvent (dis, MotionNotify, &event));
800
801				XQueryPointer (dis, root, &wjunk, &wjunk,
802					&x, &y,
803					&ijunk, &ijunk,
804					&maskReturn
805				);
806
807				newX = x - oldX + winAttr.x;
808				newY = y - oldY + winAttr.y;
809
810				if (newX < distance_from_edge) {
811
812					if (winAttr.override_redirect == 1) {
813						XMoveWindow (dis, win, distance_from_edge, newY);
814					} else {
815						wc.x = distance_from_edge;
816						wc.y = newY;
817						XConfigureWindow (dis, win, CWX | CWY, &wc);
818						sendConfigureNotify (win, CWX | CWY, &wc);
819					}
820					continue;
821				}
822
823				if (winAttr.override_redirect == 1) {
824					XMoveWindow (dis, win, newX, newY);
825				} else {
826					wc.x = newX;
827					wc.y = newY;
828					XConfigureWindow (dis, win, CWX | CWY, &wc);
829					sendConfigureNotify (win, CWX | CWY, &wc);
830				}
831			}
832			break;
833		}
834	}
835
836	/*fprintf (stderr, "win is %ld\n", win);*/
837
838	XUngrabPointer (dis, CurrentTime);
839	XFreeCursor (dis, handCursor);
840
841	XSync (dis, 0);
842
843	return TCL_OK;
844}
845
846
847XErrorHandler PanacheErrorHandler (Display *dis, XErrorEvent *event) {
848/*I've discovered that errors are frequently timing problems.
849Maybe XSync would help in some areas.
850Most errors are not fatal.
851*/
852	return 0;
853}
854
855
856int main() {
857	fd_set readfds;
858	int nfds;
859	int xFd;
860	int pipeFd;
861	int inputPipeFd;
862	ClientData data;
863	int fdsTcl;
864
865
866	dis = XOpenDisplay (NULL);
867	screen = DefaultScreen (dis);
868	root = RootWindow (dis, screen);
869	interp = Tcl_CreateInterp ();
870
871	XSetErrorHandler ((XErrorHandler) PanacheErrorHandler);
872
873	_XA_WM_STATE = XInternAtom (dis, "WM_STATE", 0);
874	_XA_WM_PROTOCOLS = XInternAtom (dis, "WM_PROTOCOLS", 0);
875	_XA_WM_DELETE_WINDOW = XInternAtom (dis, "WM_DELETE_WINDOW", 0);
876
877	keepAboveWindowList = CListInit ();
878
879#ifdef FORK_ON_START
880	{
881	int res;
882		res = fork();
883
884		if (res == -1) {
885			fprintf (stderr, "Unable to fork process.");
886			return 1;
887		}
888
889		if (res != 0) {
890			exit (0);
891		}
892	}
893#endif
894
895	if (Tcl_Init (interp) != TCL_OK) {
896		printf ("Tcl_Init error\n");
897		exit (-1);
898	}
899
900#define CREATE_CMD(cmdName,func) Tcl_CreateObjCommand (interp, \
901cmdName, func, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL)
902
903	CREATE_CMD ("map_workspace", PanacheMapWorkspaceCmd);
904	CREATE_CMD ("distance_from_edge", PanacheDFECmd);
905	CREATE_CMD ("map", PanacheMapCmd);
906	CREATE_CMD ("destroy", PanacheDestroyCmd);
907	CREATE_CMD ("add_all_windows", PanacheAddAllWindowsCmd);
908	CREATE_CMD ("focus", PanacheFocusCmd);
909	CREATE_CMD ("transient", PanacheTransientCmd);
910	CREATE_CMD ("reparent", PanacheReparentCmd);
911	CREATE_CMD ("move", PanacheMoveCmd);
912
913	Tcl_SetVar (interp, "wishInterpreter", WISH_INTERPRETER, 0);
914	Tcl_SetVar (interp, "prefix", PREFIX, 0);
915	Tcl_SetVar (interp, "panacheDirectory", PANACHE_DIRECTORY, 0);
916
917
918	if (Tcl_Eval (interp, Panache_Init_script) != TCL_OK) {
919		fprintf (stderr, "Error while evaluating Panache_Init_script within main()%s\n", Tcl_GetStringResult (interp));
920		exit (-1);
921	}
922
923	XSelectInput (dis, root, LeaveWindowMask | EnterWindowMask| \
924		PropertyChangeMask | SubstructureRedirectMask | \
925		SubstructureNotifyMask | KeyPressMask | KeyReleaseMask | \
926		ResizeRedirectMask | FocusChangeMask
927	);
928
929	xFd = ConnectionNumber (dis);
930
931	Tcl_GetChannelHandle (Tcl_GetChannel (interp, Tcl_GetVar (interp, "pipe", NULL), NULL), TCL_WRITABLE, &data);
932	pipeFd = (int) data;
933	/*fprintf (stderr, "pipeFd %d", pipeFd);*/
934
935	Tcl_GetChannelHandle (Tcl_GetChannel (interp, Tcl_GetVar (interp, "pipe", NULL), NULL), TCL_READABLE, &data);
936	inputPipeFd = (int) data;
937
938	XFlush(dis);
939
940	for (;;) {
941
942		FD_ZERO (&readfds);
943		FD_SET (xFd, &readfds);
944		FD_SET (pipeFd, &readfds);
945		FD_SET (inputPipeFd, &readfds);
946
947		fdsTcl = (pipeFd > inputPipeFd) ? pipeFd : inputPipeFd;
948		nfds = (xFd > fdsTcl) ? xFd + 1: fdsTcl + 1;
949
950		select (nfds, &readfds, NULL, NULL, NULL);
951
952		if (FD_ISSET (inputPipeFd, &readfds) != 0) {
953			if (Tcl_Eval (interp, "getFromPipe") != TCL_OK) {
954				fprintf (stderr, "getFromPipe error %s\n", Tcl_GetStringResult (interp));
955			}
956		}
957
958		if (FD_ISSET (pipeFd, &readfds) != 0) {
959			while (Tcl_DoOneEvent (TCL_DONT_WAIT));
960		}
961
962		if (FD_ISSET (xFd, &readfds) == 0) {
963			continue;
964		}
965
966		while (XPending (dis) > 0) {
967			XNextEvent (dis, &report);
968
969			/*fprintf (stderr, "type %d\n", report.type);*/
970			switch  (report.type) {
971			case ConfigureNotify:
972			/*fprintf (stderr, "ConfigureNotify \n");*/
973			break;
974
975			case CreateNotify:
976				PanacheCreateNotify (&report.xcreatewindow);
977			break;
978
979			case ConfigureRequest:
980				PanacheConfigureRequest (&report.xconfigurerequest);
981			break;
982
983			case DestroyNotify:
984				PanacheDestroyNotify (&report.xdestroywindow);
985			break;
986
987			case EnterNotify:
988			{
989				Window win = report.xcrossing.window;
990				char *winId = NULL;
991				char cmd[] = "sendToPipe [list activateWindow $winId]";
992
993				winId = charMalloc (winIdLength);
994				sprintf (winId, "%ld", win);
995				Tcl_SetVar (interp, "winId", winId, 0);
996				free (winId);
997
998				if (Tcl_Eval (interp, cmd) != TCL_OK) {
999					fprintf (stderr, "Error evaluating cmd in EnterNotify within main() %s\n", Tcl_GetStringResult (interp));
1000				}
1001
1002			}
1003			break;
1004
1005			case FocusIn:
1006			break;
1007
1008
1009			case KeyPress:
1010			{
1011				char cmd[] = "sendToPipe next";
1012
1013				if (XLookupKeysym (&report.xkey, 0) == XK_Tab && (report.xkey.state & Mod1Mask)) {
1014					fprintf (stderr, "alt tab win %ld\n", report.xkey.window);
1015					if (Tcl_Eval (interp, cmd) != TCL_OK) {
1016						fprintf (stderr, "Error evaluating cmd in KeyPress within main() %s\n", Tcl_GetStringResult (interp));
1017					}
1018				} else {
1019					/*Send XK_Tab*/
1020				}
1021
1022				/*
1023				fprintf (stderr, "1 %d \n", report.xkey.state == Mod1Mask);
1024				fprintf (stderr, "2 %d \n", report.xkey.state == Mod2Mask);
1025				fprintf (stderr, "3 %d \n", report.xkey.state == Mod3Mask);
1026				fprintf (stderr, "4 %d \n", report.xkey.state == Mod4Mask);
1027				fprintf (stderr, "5 %d \n", report.xkey.state == Mod5Mask);
1028				*/
1029			}
1030			break;
1031
1032			case MapRequest:
1033				PanacheMapRequest (&report.xmaprequest);
1034			break;
1035
1036			case UnmapNotify:
1037			{
1038				int state = PanacheGetWMState (report.xunmap.window);
1039				/*Mapped or Iconified*/
1040				if (state == 1 || state == 3) {
1041					char *winId = NULL;
1042					char cmd[] = "sendToPipe [list remove $winId]";
1043
1044					winId = charMalloc (winIdLength);
1045					sprintf (winId, "%ld", report.xunmap.window);
1046
1047					Tcl_SetVar (interp, "winId", winId, 0);
1048					free (winId);
1049
1050					PanacheSetWMState (report.xunmap.window, WithdrawnState);
1051
1052					if (Tcl_Eval (interp, cmd) != TCL_OK) {
1053						fprintf (stderr, "Tcl_Eval error in UnmapNotify within main() %s", Tcl_GetStringResult (interp));
1054					}
1055				}
1056			}
1057			break;
1058
1059			case PropertyNotify:
1060			{
1061				XTextProperty xtp;
1062				xtp.value = NULL;
1063
1064				if (XGetWMName (dis, report.xproperty.window, &xtp) == 1) {
1065					char *winId;
1066					char cmd[] = "sendToPipe [list title [list $winTitle] $winId]";
1067
1068					winId = charMalloc (winIdLength);
1069					sprintf (winId, "%ld", report.xproperty.window);
1070
1071					Tcl_SetVar (interp, "winTitle", (char *) xtp.value, 0);
1072					Tcl_SetVar (interp, "winId", winId, 0);
1073
1074					free (winId);
1075					XFree (xtp.value);
1076
1077					if (Tcl_Eval (interp, cmd) != TCL_OK) {
1078						fprintf (stderr, "Tcl_Eval error in PropertyNotify: within main() %s\n", Tcl_GetStringResult (interp));
1079					}
1080				}
1081			}
1082			break;
1083
1084
1085			case ReparentNotify:
1086			{
1087				Window win = report.xreparent.window;
1088				Window parent = report.xreparent.parent;
1089
1090				/*
1091				fprintf (stderr, "ReparentNotify\n");
1092				fprintf (stderr, "win %ld parent %ld event %ld\n", win, parent, event);
1093				*/
1094				XSelectInput (dis, win, 0);
1095
1096				if (parent != root) {
1097					char *winId;
1098					char cmd[] = "sendToPipe [list remove $winId]";
1099
1100					winId = charMalloc (winIdLength);
1101					sprintf (winId, "%ld", win);
1102					Tcl_SetVar (interp, "winId", winId, 0);
1103					free (winId);
1104
1105					if (Tcl_Eval (interp, cmd) != TCL_OK) {
1106						fprintf (stderr, "Tcl_Eval error in ReparentNotify within main() %s\n", Tcl_GetStringResult (interp));
1107					}
1108				}
1109			}
1110			break;
1111
1112
1113			case ResizeRequest:
1114			{
1115				Window twin;
1116				Window win = report.xresizerequest.window;
1117
1118				if (XGetTransientForHint (dis, win, &twin) == 1) {
1119					XResizeWindow (dis, win,
1120						report.xresizerequest.width, report.xresizerequest.height
1121					);
1122				}
1123
1124				XFlush (dis);
1125			}
1126			break;
1127
1128			default:
1129			break;
1130			}
1131		}
1132	}
1133	return 0;
1134}
1135