1 /* main.c
2 Copyright (C) 2004-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 "global.h"
21
22 #include "mygtk.h"
23 #include "memory.h"
24 #include "vcode.h"
25 #include "ani.h"
26 #include "png.h"
27 #include "mainwindow.h"
28 #include "otherwindow.h"
29 #include "viewer.h"
30 #include "inifile.h"
31 #include "canvas.h"
32 #include "layer.h"
33 #include "prefs.h"
34 #include "csel.h"
35 #include "spawn.h"
36
compare_names(const void * s1,const void * s2)37 static int compare_names(const void *s1, const void *s2)
38 {
39 return (s1 == s2 ? 0 : strcoll(*(const char **)s1, *(const char **)s2));
40 }
41
42 #ifndef WIN32
43 #include <glob.h>
44 #define PATHC_TYPE size_t
45 #else
46
47 /* This is Windows only, as POSIX systems have glob() implemented */
48
49 /* Error returns from glob() */
50 #define GLOB_NOSPACE 1 /* No memory */
51 #define GLOB_NOMATCH 3 /* No files */
52
53 #define GLOB_APPEND 0x020 /* Append to existing array */
54 #define GLOB_MAGCHAR 0x100 /* Set if any wildcards in pattern */
55
56 typedef struct {
57 int gl_pathc;
58 char **gl_pathv;
59 int gl_flags;
60 } glob_t;
61
62 #define PATHC_TYPE int
63
globfree(glob_t * pglob)64 static void globfree(glob_t *pglob)
65 {
66 int i;
67
68 if (!pglob->gl_pathv) return;
69 for (i = 0; i < pglob->gl_pathc; i++)
70 free(pglob->gl_pathv[i]);
71 free(pglob->gl_pathv);
72 }
73
74 typedef struct {
75 DIR *dir;
76 char *path, *mask; // Split up the string for them
77 int lpath;
78 } glob_dir_level;
79
80 #define MAXDEPTH (PATHBUF / 2) /* A pattern with more cannot match anything */
81
split_pattern(glob_dir_level * dirs,char * pat)82 static int split_pattern(glob_dir_level *dirs, char *pat)
83 {
84 char *tm2, *tmp, *lastpart;
85 int ch, bracket = 0, cnt = 0;
86
87
88 dirs[0].path = tmp = lastpart = pat;
89 while (tmp)
90 {
91 if (cnt >= MAXDEPTH) return (0);
92 tmp += strcspn(tmp, "?*[]");
93 ch = *tmp++;
94 if (!ch)
95 {
96 dirs[cnt].path = lastpart;
97 dirs[cnt++].mask = NULL;
98 break;
99 }
100 if (ch == '[') bracket = TRUE;
101 else if ((ch != ']') || bracket)
102 {
103 tmp = strchr(tmp, DIR_SEP);
104 if (tmp) *tmp++ = '\0';
105 tm2 = strrchr(lastpart, DIR_SEP);
106 /* 0th slot is special - path string is counted,
107 * not terminated, and includes path separator */
108 if (!cnt)
109 {
110 if (!tm2) tm2 = strchr(lastpart, ':');
111 if (tm2) dirs[0].lpath = tm2 - lastpart + 1;
112 }
113 else if (tm2) dirs[cnt].path = lastpart;
114
115 if (tm2) *tm2++ = '\0';
116 else tm2 = lastpart;
117 dirs[cnt++].mask = tm2;
118 lastpart = tmp;
119 bracket = FALSE;
120 }
121 }
122 return (cnt);
123 }
124
glob_add_file(glob_t * pglob,char * buf)125 static int glob_add_file(glob_t *pglob, char *buf)
126 {
127 void *tmp;
128 int l = pglob->gl_pathc;
129
130 /* Use doubling array technique */
131 if (!pglob->gl_pathv || (((l + 1) & ~l) > l))
132 {
133 tmp = realloc(pglob->gl_pathv, (l + 1) * 2 * sizeof(char *));
134 if (!tmp) return (-1);
135 pglob->gl_pathv = tmp;
136 }
137 /* Add the name to array */
138 if (!(pglob->gl_pathv[l++] = strdup(buf))) return (-1);
139 pglob->gl_pathv[pglob->gl_pathc = l] = NULL;
140 return (0);
141 }
142
143 /* This implementation is strictly limited to mtPaint's needs, and tuned for
144 * Win32 peculiarities; the only flag handled by it is GLOB_APPEND - WJ */
glob(const char * pattern,int flags,void * nothing,glob_t * pglob)145 static int glob(const char *pattern, int flags, void *nothing, glob_t *pglob)
146 {
147 glob_dir_level dirs[MAXDEPTH + 1], *dp;
148 struct dirent *ep;
149 struct stat sbuf;
150 char *pat, buf[PATHBUF];
151 int l, lv, maxdepth, prevcnt, memfail = 0;
152
153
154 pglob->gl_flags = flags;
155 if (!(flags & GLOB_APPEND))
156 {
157 pglob->gl_pathc = 0;
158 pglob->gl_pathv = NULL;
159 }
160 prevcnt = pglob->gl_pathc;
161
162 /* Prepare the pattern */
163 if (!pattern[0]) return (GLOB_NOMATCH);
164 pat = strdup(pattern);
165 if (!pat) goto mfail;
166 reseparate(pat);
167
168 /* Split up the pattern */
169 memset(dirs, 0, sizeof(dirs));
170 if (!(maxdepth = split_pattern(dirs, pat)))
171 {
172 free(pat);
173 return (GLOB_NOMATCH);
174 }
175
176 /* Scan through dir(s) */
177 maxdepth--;
178 for (lv = 0; lv >= 0; )
179 {
180 dp = dirs + lv--; // Step back a level in advance
181 /* Start scanning directory */
182 if (!dp->dir)
183 {
184 l = dp->lpath;
185 buf[l] = '\0';
186 if (lv < 0) memcpy(buf, dp->path, l); // Level 0
187 else if (!dp->path); // No extra path part
188 else if (l + 1 + strlen(dp->path) >= PATHBUF) // Too long
189 continue;
190 else // Add path part
191 {
192 strcpy(buf + l + 1, dp->path);
193 buf[l] = DIR_SEP;
194 }
195 dp->lpath = strlen(buf);
196 if (!dp->mask)
197 {
198 if (!stat(buf, &sbuf))
199 memfail |= glob_add_file(pglob, buf);
200 continue;
201 }
202 dp->dir = opendir(buf[0] ? buf : ".");
203 if (!dp->dir) continue;
204 }
205 /* Finish scanning directory */
206 if (memfail || !(ep = readdir(dp->dir)))
207 {
208 closedir(dp->dir);
209 dp->dir = NULL;
210 continue;
211 }
212 lv++; // Undo step back
213 /* Skip "." and ".." */
214 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
215 continue;
216 /* Filter through mask */
217 if (!wjfnmatch(dp->mask, ep->d_name, FALSE)) continue;
218 /* Combine names */
219 l = dp->lpath + !!lv;
220 if (l + strlen(ep->d_name) >= PATHBUF) // Too long
221 continue; // Should not happen, but let's make sure
222 strcpy(buf + l, ep->d_name);
223 if (lv) buf[l - 1] = DIR_SEP; // No forced separator on level 0
224 /* Filter files on lower levels */
225 if (stat(buf, &sbuf) || ((lv < maxdepth) && !S_ISDIR(sbuf.st_mode)))
226 continue;
227 /* Add to result set */
228 if (lv == maxdepth) memfail |= glob_add_file(pglob, buf);
229 /* Enter into directory */
230 else
231 {
232 dp[1].lpath = strlen(buf);
233 lv++;
234 }
235 }
236 free(pat);
237
238 /* Report the results */
239 if (memfail)
240 {
241 mfail: globfree(pglob);
242 return (GLOB_NOSPACE);
243 }
244 if (pglob->gl_pathc == prevcnt) return (GLOB_NOMATCH);
245 if (maxdepth) pglob->gl_flags |= GLOB_MAGCHAR;
246
247 /* Sort the names */
248 qsort(pglob->gl_pathv + prevcnt, pglob->gl_pathc - prevcnt,
249 sizeof(char *), compare_names);
250
251 return (0);
252 }
253
254 #endif
255
256 static char **flist;
257 int flist_len, flist_top;
258 int warnmax;
259
extend_flist(int n,int ntoo)260 static int extend_flist(int n, int ntoo)
261 {
262 char **tmp;
263
264 /* Limit the sum & guard against overflow */
265 n = n > FILES_MAX - ntoo ? FILES_MAX : n + ntoo;
266 n = n > FILES_MAX - flist_top ? FILES_MAX : n + flist_top;
267
268 if (n > flist_len)
269 {
270 n = (n + 1023) & ~1023; // Align
271 if ((tmp = realloc(flist, n * sizeof(char *))))
272 {
273 flist = tmp;
274 flist_len = n;
275 }
276 }
277 return (flist_len - flist_top);
278 }
279
280 #ifdef WIN32
281 #define LSEP "\r\n"
282 #else
283 #define LSEP "\n"
284 #endif
285
add_filelist(char * name,int nf)286 static void add_filelist(char *name, int nf)
287 {
288 char *what, *t, *err;
289 int i, l, lf = 0, a0 = 0;
290
291 if (warnmax |= flist_top >= FILES_MAX) return;
292 err = "Could not load list";
293 t = what = slurp_file_l(name, 0, &l); // Reads less than INT_MAX
294 while (what)
295 {
296 /* Let file be LF or NUL separated */
297 for (i = 0; i < l; i++)
298 {
299 lf += what[i] == '\n';
300 a0 += !what[i];
301 }
302 if (a0) lf = a0; // NUL has precedence
303 if (!lf) a0 = 1; // Take all file as single name
304
305 err = "Empty list";
306 if (lf >= l) break;
307 err = "Ignored too long list";
308 if (lf > FILES_MAX) break;
309
310 /* One extra as last name may be unterminated */
311 extend_flist(lf + 1, nf);
312
313 if (!a0) /* LF separated */
314 {
315 while (t - what < l)
316 {
317 t += strspn(t, LSEP);
318 if (t - what >= l) break;
319 if (warnmax |= flist_top >= flist_len) break;
320 flist[flist_top++] = t;
321 t += strcspn(t, LSEP);
322 *t++ = '\0';
323 }
324 }
325 else /* NUL separated */
326 {
327 while (t - what < l)
328 {
329 while (!*t && (++t - what < l));
330 if (t - what >= l) break;
331 if (warnmax |= flist_top >= flist_len) break;
332 flist[flist_top++] = t;
333 t += strlen(t) + 1;
334 }
335 }
336 return;
337 }
338 free(what);
339 printf("%s: %s\n", err, name);
340 }
341
run_init_script()342 static gboolean run_init_script()
343 {
344 char **res = NULL, *env = getenv("MTPAINT_INIT");
345
346 if (env && *env) res = wj_parse_argv(env);
347 if (res)
348 {
349 run_script(res);
350 free(res);
351 }
352
353 return (FALSE); // Do not run again (if idle handler)
354 }
355
main(int argc,char * argv[])356 int main( int argc, char *argv[] )
357 {
358 char *env;
359 glob_t globdata;
360 int file_arg_start = argc, new_empty = TRUE, get_screenshot = FALSE;
361 int i, j, l, nf, nw, nl, w0, pass, fmode, dosort = FALSE;
362
363 if (argc > 1)
364 {
365 if ( strcmp(argv[1], "--version") == 0 )
366 {
367 printf("%s\n\n", MT_VERSION);
368 exit(0);
369 }
370 if ( strcmp(argv[1], "--help") == 0 )
371 {
372 printf("%s\n\n"
373 "Usage: mtpaint [option] [imagefile ... ]\n\n"
374 "Options:\n"
375 " --help Output this help\n"
376 " --version Output version information\n"
377 " --flist Read a list of files\n"
378 " --sort Sort files passed as arguments\n"
379 " --cmd Commandline scripting mode, no GUI\n"
380 " -s Grab screenshot\n"
381 " -v Start in viewer mode\n"
382 " -- End of options\n\n"
383 , MT_VERSION);
384 exit(0);
385 }
386 if (!strcmp(argv[1], "--cmd"))
387 {
388 cmd_mode = TRUE;
389 script_cmds = argv + 2;
390 }
391 }
392
393 putenv( "G_BROKEN_FILENAMES=1" ); // Needed to read non ASCII filenames in GTK+2
394
395 #if GTK_MAJOR_VERSION == 3
396 /* No floating random stuff over canvas */
397 putenv("GTK_OVERLAY_SCROLLING=0");
398 /* Prevent confusion */
399 #ifdef GDK_WINDOWING_X11
400 gdk_set_allowed_backends("x11");
401 #endif
402 #else
403 /* Disable bug-ridden eyecandy module that breaks sizing */
404 putenv("LIBOVERLAY_SCROLLBAR=0");
405 #endif
406
407 #if GTK2VERSION >= 4
408 /* Tablet handling in GTK+ 2.18+ is broken beyond repair if this mode
409 * is set; so unset it, if g_unsetenv() is present */
410 g_unsetenv("GDK_NATIVE_WINDOWS");
411 #endif
412
413 #ifdef U_THREADS
414 /* Enable threading for GLib, but NOT for GTK+ (at least, not yet) */
415 g_thread_init(NULL);
416 #endif
417 env = getenv("MTPAINT_INI");
418 inifile_init("/etc/mtpaint/mtpaintrc", env ? env : "~/.mtpaint");
419
420 #ifdef U_NLS
421 #if GTK_MAJOR_VERSION == 1
422 /* !!! GTK+1 needs locale set up before gtk_init(); GTK+2, *QUITE*
423 * the opposite - WJ */
424 setup_language();
425 #endif
426 #endif
427
428 #ifdef U_THREADS
429 /* !!! Uncomment to allow GTK+ calls from other threads */
430 /* gdk_threads_init(); */
431 #endif
432 if (!cmd_mode)
433 {
434 gtk_init(&argc, &argv);
435 gtk_init_bugfixes();
436 }
437 #if GTK_MAJOR_VERSION == 3
438 if (!cmd_mode) init_css(inifile_get(DEFAULT_CSS_INI, ""));
439 #elif GTK_MAJOR_VERSION == 2
440 if (!cmd_mode)
441 {
442 char *theme = inifile_get(DEFAULT_THEME_INI, "");
443 if (theme[0]) gtk_rc_parse(theme);
444 }
445 else g_type_init();
446 #endif
447
448 #ifdef U_NLS
449 {
450 char *locdir = extend_path(MT_LANG_DEST);
451 #if GTK_MAJOR_VERSION >= 2
452 /* !!! GTK+2 starts acting up if this is before gtk_init() - WJ */
453 setup_language();
454 #endif
455 bindtextdomain("mtpaint", locdir);
456 g_free(locdir);
457 textdomain("mtpaint");
458 #if GTK_MAJOR_VERSION >= 2
459 bind_textdomain_codeset("mtpaint", "UTF-8");
460 #endif
461 }
462 #endif
463
464 nf = nw = nl = w0 = pass = fmode = 0;
465 memset(&globdata, 0, sizeof(globdata));
466 for (i = 1 + cmd_mode; ; i++)
467 {
468 char *arg;
469
470 if (i >= argc) // Pass is done
471 {
472 if (pass++) break; // Totally done
473
474 file_args = argv + file_arg_start;
475 files_passed = (warnmax = nf > FILES_MAX) ? FILES_MAX : nf;
476 if (!(nw | nl)) break; // Regular filenames are good to go
477
478 warnmax = 0; // Need to know WHEN limit gets hit
479
480 if (nl | nf) // List not needed for wildcards alone
481 extend_flist(nf, 1024); // Filenames and then some
482
483 /* Go forth and stuff filenames into the list */
484 i = 1 + cmd_mode;
485 fmode = 0;
486 }
487
488 arg = argv[i];
489 if (fmode); // Expect filename or wildcard
490 else if (!strcmp(arg, "--")) // End of options
491 {
492 fmode |= 2; // Files only, no wildcards
493 continue;
494 }
495 else if (cmd_mode) continue; // One more script command
496 else if (!strcmp(arg, "-g")) // Loading GIF animation frames
497 {
498 if (++i >= argc) continue;
499 sscanf(argv[i], "%i", &preserved_gif_delay);
500 continue;
501 }
502 else if (!strcmp(arg, "-v")) // Viewer mode
503 {
504 viewer_mode = TRUE;
505 continue;
506 }
507 else if (!strcmp(arg, "-s")) // Screenshot
508 {
509 get_screenshot = TRUE;
510 continue;
511 }
512 else if (!strcmp(arg, "--flist")) // Filelist
513 {
514 if (++i >= argc) continue;
515 nl++;
516 if (!pass) continue;
517
518 add_filelist(argv[i], nf);
519 if (warnmax) break;
520 continue;
521 }
522 else if (!strcmp(arg, "--sort")) // Sort names
523 {
524 dosort = TRUE;
525 continue;
526 }
527
528 /* !!! I avoid GLOB_DOOFFS here, because glibc before version 2.2 mishandled it,
529 * and quite a few copycats had cloned those buggy versions, some libc
530 * implementors among them. So it is possible to encounter a broken function
531 * in the wild, and playing it safe doesn't cost all that much - WJ */
532 if ((fmode < 2) && !strcmp(arg, "-w")) // Wildcard
533 {
534 if (++i >= argc) continue;
535 nw++;
536 if (!pass) continue;
537
538 if (warnmax |= globdata.gl_pathc >= FILES_MAX) break;
539 // Ignore errors - be glad for whatever gets returned
540 glob(argv[i], (globdata.gl_pathc ? GLOB_APPEND : 0),
541 NULL, &globdata);
542 if (!flist) continue; // Use gl_pathv
543
544 /* Add newfound filenames to flist */
545 if (globdata.gl_pathc <= (PATHC_TYPE)w0) continue; // Nothing new
546 l = (globdata.gl_pathc > FILES_MAX ? FILES_MAX :
547 (int)globdata.gl_pathc) - w0;
548 j = extend_flist(l, nf);
549 if (l > j) l = j;
550 memcpy(flist + flist_top, globdata.gl_pathv + w0,
551 l * sizeof(char *));
552 flist_top += l;
553 w0 += l;
554 if (warnmax |= globdata.gl_pathc > (PATHC_TYPE)w0) break;
555 continue;
556 }
557
558 // Believe this is a filename
559 fmode |= 1; // Only names and maybe wildcards past this point
560 if (file_arg_start > i) file_arg_start = i;
561 if (pass)
562 {
563 if (warnmax |= flist_top >= flist_len) break;
564 flist[flist_top++] = argv[i];
565 }
566 nf += 1 - pass * 2; // Counting back on second pass
567 }
568
569 /* Flist if exists, globs otherwise, commandline as default */
570 if (flist) file_args = flist , files_passed = flist_top;
571 else if (globdata.gl_pathc) file_args = globdata.gl_pathv ,
572 files_passed = globdata.gl_pathc > FILES_MAX ? FILES_MAX :
573 (int)globdata.gl_pathc;
574 if (warnmax) printf("Too many files, limiting to %d\n", files_passed);
575
576 /* Sort the list of names */
577 if (dosort && files_passed) qsort(file_args, files_passed,
578 sizeof(char *), compare_names);
579
580 if (strstr(argv[0], "mtv")) viewer_mode = TRUE;
581
582 string_init(); // Translate static strings
583 var_init(); // Load INI variables
584 mem_init(); // Set up memory & back end
585 layers_init();
586 init_cols();
587
588 if ( get_screenshot )
589 {
590 if (load_image(NULL, FS_PNG_LOAD, FT_PIXMAP) == 1)
591 new_empty = FALSE; // Successfully grabbed so no new empty
592 else get_screenshot = FALSE; // Screenshot failed
593 }
594 main_init(); // Create main window
595
596 if ( get_screenshot )
597 {
598 do_new_chores(FALSE);
599 notify_changed();
600 }
601 else
602 {
603 if ((files_passed > 0) && !do_a_load(file_args[0], FALSE))
604 new_empty = FALSE;
605 }
606
607 if ( new_empty ) // If no file was loaded, start with a blank canvas
608 {
609 create_default_image();
610 }
611
612 update_menus();
613
614 if (cmd_mode) // Console
615 run_script(script_cmds);
616 else // GUI
617 {
618 /* !!! GTK+1 hangs if error messagebox is displayed from idle
619 * handler; GTK+3 has no gtk_init_add(); GTK+2 works OK with
620 * both methods - WJ */
621 #if GTK_MAJOR_VERSION == 3
622 threads_idle_add_priority(G_PRIORITY_HIGH,
623 (GtkFunction)run_init_script, NULL);
624 #else /* if GTK_MAJOR_VERSION <= 2 */
625 gtk_init_add((GtkFunction)run_init_script, NULL);
626 #endif
627 THREADS_ENTER();
628 gtk_main();
629 THREADS_LEAVE();
630
631 inifile_quit();
632 }
633 spawn_quit();
634
635 return (cmd_mode && user_break);
636 }
637