1/*
2 * Copyright 1989 Massachusetts Institute of Technology
3 * Copyright 1992 Claude Lecommandeur.
4 */
5
6/**********************************************************************
7 *
8 * $XConsortium: icons.c,v 1.22 91/07/12 09:58:38 dave Exp $
9 *
10 * Icon releated routines
11 *
12 * 10-Apr-89 Tom LaStrange        Initial Version.
13 *
14 * Do the necessary modification to be integrated in ctwm.
15 * Can no longer be used for the standard twm.
16 *
17 * 22-April-92 Claude Lecommandeur.
18 *
19 *
20 **********************************************************************/
21
22#include "ctwm.h"
23
24#include <stdio.h>
25#include <stdlib.h>
26
27#include <X11/extensions/shape.h>
28
29#include "drawing.h"
30#include "screen.h"
31#include "iconmgr.h"
32#include "icons.h"
33#include "otp.h"
34#include "list.h"
35#include "parse.h"
36#include "util.h"
37#include "animate.h"
38#include "image.h"
39#include "win_utils.h"
40#include "workspace_manager.h"
41#include "xparsegeometry.h"
42
43
44static void splitIconRegionEntry(IconEntry *ie, RegGravity grav1,
45                                 RegGravity grav2, int w, int h);
46static void PlaceIcon(TwmWindow *tmp_win, int def_x, int def_y,
47                      int *final_x, int *final_y);
48static IconEntry *FindIconEntry(TwmWindow *tmp_win, IconRegion **irp);
49static IconEntry *prevIconEntry(IconEntry *ie, IconRegion *ir);
50static void mergeEntries(IconEntry *old, IconEntry *ie);
51static void ReshapeIcon(Icon *icon);
52static int roundUp(int v, int multiple);
53static Image *LookupIconNameOrClass(TwmWindow *tmp_win, Icon *icon,
54                                    char **pattern);
55
56
57
58/*
59 ****************************************************************
60 *
61 * First some bits related to figuring out where icons go.  Lots of
62 * IconRegion handling stuff, handling of IconEntry tracking, etc.
63 *
64 ****************************************************************
65 */
66
67
68/*
69 * This function operates in very weird and obtuse ways, especially in
70 * how it handles vertical vs. horizontal in weird recursive calls.  Part
71 * of this is what previously allowed specs with "hgrav vgrav" instead of
72 * the proper "vgrav hgrav" to sorta-work.  This should be broken up at
73 * some point into clean h/v functions, but because of the recursion it's
74 * not exactly trivial.  The parsing code now enforces v/h, so at least
75 * things can be known to come in in the right order initially.  Revisit
76 * someday.
77 */
78static void
79splitIconRegionEntry(IconEntry *ie, RegGravity grav1, RegGravity grav2,
80                     int w, int h)
81{
82	switch(grav1) {
83		case GRAV_NORTH:
84		case GRAV_SOUTH:
85			if(w != ie->w) {
86				splitIconRegionEntry(ie, grav2, grav1, w, ie->h);
87			}
88			if(h != ie->h) {
89				IconEntry *new = calloc(1, sizeof(IconEntry));
90				new->next = ie->next;
91				ie->next = new;
92				new->x = ie->x;
93				new->h = (ie->h - h);
94				new->w = ie->w;
95				ie->h = h;
96				if(grav1 == GRAV_SOUTH) {
97					new->y = ie->y;
98					ie->y = new->y + new->h;
99				}
100				else {
101					new->y = ie->y + ie->h;
102				}
103			}
104			break;
105		case GRAV_EAST:
106		case GRAV_WEST:
107			if(h != ie->h) {
108				splitIconRegionEntry(ie, grav2, grav1, ie->w, h);
109			}
110			if(w != ie->w) {
111				IconEntry *new = calloc(1, sizeof(IconEntry));
112				new->next = ie->next;
113				ie->next = new;
114				new->y = ie->y;
115				new->w = (ie->w - w);
116				new->h = ie->h;
117				ie->w = w;
118				if(grav1 == GRAV_EAST) {
119					new->x = ie->x;
120					ie->x = new->x + new->w;
121				}
122				else {
123					new->x = ie->x + ie->w;
124				}
125			}
126			break;
127	}
128}
129
130
131/*
132 * Backend for parsing IconRegion config
133 */
134name_list **
135AddIconRegion(const char *geom, RegGravity grav1, RegGravity grav2,
136              int stepx, int stepy,
137              const char *ijust, const char *just, const char *align)
138{
139	IconRegion *ir;
140	int mask, tmp;
141
142	ir = malloc(sizeof(IconRegion));
143	ir->next = NULL;
144
145	if(Scr->LastRegion) {
146		Scr->LastRegion->next = ir;
147	}
148	Scr->LastRegion = ir;
149	if(!Scr->FirstRegion) {
150		Scr->FirstRegion = ir;
151	}
152
153	ir->entries = NULL;
154	ir->clientlist = NULL;
155	ir->grav1 = grav1;
156	ir->grav2 = grav2;
157	if(stepx <= 0) {
158		stepx = 1;
159	}
160	if(stepy <= 0) {
161		stepy = 1;
162	}
163	ir->stepx = stepx;
164	ir->stepy = stepy;
165	ir->x = ir->y = ir->w = ir->h = 0;
166
167	mask = RLayoutXParseGeometry(Scr->Layout, geom, &ir->x, &ir->y,
168	                             (unsigned int *)&ir->w, (unsigned int *)&ir->h);
169
170	if(mask & XNegative) {
171		ir->x += Scr->rootw - ir->w;
172	}
173	if(mask & YNegative) {
174		ir->y += Scr->rooth - ir->h;
175	}
176
177	ir->entries = calloc(1, sizeof(IconEntry));
178	ir->entries->x = ir->x;
179	ir->entries->y = ir->y;
180	ir->entries->w = ir->w;
181	ir->entries->h = ir->h;
182
183	if((tmp = ParseTitleJustification(ijust)) < 0) {
184		twmrc_error_prefix();
185		fprintf(stderr, "ignoring invalid IconRegion argument \"%s\"\n", ijust);
186		tmp = TJ_UNDEF;
187	}
188	ir->TitleJustification = tmp;
189
190	if((tmp = ParseIRJustification(just)) < 0) {
191		twmrc_error_prefix();
192		fprintf(stderr, "ignoring invalid IconRegion argument \"%s\"\n", just);
193		tmp = IRJ_UNDEF;
194	}
195	ir->Justification = tmp;
196
197	if((tmp = ParseAlignement(align)) < 0) {
198		twmrc_error_prefix();
199		fprintf(stderr, "ignoring invalid IconRegion argument \"%s\"\n", align);
200		tmp = IRA_UNDEF;
201	}
202	ir->Alignement = tmp;
203
204	return(&(ir->clientlist));
205}
206
207
208/*
209 * Figure out where to put a window's icon based on the IconRegion
210 * specifications given in config.  Passed def_[xy] which are used
211 * if we don't find a better location ourselves.  Returns the chosen
212 * location in final_[xy], and also sets the IconRegion in tmp_win->icon
213 * if we chose one.
214 */
215static void
216PlaceIcon(TwmWindow *tmp_win, int def_x, int def_y,
217          int *final_x, int *final_y)
218{
219	IconRegion  *ir, *oldir;
220	IconEntry   *ie;
221	int         w, h;
222
223	const int iconWidth = tmp_win->icon->border_width * 2
224	                      + (Scr->ShrinkIconTitles ? tmp_win->icon->width
225	                         : tmp_win->icon->w_width);
226	const int iconHeight = tmp_win->icon->border_width * 2
227	                       + tmp_win->icon->w_height;
228
229	/*
230	 * First, check to see if the window is in a region's client list
231	 * (i.e., the win-list on an IconRegion specifier in the config).
232	 */
233	ie = NULL;
234	for(ir = Scr->FirstRegion; ir; ir = ir->next) {
235		if(LookInList(ir->clientlist, tmp_win->name, &tmp_win->class)) {
236			/*
237			 * Found one that claims it.  Figure the necessary local
238			 * size, based on the icon's side itself and the grid for
239			 * this IR.
240			 */
241			w = roundUp(iconWidth, ir->stepx);
242			h = roundUp(iconHeight, ir->stepy);
243
244			/* Find a currently-unused region that's big enough */
245			for(ie = ir->entries; ie; ie = ie->next) {
246				if(ie->used) {
247					continue;
248				}
249				if(ie->w >= w && ie->h >= h) {
250					/* Bingo */
251					break;
252				}
253			}
254
255			/* If we found one, we're done here */
256			if(ie) {
257				break;
258			}
259		}
260	}
261
262
263	/*
264	 * If we found a slot in a region claiming it, ie is set to the
265	 * IconEntry.  If not, start over and find the first available berth.
266	 */
267	if(!ie) {
268		for(ir = Scr->FirstRegion; ir; ir = ir->next) {
269			w = roundUp(iconWidth, ir->stepx);
270			h = roundUp(iconHeight, ir->stepy);
271			for(ie = ir->entries; ie; ie = ie->next) {
272				if(ie->used) {
273					continue;
274				}
275				if(ie->w >= w && ie->h >= h) {
276					/* Bingo */
277					break;
278				}
279			}
280			if(ie) {
281				break;
282			}
283		}
284	}
285
286	/* Stash for comparison */
287	oldir = tmp_win->icon->ir;
288
289	/*
290	 * If we found an appropriate region, use it.  Else, we have no
291	 * better idea, so use the x/y coords the caller passed us as our
292	 * basis.
293	 */
294	if(ie) {
295		/* XXX whatever sIRE() does */
296		splitIconRegionEntry(ie, ir->grav1, ir->grav2, w, h);
297
298		/* Adjust horizontal positioning based on IconRegionJustification */
299		switch(ir->Justification) {
300			case IRJ_LEFT:
301				*final_x = ie->x;
302				break;
303			case IRJ_UNDEF:
304			case IRJ_CENTER:
305				*final_x = ie->x + (ie->w - iconWidth) / 2;
306				break;
307			case IRJ_RIGHT:
308				*final_x = ie->x + ie->w - iconWidth;
309				break;
310			case IRJ_BORDER:
311				if(ir->grav2 == GRAV_EAST) {
312					*final_x = ie->x + ie->w - iconWidth;
313				}
314				else {
315					*final_x = ie->x;
316				}
317				break;
318		}
319
320		/* And vertical based on IconRegionAlignement */
321		switch(ir->Alignement) {
322			case IRA_TOP :
323				*final_y = ie->y;
324				break;
325			case IRA_UNDEF :
326			case IRA_CENTER :
327				*final_y = ie->y + (ie->h - iconHeight) / 2;
328				break;
329			case IRA_BOTTOM :
330				*final_y = ie->y + ie->h - iconHeight;
331				break;
332			case IRA_BORDER :
333				if(ir->grav1 == GRAV_SOUTH) {
334					*final_y = ie->y + ie->h - iconHeight;
335				}
336				else {
337					*final_y = ie->y;
338				}
339				break;
340		}
341
342		/* Tell the win/icon what region it's in, and the entry what's in it */
343		tmp_win->icon->ir = ir;
344		ie->used = true;
345		ie->twm_win = tmp_win;
346	}
347	else {
348		/* No better idea, tell caller to use theirs */
349		*final_x = def_x;
350		*final_y = def_y;
351		tmp_win->icon->ir = NULL;
352		return;
353		/* XXX Should we be doing the below in this case too? */
354	}
355
356	/* Alterations if ShrinkIconTitles is set */
357	if(Scr->ShrinkIconTitles && tmp_win->icon->has_title) {
358		*final_x -= GetIconOffset(tmp_win->icon);
359		if(tmp_win->icon->ir != oldir) {
360			ReshapeIcon(tmp_win->icon);
361		}
362	}
363
364	return;
365}
366
367
368/*
369 * Look up an IconEntry holding the icon for a given window, and
370 * optionally stash its IconRegion in irp.  Used internally in
371 * IconDown().
372 */
373static IconEntry *
374FindIconEntry(TwmWindow *tmp_win, IconRegion **irp)
375{
376	IconRegion  *ir;
377	IconEntry   *ie;
378
379	for(ir = Scr->FirstRegion; ir; ir = ir->next) {
380		for(ie = ir->entries; ie; ie = ie->next)
381			if(ie->twm_win == tmp_win) {
382				if(irp) {
383					*irp = ir;
384				}
385				return ie;
386			}
387	}
388	return NULL;
389}
390
391
392/*
393 * Find prior IE in list.  Used internally in IconDown().
394 */
395static IconEntry *
396prevIconEntry(IconEntry *ie, IconRegion *ir)
397{
398	IconEntry   *ip;
399
400	if(ie == ir->entries) {
401		return NULL;
402	}
403	for(ip = ir->entries; ip->next != ie; ip = ip->next)
404		;
405	return ip;
406}
407
408
409/*
410 * Merge two adjacent IconEntry's.  old is being freed; and is adjacent
411 * to ie.  Merge regions together.
412 */
413static void
414mergeEntries(IconEntry *old, IconEntry *ie)
415{
416	if(old->y == ie->y) {
417		ie->w = old->w + ie->w;
418		if(old->x < ie->x) {
419			ie->x = old->x;
420		}
421	}
422	else {
423		ie->h = old->h + ie->h;
424		if(old->y < ie->y) {
425			ie->y = old->y;
426		}
427	}
428}
429
430
431
432
433/*
434 ****************************************************************
435 *
436 * Next, the bits related to creating and putting together the icon
437 * windows, as well as destroying them.
438 *
439 ****************************************************************
440 */
441
442
443/*
444 * Create the window scaffolding for an icon.  Called when we need to
445 * make one, e.g. the first time a window is iconified.
446 */
447void
448CreateIconWindow(TwmWindow *tmp_win, int def_x, int def_y)
449{
450	unsigned long event_mask;
451	unsigned long valuemask;            /* mask for create windows */
452	XSetWindowAttributes attributes;    /* attributes for create windows */
453	int final_x, final_y;
454	int x;
455	Icon        *icon;
456	Image       *image = NULL;
457	char        *pattern;
458
459	icon = malloc(sizeof(struct Icon));
460
461	icon->otp           = NULL;
462	icon->border        = Scr->IconBorderColor;
463	icon->iconc.fore    = Scr->IconC.fore;
464	icon->iconc.back    = Scr->IconC.back;
465	icon->title_shrunk  = false;
466
467	GetColorFromList(Scr->IconBorderColorL, tmp_win->name, &tmp_win->class,
468	                 &icon->border);
469	GetColorFromList(Scr->IconForegroundL, tmp_win->name, &tmp_win->class,
470	                 &icon->iconc.fore);
471	GetColorFromList(Scr->IconBackgroundL, tmp_win->name, &tmp_win->class,
472	                 &icon->iconc.back);
473	if(Scr->use3Diconmanagers && !Scr->BeNiceToColormap) {
474		GetShadeColors(&icon->iconc);
475	}
476
477	FB(icon->iconc.fore, icon->iconc.back);
478
479	icon->match   = match_none;
480	icon->image   = NULL;
481	icon->ir      = NULL;
482
483	tmp_win->forced = false;
484	icon->w_not_ours = false;
485
486	pattern = NULL;
487
488	/* now go through the steps to get an icon window,  if ForceIcon is
489	 * set, then no matter what else is defined, the bitmap from the
490	 * .twmrc file is used
491	 */
492	if(Scr->ForceIcon) {
493		image = LookupIconNameOrClass(tmp_win, icon, &pattern);
494	}
495
496#ifdef EWMH
497	/*
498	 * Look to see if there is a _NET_WM_ICON property to provide an icon.
499	 */
500	if(image == NULL) {
501		image = EwmhGetIcon(Scr, tmp_win);
502		if(image != NULL) {
503			icon->match   = match_net_wm_icon;
504			icon->width   = image->width;
505			icon->height  = image->height;
506			icon->image   = image;
507		}
508	}
509#endif /* EWMH */
510
511	/* if the pixmap is still NULL, we didn't get one from the above code,
512	 * that could mean that ForceIcon was not set, or that the window
513	 * was not in the Icons list, now check the WM hints for an icon
514	 */
515	if(image == NULL && tmp_win->wmhints->flags & IconPixmapHint) {
516		unsigned int IconDepth, IconWidth, IconHeight;
517
518		if(XGetGeometry(dpy, tmp_win->wmhints->icon_pixmap,
519		                &JunkRoot, &JunkX, &JunkY, &IconWidth, &IconHeight, &JunkBW, &IconDepth)) {
520			image = AllocImage();
521			image->width  = IconWidth;
522			image->height = IconHeight;
523			image->pixmap = XCreatePixmap(dpy, Scr->Root, image->width,
524			                              image->height, Scr->d_depth);
525			if(IconDepth == Scr->d_depth)
526				XCopyArea(dpy, tmp_win->wmhints->icon_pixmap, image->pixmap, Scr->NormalGC,
527				          0, 0, image->width, image->height, 0, 0);
528			else
529				XCopyPlane(dpy, tmp_win->wmhints->icon_pixmap, image->pixmap, Scr->NormalGC,
530				           0, 0, image->width, image->height, 0, 0, 1);
531
532			icon->width   = image->width;
533			icon->height  = image->height;
534			icon->match   = match_icon_pixmap_hint;
535
536			if((tmp_win->wmhints->flags & IconMaskHint) &&
537			                XGetGeometry(dpy, tmp_win->wmhints->icon_mask,
538			                             &JunkRoot, &JunkX, &JunkY, &IconWidth, &IconHeight, &JunkBW, &IconDepth) &&
539			                (IconDepth == 1)) {
540				GC gc;
541
542				image->mask = XCreatePixmap(dpy, Scr->Root, IconWidth, IconHeight, 1);
543				if(image->mask) {
544					gc = XCreateGC(dpy, image->mask, 0, NULL);
545					if(gc) {
546						XCopyArea(dpy, tmp_win->wmhints->icon_mask, image->mask, gc,
547						          0, 0, IconWidth, IconHeight, 0, 0);
548						XFreeGC(dpy, gc);
549					}
550				}
551			}
552			icon->image = image;
553		}
554	}
555
556	/* if we still haven't got an icon, let's look in the Icon list
557	 * if ForceIcon is not set
558	 */
559	if(image == NULL && !Scr->ForceIcon) {
560		image = LookupIconNameOrClass(tmp_win, icon, &pattern);
561	}
562
563	/* if we still don't have an icon, assign the UnknownIcon */
564	if(image == NULL && Scr->UnknownImage != NULL) {
565		image = Scr->UnknownImage;
566		icon->match   = match_unknown_default;
567		icon->width   = image->width;
568		icon->height  = image->height;
569		icon->image   = image;
570	}
571
572	if(image == NULL) {
573		icon->height = 0;
574		icon->width  = 0;
575		valuemask    = 0;
576	}
577	else {
578		valuemask = CWBackPixmap;
579		attributes.background_pixmap = image->pixmap;
580	}
581
582	icon->border_width = Scr->IconBorderWidth;
583	if(Scr->NoIconTitlebar ||
584	                LookInNameList(Scr->NoIconTitle, tmp_win->icon_name) ||
585	                LookInList(Scr->NoIconTitle, tmp_win->name, &tmp_win->class)) {
586		icon->w_width  = icon->width;
587		icon->w_height = icon->height;
588		icon->x = 0;
589		icon->y = 0;
590		icon->has_title = false;
591	}
592	else {
593		XRectangle inc_rect;
594		XRectangle logical_rect;
595
596		XmbTextExtents(Scr->IconFont.font_set,
597		               tmp_win->icon_name, strlen(tmp_win->icon_name),
598		               &inc_rect, &logical_rect);
599		icon->w_width = logical_rect.width;
600
601		icon->w_width += 2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER);
602		if(icon->w_width > Scr->MaxIconTitleWidth) {
603			icon->w_width = Scr->MaxIconTitleWidth;
604		}
605		if(icon->w_width < icon->width) {
606			icon->x  = (icon->width - icon->w_width) / 2;
607			icon->x += Scr->IconManagerShadowDepth + ICON_MGR_IBORDER;
608			icon->w_width = icon->width;
609		}
610		else {
611			icon->x = Scr->IconManagerShadowDepth + ICON_MGR_IBORDER;
612		}
613		icon->y = icon->height + Scr->IconFont.height + Scr->IconManagerShadowDepth;
614		icon->w_height = icon->height + Scr->IconFont.height +
615		                 2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER);
616		icon->has_title = true;
617		if(icon->height) {
618			icon->border_width = 0;
619		}
620	}
621
622	event_mask = 0;
623	if(tmp_win->wmhints->flags & IconWindowHint) {
624		icon->w = tmp_win->wmhints->icon_window;
625		if(tmp_win->forced ||
626		                XGetGeometry(dpy, icon->w, &JunkRoot, &JunkX, &JunkY,
627		                             (unsigned int *)&icon->w_width, (unsigned int *)&icon->w_height,
628		                             &JunkBW, &JunkDepth) == 0) {
629			icon->w = None;
630			tmp_win->wmhints->flags &= ~IconWindowHint;
631		}
632		else {
633			image = NULL;
634			icon->w_not_ours = true;
635			icon->width  = icon->w_width;
636			icon->height = icon->w_height;
637			icon->image  = image;
638			icon->has_title = false;
639			event_mask = 0;
640		}
641	}
642	else {
643		icon->w = None;
644	}
645
646	if((image != NULL) &&
647	                image->mask != None &&
648	                !(tmp_win->wmhints->flags & IconWindowHint)) {
649		icon->border_width = 0;
650	}
651	if(icon->w == None) {
652		icon->w = XCreateSimpleWindow(dpy, Scr->Root,
653		                              0, 0,
654		                              icon->w_width, icon->w_height,
655		                              icon->border_width, icon->border, icon->iconc.back);
656		event_mask = ExposureMask;
657	}
658
659	if(Scr->AutoRaiseIcons || Scr->ShrinkIconTitles) {
660		event_mask |= EnterWindowMask | LeaveWindowMask;
661	}
662	event_mask |= KeyPressMask | ButtonPressMask | ButtonReleaseMask;
663
664	if(icon->w_not_ours) {
665		XWindowAttributes wattr;
666
667		XGetWindowAttributes(dpy, icon->w, &wattr);
668		if(wattr.all_event_masks & ButtonPressMask) {
669			event_mask &= ~ButtonPressMask;
670		}
671	}
672	XSelectInput(dpy, icon->w, event_mask);
673
674	if(icon->width == 0) {
675		icon->width = icon->w_width;
676	}
677	icon->bm_w = None;
678	if(image && !(tmp_win->wmhints->flags & IconWindowHint)) {
679		XRectangle rect;
680
681		x = GetIconOffset(icon);
682		icon->bm_w = XCreateWindow(dpy, icon->w, x, 0,
683		                           icon->width,
684		                           icon->height,
685		                           0, Scr->d_depth,
686		                           CopyFromParent,
687		                           Scr->d_visual, valuemask,
688		                           &attributes);
689		if(image->mask) {
690			XShapeCombineMask(dpy, icon->bm_w, ShapeBounding, 0, 0, image->mask, ShapeSet);
691			XShapeCombineMask(dpy, icon->w,    ShapeBounding, x, 0, image->mask, ShapeSet);
692		}
693		else if(icon->has_title) {
694			rect.x      = x;
695			rect.y      = 0;
696			rect.width  = icon->width;
697			rect.height = icon->height;
698			XShapeCombineRectangles(dpy, icon->w, ShapeBounding,
699			                        0, 0, &rect, 1, ShapeSet, 0);
700		}
701		if(icon->has_title) {
702			if(Scr->ShrinkIconTitles) {
703				rect.x      = x;
704				rect.y      = icon->height;
705				rect.width  = icon->width;
706				rect.height = icon->w_height - icon->height;
707				icon->title_shrunk = true;
708			}
709			else {
710				rect.x      = 0;
711				rect.y      = icon->height;
712				rect.width  = icon->w_width;
713				rect.height = icon->w_height - icon->height;
714				icon->title_shrunk = false;
715			}
716			XShapeCombineRectangles(dpy, icon->w, ShapeBounding,
717			                        0, 0, &rect, 1, ShapeUnion, 0);
718		}
719	}
720
721	if(pattern != NULL) {
722		AddToList(&tmp_win->iconslist, pattern, icon);
723	}
724
725	tmp_win->icon = icon;
726	/* I need to figure out where to put the icon window now, because
727	 * getting here means that I am going to make the icon visible
728	 */
729	final_x = final_y = 0;
730	if(tmp_win->wmhints->flags & IconPositionHint) {
731		final_x = tmp_win->wmhints->icon_x;
732		final_y = tmp_win->wmhints->icon_y;
733	}
734	else {
735		if(visible(tmp_win)) {
736			PlaceIcon(tmp_win, def_x, def_y, &final_x, &final_y);
737		}
738	}
739
740	if(visible(tmp_win) || (tmp_win->wmhints->flags & IconPositionHint)) {
741		if(final_x > Scr->rootw) {
742			final_x = Scr->rootw - icon->w_width - (2 * Scr->IconBorderWidth);
743		}
744		if(Scr->ShrinkIconTitles && icon->bm_w) {
745			if(final_x + (icon->w_width - icon->width) < 0) {
746				final_x = 0;
747			}
748		}
749		else {
750			if(final_x < 0) {
751				final_x = 0;
752			}
753		}
754		if(final_y > Scr->rooth)
755			final_y = Scr->rooth - icon->height -
756			          Scr->IconFont.height - 6 - (2 * Scr->IconBorderWidth);
757		if(final_y < 0) {
758			final_y = 0;
759		}
760
761		XMoveWindow(dpy, icon->w, final_x, final_y);
762		icon->w_x = final_x;
763		icon->w_y = final_y;
764	}
765	tmp_win->iconified = true;
766	OtpAdd(tmp_win, IconWin);
767
768	XMapSubwindows(dpy, icon->w);
769	XSaveContext(dpy, icon->w, TwmContext, (XPointer)tmp_win);
770	XSaveContext(dpy, icon->w, ScreenContext, (XPointer)Scr);
771	XDefineCursor(dpy, icon->w, Scr->IconCursor);
772	MaybeAnimate = true;
773}
774
775
776/*
777 * Delete TwmWindow.iconslist.
778 * Call it before deleting TwmWindow.icon, since we need to check
779 * that we're not deleting that Icon.
780 */
781void
782DeleteIconsList(TwmWindow *tmp_win)
783{
784	/*
785	 * Only the list itself needs to be freed, since the pointers it
786	 * contains point into various lists that belong to Scr.
787	 *
788	 * Rhialto: Hmmmm not quite sure about that! CreateIconWindow() above
789	 * always allocates a struct Icon, and doesn't attach it to Scr...
790	 * It is probably correct for the Image pointers inside those Icons though.
791	 */
792	name_list *nptr;
793	name_list *next;
794
795	for(nptr = tmp_win->iconslist; nptr != NULL;) {
796		next = nptr->next;
797		Icon *icon = (Icon *)nptr->ptr;
798		if(icon != tmp_win->icon) {
799			DeleteIcon(icon);
800		}
801		free(nptr->name);
802		free(nptr);
803		nptr = next;
804	}
805	tmp_win->iconslist = NULL;
806}
807
808
809/*
810 * Delete a single Icon.  Called iteratively from DeleteIconList(), and
811 * directly during window destruction.
812 */
813void
814DeleteIcon(Icon *icon)
815{
816	if(icon->w && !icon->w_not_ours) {
817		XDestroyWindow(dpy, icon->w);
818	}
819	ReleaseIconImage(icon);
820	free(icon);
821}
822
823
824/*
825 * Delete the Image from an icon, if it is not a shared one.  match_list
826 * ands match_unknown_default need not be freed.
827 *
828 * Formerly ReleaseImage()
829 */
830void
831ReleaseIconImage(Icon *icon)
832{
833	if(icon->match == match_icon_pixmap_hint ||
834	                icon->match == match_net_wm_icon) {
835		FreeImage(icon->image);
836	}
837}
838
839
840
841
842/*
843 ****************************************************************
844 *
845 * Bringing an icon up or down.
846 *
847 ****************************************************************
848 */
849
850
851/*
852 * Show up an icon.  Note that neither IconUp nor IconDown actually map
853 * or unmap the icon window; that's handled by the callers.  These
854 * functions limit themselves to figuring out where it should be, moving
855 * it (still unmapped) there, and linking/unlinking it from the iconentry
856 * lists.
857 */
858void
859IconUp(TwmWindow *tmp_win)
860{
861	int         x, y;
862	int         defx, defy;
863
864	/*
865	 * If the client specified a particular location, let's use it (this might
866	 * want to be an option at some point).  Otherwise, try to fit within the
867	 * icon region.
868	 */
869	if(tmp_win->wmhints->flags & IconPositionHint) {
870		return;
871	}
872
873	if(tmp_win->icon_moved) {
874		struct IconRegion *ir;
875		unsigned int iww, iwh;
876
877		if(!XGetGeometry(dpy, tmp_win->icon->w, &JunkRoot, &defx, &defy,
878		                 &iww, &iwh, &JunkBW, &JunkDepth)) {
879			return;
880		}
881
882		x = defx + ((int) iww) / 2;
883		y = defy + ((int) iwh) / 2;
884
885		for(ir = Scr->FirstRegion; ir; ir = ir->next) {
886			if(x >= ir->x && x < (ir->x + ir->w) &&
887			                y >= ir->y && y < (ir->y + ir->h)) {
888				break;
889			}
890		}
891		if(!ir) {
892			return;        /* outside icon regions, leave alone */
893		}
894	}
895
896	defx = -100;
897	defy = -100;
898	PlaceIcon(tmp_win, defx, defy, &x, &y);
899	if(x != defx || y != defy) {
900		XMoveWindow(dpy, tmp_win->icon->w, x, y);
901		tmp_win->icon->w_x = x;
902		tmp_win->icon->w_y = y;
903		tmp_win->icon_moved = false;    /* since we've restored it */
904	}
905	MaybeAnimate = true;
906	return;
907}
908
909
910/*
911 * Remove an icon from its displayed IconEntry.  x-ref comment on
912 * IconUp().
913 */
914void
915IconDown(TwmWindow *tmp_win)
916{
917	IconEntry   *ie, *ip, *in;
918	IconRegion  *ir;
919
920	ie = FindIconEntry(tmp_win, &ir);
921	if(ie) {
922		ie->twm_win = NULL;
923		ie->used = false;
924		ip = prevIconEntry(ie, ir);
925		in = ie->next;
926		for(;;) {
927			if(ip && ip->used == false &&
928			                ((ip->x == ie->x && ip->w == ie->w) ||
929			                 (ip->y == ie->y && ip->h == ie->h))) {
930				ip->next = ie->next;
931				mergeEntries(ie, ip);
932				free(ie);
933				ie = ip;
934				ip = prevIconEntry(ip, ir);
935			}
936			else if(in && in->used == false &&
937			                ((in->x == ie->x && in->w == ie->w) ||
938			                 (in->y == ie->y && in->h == ie->h))) {
939				ie->next = in->next;
940				mergeEntries(in, ie);
941				free(in);
942				in = ie->next;
943			}
944			else {
945				break;
946			}
947		}
948	}
949}
950
951
952
953
954/*
955 ****************************************************************
956 *
957 * Funcs related to drawing the icon.
958 *
959 ****************************************************************
960 */
961
962
963/*
964 * Slightly misnamed: draws the text label under an icon.
965 */
966void
967PaintIcon(TwmWindow *tmp_win)
968{
969	int         width, twidth, mwidth, len, x;
970	Icon        *icon;
971	XRectangle ink_rect;
972	XRectangle logical_rect;
973
974	if(!tmp_win || !tmp_win->icon) {
975		return;
976	}
977	icon = tmp_win->icon;
978	if(!icon->has_title) {
979		return;
980	}
981
982	x     = 0;
983	width = icon->w_width;
984	if(Scr->ShrinkIconTitles && icon->title_shrunk) {
985		x     = GetIconOffset(icon);
986		width = icon->width;
987	}
988	len    = strlen(tmp_win->icon_name);
989	XmbTextExtents(Scr->IconFont.font_set,
990	               tmp_win->icon_name, len,
991	               &ink_rect, &logical_rect);
992	twidth = logical_rect.width;
993	mwidth = width - 2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER);
994	if(Scr->use3Diconmanagers) {
995		Draw3DBorder(icon->w, x, icon->height, width,
996		             Scr->IconFont.height +
997		             2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER),
998		             Scr->IconManagerShadowDepth, icon->iconc, off, false, false);
999	}
1000	while((len > 0) && (twidth > mwidth)) {
1001		len--;
1002		XmbTextExtents(Scr->IconFont.font_set,
1003		               tmp_win->icon_name, len,
1004		               &ink_rect, &logical_rect);
1005		twidth = logical_rect.width;
1006	}
1007	FB(icon->iconc.fore, icon->iconc.back);
1008	XmbDrawString(dpy, icon->w, Scr->IconFont.font_set, Scr->NormalGC,
1009	              x + ((mwidth - twidth) / 2) +
1010	              Scr->IconManagerShadowDepth + ICON_MGR_IBORDER,
1011	              icon->y, tmp_win->icon_name, len);
1012}
1013
1014
1015/*
1016 * Handling for ShrinkIconTitles; when pointer is away from them, shrink
1017 * the titles down to the width of the image, and expand back out when it
1018 * enters.
1019 */
1020void
1021ShrinkIconTitle(TwmWindow *tmp_win)
1022{
1023	Icon        *icon;
1024	XRectangle  rect;
1025
1026	if(!tmp_win || !tmp_win->icon) {
1027		return;
1028	}
1029	icon = tmp_win->icon;
1030	if(!icon->has_title) {
1031		return;
1032	}
1033	if(icon->w_width == icon->width) {
1034		return;
1035	}
1036	if(icon->height  == 0) {
1037		return;
1038	}
1039
1040	rect.x      = GetIconOffset(icon);
1041	rect.y      = 0;
1042	rect.width  = icon->width;
1043	rect.height = icon->w_height;
1044	XShapeCombineRectangles(dpy, icon->w, ShapeBounding, 0, 0, &rect, 1,
1045	                        ShapeIntersect, 0);
1046	icon->title_shrunk = true;
1047	XClearArea(dpy, icon->w, 0, icon->height, icon->w_width,
1048	           icon->w_height - icon->height, True);
1049}
1050
1051
1052void
1053ExpandIconTitle(TwmWindow *tmp_win)
1054{
1055	Icon        *icon;
1056	XRectangle  rect;
1057
1058	if(!tmp_win || !tmp_win->icon) {
1059		return;
1060	}
1061	icon = tmp_win->icon;
1062	if(!icon->has_title) {
1063		return;
1064	}
1065	if(icon->w_width == icon->width) {
1066		return;
1067	}
1068	if(icon->height  == 0) {
1069		return;
1070	}
1071
1072	rect.x      = 0;
1073	rect.y      = icon->height;
1074	rect.width  = icon->w_width;
1075	rect.height = icon->w_height - icon->height;
1076	XShapeCombineRectangles(dpy, icon->w, ShapeBounding, 0, 0, &rect, 1, ShapeUnion,
1077	                        0);
1078	icon->title_shrunk = false;
1079	XClearArea(dpy, icon->w, 0, icon->height, icon->w_width,
1080	           icon->w_height - icon->height, True);
1081}
1082
1083
1084/*
1085 * Setup X Shape'ing around icons and their titles.
1086 *
1087 * XXX should this be checking HasShape?  It seems to be called
1088 * unconditionally...
1089 */
1090static void
1091ReshapeIcon(Icon *icon)
1092{
1093	int x;
1094	XRectangle  rect;
1095
1096	if(!icon) {
1097		return;
1098	}
1099	x = GetIconOffset(icon);
1100	XMoveWindow(dpy, icon->bm_w, x, 0);
1101
1102	if(icon->image && icon->image->mask) {
1103		XShapeCombineMask(dpy, icon->w, ShapeBounding, x, 0, icon->image->mask,
1104		                  ShapeSet);
1105	}
1106	else {
1107		rect.x      = x;
1108		rect.y      = 0;
1109		rect.width  = icon->width;
1110		rect.height = icon->height;
1111		XShapeCombineRectangles(dpy, icon->w, ShapeBounding, 0, 0, &rect, 1, ShapeSet,
1112		                        0);
1113	}
1114	rect.x      = x;
1115	rect.y      = icon->height;
1116	rect.width  = icon->width;
1117	rect.height = icon->w_height - icon->height;
1118	XShapeCombineRectangles(dpy, icon->w, ShapeBounding, 0, 0, &rect, 1, ShapeUnion,
1119	                        0);
1120}
1121
1122
1123/*
1124 * Figure horizontal positioning/offset for the icon image within its
1125 * window.
1126 */
1127int
1128GetIconOffset(Icon *icon)
1129{
1130	TitleJust justif;
1131
1132	if(!icon) {
1133		return 0;
1134	}
1135
1136	justif = icon->ir ? icon->ir->TitleJustification : Scr->IconJustification;
1137	switch(justif) {
1138		case TJ_LEFT:
1139			return 0;
1140
1141		case TJ_CENTER:
1142			return ((icon->w_width - icon->width) / 2);
1143
1144		case TJ_RIGHT:
1145			return (icon->w_width - icon->width);
1146
1147		default:
1148			/* Can't happen? */
1149			fprintf(stderr, "%s(): Invalid TitleJustification %d\n",
1150			        __func__, justif);
1151			return 0;
1152	}
1153}
1154
1155
1156/*
1157 * [Re-]lookup the image for an icon and [re-]layout it.
1158 */
1159void
1160RedoIcon(TwmWindow *win)
1161{
1162	Icon *icon, *old_icon;
1163	char *pattern;
1164
1165	old_icon = win->icon;
1166
1167	if(old_icon && (old_icon->w_not_ours || old_icon->match != match_list)) {
1168		RedoIconName(win);
1169		return;
1170	}
1171	icon = NULL;
1172	if((pattern = LookPatternInNameList(Scr->IconNames, win->icon_name))) {
1173		icon = LookInNameList(win->iconslist, pattern);
1174	}
1175	else if((pattern = LookPatternInNameList(Scr->IconNames, win->name))) {
1176		icon = LookInNameList(win->iconslist, pattern);
1177	}
1178	else if((pattern = LookPatternInList(Scr->IconNames, win->name,
1179	                                     &win->class))) {
1180		icon = LookInNameList(win->iconslist, pattern);
1181	}
1182	if(pattern == NULL) {
1183		RedoIconName(win);
1184		return;
1185	}
1186	if(icon != NULL) {
1187		if(old_icon == icon) {
1188			RedoIconName(win);
1189			return;
1190		}
1191		if(win->icon_on && visible(win)) {
1192			IconDown(win);
1193			if(old_icon && old_icon->w) {
1194				XUnmapWindow(dpy, old_icon->w);
1195			}
1196			win->icon = icon;
1197			OtpReassignIcon(win, old_icon);
1198			IconUp(win);
1199			OtpRaise(win, IconWin);
1200			XMapWindow(dpy, win->icon->w);
1201		}
1202		else {
1203			win->icon = icon;
1204			OtpReassignIcon(win, old_icon);
1205		}
1206		RedoIconName(win);
1207	}
1208	else {
1209		if(win->icon_on && visible(win)) {
1210			IconDown(win);
1211			if(old_icon && old_icon->w) {
1212				XUnmapWindow(dpy, old_icon->w);
1213			}
1214			/*
1215			 * If the icon name/class was found on one of the above lists,
1216			 * the call to CreateIconWindow() will find it again there
1217			 * and keep track of it on win->iconslist for eventual
1218			 * deallocation. (It is now checked that the current struct
1219			 * Icon is also already on that list)
1220			 */
1221			OtpFreeIcon(win);
1222			bool saveForceIcon = Scr->ForceIcon;
1223			Scr->ForceIcon = true;
1224			CreateIconWindow(win, -100, -100);
1225			Scr->ForceIcon = saveForceIcon;
1226			OtpRaise(win, IconWin);
1227			XMapWindow(dpy, win->icon->w);
1228		}
1229		else {
1230			OtpFreeIcon(win);
1231			win->icon = NULL;
1232			WMapUpdateIconName(win);
1233		}
1234		RedoIconName(win);
1235	}
1236}
1237
1238
1239/*
1240 * Resize the icon window, and reposition the image and name within it.
1241 * (a lot of the actual repositioning gets done during the later expose).
1242 */
1243void
1244RedoIconName(TwmWindow *win)
1245{
1246	int x;
1247	XRectangle ink_rect;
1248	XRectangle logical_rect;
1249
1250	if(Scr->NoIconTitlebar ||
1251	                LookInNameList(Scr->NoIconTitle, win->icon_name) ||
1252	                LookInList(Scr->NoIconTitle, win->name, &win->class)) {
1253		WMapUpdateIconName(win);
1254		return;
1255	}
1256	if(win->iconmanagerlist) {
1257		/* let the expose event cause the repaint */
1258		XClearArea(dpy, win->iconmanagerlist->w, 0, 0, 0, 0, True);
1259
1260		if(Scr->SortIconMgr) {
1261			SortIconManager(win->iconmanagerlist->iconmgr);
1262		}
1263	}
1264
1265	if(!win->icon  || !win->icon->w) {
1266		WMapUpdateIconName(win);
1267		return;
1268	}
1269
1270	if(win->icon->w_not_ours) {
1271		WMapUpdateIconName(win);
1272		return;
1273	}
1274
1275	XmbTextExtents(Scr->IconFont.font_set,
1276	               win->icon_name, strlen(win->icon_name),
1277	               &ink_rect, &logical_rect);
1278	win->icon->w_width = logical_rect.width;
1279	win->icon->w_width += 2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER);
1280	if(win->icon->w_width > Scr->MaxIconTitleWidth) {
1281		win->icon->w_width = Scr->MaxIconTitleWidth;
1282	}
1283
1284	if(win->icon->w_width < win->icon->width) {
1285		win->icon->x = (win->icon->width - win->icon->w_width) / 2;
1286		win->icon->x += Scr->IconManagerShadowDepth + ICON_MGR_IBORDER;
1287		win->icon->w_width = win->icon->width;
1288	}
1289	else {
1290		win->icon->x = Scr->IconManagerShadowDepth + ICON_MGR_IBORDER;
1291	}
1292
1293	x = GetIconOffset(win->icon);
1294	win->icon->y = win->icon->height + Scr->IconFont.height +
1295	               Scr->IconManagerShadowDepth;
1296	win->icon->w_height = win->icon->height + Scr->IconFont.height +
1297	                      2 * (Scr->IconManagerShadowDepth + ICON_MGR_IBORDER);
1298
1299	XResizeWindow(dpy, win->icon->w, win->icon->w_width,
1300	              win->icon->w_height);
1301	if(win->icon->bm_w) {
1302		XRectangle rect;
1303
1304		XMoveWindow(dpy, win->icon->bm_w, x, 0);
1305		XMapWindow(dpy, win->icon->bm_w);
1306		if(win->icon->image && win->icon->image->mask) {
1307			XShapeCombineMask(dpy, win->icon->bm_w, ShapeBounding, 0, 0,
1308			                  win->icon->image->mask, ShapeSet);
1309			XShapeCombineMask(dpy, win->icon->w, ShapeBounding, x, 0,
1310			                  win->icon->image->mask, ShapeSet);
1311		}
1312		else if(win->icon->has_title) {
1313			rect.x      = x;
1314			rect.y      = 0;
1315			rect.width  = win->icon->width;
1316			rect.height = win->icon->height;
1317			XShapeCombineRectangles(dpy, win->icon->w, ShapeBounding,
1318			                        0, 0, &rect, 1, ShapeSet, 0);
1319		}
1320		if(win->icon->has_title) {
1321			if(Scr->ShrinkIconTitles && win->icon->title_shrunk) {
1322				rect.x      = x;
1323				rect.y      = win->icon->height;
1324				rect.width  = win->icon->width;
1325				rect.height = win->icon->w_height - win->icon->height;
1326			}
1327			else {
1328				rect.x      = 0;
1329				rect.y      = win->icon->height;
1330				rect.width  = win->icon->w_width;
1331				rect.height = win->icon->w_height - win->icon->height;
1332			}
1333			XShapeCombineRectangles(dpy,  win->icon->w, ShapeBounding, 0,
1334			                        0, &rect, 1, ShapeUnion, 0);
1335		}
1336	}
1337	if(Scr->ShrinkIconTitles &&
1338	                win->icon->title_shrunk &&
1339	                win->icon_on && (visible(win))) {
1340		IconDown(win);
1341		IconUp(win);
1342	}
1343	if(win->isicon) {
1344		XClearArea(dpy, win->icon->w, 0, 0, 0, 0, True);
1345	}
1346
1347	WMapUpdateIconName(win);
1348}
1349
1350
1351
1352
1353/*
1354 ****************************************************************
1355 *
1356 * Misc internal utils.
1357 *
1358 ****************************************************************
1359 */
1360
1361
1362/*
1363 * What it says on the tin.
1364 */
1365static int
1366roundUp(int v, int multiple)
1367{
1368	return ((v + multiple - 1) / multiple) * multiple;
1369}
1370
1371
1372/*
1373 * Find the image set in Icons{} for a TwmWindow if possible.  Return the
1374 * image, record its provenance inside *icon, and pass back what pattern
1375 * it matched in **pattern.
1376 */
1377static Image *
1378LookupIconNameOrClass(TwmWindow *tmp_win, Icon *icon, char **pattern)
1379{
1380	char *icon_name = NULL;
1381	Image *image;
1382	Matchtype matched = match_none;
1383
1384	icon_name = LookInNameList(Scr->IconNames, tmp_win->icon_name);
1385	if(icon_name != NULL) {
1386		*pattern = LookPatternInNameList(Scr->IconNames, tmp_win->icon_name);
1387		matched = match_list;
1388	}
1389
1390	if(matched == match_none) {
1391		icon_name = LookInNameList(Scr->IconNames, tmp_win->name);
1392		if(icon_name != NULL) {
1393			*pattern = LookPatternInNameList(Scr->IconNames, tmp_win->name);
1394			matched = match_list;
1395		}
1396	}
1397
1398	if(matched == match_none) {
1399		icon_name = LookInList(Scr->IconNames, tmp_win->name, &tmp_win->class);
1400		if(icon_name != NULL) {
1401			*pattern = LookPatternInList(Scr->IconNames, tmp_win->name,
1402			                             &tmp_win->class);
1403			matched = match_list;
1404		}
1405	}
1406
1407	if((image  = GetImage(icon_name, icon->iconc)) != NULL) {
1408		icon->match  = matched;
1409		icon->image  = image;
1410		icon->width  = image->width;
1411		icon->height = image->height;
1412		tmp_win->forced = true;
1413	}
1414	else {
1415		icon->match = match_none;
1416		*pattern = NULL;
1417	}
1418
1419	return image;
1420}
1421