1/*
2 * Image handling functions
3 *
4 * This provides some general and hub stuff.  Details of different image
5 * type functions, and generation of builtins, go in their own files.
6 */
7
8#include "ctwm.h"
9
10#include <stdio.h>
11#include <string.h>
12#include <unistd.h>
13
14#include "list.h"
15#include "screen.h"
16
17#include "image.h"
18#include "image_bitmap.h"
19#include "image_bitmap_builtin.h"
20#include "image_xwd.h"
21#ifdef JPEG
22#include "image_jpeg.h"
23#endif
24#if defined (XPM)
25#include "image_xpm.h"
26#endif
27
28/* Flag (maybe should be retired */
29bool reportfilenotfound = false;
30Colormap AlternateCmap = None;
31
32
33/*
34 * Find (load/generate) an image by name
35 */
36Image *
37GetImage(const char *name, ColorPair cp)
38{
39	name_list **list;
40#define GIFNLEN 256
41	char fullname[GIFNLEN];
42	Image *image;
43
44	if(name == NULL) {
45		return NULL;
46	}
47	if(dpy == NULL) {
48		// May happen in special cases like --cfgchk with no $DISPLAY
49		return NULL;
50	}
51	image = NULL;
52
53	list = &Scr->ImageCache;
54	if(0) {
55		/* dummy */ ;
56	}
57	else if((name [0] == '@') || (strncmp(name, "xpm:", 4) == 0)) {
58#ifdef XPM
59		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
60
61		if((image = LookInNameList(*list, fullname)) == NULL) {
62			int startn = (name [0] == '@') ? 1 : 4;
63			if((image = GetXpmImage(name + startn, cp)) != NULL) {
64				AddToList(list, fullname, image);
65			}
66		}
67#else
68		fprintf(stderr, "XPM support disabled, ignoring image %s\n", name);
69		return NULL;
70#endif
71	}
72	else if(strncmp(name, "jpeg:", 5) == 0) {
73#ifdef JPEG
74		if((image = LookInNameList(*list, name)) == NULL) {
75			if((image = GetJpegImage(&name [5])) != NULL) {
76				AddToList(list, name, image);
77			}
78		}
79#else
80		fprintf(stderr, "JPEG support disabled, ignoring image %s\n", name);
81		return NULL;
82#endif
83	}
84	else if((strncmp(name, "xwd:", 4) == 0) || (name [0] == '|')) {
85		int startn = (name [0] == '|') ? 0 : 4;
86		if((image = LookInNameList(*list, name)) == NULL) {
87			if((image = GetXwdImage(&name [startn], cp)) != NULL) {
88				AddToList(list, name, image);
89			}
90		}
91	}
92	else if(strncmp(name, ":xpm:", 5) == 0) {
93		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
94		if((image = LookInNameList(*list, fullname)) == NULL) {
95			image = get_builtin_scalable_pixmap(name, cp);
96			if(image == NULL) {
97				/* g_b_s_p() already warned */
98				return NULL;
99			}
100			AddToList(list, fullname, image);
101		}
102	}
103	else if(strncmp(name, "%xpm:", 5) == 0) {
104		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
105		if((image = LookInNameList(*list, fullname)) == NULL) {
106			image = get_builtin_animated_pixmap(name, cp);
107			if(image == NULL) {
108				/* g_b_a_p() already warned */
109				return NULL;
110			}
111			AddToList(list, fullname, image);
112		}
113	}
114	else if(name [0] == ':') {
115		unsigned int    width, height;
116		Pixmap          pm = 0;
117		XGCValues       gcvalues;
118
119		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
120		if((image = LookInNameList(*list, fullname)) == NULL) {
121			pm = get_builtin_plain_pixmap(name, &width, &height);
122			if(pm == None) {
123				/* g_b_p_p() already warned */
124				return NULL;
125			}
126			image = AllocImage();
127			image->pixmap = XCreatePixmap(dpy, Scr->Root, width, height, Scr->d_depth);
128			if(Scr->rootGC == (GC) 0) {
129				Scr->rootGC = XCreateGC(dpy, Scr->Root, 0, &gcvalues);
130			}
131			gcvalues.background = cp.back;
132			gcvalues.foreground = cp.fore;
133			XChangeGC(dpy, Scr->rootGC, GCForeground | GCBackground, &gcvalues);
134			XCopyPlane(dpy, pm, image->pixmap, Scr->rootGC, 0, 0, width, height, 0, 0,
135			           (unsigned long) 1);
136			image->width  = width;
137			image->height = height;
138			AddToList(list, fullname, image);
139		}
140	}
141	else {
142		snprintf(fullname, GIFNLEN, "%s%dx%d", name, (int) cp.fore, (int) cp.back);
143		if((image = LookInNameList(*list, fullname)) == NULL) {
144			if((image = GetBitmapImage(name, cp)) != NULL) {
145				AddToList(list, fullname, image);
146			}
147		}
148	}
149	return image;
150#undef GIFNLEN
151}
152
153
154/*
155 * Creation/cleanup of Image structs
156 */
157Image *
158AllocImage(void)
159{
160	return calloc(1, sizeof(Image));
161}
162
163void
164FreeImage(Image *image)
165{
166	Image *im, *im2;
167
168	im = image;
169	while(im != NULL) {
170		/* Cleanup sub-bits */
171		if(im->pixmap) {
172			XFreePixmap(dpy, im->pixmap);
173		}
174		if(im->mask) {
175			XFreePixmap(dpy, im->mask);
176		}
177
178		/* Cleanup self */
179		im2 = im->next;
180		im->next = NULL;
181		free(im);
182
183		/*
184		 * Loop back around, unless we hit the original.  e.g.,
185		 * "foo%.xpm" animations load the images into a closed loop, so
186		 * FreeImage() would do Very Bad Things running around the track
187		 * until it segfaults or the like.
188		 */
189		if(im2 == image) {
190			break;
191		}
192		im = im2;
193	}
194}
195
196
197
198/*
199 * Utils for image*
200 */
201
202/*
203 * Expand out the real pathname for an image.  Turn ~ into $HOME if
204 * it's there, and look under the entries in PixmapDirectory if the
205 * result isn't a full path.
206 */
207char *
208ExpandPixmapPath(const char *name)
209{
210	char *ret;
211
212	ret = NULL;
213
214	/* If it starts with '~/', replace it with our homedir */
215	if(name[0] == '~' && name[1] == '/') {
216		asprintf(&ret, "%s/%s", Home, name + 2);
217		return ret;
218	}
219
220	/*
221	 * If it starts with /, it's an absolute path, so just pass it
222	 * through.
223	 */
224	if(name[0] == '/') {
225		return strdup(name);
226	}
227
228	/*
229	 * If we got here, it's some sort of relative path (or a bare
230	 * filename), so search for it under PixmapDirectory if we have it.
231	 */
232	if(Scr->PixmapDirectory) {
233		char *colon;
234		char *p = Scr->PixmapDirectory;
235
236		/* PixmapDirectory is a colon-separated list */
237		while((colon = strchr(p, ':'))) {
238			*colon = '\0';
239			asprintf(&ret, "%s/%s", p, name);
240			*colon = ':';
241			if(!access(ret, R_OK)) {
242				return (ret);
243			}
244			free(ret);
245			p = colon + 1;
246		}
247
248		asprintf(&ret, "%s/%s", p, name);
249		if(!access(ret, R_OK)) {
250			return (ret);
251		}
252		free(ret);
253	}
254
255
256	/*
257	 * If we get here, we have no idea.  For simplicity and consistency
258	 * for our callers, just return what we were given.
259	 */
260	return strdup(name);
261}
262
263
264/*
265 * Generalized loader for animations.
266 *
267 * These are specified with a '%' in the filename, which is replaced by a
268 * series of numbers.  So e.g.
269 *
270 * "foo%.xpm" -> [ "foo1.xpm", "foo2.xpm", ...]
271 *
272 * These then turn into a looped-linked-list of Image's.  We support
273 * these for all types of images, so write it up into a central handler
274 * once to centralize the logic.
275 */
276Image *
277get_image_anim_cp(const char *name,
278                  ColorPair cp, Image * (*imgloader)(const char *, ColorPair))
279{
280	Image   *head, *tail;
281	char    *pref, *suff, *stmp;
282	int     i;
283
284	/* This shouldn't get called for non-animations */
285	if((stmp = strchr(name, '%')) == NULL) {
286		fprintf(stderr, "%s() called for non-animation '%s'\n", __func__, name);
287		return NULL;
288	}
289	if(stmp[1] == '\0') {
290		fprintf(stderr, "%s(): nothing after %% in '%s'\n", __func__, name);
291		return NULL;
292	}
293	stmp = NULL;
294
295	/*
296	 * For animated requests, we load a series of files, replacing the %
297	 * with numbers in series.
298	 */
299	tail = head = NULL;
300
301	/* Working copy of the filename split to before/after the % */
302	pref = strdup(name);
303	suff = strchr(pref, '%');
304	*suff++ = '\0';
305
306	/* "foo%.xpm" -> [ "foo1.xpm", "foo2.xpm", ...] */
307	for(i = 1 ; ; i++) {
308#define ANIM_PATHLEN 256
309		char path[ANIM_PATHLEN];
310		Image *tmp;
311
312		if(snprintf(path, ANIM_PATHLEN, "%s%d%s", pref, i,
313		                suff) >= (ANIM_PATHLEN - 1)) {
314			fprintf(stderr, "%s(): generated filename for '%s' #%d longer than %d.\n",
315			        __func__, name, i, ANIM_PATHLEN);
316			FreeImage(head);
317			free(pref);
318			return NULL;
319		}
320#undef ANIM_PATHLEN
321
322		/*
323		 * Load this image, and set ->next so it's explicitly the
324		 * [current] tail of the list.
325		 */
326		tmp = imgloader(path, cp);
327		if(tmp == NULL) {
328			break;
329		}
330		tmp->next = NULL;
331
332		/*
333		 * If it's the first, it's the head (image) we return, as well as
334		 * our current tail marker (s).  Else, append to that tail.
335		 */
336		if(head == NULL) {
337			tail = head = tmp;
338		}
339		else {
340			tail->next = tmp;
341			tail = tmp;
342		}
343	}
344	free(pref);
345
346	/* Set the tail to loop back to the head */
347	if(tail != NULL) {
348		tail->next = head;
349	}
350
351	/* Warn if we got nothing */
352	if(head == NULL) {
353		fprintf(stderr, "Cannot find any image frames for '%s'\n", name);
354	}
355
356	return head;
357}
358