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