1/*
2 * Command-line arg handling
3 */
4
5#include "ctwm.h"
6
7#include <getopt.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12#include "clargs.h"
13#include "ctopts.h"
14#include "deftwmrc.h"
15#include "screen.h"
16#include "version.h"
17
18static void usage(void) __attribute__((noreturn));
19static void print_version(void);
20static void DisplayInfo(void);
21static void dump_default_config(void);
22
23
24/*
25 * Command-line args.  Initialize with useful default values.
26 */
27ctwm_cl_args CLarg = {
28	.MultiScreen     = true,
29	.Monochrome      = false,
30	.cfgchk          = false,
31	.InitFile        = NULL,
32	.display_name    = NULL,
33	.PrintErrorMessages = false,
34#ifdef DEBUG
35	.ShowWelcomeWindow  = false,
36#else
37	.ShowWelcomeWindow  = true,
38#endif
39#ifdef CAPTIVE
40	.is_captive      = false,
41	.capwin          = (Window) 0,
42	.captivename     = NULL,
43#endif
44#ifdef USEM4
45	.KeepTmpFile     = false,
46	.keepM4_filename = NULL,
47	.GoThroughM4     = true,
48#endif
49#ifdef EWMH
50	.ewmh_replace    = false,
51#endif
52	.client_id       = NULL,
53	.restore_filename = NULL,
54};
55
56
57
58/*
59 * Parse them out and setup CLargs.
60 */
61void
62clargs_parse(int argc, char *argv[])
63{
64	int ch, optidx;
65
66	/*
67	 * Setup long options for arg parsing
68	 */
69	static struct option long_options[] = {
70		/* Simple flags */
71		{ "single",    no_argument,       NULL, 0 },
72		{ "mono",      no_argument,       NULL, 0 },
73		{ "verbose",   no_argument,       NULL, 'v' },
74		{ "quiet",     no_argument,       NULL, 'q' },
75		{ "nowelcome", no_argument,       NULL, 'W' },
76
77		/* Config/file related */
78		{ "file",      required_argument, NULL, 'f' },
79		{ "cfgchk",    no_argument,       NULL, 0 },
80
81		/* Show something and exit right away */
82		{ "help",      no_argument,       NULL, 'h' },
83		{ "version",   no_argument,       NULL, 0 },
84		{ "info",      no_argument,       NULL, 0 },
85		{ "dumpcfg",   no_argument,       NULL, 0 },
86
87		/* Misc control bits */
88		{ "display",   required_argument, NULL, 'd' },
89		{ "xrm",       required_argument, NULL, 0 },
90#ifdef CAPTIVE
91		{ "window",    optional_argument, NULL, 'w' },
92		{ "name",      required_argument, NULL, 0 },
93#endif
94
95#ifdef EWMH
96		{ "replace",   no_argument,       NULL, 0 },
97#endif
98
99		/* M4 control params */
100#ifdef USEM4
101		{ "keep-defs", no_argument,       NULL, 'k' },
102		{ "keep",      required_argument, NULL, 'K' },
103		{ "nom4",      no_argument,       NULL, 'n' },
104#endif
105
106		/* Random session-related bits */
107		{ "clientId",  required_argument, NULL, 0 },
108		{ "restore",   required_argument, NULL, 0 },
109
110		{ NULL,        0,                 NULL, 0 },
111	};
112
113
114	/*
115	 * Short aliases for some
116	 *
117	 * I assume '::' for optional args is portable; getopt_long(3)
118	 * doesn't describe it, but it's a GNU extension for getopt(3).
119	 */
120	const char *short_options = "vqWf:hd:"
121#ifdef CAPTIVE
122	                            "w::"
123#endif
124#ifdef USEM4
125	                            "kK:n"
126#endif
127	                            ;
128
129
130	/*
131	 * Backward-compat cheat: accept a few old-style long args if they
132	 * came first.  Of course, this assumed argv[x] is editable, which on
133	 * most systems it is, and C99 requires it.
134	 */
135	if(argc > 1) {
136#define CHK(x) else if(strcmp(argv[1], (x)) == 0)
137		if(0) {
138			/* nada */
139		}
140		CHK("-version") {
141			print_version();
142			exit(0);
143		}
144		CHK("-info") {
145			DisplayInfo();
146			exit(0);
147		}
148		CHK("-cfgchk") {
149			CLarg.cfgchk = true;
150			*argv[1] = '\0';
151		}
152		CHK("-display") {
153			if(argc <= 2 || strlen(argv[2]) < 1) {
154				usage();
155			}
156			CLarg.display_name = strdup(argv[2]);
157
158			*argv[1] = '\0';
159			*argv[2] = '\0';
160		}
161#undef CHK
162	}
163
164
165	/*
166	 * Parse out the args
167	 */
168	optidx = 0;
169	while((ch = getopt_long(argc, argv, short_options, long_options,
170	                        &optidx)) != -1) {
171		switch(ch) {
172			/* First handle the simple cases that have short args */
173			case 'v':
174				CLarg.PrintErrorMessages = true;
175				break;
176			case 'q':
177				CLarg.PrintErrorMessages = false;
178				break;
179			case 'W':
180				CLarg.ShowWelcomeWindow = false;
181				break;
182			case 'f':
183				CLarg.InitFile = optarg;
184				break;
185			case 'h':
186				usage();
187			case 'd':
188				CLarg.display_name = optarg;
189				break;
190#ifdef CAPTIVE
191			case 'w':
192				CLarg.is_captive = true;
193				CLarg.MultiScreen = false;
194				if(optarg != NULL) {
195					sscanf(optarg, "%x", (unsigned int *)&CLarg.capwin);
196					/* Failure will just leave capwin as initialized */
197				}
198				break;
199#endif
200
201#ifdef USEM4
202			/* Args that only mean anything if we're built with m4 */
203			case 'k':
204				CLarg.KeepTmpFile = true;
205				break;
206			case 'K':
207				CLarg.keepM4_filename = optarg;
208				break;
209			case 'n':
210				CLarg.GoThroughM4 = false;
211				break;
212#endif
213
214
215			/*
216			 * Now the stuff that doesn't have short variants.
217			 */
218			case 0:
219
220#define IFIS(x) if(strcmp(long_options[optidx].name, (x)) == 0)
221				/* Simple flag-setting */
222				IFIS("single") {
223					CLarg.MultiScreen = false;
224					break;
225				}
226				IFIS("mono") {
227					CLarg.Monochrome = true;
228					break;
229				}
230				IFIS("cfgchk") {
231					CLarg.cfgchk = true;
232					break;
233				}
234#ifdef EWMH
235				IFIS("replace") {
236					CLarg.ewmh_replace = true;
237					break;
238				}
239#endif
240
241				/* Simple value-setting */
242#ifdef CAPTIVE
243				IFIS("name") {
244					CLarg.captivename = optarg;
245					break;
246				}
247#endif
248				IFIS("clientId") {
249					CLarg.client_id = optarg;
250					break;
251				}
252				IFIS("restore") {
253					CLarg.restore_filename = optarg;
254					break;
255				}
256
257				/* Some immediate actions */
258				IFIS("version") {
259					print_version();
260					exit(0);
261				}
262				IFIS("info") {
263					DisplayInfo();
264					exit(0);
265				}
266				IFIS("dumpcfg") {
267					dump_default_config();
268					exit(0);
269				}
270
271				/* Misc */
272				IFIS("xrm") {
273					/*
274					 * Quietly ignored by us; Xlib processes it
275					 * internally in XtToolkitInitialize();
276					 */
277					break;
278				}
279#undef IFIS
280
281				/*
282				 * Some choices may just be internally setting a flag.
283				 * We have none right now, but leave this in case we grow
284				 * more later.
285				 */
286				if(long_options[optidx].flag != NULL) {
287					break;
288				}
289
290				/* Don't think it should be possible to get here... */
291				fprintf(stderr, "Internal error in getopt: '%s' unhandled.\n",
292				        long_options[optidx].name);
293				usage();
294
295			/* Something totally unexpected */
296			case '?':
297				/* getopt_long() already printed an error */
298				usage();
299
300			default:
301				/* Uhhh...  */
302				fprintf(stderr, "Internal error: getopt confused us.\n");
303				usage();
304		}
305	}
306
307
308	/* Should do it */
309	return;
310}
311
312
313/*
314 * Sanity check CLarg's
315 */
316void
317clargs_check(void)
318{
319
320#ifdef USEM4
321	/* If we're not doing m4, don't specify m4 options */
322	if(!CLarg.GoThroughM4) {
323		if(CLarg.KeepTmpFile) {
324			fprintf(stderr, "--keep-defs is incompatible with --nom4.\n");
325			usage();
326		}
327		if(CLarg.keepM4_filename) {
328			fprintf(stderr, "--keep is incompatible with --nom4.\n");
329			usage();
330		}
331	}
332#endif
333
334#ifdef CAPTIVE
335	/* If we're not captive, captivename is meaningless too */
336	if(CLarg.captivename && !CLarg.is_captive) {
337		fprintf(stderr, "--name is meaningless without --window.\n");
338		usage();
339	}
340
341	/*
342	 * Being captive and --cfgchk'ing is kinda meaningless.  There's no
343	 * reason to create a window just to destroy things, and it never
344	 * adds anything.  And it's one more way we're forcing changes on the
345	 * X side before we parse the actual config, so let's just disallow
346	 * it.
347	 */
348	if(CLarg.is_captive && CLarg.cfgchk) {
349		fprintf(stderr, "--window is incompatible with --cfgchk.\n");
350		usage();
351	}
352#endif
353
354	/* Guess that's it */
355	return;
356}
357
358
359/*
360 * Small utils only currently used in this file.  Over time they may need
361 * to be exported, if we start using them from more places.
362 */
363static void
364usage(void)
365{
366	/* How far to indent continuation lines */
367	int llen = 10;
368
369	fprintf(stderr, "usage: %s [(--display | -d) dpy]  "
370#ifdef EWMH
371	        "[--replace]  "
372#endif
373	        "[--single]\n", ProgramName);
374
375	fprintf(stderr, "%*s[(--file | -f) initfile]  [--cfgchk]  [--dumpcfg]\n",
376	        llen, "");
377
378#ifdef USEM4
379	fprintf(stderr, "%*s[--nom4 | -n]  [--keep-defs | -k]  "
380	        "[(--keep | -K) m4file]\n", llen, "");
381#endif
382
383	fprintf(stderr, "%*s[--verbose | -v]  [--quiet | -q]  [--mono]  "
384	        "[--xrm resource]\n", llen, "");
385
386	fprintf(stderr, "%*s[--version]  [--info]  [--nowelcome | -W]\n",
387	        llen, "");
388
389#ifdef CAPTIVE
390	fprintf(stderr, "%*s[(--window | -w) [win-id]]  [--name name]\n", llen, "");
391#endif
392
393	/* Semi-intentionally not documenting --clientId/--restore */
394
395	fprintf(stderr, "%*s[--help]\n", llen, "");
396
397
398	exit(1);
399}
400
401
402static void
403print_version(void)
404{
405	printf("ctwm %s\n", VersionNumberFull);
406	if(VCSType && VCSRevision) {
407		printf(" (%s:%s)\n", VCSType, VCSRevision);
408	}
409}
410
411
412static void
413DisplayInfo(void)
414{
415	char *ctopts;
416
417	printf("Twm version:  %s\n", TwmVersion);
418
419	ctopts = ctopts_string(" ");
420	printf("Compile time options : %s\n", ctopts);
421	free(ctopts);
422}
423
424
425static void
426dump_default_config(void)
427{
428	int i;
429
430	for(i = 0 ; defTwmrc[i] != NULL ; i++) {
431		printf("%s\n", defTwmrc[i]);
432	}
433}
434