1/*
2 * Colormap handling
3 */
4
5#include "ctwm.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9
10#include "colormaps.h"
11#include "screen.h"
12
13
14/*
15 * From events.c; imported manually since I'm not listing it in events.h
16 * because nowhere but here needs it.
17 */
18extern bool ColortableThrashing;
19
20static Bool UninstallRootColormapQScanner(Display *display, XEvent *ev,
21                char *args);
22
23
24/***********************************************************************
25 *
26 *  Procedure:
27 *      InstallWindowColormaps - install the colormaps for one twm window
28 *
29 *  Inputs:
30 *      type    - type of event that caused the installation
31 *      tmp     - for a subset of event types, the address of the
32 *                window structure, whose colormaps are to be installed.
33 *
34 ***********************************************************************
35 *
36 * Previously in events.c
37 */
38bool
39InstallWindowColormaps(int type, TwmWindow *tmp)
40{
41	if(tmp) {
42		return InstallColormaps(type, &tmp->cmaps);
43	}
44	else {
45		return InstallColormaps(type, NULL);
46	}
47}
48
49
50bool
51InstallColormaps(int type, Colormaps *cmaps)
52{
53	int i, j, n, number_cwins, state;
54	ColormapWindow **cwins, *cwin, **maxcwin = NULL;
55	TwmColormap *cmap;
56	char *row, *scoreboard;
57
58	switch(type) {
59		case EnterNotify:
60		case LeaveNotify:
61		case DestroyNotify:
62		default:
63			/* Save the colormap to be loaded for when force loading of
64			 * root colormap(s) ends.
65			 */
66			Scr->cmapInfo.pushed_cmaps = cmaps;
67			/* Don't load any new colormap if root colormap(s) has been
68			 * force loaded.
69			 */
70			if(Scr->cmapInfo.root_pushes) {
71				return false;
72			}
73			/* Don't reload the current window colormap list.
74			if (Scr->cmapInfo.cmaps == cmaps)
75			    return false;
76			 */
77			if(Scr->cmapInfo.cmaps) {
78				for(i = Scr->cmapInfo.cmaps->number_cwins,
79				                cwins = Scr->cmapInfo.cmaps->cwins; i-- > 0; cwins++) {
80					(*cwins)->colormap->state &= ~CM_INSTALLABLE;
81				}
82			}
83			Scr->cmapInfo.cmaps = cmaps;
84			break;
85
86		case PropertyNotify:
87		case VisibilityNotify:
88		case ColormapNotify:
89			break;
90	}
91
92	number_cwins = Scr->cmapInfo.cmaps->number_cwins;
93	cwins = Scr->cmapInfo.cmaps->cwins;
94	scoreboard = Scr->cmapInfo.cmaps->scoreboard;
95
96	ColortableThrashing = false; /* in case installation aborted */
97
98	state = CM_INSTALLED;
99
100	for(i = 0; i < number_cwins; i++) {
101		cwins[i]->colormap->state &= ~CM_INSTALL;
102	}
103	for(i = n = 0; i < number_cwins && n < Scr->cmapInfo.maxCmaps; i++) {
104		cwin = cwins[i];
105		cmap = cwin->colormap;
106		if(cmap->state & CM_INSTALL) {
107			continue;
108		}
109		cmap->state |= CM_INSTALLABLE;
110		cmap->w = cwin->w;
111		if(cwin->visibility != VisibilityFullyObscured) {
112			row = scoreboard + (i * (i - 1) / 2);
113			for(j = 0; j < i; j++)
114				if(row[j] && (cwins[j]->colormap->state & CM_INSTALL)) {
115					break;
116				}
117			if(j != i) {
118				continue;
119			}
120			n++;
121			maxcwin = &cwins[i];
122			state &= (cmap->state & CM_INSTALLED);
123			cmap->state |= CM_INSTALL;
124		}
125	}
126
127	// Hack: special-case startup
128	if(!dpy) {
129		return true;
130	}
131
132	Scr->cmapInfo.first_req = NextRequest(dpy);
133
134	for(; n > 0 && maxcwin >= &cwins[0]; maxcwin--) {
135		cmap = (*maxcwin)->colormap;
136		if(cmap->state & CM_INSTALL) {
137			cmap->state &= ~CM_INSTALL;
138			if(!(state & CM_INSTALLED)) {
139				cmap->install_req = NextRequest(dpy);
140				/* printf ("XInstallColormap : %x, %x\n", cmap, cmap->c); */
141				XInstallColormap(dpy, cmap->c);
142			}
143			cmap->state |= CM_INSTALLED;
144			n--;
145		}
146	}
147	return true;
148}
149
150
151
152/***********************************************************************
153 *
154 *  Procedures:
155 *      <Uni/I>nstallRootColormap - Force (un)loads root colormap(s)
156 *
157 *         These matching routines provide a mechanism to insure that
158 *         the root colormap(s) is installed during operations like
159 *         rubber banding or menu display that require colors from
160 *         that colormap.  Calls may be nested arbitrarily deeply,
161 *         as long as there is one UninstallRootColormap call per
162 *         InstallRootColormap call.
163 *
164 *         The final UninstallRootColormap will cause the colormap list
165 *         which would otherwise have be loaded to be loaded, unless
166 *         Enter or Leave Notify events are queued, indicating some
167 *         other colormap list would potentially be loaded anyway.
168 ***********************************************************************
169 *
170 * Previously in events.c
171 */
172void
173InstallRootColormap(void)
174{
175	Colormaps *tmp;
176	if(Scr->cmapInfo.root_pushes == 0) {
177		/*
178		 * The saving and restoring of cmapInfo.pushed_window here
179		 * is a slimy way to remember the actual pushed list and
180		 * not that of the root window.
181		 */
182		tmp = Scr->cmapInfo.pushed_cmaps;
183		InstallColormaps(0, &Scr->RootColormaps);
184		Scr->cmapInfo.pushed_cmaps = tmp;
185	}
186	Scr->cmapInfo.root_pushes++;
187}
188
189
190/* ARGSUSED*/
191static Bool
192UninstallRootColormapQScanner(Display *display, XEvent *ev,
193                              char *args)
194{
195	if(!*args) {
196		if(ev->type == EnterNotify) {
197			if(ev->xcrossing.mode != NotifyGrab) {
198				*args = 1;
199			}
200		}
201		else if(ev->type == LeaveNotify) {
202			if(ev->xcrossing.mode == NotifyNormal) {
203				*args = 1;
204			}
205		}
206	}
207
208	return (False);
209}
210
211
212void
213UninstallRootColormap(void)
214{
215	char args;
216	XEvent dummy;
217
218	if(Scr->cmapInfo.root_pushes) {
219		Scr->cmapInfo.root_pushes--;
220	}
221
222	if(!Scr->cmapInfo.root_pushes) {
223		/*
224		 * If we have subsequent Enter or Leave Notify events,
225		 * we can skip the reload of pushed colormaps.
226		 */
227		XSync(dpy, 0);
228		args = 0;
229		XCheckIfEvent(dpy, &dummy, UninstallRootColormapQScanner, &args);
230
231		if(!args) {
232			InstallColormaps(0, Scr->cmapInfo.pushed_cmaps);
233		}
234	}
235}
236
237
238/*
239 * Create a TwmColormap struct and tie it to an [X] Colormap.  Places
240 * that need to mess with colormaps and look up the metainfo we hang off
241 * them need to look this up and find it via the X Context.
242 *
243 * Previously in add_window.c
244 */
245TwmColormap *
246CreateTwmColormap(Colormap c)
247{
248	TwmColormap *cmap;
249	cmap = malloc(sizeof(TwmColormap));
250	if(!cmap) {
251		return NULL;
252	}
253	cmap->c = c;
254	cmap->state = 0;
255	cmap->install_req = 0;
256	cmap->w = None;
257	cmap->refcnt = 1;
258
259	if(XSaveContext(dpy, c, ColormapContext, (XPointer) cmap)) {
260		free(cmap);
261		return NULL;
262	}
263	return (cmap);
264}
265
266
267/*
268 * Put together a ColormapWindow struct.  This is a thing we hang off a
269 * TwmWindow for some colormap tracking stuff.
270 *
271 * Previously in add_window.c
272 */
273ColormapWindow *
274CreateColormapWindow(Window w, bool creating_parent, bool property_window)
275{
276	ColormapWindow *cwin;
277	TwmColormap *cmap;
278	XWindowAttributes attributes;
279
280	cwin = malloc(sizeof(ColormapWindow));
281	if(cwin == NULL) {
282		return NULL;
283	}
284
285	// Common
286	cwin->w = w;
287
288	/*
289	 * Assume that windows in colormap list are
290	 * obscured if we are creating the parent window.
291	 * Otherwise, we assume they are unobscured.
292	 */
293	cwin->visibility = creating_parent ?
294	                   VisibilityPartiallyObscured : VisibilityUnobscured;
295	cwin->refcnt = 1;
296
297
298	// Stub for special cases
299	if(dpy == NULL) {
300		cwin->colormap = NULL;
301		cwin->colormap = calloc(1, sizeof(TwmColormap));
302		cwin->colormap->refcnt = 1;
303
304		return cwin;
305	}
306
307
308	if(!XGetWindowAttributes(dpy, w, &attributes) ||
309	                XSaveContext(dpy, w, ColormapContext, (XPointer) cwin)) {
310		free(cwin);
311		return (NULL);
312	}
313
314	if(XFindContext(dpy, attributes.colormap,  ColormapContext,
315	                (XPointer *)&cwin->colormap) == XCNOENT) {
316		cwin->colormap = cmap = CreateTwmColormap(attributes.colormap);
317		if(!cmap) {
318			XDeleteContext(dpy, w, ColormapContext);
319			free(cwin);
320			return (NULL);
321		}
322	}
323	else {
324		cwin->colormap->refcnt++;
325	}
326
327	/*
328	 * If this is a ColormapWindow property window and we
329	 * are not monitoring ColormapNotify or VisibilityNotify
330	 * events, we need to.
331	 */
332	if(property_window &&
333	                (attributes.your_event_mask &
334	                 (ColormapChangeMask | VisibilityChangeMask)) !=
335	                (ColormapChangeMask | VisibilityChangeMask)) {
336		XSelectInput(dpy, w, attributes.your_event_mask |
337		             (ColormapChangeMask | VisibilityChangeMask));
338	}
339
340	return (cwin);
341}
342
343
344/*
345 * Do something with looking up stuff from WM_COLORMAPS_WINDOWS (relating
346 * to windows with their own colormap) and finding or putting this window
347 * into it.
348 *
349 * XXX Someone should figure it out better than that...
350 *
351 * Previously in add_window.c
352 */
353void
354FetchWmColormapWindows(TwmWindow *tmp)
355{
356	int i, j;
357	Window *cmap_windows = NULL;
358	bool can_free_cmap_windows = false;
359	int number_cmap_windows = 0;
360	ColormapWindow **cwins = NULL;
361	bool previnst;
362
363	number_cmap_windows = 0;
364
365	previnst = (Scr->cmapInfo.cmaps == &tmp->cmaps && tmp->cmaps.number_cwins);
366	if(previnst) {
367		cwins = tmp->cmaps.cwins;
368		for(i = 0; i < tmp->cmaps.number_cwins; i++) {
369			cwins[i]->colormap->state = 0;
370		}
371	}
372
373	if(XGetWMColormapWindows(dpy, tmp->w, &cmap_windows,
374	                         &number_cmap_windows) &&
375	                number_cmap_windows > 0) {
376
377		/*
378		 * check if the top level is in the list, add to front if not
379		 */
380		for(i = 0; i < number_cmap_windows; i++) {
381			if(cmap_windows[i] == tmp->w) {
382				break;
383			}
384		}
385		if(i == number_cmap_windows) {   /* not in list */
386			Window *new_cmap_windows =
387			        calloc((number_cmap_windows + 1), sizeof(Window));
388
389			if(!new_cmap_windows) {
390				fprintf(stderr,
391				        "%s:  unable to allocate %d element colormap window array\n",
392				        ProgramName, number_cmap_windows + 1);
393				goto done;
394			}
395			new_cmap_windows[0] = tmp->w;  /* add to front */
396			for(i = 0; i < number_cmap_windows; i++) {   /* append rest */
397				new_cmap_windows[i + 1] = cmap_windows[i];
398			}
399			XFree(cmap_windows);
400			can_free_cmap_windows = true;  /* do not use XFree any more */
401			cmap_windows = new_cmap_windows;
402			number_cmap_windows++;
403		}
404
405		cwins = calloc(number_cmap_windows, sizeof(ColormapWindow *));
406		if(cwins) {
407			for(i = 0; i < number_cmap_windows; i++) {
408
409				/*
410				 * Copy any existing entries into new list.
411				 */
412				for(j = 0; j < tmp->cmaps.number_cwins; j++) {
413					if(tmp->cmaps.cwins[j]->w == cmap_windows[i]) {
414						cwins[i] = tmp->cmaps.cwins[j];
415						cwins[i]->refcnt++;
416						break;
417					}
418				}
419
420				/*
421				 * If the colormap window is not being pointed by
422				 * some other applications colormap window list,
423				 * create a new entry.
424				 */
425				if(j == tmp->cmaps.number_cwins) {
426					if(XFindContext(dpy, cmap_windows[i], ColormapContext,
427					                (XPointer *)&cwins[i]) == XCNOENT) {
428						if((cwins[i] = CreateColormapWindow(cmap_windows[i],
429						                                    tmp->cmaps.number_cwins == 0,
430						                                    true)) == NULL) {
431							int k;
432							for(k = i + 1; k < number_cmap_windows; k++) {
433								cmap_windows[k - 1] = cmap_windows[k];
434							}
435							i--;
436							number_cmap_windows--;
437						}
438					}
439					else {
440						cwins[i]->refcnt++;
441					}
442				}
443			}
444		}
445	}
446
447	/* No else here, in case we bailed out of clause above.
448	 */
449	if(number_cmap_windows == 0) {
450
451		number_cmap_windows = 1;
452
453		cwins = malloc(sizeof(ColormapWindow *));
454		if(XFindContext(dpy, tmp->w, ColormapContext, (XPointer *)&cwins[0]) ==
455		                XCNOENT)
456			cwins[0] = CreateColormapWindow(tmp->w,
457			                                tmp->cmaps.number_cwins == 0, false);
458		else {
459			cwins[0]->refcnt++;
460		}
461	}
462
463	if(tmp->cmaps.number_cwins) {
464		free_cwins(tmp);
465	}
466
467	tmp->cmaps.cwins = cwins;
468	tmp->cmaps.number_cwins = number_cmap_windows;
469	if(number_cmap_windows > 1)
470		tmp->cmaps.scoreboard =
471		        calloc(1, ColormapsScoreboardLength(&tmp->cmaps));
472
473	if(previnst) {
474		InstallColormaps(PropertyNotify, NULL);
475	}
476
477done:
478	if(cmap_windows) {
479		if(can_free_cmap_windows) {
480			free(cmap_windows);
481		}
482		else {
483			XFree(cmap_windows);
484		}
485	}
486}
487
488
489/*
490 * BumpWindowColormap - rotate our internal copy of WM_COLORMAP_WINDOWS.
491 * This is the backend for f.colormap.
492 *
493 * Previously in functions.c
494 */
495void
496BumpWindowColormap(TwmWindow *tmp, int inc)
497{
498	int i, j;
499	bool previously_installed;
500	ColormapWindow **cwins;
501
502	if(!tmp) {
503		return;
504	}
505
506	if(inc && tmp->cmaps.number_cwins > 0) {
507		cwins = calloc(tmp->cmaps.number_cwins, sizeof(ColormapWindow *));
508		if(cwins) {
509			previously_installed = (Scr->cmapInfo.cmaps == &tmp->cmaps &&
510			                        tmp->cmaps.number_cwins);
511			if(previously_installed) {
512				for(i = tmp->cmaps.number_cwins; i-- > 0;) {
513					tmp->cmaps.cwins[i]->colormap->state = 0;
514				}
515			}
516
517			for(i = 0; i < tmp->cmaps.number_cwins; i++) {
518				j = i - inc;
519				if(j >= tmp->cmaps.number_cwins) {
520					j -= tmp->cmaps.number_cwins;
521				}
522				else if(j < 0) {
523					j += tmp->cmaps.number_cwins;
524				}
525				cwins[j] = tmp->cmaps.cwins[i];
526			}
527
528			free(tmp->cmaps.cwins);
529
530			tmp->cmaps.cwins = cwins;
531
532			if(tmp->cmaps.number_cwins > 1)
533				memset(tmp->cmaps.scoreboard, 0,
534				       ColormapsScoreboardLength(&tmp->cmaps));
535
536			if(previously_installed) {
537				InstallColormaps(PropertyNotify, NULL);
538			}
539		}
540	}
541	else {
542		FetchWmColormapWindows(tmp);
543	}
544	return;
545}
546
547
548/*
549 * Handlers for creating and linkin in, as well as deleting, a StdCmap
550 * for a given XStandardColormap.
551 *
552 * Previously in util.c
553 */
554void
555InsertRGBColormap(Atom a, XStandardColormap *maps, int nmaps,
556                  bool replace)
557{
558	StdCmap *sc = NULL;
559
560	if(replace) {                       /* locate existing entry */
561		for(sc = Scr->StdCmapInfo.head; sc; sc = sc->next) {
562			if(sc->atom == a) {
563				break;
564			}
565		}
566	}
567
568	if(!sc) {                           /* no existing, allocate new */
569		sc = calloc(1, sizeof(StdCmap));
570		if(!sc) {
571			fprintf(stderr, "%s:  unable to allocate %lu bytes for StdCmap\n",
572			        ProgramName, (unsigned long) sizeof(StdCmap));
573			return;
574		}
575		replace = false;  // Didn't find one, can't replace
576	}
577
578	if(replace) {                       /* just update contents */
579		if(sc->maps) {
580			XFree(sc->maps);
581		}
582		if(sc == Scr->StdCmapInfo.mru) {
583			Scr->StdCmapInfo.mru = NULL;
584		}
585	}
586	else {                              /* else appending */
587		sc->next = NULL;
588		sc->atom = a;
589		if(Scr->StdCmapInfo.tail) {
590			Scr->StdCmapInfo.tail->next = sc;
591		}
592		else {
593			Scr->StdCmapInfo.head = sc;
594		}
595		Scr->StdCmapInfo.tail = sc;
596	}
597	sc->nmaps = nmaps;
598	sc->maps = maps;
599
600	return;
601}
602
603
604void
605RemoveRGBColormap(Atom a)
606{
607	StdCmap *sc, *prev;
608
609	prev = NULL;
610	for(sc = Scr->StdCmapInfo.head; sc; sc = sc->next) {
611		if(sc->atom == a) {
612			break;
613		}
614		prev = sc;
615	}
616	if(sc) {                            /* found one */
617		if(sc->maps) {
618			XFree(sc->maps);
619		}
620		if(prev) {
621			prev->next = sc->next;
622		}
623		if(Scr->StdCmapInfo.head == sc) {
624			Scr->StdCmapInfo.head = sc->next;
625		}
626		if(Scr->StdCmapInfo.tail == sc) {
627			Scr->StdCmapInfo.tail = prev;
628		}
629		if(Scr->StdCmapInfo.mru == sc) {
630			Scr->StdCmapInfo.mru = NULL;
631		}
632	}
633	return;
634}
635
636
637/*
638 * Go through all the properties of the root window and setup
639 * XStandardColormap's and our StdCmap's for any of them that are
640 * actually RGB_COLORMAP types.  We're not actually _checking_ the types,
641 * just letting XGetRGBColormaps() refuse to handle probably most of
642 * them.  Called during startup.
643 *
644 * Previouly in util.c
645 */
646void
647LocateStandardColormaps(void)
648{
649	Atom *atoms;
650	int natoms;
651	int i;
652
653	atoms = XListProperties(dpy, Scr->Root, &natoms);
654	for(i = 0; i < natoms; i++) {
655		XStandardColormap *maps = NULL;
656		int nmaps;
657
658		if(XGetRGBColormaps(dpy, Scr->Root, &maps, &nmaps, atoms[i])) {
659			/* if got one, then append to current list */
660			InsertRGBColormap(atoms[i], maps, nmaps, false);
661		}
662	}
663	if(atoms) {
664		XFree(atoms);
665	}
666	return;
667}
668
669
670/*
671 * Clear out and free TwmWindow.cmaps (struct Colormaps) bits for a window.
672 *
673 * Previously in events.c
674 */
675void
676free_cwins(TwmWindow *tmp)
677{
678	int i;
679	TwmColormap *cmap;
680
681	if(tmp->cmaps.number_cwins) {
682		for(i = 0; i < tmp->cmaps.number_cwins; i++) {
683			if(--tmp->cmaps.cwins[i]->refcnt == 0) {
684				cmap = tmp->cmaps.cwins[i]->colormap;
685				if(--cmap->refcnt == 0) {
686					XDeleteContext(dpy, cmap->c, ColormapContext);
687					free(cmap);
688				}
689				XDeleteContext(dpy, tmp->cmaps.cwins[i]->w, ColormapContext);
690				free(tmp->cmaps.cwins[i]);
691			}
692		}
693		free(tmp->cmaps.cwins);
694		if(tmp->cmaps.number_cwins > 1) {
695			free(tmp->cmaps.scoreboard);
696			tmp->cmaps.scoreboard = NULL;
697		}
698		tmp->cmaps.number_cwins = 0;
699	}
700}
701