1/*
2 * Window-handling utility funcs
3 */
4
5#include "ctwm.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9
10#include <X11/Xatom.h>
11
12#include "add_window.h" // NoName
13#include "ctwm_atoms.h"
14#include "drawing.h"
15#include "events.h"
16#include "event_internal.h" // Temp?
17#ifdef EWMH
18# include "ewmh_atoms.h"
19#endif
20#include "icons.h"
21#include "list.h"
22#include "occupation.h"
23#include "otp.h"
24#include "r_area.h"
25#include "r_area_list.h"
26#include "r_layout.h"
27#include "screen.h"
28#include "util.h"
29#include "win_decorations.h"
30#include "win_ops.h"
31#include "win_utils.h"
32#include "workspace_utils.h"
33
34
35/*
36 * Fill in size hints for a window from WM_NORMAL_HINTS prop.
37 *
38 * Formerly in add_window.c
39 */
40void
41GetWindowSizeHints(TwmWindow *tmp)
42{
43	long supplied = 0;
44	XSizeHints *hints = &tmp->hints;
45
46	if(!XGetWMNormalHints(dpy, tmp->w, hints, &supplied)) {
47		hints->flags = 0;
48	}
49
50	if(hints->flags & PResizeInc) {
51		if(hints->width_inc == 0) {
52			hints->width_inc = 1;
53		}
54		if(hints->height_inc == 0) {
55			hints->height_inc = 1;
56		}
57	}
58
59	if(!(supplied & PWinGravity) && (hints->flags & USPosition)) {
60		static int gravs[] = { SouthEastGravity, SouthWestGravity,
61		                       NorthEastGravity, NorthWestGravity
62		                     };
63		int right =  tmp->attr.x + tmp->attr.width + 2 * tmp->old_bw;
64		int bottom = tmp->attr.y + tmp->attr.height + 2 * tmp->old_bw;
65		hints->win_gravity =
66		        gravs[((Scr->rooth - bottom <
67		                tmp->title_height + 2 * tmp->frame_bw3D) ? 0 : 2) |
68		              ((Scr->rootw - right   <
69		                tmp->title_height + 2 * tmp->frame_bw3D) ? 0 : 1)];
70		hints->flags |= PWinGravity;
71	}
72
73	/* Check for min size < max size */
74	if((hints->flags & (PMinSize | PMaxSize)) == (PMinSize | PMaxSize)) {
75		if(hints->max_width < hints->min_width) {
76			if(hints->max_width > 0) {
77				hints->min_width = hints->max_width;
78			}
79			else if(hints->min_width > 0) {
80				hints->max_width = hints->min_width;
81			}
82			else {
83				hints->max_width = hints->min_width = 1;
84			}
85		}
86
87		if(hints->max_height < hints->min_height) {
88			if(hints->max_height > 0) {
89				hints->min_height = hints->max_height;
90			}
91			else if(hints->min_height > 0) {
92				hints->max_height = hints->min_height;
93			}
94			else {
95				hints->max_height = hints->min_height = 1;
96			}
97		}
98	}
99}
100
101
102/*
103 * Fill in info from WM_PROTOCOLS property
104 *
105 * Formerly in add_window.c
106 */
107void
108FetchWmProtocols(TwmWindow *tmp)
109{
110	unsigned long flags = 0L;
111	Atom *protocols = NULL;
112	int n;
113
114	if(XGetWMProtocols(dpy, tmp->w, &protocols, &n)) {
115		int i;
116		Atom *ap;
117
118		for(i = 0, ap = protocols; i < n; i++, ap++) {
119			if(*ap == XA_WM_TAKE_FOCUS) {
120				flags |= DoesWmTakeFocus;
121			}
122			if(*ap == XA_WM_SAVE_YOURSELF) {
123				flags |= DoesWmSaveYourself;
124			}
125			if(*ap == XA_WM_DELETE_WINDOW) {
126				flags |= DoesWmDeleteWindow;
127			}
128		}
129		if(protocols) {
130			XFree(protocols);
131		}
132	}
133	tmp->protocols = flags;
134}
135
136
137/*
138 * Figure signs for calculating location offsets for a window dependent
139 * on its gravity.
140 *
141 * Depending on how its gravity is set, offsets to window coordinates for
142 * e.g. border widths may need to move either down (right) or up (left).
143 * Or possibly not at all.  So we write multipliers into passed vars for
144 * callers.
145 *
146 * Formerly in add_window.c
147 */
148void
149GetGravityOffsets(TwmWindow *tmp, int *xp, int *yp)
150{
151	static struct _gravity_offset {
152		int x, y;
153	} gravity_offsets[] = {
154		[ForgetGravity]    = {  0,  0 },
155		[NorthWestGravity] = { -1, -1 },
156		[NorthGravity]     = {  0, -1 },
157		[NorthEastGravity] = {  1, -1 },
158		[WestGravity]      = { -1,  0 },
159		[CenterGravity]    = {  0,  0 },
160		[EastGravity]      = {  1,  0 },
161		[SouthWestGravity] = { -1,  1 },
162		[SouthGravity]     = {  0,  1 },
163		[SouthEastGravity] = {  1,  1 },
164		[StaticGravity]    = {  0,  0 },
165	};
166	int g = ((tmp->hints.flags & PWinGravity)
167	         ? tmp->hints.win_gravity : NorthWestGravity);
168
169	if(g < ForgetGravity || g > StaticGravity) {
170		*xp = *yp = 0;
171	}
172	else {
173		*xp = gravity_offsets[g].x;
174		*yp = gravity_offsets[g].y;
175	}
176}
177
178
179/*
180 * Finds the TwmWindow structure associated with a Window (if any), or
181 * NULL.
182 *
183 * This is a relatively cheap function since it does not involve
184 * communication with the server. Probably faster than walking the list
185 * of TwmWindows, since the lookup is by a hash table.
186 *
187 * Formerly in add_window.c
188 */
189TwmWindow *
190GetTwmWindow(Window w)
191{
192	TwmWindow *twmwin;
193	int stat;
194
195	stat = XFindContext(dpy, w, TwmContext, (XPointer *)&twmwin);
196	if(stat == XCNOENT) {
197		twmwin = NULL;
198	}
199
200	return twmwin;
201}
202
203
204/***********************************************************************
205 *
206 *  Procedure:
207 *      GetWMPropertyString - Get Window Manager text property and
208 *                              convert it to a string.
209 *
210 *  Returned Value:
211 *      (char *) - pointer to the malloc'd string or NULL
212 *
213 *  Inputs:
214 *      w       - the id of the window whose property is to be retrieved
215 *      prop    - property atom (typically WM_NAME or WM_ICON_NAME)
216 *
217 ***********************************************************************
218 *
219 * Formerly in util.c
220 */
221char *
222GetWMPropertyString(Window w, Atom prop)
223{
224	XTextProperty       text_prop;
225	char                *stringptr;
226
227	XGetTextProperty(dpy, w, &text_prop, prop);
228	if(text_prop.value == NULL) {
229		return NULL;
230	}
231
232	if(text_prop.encoding == XA_STRING
233	                || text_prop.encoding == XA_UTF8_STRING
234	                || text_prop.encoding == XA_COMPOUND_TEXT) {
235		/* property is encoded as compound text - convert to locale string */
236		char **text_list;
237		int  text_list_count;
238		int status;
239
240		/* Check historical strictness */
241		if(Scr->StrictWinNameEncoding) {
242			bool fail = false;
243
244			if((prop == XA_WM_NAME || prop == XA_WM_ICON_NAME)
245			                && text_prop.encoding != XA_STRING
246			                && text_prop.encoding != XA_COMPOUND_TEXT) {
247				fail = true;
248			}
249
250#ifdef EWMH
251			if((prop == XA__NET_WM_NAME || prop == XA__NET_WM_ICON_NAME)
252			                && text_prop.encoding != XA_UTF8_STRING) {
253				fail = true;
254			}
255#endif // EWMH
256
257			if(fail) {
258				fprintf(stderr, "%s: Invalid encoding for property %s "
259				        "of window 0x%lx\n", ProgramName,
260				        XGetAtomName(dpy, prop), w);
261				XFree(text_prop.value);
262				return NULL;
263			}
264		}
265
266
267		status = XmbTextPropertyToTextList(dpy, &text_prop, &text_list,
268		                                   &text_list_count);
269		if(text_list_count == 0
270		                || text_list == NULL
271		                || text_list[0] == NULL) {
272			// Got nothing
273			XFree(text_prop.value);
274			return NULL;
275		}
276		else if(status < 0 || text_list_count < 0) {
277			// Got an error statuf
278			switch(status) {
279				case XConverterNotFound:
280					fprintf(stderr,
281					        "%s: Converter not found; unable to convert property %s of window ID %lx.\n",
282					        ProgramName, XGetAtomName(dpy, prop), w);
283					break;
284				case XNoMemory:
285					fprintf(stderr,
286					        "%s: Insufficient memory; unable to convert property %s of window ID %lx.\n",
287					        ProgramName, XGetAtomName(dpy, prop), w);
288					break;
289				case XLocaleNotSupported:
290					fprintf(stderr,
291					        "%s: Locale not supported; unable to convert property %s of window ID %lx.\n",
292					        ProgramName, XGetAtomName(dpy, prop), w);
293					break;
294			}
295			stringptr = NULL;
296			/*
297			   don't call XFreeStringList - text_list appears to have
298			   invalid address if status is bad
299			   XFreeStringList(text_list);
300			*/
301		}
302		else {
303			// Actually got the data!
304			stringptr = strdup(text_list[0]);
305			XFreeStringList(text_list);
306		}
307	}
308	else {
309		/* property is encoded in a format we don't understand */
310		fprintf(stderr,
311		        "%s: Encoding not STRING or COMPOUND_TEXT; unable to decode property %s of window ID %lx.\n",
312		        ProgramName, XGetAtomName(dpy, prop), w);
313		stringptr = NULL;
314	}
315	XFree(text_prop.value);
316
317	return stringptr;
318}
319
320
321/*
322 * Cleanup something stored that we got from the above originally.
323 *
324 * Formerly in util.c
325 */
326void
327FreeWMPropertyString(char *prop)
328{
329	if(prop && prop != NoName) {
330		free(prop);
331	}
332}
333
334
335/*
336 * Window mapped on some virtual screen?
337 *
338 * Formerly in util.c
339 */
340bool
341visible(const TwmWindow *tmp_win)
342{
343	return (tmp_win->vs != NULL);
344}
345
346
347/*
348 * Various code paths do a dance of "mask off notifications of event type
349 * ; do something that triggers that event (but we're doing it, so we
350 * don't need the notification) ; restore previous mask".  So have some
351 * util funcs to make it more visually obvious.
352 *
353 * e.g.:
354 *     long prev_mask = mask_out_event(w, PropertyChangeMask);
355 *     do_something_that_changes_properties();
356 *     restore_mask(prev_mask);
357 *
358 * We're cheating a little with the -1 return on mask_out_event(), as
359 * that's theoretically valid for the data type.  It's not as far as I
360 * can tell for X or us though; having all the bits set (well, I guess
361 * I'm assuming 2s-complement too) is pretty absurd, and there are only
362 * 25 defined bits in Xlib, so even on 32-bit systems, it shouldn't fill
363 * up long.
364 */
365long
366mask_out_event(Window w, long ignore_event)
367{
368	XWindowAttributes wattr;
369
370	/* Get current mask */
371	if(XGetWindowAttributes(dpy, w, &wattr) == 0) {
372		return -1;
373	}
374
375	/*
376	 * If we're ignoring nothing, nothing to do.  This is probably not
377	 * strictly speaking a useful thing to ask for in general, but it's
378	 * the right thing for us to do if we're asked to do nothing.
379	 */
380	if(ignore_event == 0) {
381		return wattr.your_event_mask;
382	}
383
384	/* Delegate */
385	return mask_out_event_mask(w, ignore_event, wattr.your_event_mask);
386}
387
388long
389mask_out_event_mask(Window w, long ignore_event, long curmask)
390{
391	/* Set to the current, minus what we're wanting to ignore */
392	XSelectInput(dpy, w, (curmask & ~ignore_event));
393
394	/* Return what it was */
395	return curmask;
396}
397
398int
399restore_mask(Window w, long restore)
400{
401	return XSelectInput(dpy, w, restore);
402}
403
404
405/*
406 * Setting and getting WM_STATE property.
407 *
408 * x-ref ICCCM section 4.1.3.1
409 * https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.3.1
410 *
411 * XXX These should probably be named more alike, as they're
412 * complementary ops.
413 */
414void
415SetMapStateProp(TwmWindow *tmp_win, int state)
416{
417	unsigned long data[2];              /* "suggested" by ICCCM version 1 */
418
419	data[0] = (unsigned long) state;
420	data[1] = (unsigned long)(tmp_win->iconify_by_unmapping ? None :
421	                          (tmp_win->icon ? tmp_win->icon->w : None));
422
423	XChangeProperty(dpy, tmp_win->w, XA_WM_STATE, XA_WM_STATE, 32,
424	                PropModeReplace, (unsigned char *) data, 2);
425}
426
427
428bool
429GetWMState(Window w, int *statep, Window *iwp)
430{
431	Atom actual_type;
432	int actual_format;
433	unsigned long nitems, bytesafter;
434	unsigned long *datap = NULL;
435	bool retval = false;
436
437	if(XGetWindowProperty(dpy, w, XA_WM_STATE, 0L, 2L, False, XA_WM_STATE,
438	                      &actual_type, &actual_format, &nitems, &bytesafter,
439	                      (unsigned char **) &datap) != Success || !datap) {
440		return false;
441	}
442
443	if(nitems <= 2) {                   /* "suggested" by ICCCM version 1 */
444		*statep = (int) datap[0];
445		*iwp = (Window) datap[1];
446		retval = true;
447	}
448
449	XFree(datap);
450	return retval;
451}
452
453
454/*
455 * Display a window's position in the dimensions window.  This is used
456 * during various window positioning (during new window popups, moves,
457 * etc).
458 *
459 * This reuses the same window for the position as is used during
460 * resizing for the dimesions of the window in DisplaySize().  The
461 * innards of the funcs can probably be collapsed together a little, and
462 * the higher-level knowledge of Scr->SizeWindow (e.g., for unmapping
463 * after ths op is done) should probably be encapsulated a bit better.
464 */
465void
466DisplayPosition(const TwmWindow *_unused_tmp_win, int x, int y)
467{
468	char str [100];
469	char signx = '+';
470	char signy = '+';
471
472	if(x < 0) {
473		x = -x;
474		signx = '-';
475	}
476	if(y < 0) {
477		y = -y;
478		signy = '-';
479	}
480	sprintf(str, " %c%-4d %c%-4d ", signx, x, signy, y);
481	XRaiseWindow(dpy, Scr->SizeWindow);
482
483	Draw3DBorder(Scr->SizeWindow, 0, 0,
484	             Scr->SizeStringOffset + Scr->SizeStringWidth + SIZE_HINDENT,
485	             Scr->SizeFont.height + SIZE_VINDENT * 2,
486	             2, Scr->DefaultC, off, false, false);
487
488	FB(Scr->DefaultC.fore, Scr->DefaultC.back);
489	XmbDrawImageString(dpy, Scr->SizeWindow, Scr->SizeFont.font_set,
490	                   Scr->NormalGC, Scr->SizeStringOffset,
491	                   Scr->SizeFont.ascent + SIZE_VINDENT, str, 13);
492}
493
494void
495MoveResizeSizeWindow(int x, int y, unsigned int width, unsigned int height)
496{
497	XResizeWindow(dpy, Scr->SizeWindow, width, height);
498
499	if(Scr->CenterFeedbackWindow) {
500		RArea monitor = RLayoutGetAreaAtXY(Scr->BorderedLayout, x, y);
501
502		XMoveWindow(dpy, Scr->SizeWindow,
503		            monitor.x + monitor.width / 2 - width / 2,
504		            monitor.y + monitor.height / 2 - height / 2);
505	}
506}
507
508
509/*
510 * Various funcs for adjusting coordinates for windows based on
511 * resistances etc.
512 *
513 * XXX In desperate need of better commenting.
514 */
515static void
516_tryToPack(RArea *final, const RArea *cur_win)
517{
518	if(final->x >= cur_win->x + cur_win->width) {
519		return;
520	}
521	if(final->y >= cur_win->y + cur_win->height) {
522		return;
523	}
524	if(final->x + final->width <= cur_win->x) {
525		return;
526	}
527	if(final->y + final->height <= cur_win->y) {
528		return;
529	}
530
531	if(final->x + Scr->MovePackResistance > cur_win->x +
532	                cur_win->width) {  /* left */
533		final->x = MAX(final->x, cur_win->x + cur_win->width);
534		return;
535	}
536	if(final->x + final->width < cur_win->x +
537	                Scr->MovePackResistance) {  /* right */
538		final->x = MIN(final->x, cur_win->x - final->width);
539		return;
540	}
541	if(final->y + Scr->MovePackResistance > cur_win->y +
542	                cur_win->height) {  /* top */
543		final->y = MAX(final->y, cur_win->y + cur_win->height);
544		return;
545	}
546	if(final->y + final->height < cur_win->y +
547	                Scr->MovePackResistance) {  /* bottom */
548		final->y = MIN(final->y, cur_win->y - final->height);
549	}
550}
551
552static bool
553_tryToPackVsEachMonitor(const RArea *monitor_area, void *vfinal)
554{
555	_tryToPack((RArea *)vfinal, monitor_area);
556	return false;
557}
558
559void
560TryToPack(TwmWindow *tmp_win, int *x, int *y)
561{
562	TwmWindow   *t;
563	RArea cur_win;
564	RArea final = RAreaNew(*x, *y,
565	                       tmp_win->frame_width  + 2 * tmp_win->frame_bw,
566	                       tmp_win->frame_height + 2 * tmp_win->frame_bw);
567
568	/* Global layout is not a single rectangle, check against the
569	 * monitor borders */
570	if(Scr->BorderedLayout->horiz->len > 1) {
571		RAreaListForeach(
572		        Scr->BorderedLayout->monitors, _tryToPackVsEachMonitor, &final);
573	}
574
575	for(t = Scr->FirstWindow; t != NULL; t = t->next) {
576		if(t == tmp_win) {
577			continue;
578		}
579#ifdef WINBOX
580		if(t->winbox != tmp_win->winbox) {
581			continue;
582		}
583#endif
584		if(t->vs != tmp_win->vs) {
585			continue;
586		}
587		if(!t->mapped) {
588			continue;
589		}
590
591		cur_win = RAreaNew(t->frame_x, t->frame_y,
592		                   t->frame_width  + 2 * t->frame_bw,
593		                   t->frame_height + 2 * t->frame_bw);
594
595		_tryToPack(&final, &cur_win);
596	}
597
598	*x = final.x;
599	*y = final.y;
600}
601
602
603/*
604 * Directionals for TryToPush_be().  These differ from the specs for
605 * jump/pack/fill in functions. because there's an indeterminate option.
606 */
607typedef enum {
608	PD_ANY,
609	PD_BOTTOM,
610	PD_LEFT,
611	PD_RIGHT,
612	PD_TOP,
613} PushDirection;
614static void TryToPush_be(TwmWindow *tmp_win, int x, int y, PushDirection dir);
615
616void
617TryToPush(TwmWindow *tmp_win, int x, int y)
618{
619	TryToPush_be(tmp_win, x, y, PD_ANY);
620}
621
622static void
623TryToPush_be(TwmWindow *tmp_win, int x, int y, PushDirection dir)
624{
625	TwmWindow   *t;
626	int         newx, newy, ndir;
627	bool        move;
628	int         w, h;
629	int         winw = tmp_win->frame_width  + 2 * tmp_win->frame_bw;
630	int         winh = tmp_win->frame_height + 2 * tmp_win->frame_bw;
631
632	for(t = Scr->FirstWindow; t != NULL; t = t->next) {
633		if(t == tmp_win) {
634			continue;
635		}
636#ifdef WINBOX
637		if(t->winbox != tmp_win->winbox) {
638			continue;
639		}
640#endif
641		if(t->vs != tmp_win->vs) {
642			continue;
643		}
644		if(!t->mapped) {
645			continue;
646		}
647
648		w = t->frame_width  + 2 * t->frame_bw;
649		h = t->frame_height + 2 * t->frame_bw;
650		if(x >= t->frame_x + w) {
651			continue;
652		}
653		if(y >= t->frame_y + h) {
654			continue;
655		}
656		if(x + winw <= t->frame_x) {
657			continue;
658		}
659		if(y + winh <= t->frame_y) {
660			continue;
661		}
662
663		move = false;
664		if((dir == PD_ANY || dir == PD_LEFT) &&
665		                (x + Scr->MovePackResistance > t->frame_x + w)) {
666			newx = x - w;
667			newy = t->frame_y;
668			ndir = PD_LEFT;
669			move = true;
670		}
671		else if((dir == PD_ANY || dir == PD_RIGHT) &&
672		                (x + winw < t->frame_x + Scr->MovePackResistance)) {
673			newx = x + winw;
674			newy = t->frame_y;
675			ndir = PD_RIGHT;
676			move = true;
677		}
678		else if((dir == PD_ANY || dir == PD_TOP) &&
679		                (y + Scr->MovePackResistance > t->frame_y + h)) {
680			newx = t->frame_x;
681			newy = y - h;
682			ndir = PD_TOP;
683			move = true;
684		}
685		else if((dir == PD_ANY || dir == PD_BOTTOM) &&
686		                (y + winh < t->frame_y + Scr->MovePackResistance)) {
687			newx = t->frame_x;
688			newy = y + winh;
689			ndir = PD_BOTTOM;
690			move = true;
691		}
692		if(move) {
693			TryToPush_be(t, newx, newy, ndir);
694			TryToPack(t, &newx, &newy);
695			ConstrainByBorders(tmp_win,
696			                   &newx, t->frame_width  + 2 * t->frame_bw,
697			                   &newy, t->frame_height + 2 * t->frame_bw);
698			SetupWindow(t, newx, newy, t->frame_width, t->frame_height, -1);
699		}
700	}
701}
702
703
704void
705TryToGrid(TwmWindow *tmp_win, int *x, int *y)
706{
707	int w    = tmp_win->frame_width  + 2 * tmp_win->frame_bw;
708	int h    = tmp_win->frame_height + 2 * tmp_win->frame_bw;
709	int grav = ((tmp_win->hints.flags & PWinGravity)
710	            ? tmp_win->hints.win_gravity : NorthWestGravity);
711
712	switch(grav) {
713		case ForgetGravity :
714		case StaticGravity :
715		case NorthWestGravity :
716		case NorthGravity :
717		case WestGravity :
718		case CenterGravity :
719			*x = ((*x - Scr->BorderLeft) / Scr->XMoveGrid) * Scr->XMoveGrid
720			     + Scr->BorderLeft;
721			*y = ((*y - Scr->BorderTop) / Scr->YMoveGrid) * Scr->YMoveGrid
722			     + Scr->BorderTop;
723			break;
724		case NorthEastGravity :
725		case EastGravity :
726			*x = (((*x + w - Scr->BorderLeft) / Scr->XMoveGrid) *
727			      Scr->XMoveGrid) - w + Scr->BorderLeft;
728			*y = ((*y - Scr->BorderTop) / Scr->YMoveGrid) *
729			     Scr->YMoveGrid + Scr->BorderTop;
730			break;
731		case SouthWestGravity :
732		case SouthGravity :
733			*x = ((*x - Scr->BorderLeft) / Scr->XMoveGrid) * Scr->XMoveGrid
734			     + Scr->BorderLeft;
735			*y = (((*y + h - Scr->BorderTop) / Scr->YMoveGrid) * Scr->YMoveGrid)
736			     - h + Scr->BorderTop;
737			break;
738		case SouthEastGravity :
739			*x = (((*x + w - Scr->BorderLeft) / Scr->XMoveGrid) *
740			      Scr->XMoveGrid) - w + Scr->BorderLeft;
741			*y = (((*y + h - Scr->BorderTop) / Scr->YMoveGrid) *
742			      Scr->YMoveGrid) - h + Scr->BorderTop;
743			break;
744	}
745}
746
747
748
749#ifdef WINBOX
750/*
751 * Functions related to keeping windows from being placed off-screen (or
752 * off-screen too far).  Involved in handling of params like DontMoveOff
753 * and MoveOffResistance, etc.
754 */
755static void ConstrainLeftTop(int *value, int border);
756static void ConstrainRightBottom(int *value, int size1, int border, int size2);
757#endif
758
759bool
760ConstrainByLayout(RLayout *layout, int move_off_res, int *left, int width,
761                  int *top, int height)
762{
763	RArea area = RAreaNew(*left, *top, width, height);
764	int limit;
765	bool clipped = false;
766
767	limit = RLayoutFindBottomEdge(layout, &area) - height + 1;
768	if(area.y > limit) {
769		if(move_off_res >= 0 && area.y >= limit + move_off_res) {
770			area.y -= move_off_res;
771		}
772		else {
773			area.y = limit;
774			clipped = true;
775		}
776	}
777
778	limit = RLayoutFindRightEdge(layout, &area) - width + 1;
779	if(area.x > limit) {
780		if(move_off_res >= 0 && area.x >= limit + move_off_res) {
781			area.x -= move_off_res;
782		}
783		else {
784			area.x = limit;
785			clipped = true;
786		}
787	}
788
789	limit = RLayoutFindLeftEdge(layout, &area);
790	if(area.x < limit) {
791		if(move_off_res >= 0 && area.x <= limit - move_off_res) {
792			area.x += move_off_res;
793		}
794		else {
795			area.x = limit;
796			clipped = true;
797		}
798	}
799
800	limit = RLayoutFindTopEdge(layout, &area);
801	if(area.y < limit) {
802		if(move_off_res >= 0 && area.y <= limit - move_off_res) {
803			area.y += move_off_res;
804		}
805		else {
806			area.y = limit;
807			clipped = true;
808		}
809	}
810
811	*left = area.x;
812	*top = area.y;
813
814	return clipped;
815}
816
817void
818ConstrainByBorders1(int *left, int width, int *top, int height)
819{
820	ConstrainByLayout(Scr->BorderedLayout, Scr->MoveOffResistance,
821	                  left, width, top, height);
822}
823
824void
825ConstrainByBorders(TwmWindow *twmwin, int *left, int width,
826                   int *top, int height)
827{
828	if(false) {
829		// Dummy
830	}
831#ifdef WINBOX
832	else if(twmwin->winbox) {
833		XWindowAttributes attr;
834		XGetWindowAttributes(dpy, twmwin->winbox->window, &attr);
835		ConstrainRightBottom(left, width, 0, attr.width);
836		ConstrainLeftTop(left, 0);
837		ConstrainRightBottom(top, height, 0, attr.height);
838		ConstrainLeftTop(top, 0);
839	}
840#endif
841	else {
842		ConstrainByBorders1(left, width, top, height);
843	}
844}
845
846#ifdef WINBOX
847static void
848ConstrainLeftTop(int *value, int border)
849{
850	if(*value < border) {
851		if(Scr->MoveOffResistance < 0 ||
852		                *value > border - Scr->MoveOffResistance) {
853			*value = border;
854		}
855		else if(Scr->MoveOffResistance > 0 &&
856		                *value <= border - Scr->MoveOffResistance) {
857			*value = *value + Scr->MoveOffResistance;
858		}
859	}
860}
861
862static void
863ConstrainRightBottom(int *value, int size1, int border, int size2)
864{
865	if(*value + size1 > size2 - border) {
866		if(Scr->MoveOffResistance < 0 ||
867		                *value + size1 < size2 - border + Scr->MoveOffResistance) {
868			*value = size2 - size1 - border;
869		}
870		else if(Scr->MoveOffResistance > 0 &&
871		                *value + size1 >= size2 - border + Scr->MoveOffResistance) {
872			*value = *value - Scr->MoveOffResistance;
873		}
874	}
875}
876#endif
877
878
879/*
880 * Zoom over to a particular window.
881 */
882void
883WarpToWindow(TwmWindow *t, bool must_raise)
884{
885	int x, y;
886
887	if(t->ring.cursor_valid) {
888		x = t->ring.curs_x;
889		y = t->ring.curs_y;
890#ifdef DEBUG
891		fprintf(stderr, "WarpToWindow: cursor_valid; x == %d, y == %d\n", x, y);
892#endif
893
894		/*
895		 * XXX is this correct with 3D borders? Easier check possible?
896		 * frame_bw is for the left border.
897		 */
898		if(x < t->frame_bw) {
899			x = t->frame_bw;
900		}
901		if(x >= t->frame_width + t->frame_bw) {
902			x  = t->frame_width + t->frame_bw - 1;
903		}
904		if(y < t->title_height + t->frame_bw) {
905			y = t->title_height + t->frame_bw;
906		}
907		if(y >= t->frame_height + t->frame_bw) {
908			y  = t->frame_height + t->frame_bw - 1;
909		}
910#ifdef DEBUG
911		fprintf(stderr, "WarpToWindow: adjusted    ; x := %d, y := %d\n", x, y);
912#endif
913	}
914	else {
915		x = t->frame_width / 2;
916		y = t->frame_height / 2;
917#ifdef DEBUG
918		fprintf(stderr, "WarpToWindow: middle; x := %d, y := %d\n", x, y);
919#endif
920	}
921#if 0
922	int dest_x, dest_y;
923	Window child;
924
925	/*
926	 * Check if the proposed position actually is visible. If not, raise the window.
927	 * "If the coordinates are contained in a mapped
928	 * child of dest_w, that child is returned to child_return."
929	 * We'll need to check for the right child window; the frame probably.
930	 * (What about XXX window boxes?)
931	 *
932	 * Alternatively, use XQueryPointer() which returns the root window
933	 * the pointer is in, but XXX that won't work for VirtualScreens.
934	 */
935	if(XTranslateCoordinates(dpy, t->frame, Scr->Root, x, y, &dest_x, &dest_y,
936	                         &child)) {
937		if(child != t->frame) {
938			must_raise = true;
939		}
940	}
941#endif
942	if(t->auto_raise || must_raise) {
943		AutoRaiseWindow(t);
944	}
945	if(! visible(t)) {
946		WorkSpace *wlist;
947
948		for(wlist = Scr->workSpaceMgr.workSpaceList; wlist != NULL;
949		                wlist = wlist->next) {
950			if(OCCUPY(t, wlist)) {
951				break;
952			}
953		}
954		if(wlist != NULL) {
955			GotoWorkSpace(Scr->currentvs, wlist);
956		}
957	}
958
959	XWarpPointer(dpy, None, Scr->Root, 0, 0, 0, 0, x + t->frame_x, y + t->frame_y);
960	SetFocus(t, EventTime);
961
962#ifdef DEBUG
963	{
964		Window root_return;
965		Window child_return;
966		int root_x_return;
967		int root_y_return;
968		int win_x_return;
969		int win_y_return;
970		unsigned int mask_return;
971
972		if(XQueryPointer(dpy, t->frame, &root_return, &child_return, &root_x_return,
973		                 &root_y_return, &win_x_return, &win_y_return, &mask_return)) {
974			fprintf(stderr,
975			        "XQueryPointer: root_return=%x, child_return=%x, root_x_return=%d, root_y_return=%d, win_x_return=%d, win_y_return=%d\n",
976			        root_return, child_return, root_x_return, root_y_return, win_x_return,
977			        win_y_return);
978		}
979	}
980#endif
981}
982
983
984/*
985 * ICCCM Client Messages - Section 4.2.8 of the ICCCM dictates that all
986 * client messages will have the following form:
987 *
988 *     event type       ClientMessage
989 *     message type     XA_WM_PROTOCOLS
990 *     window           tmp->w
991 *     format           32
992 *     data[0]          message atom
993 *     data[1]          time stamp
994 */
995void
996send_clientmessage(Window w, Atom a, Time timestamp)
997{
998	XClientMessageEvent ev;
999
1000	ev.type = ClientMessage;
1001	ev.window = w;
1002	ev.message_type = XA_WM_PROTOCOLS;
1003	ev.format = 32;
1004	ev.data.l[0] = a;
1005	ev.data.l[1] = timestamp;
1006	XSendEvent(dpy, w, False, 0L, (XEvent *) &ev);
1007}
1008
1009
1010/*
1011 * Create synthetic WM_HINTS info for windows.  When a window specifies
1012 * stuff, we should probably pay attention to it (though we don't
1013 * always; x-ref comments in AddWindow() especially about focus).
1014 * However, when it doesn't tell us anything at all, we should assume
1015 * something useful.  "Window managers are free to assume convenient
1016 * values for all fields of the WM_HINTS property if a window is mapped
1017 * without one."  (ICCCM Ch. 4,
1018 * <https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#Client_Properties>).
1019 *
1020 * Specifically, we assume it wants us to give it focus.  It's fairly
1021 * bogus for a window not to tell us anything, but e.g current versions
1022 * of Chrome do (don't do) just that.  So we better make up something
1023 * useful.
1024 *
1025 * Should probably be some configurability for this, so make the func
1026 * take the window, even though we don't currently do anything useful
1027 * with it...
1028 */
1029XWMHints *
1030gen_synthetic_wmhints(TwmWindow *win)
1031{
1032	XWMHints *hints;
1033
1034	hints = XAllocWMHints();
1035	if(!hints) {
1036		return NULL;
1037	}
1038
1039	/*
1040	 * Reasonable defaults.  Takes input, in normal state.
1041	 *
1042	 * XXX Make configurable?
1043	 */
1044	hints->flags = InputHint | StateHint;
1045	hints->input = True;
1046	hints->initial_state = NormalState;
1047
1048	return hints;
1049}
1050
1051
1052/**
1053 * Perform whatever adaptations of WM_HINTS info we do.
1054 *
1055 * Most of these relate to focus, but we also fiddle with group
1056 * membership.
1057 */
1058XWMHints *
1059munge_wmhints(TwmWindow *win, XWMHints *hints)
1060{
1061	/*
1062	 * If we have WM_HINTS, but they don't tell us anything about focus,
1063	 * force it to true for our purposes.
1064	 *
1065	 * CL: Having with not willing focus cause problems with AutoSqueeze
1066	 * and a few others things. So I suppress it. And the whole focus
1067	 * thing is buggy anyway.
1068	 */
1069	if(!(hints->flags & InputHint)) {
1070		hints->input = True;
1071	}
1072
1073	/*
1074	 * Now we're expecting to give the window focus if it asked for it
1075	 * via WM_HINTS, if it didn't say anything one way or the other in
1076	 * WM_HINTS, or if it didn't give us any WM_HINTS at all.  But if it
1077	 * explicitly asked not to, we don't give it unless overridden by
1078	 * config.
1079	 */
1080	if(Scr->ForceFocus || IsInList(Scr->ForceFocusL, win)) {
1081		hints->input = True;
1082	}
1083
1084
1085	/* Setup group bits */
1086	if(hints->flags & WindowGroupHint) {
1087		win->group = hints->window_group;
1088		if(win->group) {
1089			/*
1090			 * GTK windows often have a spurious "group leader" window which is
1091			 * never reported to us and therefore does not really exist.  This
1092			 * is annoying because we treat group members a lot like transient
1093			 * windows.  Look for that here. It is in fact a duplicate of the
1094			 * WM_CLIENT_LEADER property.
1095			 */
1096			if(win->group != win->w && !GetTwmWindow(win->group)) {
1097				win->group = 0;
1098			}
1099		}
1100	}
1101	else {
1102		win->group = 0;
1103	}
1104
1105	return hints;
1106}
1107
1108
1109/**
1110 * [Re]set a window's name.  This goes over the available naming sources
1111 * for the window and points the TwmWindow::name at the appropriate one.
1112 * It may also set a property to signal other EWMH-aware clients when
1113 * we're naming it a way they can't see themselves.
1114 *
1115 * \note This should rarely be called directly; apply_window_name()
1116 * should be used instead.  It's split out because we need to do this
1117 * step individually in AddWindow().
1118 *
1119 * \note Note also that we never need to worry about freeing the
1120 * TwmWindow::name; it always points to one of the TwmWindow::names
1121 * values (which are free'd by the event handler when they change) or to
1122 * NoName (which is static).  So we can just casually flip it around at
1123 * will.
1124 */
1125bool
1126set_window_name(TwmWindow *win)
1127{
1128	char *newname = NULL;
1129#define TRY(fld) { \
1130                if(newname == NULL && win->names.fld != NULL) { \
1131                        newname = win->names.fld; \
1132                } \
1133        }
1134	TRY(ctwm_wm_name)
1135#ifdef EWMH
1136	TRY(net_wm_name)
1137#endif
1138	TRY(wm_name)
1139#undef TRY
1140
1141	if(newname == NULL) {
1142		newname = NoName;
1143	}
1144	if(win->name == newname) {
1145		return false; // Nothing to do
1146	}
1147
1148	// Now we know what to call it
1149	win->name = newname;
1150
1151#ifdef EWMH
1152	// EWMH says we set an additional property on any windows where what
1153	// we consider the name isn't what's in _NET_WM_NAME, so pagers etc
1154	// can call it the same as we do.
1155	//
1156	// The parts of the text describing it conflict a little; at one
1157	// place, it implies this should be set unless we're using
1158	// _NET_WM_NAME, in another it seems to suggest WM_NAME should be
1159	// considered applicable too.  I choose to implement it excluding
1160	// both, so this only gets set if we're overriding either standard
1161	// naming (probably rare).
1162	if(win->name != win->names.net_wm_name && win->name != win->names.wm_name) {
1163		// XXX We're not doing any checking of the encoding here...  I
1164		// don't see that Xlib helps us any, so we probably have to fall
1165		// back to iconv?  That came into the base in POSIX 2008, but was
1166		// in XSI back into the 90's I believe?
1167		XChangeProperty(dpy, win->w, XA__NET_WM_VISIBLE_NAME, XA_UTF8_STRING,
1168		                8, PropModeReplace, (unsigned char *)win->name,
1169		                strlen(win->name));
1170	}
1171	else {
1172		XDeleteProperty(dpy, win->w, XA__NET_WM_VISIBLE_NAME);
1173	}
1174#endif // EWMH
1175
1176	// We set a name
1177	return true;
1178}
1179
1180
1181/**
1182 * [Re]set and apply changes to a window's name.  This is called after
1183 * we've received a new WM_NAME (or other name-setting) property, to
1184 * update our titlebars, icon managers, etc.
1185 */
1186void
1187apply_window_name(TwmWindow *win)
1188{
1189	/* [Re]set ->name */
1190	if(set_window_name(win) == false) {
1191		// No change
1192		return;
1193	}
1194	win->nameChanged = true;
1195
1196
1197	/* Update the active name */
1198	{
1199		XRectangle inc_rect;
1200		XRectangle logical_rect;
1201
1202		XmbTextExtents(Scr->TitleBarFont.font_set,
1203		               win->name, strlen(win->name),
1204		               &inc_rect, &logical_rect);
1205		win->name_width = logical_rect.width;
1206	}
1207
1208	/* recompute the priority if necessary */
1209	if(Scr->AutoPriority) {
1210		OtpRecomputePrefs(win);
1211	}
1212
1213	SetupWindow(win, win->frame_x, win->frame_y,
1214	            win->frame_width, win->frame_height, -1);
1215
1216	if(win->title_w) {
1217		XClearArea(dpy, win->title_w, 0, 0, 0, 0, True);
1218	}
1219	if(Scr->AutoOccupy) {
1220		WmgrRedoOccupation(win);
1221	}
1222
1223#if 0
1224	/* Experimental, not yet working. */
1225	{
1226		ColorPair cp;
1227		int f, b;
1228
1229		f = GetColorFromList(Scr->TitleForegroundL, win->name,
1230		                     &win->class, &cp.fore);
1231		b = GetColorFromList(Scr->TitleBackgroundL, win->name,
1232		                     &win->class, &cp.back);
1233		if(f || b) {
1234			if(Scr->use3Dtitles  && !Scr->BeNiceToColormap) {
1235				GetShadeColors(&cp);
1236			}
1237			win->title = cp;
1238		}
1239		f = GetColorFromList(Scr->BorderColorL, win->name,
1240		                     &win->class, &cp.fore);
1241		b = GetColorFromList(Scr->BorderColorL, win->name,
1242		                     &win->class, &cp.back);
1243		if(f || b) {
1244			if(Scr->use3Dborders && !Scr->BeNiceToColormap) {
1245				GetShadeColors(&cp);
1246			}
1247			win->borderC = cp;
1248		}
1249
1250		f = GetColorFromList(Scr->BorderTileForegroundL, win->name,
1251		                     &win->class, &cp.fore);
1252		b = GetColorFromList(Scr->BorderTileBackgroundL, win->name,
1253		                     &win->class, &cp.back);
1254		if(f || b) {
1255			if(Scr->use3Dborders && !Scr->BeNiceToColormap) {
1256				GetShadeColors(&cp);
1257			}
1258			win->border_tile = cp;
1259		}
1260	}
1261#endif
1262
1263	/*
1264	 * If we haven't set a separate icon name, we use the window name, so
1265	 * we need to update it.
1266	 */
1267	if(win->names.icon_set == false) {
1268		apply_window_icon_name(win);
1269	}
1270	AutoPopupMaybe(win);
1271
1272	return;
1273}
1274
1275
1276/**
1277 * [Re]set a window's icon name.  As with the window name version in
1278 * set_window_name(), this is mostly separate so the AddWindow() process
1279 * can call it.
1280 *
1281 * \note As with TwmWindow::name, we never want to try free()'ing or the
1282 * like TwmWindow::icon_name.
1283 *
1284 * \sa set_window_name() for details; this is just the icon name
1285 * equivalent of it.
1286 */
1287bool
1288set_window_icon_name(TwmWindow *win)
1289{
1290	char *newname = NULL;
1291#define TRY(fld) { \
1292                if(newname == NULL && win->names.fld != NULL) { \
1293                        newname = win->names.fld; \
1294                        win->names.icon_set = true; \
1295                } \
1296        }
1297	TRY(ctwm_wm_icon_name)
1298#ifdef EWMH
1299	TRY(net_wm_icon_name)
1300#endif
1301	TRY(wm_icon_name)
1302#undef TRY
1303
1304	// Our fallback for icon names is the window name.  Flag when we're
1305	// doing that, so the window name handler can know when it needs to
1306	// call us.
1307	if(newname == NULL) {
1308		newname = win->name;
1309		win->names.icon_set = false;
1310	}
1311	if(win->icon_name == newname) {
1312		return false; // Nothing to do
1313	}
1314
1315	// A name is chosen
1316	win->icon_name = newname;
1317
1318#ifdef EWMH
1319	// EWMH asks for _NET_WM_VISIBLE_ICON_NAME in various cases where
1320	// we're not using 'standard' properties' values.  x-ref comments above in
1321	// set_window_name() about the parallel property for the window name
1322	// for various caveats.
1323	if(win->icon_name != win->names.net_wm_icon_name
1324	                && win->icon_name != win->names.wm_icon_name) {
1325		// XXX Still encoding questionable; x-ref above.
1326		XChangeProperty(dpy, win->w, XA__NET_WM_VISIBLE_ICON_NAME,
1327		                XA_UTF8_STRING,
1328		                8, PropModeReplace, (unsigned char *)win->icon_name,
1329		                strlen(win->icon_name));
1330	}
1331	else {
1332		XDeleteProperty(dpy, win->w, XA__NET_WM_VISIBLE_ICON_NAME);
1333	}
1334#endif // EWMH
1335
1336	// Did it
1337	return true;
1338}
1339
1340
1341/**
1342 * [Re]set and apply changes to a window's icon name.  This is called
1343 * after we've received a new WM_ICON_NAME (or other name-setting)
1344 * property, to update our titlebars, icon managers, etc.
1345 *
1346 * \sa apply_window_name() which does the same for the window title.
1347 */
1348void
1349apply_window_icon_name(TwmWindow *win)
1350{
1351	/* [Re]set ->icon_name */
1352	if(set_window_icon_name(win) == false) {
1353		// No change
1354		return;
1355	}
1356
1357
1358	/* Lot less to do for icons... */
1359	RedoIcon(Tmp_win);
1360	AutoPopupMaybe(Tmp_win);
1361
1362	return;
1363}
1364