1/*
2 * Functions related to manipulating windows.
3 *
4 * This doesn't include stuff related to changing their occupation (in
5 * functions_workspaces.c), or moving/resizing (in
6 * functions_win_moveresize.c), or a few other cases of things
7 * peripherally involving windows.
8 */
9
10#include "ctwm.h"
11
12#include <stdlib.h>
13
14#include "colormaps.h"
15#include "ctwm_atoms.h"
16#include "events.h"
17#include "event_handlers.h"
18#include "functions.h"
19#include "functions_defs.h"
20#include "functions_internal.h"
21#include "icons.h"
22#include "occupation.h"
23#include "otp.h"
24#include "parse.h"
25#include "screen.h"
26#include "win_decorations.h"
27#include "win_iconify.h"
28#include "win_ops.h"
29#include "win_utils.h"
30#include "workspace_manager.h"
31
32
33
34/*
35 *************************************************************
36 *
37 * Moving windows on/off the AutoRaise/AutoLower lists
38 *
39 *************************************************************
40 */
41DFHANDLER(autoraise)
42{
43	tmp_win->auto_raise = !tmp_win->auto_raise;
44	if(tmp_win->auto_raise) {
45		++(Scr->NumAutoRaises);
46	}
47	else {
48		--(Scr->NumAutoRaises);
49	}
50}
51
52DFHANDLER(autolower)
53{
54	tmp_win->auto_lower = !tmp_win->auto_lower;
55	if(tmp_win->auto_lower) {
56		++(Scr->NumAutoLowers);
57	}
58	else {
59		--(Scr->NumAutoLowers);
60	}
61}
62
63
64
65
66/*
67 *************************************************************
68 *
69 * Raising and lowering
70 *
71 *************************************************************
72 */
73
74/* Separate function because raise and raiseorsqueeze can both need it */
75static void
76raise_handler(EF_FULLPROTO)
77{
78	/* check to make sure raise is not from the WindowFunction */
79	if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT)  {
80		OtpRaise(tmp_win, IconWin);
81	}
82	else {
83		OtpRaise(tmp_win, WinWin);
84		WMapRaise(tmp_win);
85	}
86}
87
88DFHANDLER(raise)
89{
90	raise_handler(EF_ARGS);
91}
92
93DFHANDLER(raiseorsqueeze)
94{
95	/* FIXME using the same double-click ConstrainedMoveTime here */
96	if((eventp->xbutton.time - last_time) < ConstrainedMoveTime) {
97		Squeeze(tmp_win);
98		return;
99	}
100	last_time = eventp->xbutton.time;
101
102	/* intentional fall-thru into F_RAISE */
103	raise_handler(EF_ARGS);
104}
105
106DFHANDLER(lower)
107{
108	if(tmp_win->icon && (w == tmp_win->icon->w)) {
109		OtpLower(tmp_win, IconWin);
110	}
111	else {
112		OtpLower(tmp_win, WinWin);
113		WMapLower(tmp_win);
114	}
115}
116
117DFHANDLER(raiselower)
118{
119	if(!WindowMoved) {
120		if(tmp_win->icon && w == tmp_win->icon->w) {
121			OtpRaiseLower(tmp_win, IconWin);
122		}
123		else {
124			OtpRaiseLower(tmp_win, WinWin);
125			WMapRaiseLower(tmp_win);
126		}
127	}
128}
129
130
131/*
132 * Smaller raise/lower
133 */
134DFHANDLER(tinyraise)
135{
136	/* check to make sure raise is not from the WindowFunction */
137	if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT) {
138		OtpTinyRaise(tmp_win, IconWin);
139	}
140	else {
141		OtpTinyRaise(tmp_win, WinWin);
142		WMapRaise(tmp_win);
143	}
144}
145
146DFHANDLER(tinylower)
147{
148	/* check to make sure raise is not from the WindowFunction */
149	if(tmp_win->icon && (w == tmp_win->icon->w) && Context != C_ROOT) {
150		OtpTinyLower(tmp_win, IconWin);
151	}
152	else {
153		OtpTinyLower(tmp_win, WinWin);
154		WMapLower(tmp_win);
155	}
156}
157
158
159/*
160 * Raising/lowering a non-targetted window
161 */
162DFHANDLER(circleup)
163{
164	OtpCirculateSubwindows(Scr->currentvs, RaiseLowest);
165}
166
167DFHANDLER(circledown)
168{
169	OtpCirculateSubwindows(Scr->currentvs, LowerHighest);
170}
171
172
173
174
175/*
176 *************************************************************
177 *
178 * Iconification and its inverse.
179 *
180 *************************************************************
181 */
182static void
183iconify_handler(EF_FULLPROTO)
184{
185	if(tmp_win->isicon) {
186		DeIconify(tmp_win);
187	}
188	else if(func == F_ICONIFY) {
189		Iconify(tmp_win, eventp->xbutton.x_root - 5,
190		        eventp->xbutton.y_root - 5);
191	}
192}
193
194DFHANDLER(deiconify)
195{
196	iconify_handler(EF_ARGS);
197}
198DFHANDLER(iconify)
199{
200	iconify_handler(EF_ARGS);
201}
202
203
204/*
205 * This is a synthetic function; it only exists as an action in some
206 * magic menus like TwmWindows (x-ref f.winwarp as well).  It acts as a
207 * sort of deiconify, so I've stuck it here.
208 */
209DFHANDLER(popup)
210{
211	/*
212	 * This is a synthetic function; it exists only to be called
213	 * internally from the various magic menus like TwmWindows
214	 * etc.
215	 */
216	tmp_win = (TwmWindow *)action;
217	if(! tmp_win) {
218		return;
219	}
220	if(Scr->WindowFunction.func != 0) {
221		ExecuteFunction(Scr->WindowFunction.func,
222		                Scr->WindowFunction.item->action,
223		                w, tmp_win, eventp, C_FRAME, false);
224	}
225	else {
226		DeIconify(tmp_win);
227		OtpRaise(tmp_win, WinWin);
228	}
229}
230
231
232
233
234/*
235 *************************************************************
236 *
237 * Focus locking
238 *
239 *************************************************************
240 */
241DFHANDLER(focus)
242{
243	if(!tmp_win->isicon) {
244		if(!Scr->FocusRoot && Scr->Focus == tmp_win) {
245			FocusOnRoot();
246		}
247		else {
248			InstallWindowColormaps(0, tmp_win);
249			SetFocus(tmp_win, eventp->xbutton.time);
250			Scr->FocusRoot = false;
251		}
252	}
253}
254
255DFHANDLER(unfocus)
256{
257	FocusOnRoot();
258}
259
260
261
262
263/*
264 *************************************************************
265 *
266 * Window destruction
267 *
268 *************************************************************
269 */
270static void
271SendDeleteWindowMessage(TwmWindow *tmp, Time timestamp)
272{
273	send_clientmessage(tmp->w, XA_WM_DELETE_WINDOW, timestamp);
274}
275
276DFHANDLER(delete)
277{
278	if(tmp_win->isiconmgr) {     /* don't send ourself a message */
279		HideIconManager();
280		return;
281	}
282	if(tmp_win->iswspmgr
283#ifdef WINBOX
284	                || tmp_win->iswinbox
285#endif
286	                || (Scr->workSpaceMgr.occupyWindow
287	                    && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) {
288		XBell(dpy, 0);
289		return;
290	}
291	if(tmp_win->protocols & DoesWmDeleteWindow) {
292		SendDeleteWindowMessage(tmp_win, EventTime);
293		if(ButtonPressed != -1) {
294			XEvent kev;
295
296			XMaskEvent(dpy, ButtonReleaseMask, &kev);
297			if(kev.xbutton.window == tmp_win->w) {
298				kev.xbutton.window = Scr->Root;
299			}
300			XPutBackEvent(dpy, &kev);
301		}
302		return;
303	}
304	XBell(dpy, 0);
305}
306
307DFHANDLER(destroy)
308{
309	if(tmp_win->isiconmgr || tmp_win->iswspmgr
310#ifdef WINBOX
311	                || tmp_win->iswinbox
312#endif
313	                || (Scr->workSpaceMgr.occupyWindow
314	                    && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) {
315		XBell(dpy, 0);
316		return;
317	}
318	XKillClient(dpy, tmp_win->w);
319	if(ButtonPressed != -1) {
320		XEvent kev;
321
322		XMaskEvent(dpy, ButtonReleaseMask, &kev);
323		if(kev.xbutton.window == tmp_win->w) {
324			kev.xbutton.window = Scr->Root;
325		}
326		XPutBackEvent(dpy, &kev);
327	}
328}
329
330DFHANDLER(deleteordestroy)
331{
332	if(tmp_win->isiconmgr) {
333		HideIconManager();
334		return;
335	}
336	if(tmp_win->iswspmgr
337#ifdef WINBOX
338	                || tmp_win->iswinbox
339#endif
340	                || (Scr->workSpaceMgr.occupyWindow
341	                    && tmp_win == Scr->workSpaceMgr.occupyWindow->twm_win)) {
342		XBell(dpy, 0);
343		return;
344	}
345	if(tmp_win->protocols & DoesWmDeleteWindow) {
346		SendDeleteWindowMessage(tmp_win, EventTime);
347	}
348	else {
349		XKillClient(dpy, tmp_win->w);
350	}
351	if(ButtonPressed != -1) {
352		XEvent kev;
353
354		XMaskEvent(dpy, ButtonReleaseMask, &kev);
355		if(kev.xbutton.window == tmp_win->w) {
356			kev.xbutton.window = Scr->Root;
357		}
358		XPutBackEvent(dpy, &kev);
359	}
360}
361
362
363
364
365/*
366 *************************************************************
367 *
368 * Messing with OnTopPriority bits
369 *
370 *************************************************************
371 */
372static void
373otp_priority_handler(EF_FULLPROTO)
374{
375	WinType wintype;
376	int pri;
377	char *endp;
378
379	if(tmp_win->icon && w == tmp_win->icon->w) {
380		wintype = IconWin;
381	}
382	else {
383		wintype = WinWin;
384	}
385	switch(func) {
386		case F_PRIORITYSWITCHING:
387			OtpToggleSwitching(tmp_win, wintype);
388			break;
389		case F_SETPRIORITY:
390			pri = (int)strtol(action, &endp, 10);
391			OtpSetPriority(tmp_win, wintype, pri,
392			               (*endp == '<' || *endp == 'b') ? Below : Above);
393			break;
394		case F_CHANGEPRIORITY:
395			OtpChangePriority(tmp_win, wintype, atoi(action));
396			break;
397		case F_SWITCHPRIORITY:
398			OtpSwitchPriority(tmp_win, wintype);
399			break;
400	}
401
402	/*
403	 * Stash up our current flags if there aren't any set yet.  This is
404	 * necessary because otherwise the EWMH prop we [may] stash below
405	 * would be taken as gospel on restart, when it shouldn't be.
406	 */
407	OtpStashAflagsFirstTime(tmp_win);
408
409#ifdef EWMH
410	/*
411	 * We changed the priority somehow, so we may have changed where it
412	 * sits relative to the middle.  So trigger rechecking/setting of the
413	 * _STATE_{ABOVE,BELOW}.  (_ABOVE in changes arg covers both)
414	 */
415	EwmhSet_NET_WM_STATE(tmp_win, EWMH_STATE_ABOVE);
416#endif /* EWMH */
417}
418DFHANDLER(priorityswitching)
419{
420	otp_priority_handler(EF_ARGS);
421}
422DFHANDLER(switchpriority)
423{
424	otp_priority_handler(EF_ARGS);
425}
426DFHANDLER(setpriority)
427{
428	otp_priority_handler(EF_ARGS);
429}
430DFHANDLER(changepriority)
431{
432	otp_priority_handler(EF_ARGS);
433}
434
435
436
437
438/*
439 *************************************************************
440 *
441 * Some misc utilities
442 *
443 *************************************************************
444 */
445DFHANDLER(saveyourself)
446{
447	if(tmp_win->protocols & DoesWmSaveYourself) {
448		send_clientmessage(tmp_win->w, XA_WM_SAVE_YOURSELF, EventTime);
449	}
450	else {
451		XBell(dpy, 0);
452	}
453}
454
455DFHANDLER(colormap)
456{
457	/* XXX Window targetting; should this be on the Defer list? */
458	if(strcmp(action, COLORMAP_NEXT) == 0) {
459		BumpWindowColormap(tmp_win, 1);
460	}
461	else if(strcmp(action, COLORMAP_PREV) == 0) {
462		BumpWindowColormap(tmp_win, -1);
463	}
464	else {
465		BumpWindowColormap(tmp_win, 0);
466	}
467}
468
469DFHANDLER(refresh)
470{
471	XSetWindowAttributes attributes;
472	unsigned long valuemask;
473
474	valuemask = CWBackPixel;
475	attributes.background_pixel = Scr->Black;
476	w = XCreateWindow(dpy, Scr->Root, 0, 0,
477	                  Scr->rootw,
478	                  Scr->rooth,
479	                  0,
480	                  CopyFromParent, CopyFromParent,
481	                  CopyFromParent, valuemask,
482	                  &attributes);
483	XMapWindow(dpy, w);
484	XDestroyWindow(dpy, w);
485	XFlush(dpy);
486
487}
488
489DFHANDLER(winrefresh)
490{
491	if(context == C_ICON && tmp_win->icon && tmp_win->icon->w)
492		w = XCreateSimpleWindow(dpy, tmp_win->icon->w,
493		                        0, 0, 9999, 9999, 0, Scr->Black, Scr->Black);
494	else
495		w = XCreateSimpleWindow(dpy, tmp_win->frame,
496		                        0, 0, 9999, 9999, 0, Scr->Black, Scr->Black);
497
498	XMapWindow(dpy, w);
499	XDestroyWindow(dpy, w);
500	XFlush(dpy);
501}
502
503
504
505
506/*
507 *************************************************************
508 *
509 * Window squeezing related bits
510 *
511 *************************************************************
512 */
513DFHANDLER(squeeze)
514{
515	Squeeze(tmp_win);
516}
517
518DFHANDLER(unsqueeze)
519{
520	if(tmp_win->squeezed) {
521		Squeeze(tmp_win);
522	}
523}
524
525
526DFHANDLER(movetitlebar)
527{
528	Window grabwin;
529	Window rootw;
530	int deltax = 0, newx = 0;
531	int origX;
532	int origNum;
533	SqueezeInfo *si;
534
535	PopDownMenu();
536	if(tmp_win->squeezed ||
537	                !tmp_win->squeeze_info ||
538	                !tmp_win->title_w ||
539	                context == C_ICON) {
540		XBell(dpy, 0);
541		return;
542	}
543
544	/* If the SqueezeInfo isn't copied yet, do it now */
545	if(!tmp_win->squeeze_info_copied) {
546		SqueezeInfo *s = malloc(sizeof(SqueezeInfo));
547		if(!s) {
548			return;
549		}
550		*s = *tmp_win->squeeze_info;
551		tmp_win->squeeze_info = s;
552		tmp_win->squeeze_info_copied = true;
553	}
554	si = tmp_win->squeeze_info;
555
556	if(si->denom != 0) {
557		int target_denom = tmp_win->frame_width;
558		/*
559		 * If not pixel based, scale the denominator to equal the
560		 * window width, so the numerator equals pixels.
561		 * That way we can just modify it by pixel units, just
562		 * like the other case.
563		 */
564
565		if(si->denom != target_denom) {
566			float scale = (float)target_denom / si->denom;
567			si->num *= scale;
568			si->denom = target_denom; /* s->denom *= scale; */
569		}
570	}
571
572	/* now move the mouse */
573#ifdef WINBOX
574	if(tmp_win->winbox) {
575		XTranslateCoordinates(dpy, Scr->Root, tmp_win->winbox->window,
576		                      eventp->xbutton.x_root, eventp->xbutton.y_root,
577		                      &eventp->xbutton.x_root, &eventp->xbutton.y_root, &JunkChild);
578	}
579#endif
580	/*
581	 * the event is always a button event, since key events
582	 * are "weeded out" - although incompletely only
583	 * F_MOVE and F_RESIZE - in HandleKeyPress().
584	 */
585
586	/*
587	 * XXX This var may be actually unnecessary; it's used only
588	 * once as an arg to a later X call, but during that time I
589	 * don't believe anything can mutate eventp or anything near
590	 * the root.  However, due to the union nature of XEvent,
591	 * it's hard to be sure without more investigation, so I
592	 * leave the intermediate var for now.
593	 *
594	 * Note that we're looking inside the XButtonEvent member
595	 * here, but other bits of this code later look at the
596	 * XMotionEvent piece.  This should be further investigated
597	 * and resolved; they can't both be right (though the
598	 * structure of the structs are such that almost all the
599	 * similar elements are in the same place, at least in
600	 * theory).
601	 */
602	rootw = eventp->xbutton.root;
603
604	EventHandler[EnterNotify] = HandleUnknown;
605	EventHandler[LeaveNotify] = HandleUnknown;
606
607	if(!Scr->NoGrabServer) {
608		XGrabServer(dpy);
609	}
610
611	grabwin = Scr->Root;
612#ifdef WINBOX
613	if(tmp_win->winbox) {
614		grabwin = tmp_win->winbox->window;
615	}
616#endif
617	XGrabPointer(dpy, grabwin, True,
618	             ButtonPressMask | ButtonReleaseMask |
619	             ButtonMotionMask | PointerMotionMask, /* PointerMotionHintMask */
620	             GrabModeAsync, GrabModeAsync, grabwin, Scr->MoveCursor, CurrentTime);
621
622#if 0   /* what's this for ? */
623	if(! tmp_win->icon || w != tmp_win->icon->w) {
624		XTranslateCoordinates(dpy, w, tmp_win->frame,
625		                      eventp->xbutton.x,
626		                      eventp->xbutton.y,
627		                      &DragX, &DragY, &JunkChild);
628
629		w = tmp_win->frame;
630	}
631#endif
632
633	DragWindow = None;
634
635	XGetGeometry(dpy, tmp_win->title_w, &JunkRoot, &origDragX, &origDragY,
636	             &DragWidth, &DragHeight, &DragBW,
637	             &JunkDepth);
638
639	origX = eventp->xbutton.x_root;
640	origNum = si->num;
641
642	if(menuFromFrameOrWindowOrTitlebar) {
643		/* warp the pointer to the middle of the window */
644		XWarpPointer(dpy, None, Scr->Root, 0, 0, 0, 0,
645		             origDragX + DragWidth / 2,
646		             origDragY + DragHeight / 2);
647		XFlush(dpy);
648	}
649
650	while(1) {
651		long releaseEvent = menuFromFrameOrWindowOrTitlebar ?
652		                    ButtonPress : ButtonRelease;
653		long movementMask = menuFromFrameOrWindowOrTitlebar ?
654		                    PointerMotionMask : ButtonMotionMask;
655
656		/* block until there is an interesting event */
657		XMaskEvent(dpy, ButtonPressMask | ButtonReleaseMask |
658		           EnterWindowMask | LeaveWindowMask |
659		           ExposureMask | movementMask |
660		           VisibilityChangeMask, &Event);
661
662		/* throw away enter and leave events until release */
663		if(Event.xany.type == EnterNotify ||
664		                Event.xany.type == LeaveNotify) {
665			continue;
666		}
667
668		if(Event.type == MotionNotify) {
669			/* discard any extra motion events before a logical release */
670			while(XCheckMaskEvent(dpy,
671			                      movementMask | releaseEvent, &Event)) {
672				if(Event.type == releaseEvent) {
673					break;
674				}
675			}
676		}
677
678		if(!DispatchEvent2()) {
679			continue;
680		}
681
682		if(Event.type == releaseEvent) {
683			break;
684		}
685
686		/* something left to do only if the pointer moved */
687		if(Event.type != MotionNotify) {
688			continue;
689		}
690
691		/* get current pointer pos, useful when there is lag */
692		XQueryPointer(dpy, rootw, &eventp->xmotion.root, &JunkChild,
693		              &eventp->xmotion.x_root, &eventp->xmotion.y_root,
694		              &JunkX, &JunkY, &JunkMask);
695
696		FixRootEvent(eventp);
697#ifdef WINBOX
698		if(tmp_win->winbox) {
699			XTranslateCoordinates(dpy, Scr->Root, tmp_win->winbox->window,
700			                      eventp->xmotion.x_root, eventp->xmotion.y_root,
701			                      &eventp->xmotion.x_root, &eventp->xmotion.y_root, &JunkChild);
702		}
703#endif
704
705		if(!Scr->NoRaiseMove && Scr->OpaqueMove && !WindowMoved) {
706			OtpRaise(tmp_win, WinWin);
707		}
708
709		deltax = eventp->xmotion.x_root - origX;
710		newx = origNum + deltax;
711
712		/*
713		 * Clamp to left and right.
714		 * If we're in pixel size, keep within [ 0, frame_width >.
715		 * If we're proportional, don't cross the 0.
716		 * Also don't let the nominator get bigger than the denominator.
717		 * Keep within [ -denom, -1] or [ 0, denom >.
718		 */
719		{
720			int wtmp = tmp_win->frame_width; /* or si->denom; if it were != 0 */
721			if(origNum < 0) {
722				if(newx >= 0) {
723					newx = -1;
724				}
725				else if(newx < -wtmp) {
726					newx = -wtmp;
727				}
728			}
729			else if(origNum >= 0) {
730				if(newx < 0) {
731					newx = 0;
732				}
733				else if(newx >= wtmp) {
734					newx = wtmp - 1;
735				}
736			}
737		}
738
739		si->num = newx;
740		/* This, finally, actually moves the title bar */
741		/* XXX pressing a second button should cancel and undo this */
742		SetFrameShape(tmp_win);
743	}
744
745	/*
746	 * The ButtonRelease handler will have taken care of
747	 * ungrabbing our pointer.
748	 */
749	return;
750}
751