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