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