1 /*	spawn.c
2 	Copyright (C) 2007-2021 Mark Tyler and Dmitry Groshev
3 
4 	This file is part of mtPaint.
5 
6 	mtPaint is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 3 of the License, or
9 	(at your option) any later version.
10 
11 	mtPaint is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with mtPaint in the file COPYING.
18 */
19 
20 #include <fcntl.h>
21 
22 #include "global.h"
23 #undef _
24 #define _(X) X
25 
26 #include "mygtk.h"
27 #include "inifile.h"
28 #include "memory.h"
29 #include "vcode.h"
30 #include "png.h"
31 #include "canvas.h"
32 #include "mainwindow.h"
33 #include "spawn.h"
34 
35 static char *mt_temp_dir;
36 
get_tempdir()37 static char *get_tempdir()
38 {
39 	char *env;
40 
41 	env = getenv("TMPDIR");
42 	if (!env || !*env) env = getenv("TMP");
43 	if (!env || !*env) env = getenv("TEMP");
44 #ifdef P_tmpdir
45 	if (!env || !*env) env = P_tmpdir;
46 #endif
47 	if (!env || !*env || (strlen(env) >= PATHBUF)) // Bad if too long
48 #ifdef WIN32
49 		env = "\\";
50 #else
51 		env = "/tmp";
52 #endif
53 	return (env);
54 }
55 
new_temp_dir()56 static char *new_temp_dir()
57 {
58 	char *buf, *base = get_tempdir();
59 
60 #ifdef HAVE_MKDTEMP
61 	buf = file_in_dir(NULL, base, "mtpaintXXXXXX", PATHBUF);
62 	if (!buf) return (NULL);
63 	if (mkdtemp(buf))
64 	{
65 		chmod(buf, 0755);
66 		return (buf);
67 	}
68 #else
69 	buf = tempnam(base, "mttmp");
70 	if (!buf) return (NULL);
71 #ifdef WIN32 /* No mkdtemp() in MinGW */
72 	/* tempnam() may use Unix path separator */
73 	reseparate(buf);
74 	if (!mkdir(buf)) return (buf);
75 #else
76 	if (!mkdir(buf, 0755)) return (buf);
77 #endif
78 #endif
79 	free(buf);
80 	return (NULL);
81 }
82 
83 /* Store index for name, or fetch it (when idx < 0) */
last_temp_index(char * name,int idx)84 static int last_temp_index(char *name, int idx)
85 {
86 // !!! For now, use simplest model - same index regardless of name
87 	static int index;
88 	if (idx >= 0) index = idx;
89 	return (index);
90 }
91 
92 typedef struct {
93 	void *next, *id;
94 	int type, rgb;
95 	char name[1];
96 } tempfile;
97 
98 static void *tempchain;
99 
remember_temp_file(char * name,int type,int rgb)100 static char *remember_temp_file(char *name, int type, int rgb)
101 {
102 	static wjmem *tempstore;
103 	tempfile *tmp, *tm0;
104 
105 	if ((tempstore || (tempstore = wjmemnew(0, 0))) &&
106 		(tmp = wjmalloc(tempstore, offsetof(tempfile, name) +
107 		strlen(name) + 1, ALIGNOF(tempfile))))
108 	{
109 		tmp->type = type;
110 		tmp->rgb = rgb;
111 		strcpy(tmp->name, name);
112 		if (mem_tempfiles) /* Have anchor - insert after it */
113 		{
114 			tm0 = tmp->id = mem_tempfiles;
115 			tmp->next = tm0->next;
116 			tm0->next = (void *)tmp;
117 		}
118 		else /* No anchor - insert before all */
119 		{
120 			tmp->next = tempchain;
121 			mem_tempfiles = tempchain = tmp->id = (void *)tmp;
122 		}
123 		return (tmp->name);
124 	}
125 	return (NULL); // Failed to allocate
126 }
127 
spawn_quit()128 void spawn_quit()
129 {
130 	tempfile *tmp;
131 
132 	for (tmp = tempchain; tmp; tmp = tmp->next) unlink(tmp->name);
133 	if (mt_temp_dir) rmdir(mt_temp_dir);
134 }
135 
get_tempname(char * buf,char * f,int type)136 int get_tempname(char *buf, char *f, int type)
137 {
138 	char nstub[NAMEBUF], ids[32], *c;
139 	int fd, cnt, idx;
140 
141 	/* Prepare temp directory */
142 	if (!mt_temp_dir) mt_temp_dir = new_temp_dir();
143 	if (!mt_temp_dir) return (FALSE); /* Temp dir creation failed */
144 
145 	/* Stubify filename */
146 	strcpy(nstub, "tmp");
147 	if (f) /* Have original filename */
148 	{
149 		c = strrchr(f, DIR_SEP);
150 		if (c) f = c + 1;
151 		c = strrchr(f, '.');
152 		if (c != f) /* Extension should not be alone */
153 			wjstrcat(nstub, NAMEBUF, f, c ? c - f : strlen(f), NULL);
154 	}
155 	f = nstub;
156 
157 	/* Create temp file */
158 	while (TRUE)
159 	{
160 		idx = last_temp_index(f, -1);
161 		ids[0] = 0;
162 		for (cnt = 0; cnt < 256; cnt++ , idx++)
163 		{
164 			if (idx) sprintf(ids, "%d", idx);
165 			snprintf(buf, PATHBUF, "%s" DIR_SEP_STR "%s%s.%s",
166 				mt_temp_dir, f, ids, file_formats[type].ext);
167 			fd = open(buf, O_WRONLY | O_CREAT | O_EXCL, 0644);
168 			if (fd >= 0) break;
169 		}
170 		last_temp_index(f, idx);
171 		if (fd >= 0) break;
172 		if (!strcmp(f, "tmp")) return (FALSE); /* Utter failure */
173 		f = "tmp"; /* Try again with "tmp" */
174 	}
175 	close(fd);
176 	return (TRUE);
177 }
178 
get_temp_file(int type,int rgb)179 static char *get_temp_file(int type, int rgb)
180 {
181 	ls_settings settings;
182 	tempfile *tmp;
183 	unsigned char *img = NULL;
184 	char buf[PATHBUF], *f = "tmp.png";
185 	int res;
186 
187 	/* Use the original file if possible */
188 	if (!mem_changed && mem_filename && (!rgb ^ (mem_img_bpp == 3)) &&
189 		((type == FT_NONE) ||
190 		 (detect_file_format(mem_filename, FALSE) == type)))
191 		return (mem_filename);
192 
193 	/* Analyze name */
194 	if ((type == FT_NONE) && mem_filename)
195 		type = file_type_by_ext(f = mem_filename, FF_SAVE_MASK);
196 	if (type == FT_NONE) type = FT_PNG;
197 
198 	/* Use existing file if possible */
199 	for (tmp = mem_tempfiles; tmp; tmp = tmp->next)
200 	{
201 		if (tmp->id != mem_tempfiles) break;
202 		if ((tmp->type == type) && (tmp->rgb == rgb))
203 			return (tmp->name);
204 	}
205 
206 	/* Create temp file */
207 	if (!get_tempname(buf, f, type)) return (NULL); /* Fail */
208 
209 	/* Save image */
210 	init_ls_settings(&settings, NULL);
211 	memcpy(settings.img, mem_img, sizeof(chanlist));
212 	settings.pal = mem_pal;
213 	settings.width = mem_width;
214 	settings.height = mem_height;
215 	settings.bpp = mem_img_bpp;
216 	settings.colors = mem_cols;
217 	settings.ftype = type;
218 	if (rgb && (mem_img_bpp == 1)) /* Save indexed as RGB */
219 	{
220 		settings.img[CHN_IMAGE] = img =
221 			malloc(mem_width * mem_height * 3);
222 		if (!img) return (NULL); /* Failed to allocate RGB buffer */
223 		settings.bpp = 3;
224 		do_convert_rgb(0, 1, mem_width * mem_height, img,
225 			mem_img[CHN_IMAGE], mem_pal);
226 	}
227 	res = save_image(buf, &settings);
228 	free(img);
229 	if (res) return (NULL); /* Failed to save */
230 
231 	return (remember_temp_file(buf, type, rgb));
232 }
233 
escape_filename(char * buf,char * name,int tail)234 static int escape_filename(char *buf, char *name, int tail)
235 {
236 #define QF  1 /* Quote it */
237 #define WF  2 /* Wildcard: quote or unquote */
238 #define UF  4 /* Unquote it */
239 #define EF  8 /* Escape it */
240 	static char *spec = " !\"#$%&'()*+,:;<=>?@[]^`{|}~";
241 	static char what[256];
242 	char *p;
243 	int i, l, v, q = QF, d = 0;
244 
245 
246 	/* Initialize char types table */
247 	if (!what[0])
248 	{
249 		/* Let's be extra safe and quote most anything at all non-alphanumeric */
250 		for (i = 0; i < ' '; i++) what[i] = QF;
251 		for (p = spec; *p; p++) what[(unsigned char)*p] = QF;
252 		/* Wildcards may need outquoting instead */
253 		what['?'] = what['*'] = WF;
254 #ifdef WIN32
255 		/* !!! With delayed expansion '^' and '!' need extra '^' quoting
256 		 * them, but I don't know how to detect if it's enabled globally,
257 		 * so if it is, user is out of luck - WJ
258 		 */
259 		what['%'] |= EF; // Escape '%'
260 #define QUOTE '"'
261 #define ESCAPE '%'
262 #else
263 		/* Backslashes need quoting or escaping too outside Windows */
264 		what['\\'] = QF;
265 		/* And single quotes need outquoting and escaping */
266 		what['\''] = UF | EF;
267 #define QUOTE '\''
268 #define ESCAPE '\\'
269 #endif
270 	}
271 
272 	l = strlen(name);
273 	if (tail < 0) tail = l; // No outquoting wildcards at all
274 
275 	/* See if we need to quote it, or not, or what */
276 	for (i = 0; i < l; i++)
277 		if ((v = what[(unsigned char)name[i]] & (QF | WF | UF))) break;
278 	if (v & WF) v |= i >= tail ? UF : QF;
279 
280 	/* If we need quoting first, start from beginning for nicer look */
281 	if (v & QF)
282 	{
283 		if (buf) buf[d] = QUOTE;
284 		d++ , q = UF;
285 	}
286 
287 	/* Minus sign as a first character is a special problem */
288 	if (name[0] == '-')
289 	{
290 		if (buf) buf[d] = '.' , buf[d + 1] = DIR_SEP;
291 		d += 2;
292 	}
293 
294 	for (i = 0; i < l; i++)
295 	{
296 		v = what[(unsigned char)name[i]];
297 		if (v & WF) // Wildcard
298 			v |= i >= tail ? UF : QF;
299 		if (v & q) // Quote/outquote
300 		{
301 			if (buf) buf[d] = QUOTE;
302 			q ^= QF | UF;
303 			d++;
304 		}
305 		if (v & EF) // Escape
306 		{
307 			if (buf) buf[d] = ESCAPE;
308 			d++;
309 		}
310 		if (buf) buf[d] = name[i];
311 		d++;
312 	}
313 	if (q & UF) // Add end quote
314 	{
315 		if (buf) buf[d] = QUOTE;
316 		d++;
317 	}
318 	if (buf) buf[d] = 0; // Terminate the string
319 
320 	return (d);
321 #undef QUOTE
322 #undef ESCAPE
323 #undef QF
324 #undef WF
325 #undef UF
326 #undef EF
327 }
328 
329 static char *pat_chars = "%fNxywhXYWHCTBASM";
330 enum
331 {
332 	PAT_percent = 0,
333 	PAT_f,
334 	PAT_N,
335 	PAT_x,
336 	PAT_y,
337 	PAT_w,
338 	PAT_h,
339 	PAT_X,
340 	PAT_Y,
341 	PAT_W,
342 	PAT_H,
343 	PAT_C,
344 	PAT_T,
345 	PAT_B,
346 	PAT_A,
347 	PAT_S,
348 	PAT_M,
349 	PAT_NONE
350 };
351 
interpolate_line(char * pattern,int cmd)352 char *interpolate_line(char *pattern, int cmd)
353 {
354 	char buf[64], *fname = NULL, *line = NULL, *pat = pattern;
355 	int rgb = mem_img_bpp == 3, fform = FT_NONE, extend = !cmd;
356 	int i, j, l, rect[4];
357 
358 	while (cmd)
359 	{
360 		/* Skip initial whitespace if any */
361 		pat += strspn(pat, " \t");
362 		/* Finish if not a transform request */
363 		if (*pat != '>') break;
364 		l = strcspn(++pat, "> \t");
365 		if (!strncasecmp("RGB", pat, l)) rgb = TRUE;
366 		else if (!strncmp("%", pat, l)) extend = TRUE;
367 		else
368 		{
369 			for (i = FT_NONE + 1; i < NUM_FTYPES; i++)
370 			{
371 				fformat *ff = file_formats + i;
372 				if ((ff->flags & FF_IMAGE) &&
373 					!(ff->flags & FF_NOSAVE) &&
374 					(!strncasecmp(ff->name, pat, l) ||
375 					!strncasecmp(ff->ext, pat, l) ||
376 					(ff->ext2[0] &&
377 					!strncasecmp(ff->ext2, pat, l))))
378 					fform = i;
379 			}
380 		}
381 		pat += l;
382 	}
383 	if (!extend && !strstr(pattern, "%f")) return (pattern); // Leave alone
384 
385 	if (fform != FT_NONE)
386 	{
387 		unsigned int flags = file_formats[fform].flags;
388 		if (rgb && !(flags & FF_RGB)) fform = FT_NONE; // No way
389 		else if (flags & FF_SAVE_MASK); // Is OK
390 		else if (flags & FF_RGB) rgb = TRUE; // Fallback
391 		else fform = FT_NONE; // Give up
392 	}
393 
394 	/* Current selection */
395 	memset(rect, 0, sizeof(rect));
396 	if (marq_status > MARQUEE_NONE) marquee_at(rect);
397 
398 	while (TRUE)
399 	{
400 		char c, *res, *p = pat;
401 
402 		l = 0;
403 		while ((c = *p++))
404 		{
405 			i = PAT_NONE;
406 			if ((c == '%') && *p && ((res = strchr(pat_chars, *p))) &&
407 				(extend || (res == pat_chars + PAT_f)))
408 				i = res - pat_chars;
409 			switch (i)
410 			{
411 			/* Current file, quoted, for processing */
412 			case PAT_f:
413 				p++;
414 				if (!cmd) continue; // No temp files in info mode
415 				if (!fname)
416 				{
417 					fname = get_temp_file(fform, rgb);
418 					/* Temp save failed */
419 					if (!fname) return (NULL);
420 				}
421 				/* Add whatever quotes the filename needs */
422 				j = escape_filename(line ? line + l : NULL, fname, -1);
423 				l += j;
424 				continue;
425 			/* Original filename, unquoted, for displaying &c. */
426 			case PAT_N:
427 				p++;
428 				if (!mem_filename) continue;
429 				j = strlen(mem_filename);
430 				if (line) memcpy(line + l, mem_filename, j);
431 				l += j;
432 				continue;
433 			/* Selected area */
434 			case PAT_x:
435 			case PAT_y:
436 			case PAT_w:
437 			case PAT_h:
438 				j = rect[i - PAT_x];
439 				break;
440 			/* Cursor position */
441 			case PAT_X: j = perim_wx; break;
442 			case PAT_Y: j = perim_wy; break;
443 			/* Image geometry */
444 			case PAT_W: j = mem_width; break;
445 			case PAT_H: j = mem_height; break;
446 			/* Colors in palette */
447 			case PAT_C: j = mem_cols; break;
448 			/* Transparent index */
449 			case PAT_T: j = mem_xpm_trans; break;
450 			/* Bytes per pixel */
451 			case PAT_B: j = mem_img_bpp; break;
452 			/* Extra channels */
453 			case PAT_A:
454 			case PAT_S:
455 			case PAT_M:
456 				j = !!mem_img[CHN_ALPHA + i - PAT_A];
457 				break;
458 			/* Doubled percent sign */
459 			case PAT_percent:
460 				p++;
461 				// Fallthrough
462 			/* Everything else */
463 			default:
464 				if (line) line[l] = c;
465 				l++;
466 				continue;
467 			}
468 			/* Anything expanding to a number */
469 			j = sprintf(buf, "%d", j);
470 			if (line) memcpy(line + l, buf, j);
471 			l += j;
472 			p++;
473 		}
474 		if (line) return (line);
475 		line = calloc(l + 1, 1);
476 		if (!line) return (NULL); // No memory to expand this pattern
477 	}
478 }
479 
spawn_expansion(char * cline,char * directory)480 int spawn_expansion(char *cline, char *directory)
481 	// Replace %f with "current filename", then run via shell
482 {
483 	int res = -1;
484 	char *s1;
485 #ifdef WIN32
486 	char *argv[4] = { getenv("COMSPEC"), "/C", cline, NULL };
487 #else
488 	char *argv[4] = { "sh", "-c", cline, NULL };
489 #endif
490 
491 	s1 = interpolate_line(cline, TRUE);
492 	if (!s1) return (-1);
493 	else if (s1 != cline)
494 	{
495 		argv[2] = s1;
496 		res = spawn_process(argv, directory);
497 		free(s1);
498 	}
499 	else
500 	{
501 		res = spawn_process(argv, directory);
502 	}
503 
504 		// Note that on Linux systems, returning 0 means that the shell was
505 		// launched OK, but gives no info on the success of the program being run by the shell.
506 		// To find out what happened you will need to use the output window.
507 		// MT 18-1-2007
508 
509 	return res;
510 }
511 
512 
513 
514 // Front End stuff
515 
516 static char *faction_ini[3] = { "fact%dName", "fact%dCommand", "fact%dDir" };
517 
518 #define MAXNAMELEN 2048
519 typedef struct {
520 	char name[MAXNAMELEN];
521 	char cmd[PATHTXT]; // UTF-8
522 	char dir[PATHBUF]; // system encoding
523 } spawn_row;
524 
525 typedef struct {
526 	spawn_row *strs;
527 	char *name, *cmd;
528 	void **list, **group;
529 	int idx, nidx, cnt;
530 	int lock;
531 	char dir[PATHBUF];
532 } spawn_dd;
533 
534 
pressed_file_action(int item)535 void pressed_file_action(int item)
536 {
537 	char *comm, *dir, txt[64];
538 
539 	sprintf(txt, faction_ini[1], item);
540 	comm = inifile_get(txt,"");
541 	sprintf(txt, faction_ini[2], item);
542 	dir = inifile_get(txt,"");
543 
544 	spawn_expansion(comm, dir);
545 }
546 
faction_changed(spawn_dd * dt,void ** wdata,int what,void ** where)547 static void faction_changed(spawn_dd *dt, void **wdata, int what, void **where)
548 {
549 	void *cause;
550 	spawn_row *rp;
551 
552 	if (dt->lock) return;
553 	cause = cmd_read(where, dt);
554 
555 	rp = dt->strs + dt->idx;
556 	if (cause == dt->dir) strncpy(rp->dir, dt->dir, sizeof(rp->dir));
557 	else
558 	{
559 		strncpy0(rp->name, dt->name, sizeof(rp->name));
560 		strncpy0(rp->cmd, dt->cmd, sizeof(rp->cmd));
561 		cmd_setv(dt->list, (void *)dt->idx, LISTC_RESET_ROW);
562 	}
563 }
564 
faction_select_row(spawn_dd * dt,void ** wdata,int what,void ** where)565 static void faction_select_row(spawn_dd *dt, void **wdata, int what, void **where)
566 {
567 	spawn_row *rp;
568 
569 	cmd_read(where, dt);
570 
571 	if (dt->nidx == dt->idx) return; // no change
572 	dt->lock = TRUE;
573 
574 	/* Get strings from array, and reset entries */
575 	rp = dt->strs + (dt->idx = dt->nidx);
576 	dt->name = rp->name;
577 	dt->cmd = rp->cmd;
578 	strncpy(dt->dir, rp->dir, PATHBUF);
579 	cmd_reset(dt->group, dt);
580 
581 	dt->lock = FALSE;
582 }
583 
update_faction_menu()584 static void update_faction_menu()	// Populate menu
585 {
586 	int i, v, items = 0;
587 	char txt[64], *nm, *cm;
588 	void **slot;
589 
590 	/* Show valid slots in menu */
591 	for (i = 1; i <= FACTION_PRESETS_TOTAL; i++)
592 	{
593 		sprintf(txt, faction_ini[0], i);
594 		nm = inifile_get(txt, "");
595 
596 		sprintf(txt, faction_ini[1], i);
597 		cm = inifile_get(txt, "");
598 
599 		slot = menu_slots[MENU_FACTION1 - 1 + i];
600 
601 		if ((v = nm && nm[0] && (nm[0] != '#') &&
602 			cm && cm[0] && (cm[0] != '#')))
603 			cmd_setv(slot, nm, LABEL_VALUE);
604 
605 		cmd_showhide(slot, v); // Hide by default
606 		cmd_sensitive(slot, v); // Make insensitive for shortcuts
607 		items += v;
608 	}
609 
610 	/* Hide separator if no valid slots */
611 	cmd_showhide(menu_slots[MENU_FACTION_S], items);
612 }
613 
init_factions()614 void init_factions()
615 {
616 #ifndef WIN32
617 	int i, j;
618 	static char *row_def[][3] = {
619 		{"View EXIF data (leafpad)", "exif %f | leafpad"},
620 		{"View filesystem data (xterm)", "xterm -hold -e ls -l %f"},
621 		{"Edit in Gimp", "gimp %f"},
622 		{"View in GQview", "gqview %f"},
623 		{"Print image", "kprinter %f"},
624 		{"Email image", "seamonkey -compose attachment=file://%f"},
625 		{"Send image to Firefox", "firefox %f"},
626 		{"Send image to OpenOffice", "soffice %f"},
627 		{"Edit Clipboards", "mtpaint ~/.clip*"},
628 		{"Time delayed screenshot", "sleep 10; mtpaint -s &"},
629 		{"View image information", "xterm -hold -sb -rightbar -geometry 100x100 -e identify -verbose %f"},
630 		{"#Create temp directory", "mkdir ~/images"},
631 		{"#Remove temp directory", "rm -rf ~/images"},
632 		{"#GIF to PNG conversion (in situ)", "mogrify -format png *.gif"},
633 		{"#ICO to PNG conversion (temp directory)", "ls --file-type *.ico | xargs -I FILE convert FILE ~/images/FILE.png"},
634 		{"Convert image to ICO file", "mogrify -format ico %f"},
635 		{"Create thumbnails in temp directory", "ls --file-type * | xargs -I FILE convert FILE -thumbnail 120x120 -sharpen 1 -quality 95 ~/images/th_FILE.jpg"},
636 		{"Create thumbnails (in situ)", "ls --file-type * | xargs -I FILE convert FILE -thumbnail 120x120 -sharpen 1 -quality 95 th_FILE.jpg"},
637 		{"Peruse temp images", "mtpaint ~/images/*"},
638 		{"Rename *.jpeg to *.jpg", "rename .jpeg .jpg *.jpeg"},
639 		{"Remove spaces from filenames", "for file in *\" \"*; do mv \"$file\" `echo $file | sed -e 's/ /_/g'`; done"},
640 		{"Remove extra .jpg. from filename", "rename .jpg. . *.jpg.jpg"},
641 //		{"", ""},
642 		{NULL, NULL, NULL}
643 		},
644 		txt[64];
645 
646 	for (i = 0; row_def[i][0]; i++)		// Needed for first time usage - populate inifile list
647 	{
648 		for (j = 0; j < 3; j++)
649 		{
650 			sprintf(txt, faction_ini[j], i + 1);
651 			inifile_get(txt, row_def[i][j]);
652 		}
653 	}
654 #endif
655 
656 	update_faction_menu();			// Prepare menu
657 }
658 
faction_btn(spawn_dd * dt,void ** wdata,int what,void ** where)659 static void faction_btn(spawn_dd *dt, void **wdata, int what, void **where)
660 {
661 	spawn_row *rp = dt->strs;
662 	char *s, txt[64], buf[PATHBUF];
663 	int i, j, idx[FACTION_ROWS_TOTAL];
664 
665 	if (what == op_EVT_CLICK) /* Execute */
666 	{
667 		gtkncpy(buf, dt->cmd, PATHBUF);
668 		spawn_expansion(buf, dt->dir);
669 		return;
670 	}
671 
672 	cmd_peekv(dt->list, idx, sizeof(idx), LISTC_ORDER);
673 	for (i = 0; i < FACTION_ROWS_TOTAL; i++ , rp++)
674 	{
675 		for (j = 0; j < 3; j++)
676 		{
677 			sprintf(txt, faction_ini[j], idx[i] + 1);
678 			if (!j) s = rp->name;
679 			else if (j == 1) gtkncpy(s = buf, rp->cmd, sizeof(buf));
680 			else s = rp->dir;
681 			inifile_set(txt, s);
682 		}
683 	}
684 	update_faction_menu();
685 	run_destroy(wdata);
686 }
687 
688 #define WBbase spawn_dd
689 static void *spawn_code[] = {
690 	WINDOWm(_("Configure File Actions")),
691 	DEFSIZE(500, 400),
692 	XVBOXB, // !!! Originally the main vbox was that way
693 	XSCROLL(1, 1), // auto/auto
694 	WLIST,
695 	NTXTCOLUMND(_("Action"), spawn_row, name, 200, 0),
696 	NTXTCOLUMND(_("Command"), spawn_row, cmd, 0 /* 200 */, 0),
697 	COLUMNDATA(strs, sizeof(spawn_row)), CLEANUP(strs),
698 	REF(list), LISTCd(nidx, cnt, faction_select_row), TRIGGER,
699 	REF(group), GROUPR,
700 	BORDER(ENTRY, 0),
701 	FHBOXB(_("Action")), XENTRY(name), EVENT(CHANGE, faction_changed), WDONE,
702 	FHBOXB(_("Command")), XENTRY(cmd), EVENT(CHANGE, faction_changed), WDONE,
703 	PATH(_("Directory"), _("Select Directory"), FS_SELECT_DIR, dir),
704 	EVENT(CHANGE, faction_changed),
705 	OKBOX3(_("OK"), faction_btn, _("Cancel"), NULL, _("Execute"), faction_btn),
706 	WSHOW
707 };
708 #undef WBbase
709 
pressed_file_configure()710 void pressed_file_configure()
711 {
712 	spawn_dd tdata;
713 	spawn_row *rp;
714 	char txt[64], *s;
715 	int i, j;
716 
717 	rp = tdata.strs = calloc(FACTION_ROWS_TOTAL, sizeof(spawn_row));
718 	if (!rp) return; // Not enough memory
719 
720 	for (i = 1; i <= FACTION_ROWS_TOTAL; i++ , rp++)
721 	{
722 		for (j = 0; j < 3; j++)
723 		{
724 			sprintf(txt, faction_ini[j], i);
725 			s = inifile_get(txt, "");
726 			if (!j) strncpy0(rp->name, s, sizeof(rp->name));
727 			else if (j == 1) gtkuncpy(rp->cmd, s, sizeof(rp->cmd));
728 			else strncpy0(rp->dir, s, sizeof(rp->dir));
729 		}
730 	}
731 
732 	tdata.name = tdata.cmd = "";
733 	tdata.dir[0] = '\0';
734 	tdata.idx = -1;
735 	tdata.nidx = 0;
736 	tdata.cnt = FACTION_ROWS_TOTAL;
737 	tdata.lock = FALSE;
738 
739 	run_create(spawn_code, &tdata, sizeof(tdata));
740 }
741 
742 
743 // Process spawning code
744 
745 #ifdef WIN32
746 
747 /* With Glib's g_spawn*() smart-ass tricks making it utterly unusable in
748  * Windows, the only way is to reimplement the functionality - WJ */
749 
750 #define WIN32_LEAN_AND_MEAN
751 #include <windows.h>
752 
spawn_process(char * argv[],char * directory)753 int spawn_process(char *argv[], char *directory)
754 {
755 	PROCESS_INFORMATION pi;
756 	STARTUPINFO st;
757 	gchar *cmdline, *c;
758 	int res;
759 
760 	memset(&pi, 0, sizeof(pi));
761 	memset(&st, 0, sizeof(st));
762 	st.cb = sizeof(st);
763 	cmdline = g_strjoinv(" ", argv);
764 	if (directory && !directory[0]) directory = NULL;
765 
766 	c = g_locale_from_utf8(cmdline, -1, NULL, NULL, NULL);
767 	if (c)
768 	{
769 		g_free(cmdline);
770 		cmdline = c;
771 		c = NULL;
772 	}
773 	if (directory)
774 	{
775 		c = g_locale_from_utf8(directory, -1, NULL, NULL, NULL);
776 		if (c) directory = c;
777 	}
778 
779 	res = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE,
780 		/* CREATE_NEW_CONSOLE | */
781 		CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS,
782 		NULL, directory, &st, &pi);
783 	g_free(cmdline);
784 	g_free(c);
785 	if (!res) return (1);
786 	CloseHandle(pi.hProcess);
787 	CloseHandle(pi.hThread);
788 	return (0);
789 }
790 
791 #else
792 
793 #include <errno.h>
794 #include <sys/types.h>
795 #include <sys/wait.h>
796 
spawn_process(char * argv[],char * directory)797 int spawn_process(char *argv[], char *directory)
798 {
799 	pid_t child, grandchild;
800 	int res = -1, err, fd_flags, fds[2], status;
801 
802 	child = fork();
803 	if ( child < 0 ) return 1;
804 
805 	if (child == 0)				// Child
806 	{
807 		if (pipe(fds) == -1) _exit(1);		// Set up the pipe
808 
809 		grandchild = fork();
810 
811 		if (grandchild == 0)		// Grandchild
812 		{
813 			if (directory) chdir(directory);
814 
815 			if (close(fds[0]) != -1)	// Close the read pipe
816 				/* Make the write end close-on-exec */
817 			if ((fd_flags = fcntl(fds[1], F_GETFD)) != -1)
818 				if (fcntl(fds[1], F_SETFD, fd_flags | FD_CLOEXEC) != -1)
819 					execvp(argv[0], &argv[0]); /* Run program */
820 
821 			/* If we are here, an error occurred */
822 			/* Send the error code to the parent */
823 			err = errno;
824 			write(fds[1], &err, sizeof(err));
825 			_exit(1);
826 		}
827 		/* Close the write pipe - has to be done BEFORE read */
828 		close(fds[1]);
829 
830 		/* Get the error code from the grandchild */
831 		if (grandchild > 0) res = read(fds[0], &err, sizeof(err));
832 
833 		close(fds[0]);			// Close the read pipe
834 
835 		_exit(res);
836 	}
837 
838 	waitpid(child, &status, 0);		// Wait for child to die
839 	res = WEXITSTATUS(status);		// Find out the childs exit value
840 
841 	return (res);
842 }
843 
844 #endif
845 
846 // Wrapper for animated GIF handling, or other builtin actions
847 
848 #ifdef U_ANIM_IMAGICK
849 	/* Use Image Magick for animated GIF processing; not really recommended,
850 	 * because it is many times slower than Gifsicle, and less efficient */
851 	/* !!! Image Magick version prior to 6.2.6-3 won't work properly */
852 #define CMD_GIF_CREATE \
853 	"convert ((srcmask)) -layers optimize -set delay ((delay)) -loop 0 ((dest))"
854 #define CMD_GIF_PLAY "animate ((src)) &"
855 
856 #else	/* Use Gifsicle; the default choice for animated GIFs */
857 
858 /*	global colourmaps, suppress warning, high optimizations, background
859  *	removal method, infinite loops, ensure result works with Java & MS IE */
860 #define CMD_GIF_CREATE \
861 	"gifsicle --colors 256 -w -O2 -D 2 -l0 --careful -d ((delay)) ((srcmask)) -o ((dest))"
862 #define CMD_GIF_PLAY "gifview -a ((src)) &"
863 
864 #endif
865 
866 /* 'gifviev' and 'animate' both are X-only */
867 #if (GTK_MAJOR_VERSION > 1) && !defined(GDK_WINDOWING_X11)
868 #undef CMD_GIF_PLAY
869 #define CMD_GIF_PLAY NULL
870 #endif
871 
872 /* Windows shell does not know "&", nor needs it to run mtPaint detached */
873 #ifndef WIN32
874 
875 #define CMD_DETACH " &"
876 
877 #else /* WIN32 */
878 
879 #define CMD_DETACH
880 #ifndef WEXITSTATUS
881 #define WEXITSTATUS(A) ((A) & 0xFF)
882 #endif
883 
884 #endif
885 
886 static char *def_actions[DA_NCODES] = {
887 	CMD_GIF_CREATE,
888 	CMD_GIF_PLAY,
889 	"mtpaint -g ((delay)) -w ((src)).\"???\" -w ((src)).\"????\"",
890 	"rsvg-convert ((w)) ((h)) -o ((dest)) ((src))",
891 	"vwebp ((src))" CMD_DETACH,
892 	"ffplay -loop 0 ((src))" CMD_DETACH,
893 };
894 
interpolate_action(char * pattern,da_settings * ds)895 static char *interpolate_action(char *pattern, da_settings *ds)
896 {
897 	char buf[64], *line = NULL;
898 	int j, l, n;
899 
900 	while (TRUE)
901 	{
902 		char c, *res, *p = pattern;
903 
904 		l = 0;
905 		while ((c = *p++))
906 		{
907 			/* Substitutions are ((var)) , copy everything else */
908 			if ((c != '(') || (*p != '(') ||
909 				(p[1] == '(') || !((res = strstr(p + 1, "))"))))
910 			{
911 				if (line) line[l] = c;
912 				l++;
913 				continue;
914 			}
915 			n = res - ++p; // Position & length of var name
916 			j = 0;
917 			if (!n); // Paranoia: skip if no name
918 			/* Source file */
919 			else if (!strncmp(p, "src", n))
920 				j = escape_filename(line ? line + l : NULL, ds->sname, -1);
921 			/* Source mask */
922 			else if (!strncmp(p, "srcmask", n))
923 			{
924 				char *w = strrchr(ds->sname, DIR_SEP);
925 				j = escape_filename(line ? line + l : NULL, ds->sname,
926 					w ? w - ds->sname : 0);
927 			}
928 			/* Destination file */
929 			else if (!strncmp(p, "dest", n))
930 				j = escape_filename(line ? line + l : NULL, ds->dname, -1);
931 			/* Delay in 1/100s */
932 			else if (!strncmp(p, "delay", n))
933 				j = sprintf(line ? line + l : buf, "%d", ds->delay);
934 			/* Width */
935 			else if (!strncmp(p, "w", n))
936 			{
937 				if (ds->width) j = sprintf(line ? line + l : buf,
938 					"-w %d", ds->width);
939 			}
940 			/* Height */
941 			else if (!strncmp(p, "h", n))
942 			{
943 				if (ds->height) j = sprintf(line ? line + l : buf,
944 					"-h %d", ds->height);
945 			}
946 			/* Unknown vars are skipped */
947 
948 			l += j; // Add new part to output
949 			p += n + 2;
950 		}
951 		if (line) return (line);
952 		line = calloc(l + 1, 1);
953 		if (!line) return (NULL); // No memory to expand this pattern
954 	}
955 }
956 
run_def_action(int action,char * sname,char * dname,int delay)957 int run_def_action(int action, char *sname, char *dname, int delay)
958 {
959 	da_settings ds;
960 
961 	memset(&ds, 0, sizeof(ds));
962 	ds.sname = sname;
963 	ds.dname = dname;
964 	ds.delay = delay;
965 	return (run_def_action_x(action, &ds));
966 }
967 
run_def_action_x(int action,da_settings * settings)968 int run_def_action_x(int action, da_settings *settings)
969 {
970 	char *msg, *c8, *command = NULL;
971 	int res, code;
972 
973 	if ((action >= 0) && (action < DA_NCODES)) // Paranoia
974 		command = def_actions[action];
975 	if (command) command = interpolate_action(command, settings);
976 	if (!command) return (-1);
977 
978 	res = system(command);
979 	if (res)
980 	{
981 		if (res > 0) code = WEXITSTATUS(res);
982 		else code = res;
983 		c8 = gtkuncpy(NULL, command, 0);
984 		msg = g_strdup_printf(__("Error %i reported when trying to run %s"),
985 			code, c8);
986 		alert_box(_("Error"), msg, NULL);
987 		g_free(msg);
988 		g_free(c8);
989 	}
990 	free(command);
991 
992 	return (res);
993 }
994 
995 
996 #ifdef WIN32
997 
998 #include <shellapi.h>
999 #define HANDBOOK_LOCATION_WIN "..\\docs\\index.html"
1000 
1001 #else /* Linux */
1002 
1003 #define HANDBOOK_BROWSER "seamonkey"
1004 #define HANDBOOK_LOCATION "/usr/doc/mtpaint/index.html"
1005 #define HANDBOOK_LOCATION2 "/usr/share/doc/mtpaint/index.html"
1006 
1007 #endif
1008 
show_html(char * browser,char * docs)1009 int show_html(char *browser, char *docs)
1010 {
1011 	char buf[PATHBUF + 2], buf2[PATHBUF];
1012 	int i=-1;
1013 #ifdef WIN32
1014 	char *r;
1015 
1016 	if (!docs || !docs[0])
1017 	{
1018 		/* Use default path relative to installdir */
1019 		docs = buf + 1;
1020 		i = GetModuleFileNameA(NULL, docs, PATHBUF);
1021 		if (!i) return (-1);
1022 		r = strrchr(docs, '\\');
1023 		if (!r) return (-1);
1024 		r[1] = 0;
1025 		strnncat(docs, HANDBOOK_LOCATION_WIN, PATHBUF);
1026 	}
1027 #else /* Linux */
1028 	char *argv[5];
1029 
1030 	if (!docs || !docs[0])
1031 	{
1032 		docs = HANDBOOK_LOCATION;
1033 		if (valid_file(docs) < 0) docs = HANDBOOK_LOCATION2;
1034 	}
1035 #endif
1036 	else docs = gtkncpy(buf + 1, docs, PATHBUF);
1037 
1038 	if ((valid_file(docs) < 0))
1039 	{
1040 		alert_box( _("Error"),
1041 			_("I am unable to find the documentation.  Either you need to download the mtPaint Handbook from the web site and install it, or you need to set the correct location in the Preferences window."), NULL);
1042 		return (-1);
1043 	}
1044 
1045 #ifdef WIN32
1046 	if (browser && !browser[0]) browser = NULL;
1047 	if (browser)
1048 	{
1049 		/* Quote the filename */
1050 		i = strlen(docs);
1051 		buf[0] = docs[i] = '"';
1052 		docs[i + 1] = '\0';
1053 		/* Rearrange parameters */
1054 		docs = gtkncpy(buf2, browser, PATHBUF);
1055 		browser = buf;
1056 	}
1057 	if ((unsigned int)ShellExecuteA(NULL, "open", docs, browser,
1058 		NULL, SW_SHOW) <= 32) i = -1;
1059 	else i = 0;
1060 #else
1061 	argv[1] = docs;
1062 	argv[2] = NULL;
1063 	/* Try to use default browser */
1064 	if (!browser || !browser[0])
1065 	{
1066 		argv[0] = "xdg-open";
1067 		i = spawn_process(argv, NULL);
1068 		if (!i) return (0); // System has xdg-utils installed
1069 		// No xdg-utils - try "BROWSER" variable then
1070 		browser = getenv("BROWSER");
1071 	}
1072 	else browser = gtkncpy(buf2, browser, PATHBUF);
1073 
1074 	if (!browser) browser = HANDBOOK_BROWSER;
1075 
1076 	argv[0] = browser;
1077 	i = spawn_process(argv, NULL);
1078 #endif
1079 	if (i) alert_box( _("Error"),
1080 		_("There was a problem running the HTML browser.  You need to set the correct program name in the Preferences window."), NULL);
1081 	return (i);
1082 }
1083