1/*
2 * Copyright 2008-2011, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <ctype.h>
8#include <getopt.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include <Alert.h>
14#include <Application.h>
15#include <Screen.h>
16
17#include "ScreenMode.h"
18
19
20static struct option const kLongOptions[] = {
21	{"fall-back", no_argument, 0, 'f'},
22	{"dont-confirm", no_argument, 0, 'q'},
23	{"modeline", no_argument, 0, 'm'},
24	{"short", no_argument, 0, 's'},
25	{"list", no_argument, 0, 'l'},
26	{"help", no_argument, 0, 'h'},
27	{NULL}
28};
29
30extern const char *__progname;
31static const char *kProgramName = __progname;
32
33
34static color_space
35color_space_for_depth(int32 depth)
36{
37	switch (depth) {
38		case 8:
39			return B_CMAP8;
40		case 15:
41			return B_RGB15;
42		case 16:
43			return B_RGB16;
44		case 24:
45			return B_RGB24;
46		case 32:
47		default:
48			return B_RGB32;
49	}
50}
51
52
53static void
54print_mode(const screen_mode& mode, bool shortOutput)
55{
56	const char* format
57		= shortOutput ? "%ld %ld %ld %g\n" : "%ld %ld, %ld bits, %g Hz\n";
58	printf(format, mode.width, mode.height, mode.BitsPerPixel(), mode.refresh);
59}
60
61
62static void
63print_mode(const display_mode& displayMode, const screen_mode& mode)
64{
65	const display_timing& timing = displayMode.timing;
66
67	printf("%lu  %u %u %u %u  %u %u %u %u ", timing.pixel_clock / 1000,
68		timing.h_display, timing.h_sync_start, timing.h_sync_end,
69		timing.h_total, timing.v_display, timing.v_sync_start,
70		timing.v_sync_end, timing.v_total);
71
72	// TODO: more flags?
73	if ((timing.flags & B_POSITIVE_HSYNC) != 0)
74		printf(" +HSync");
75	if ((timing.flags & B_POSITIVE_VSYNC) != 0)
76		printf(" +VSync");
77	if ((timing.flags & B_TIMING_INTERLACED) != 0)
78		printf(" Interlace");
79	printf(" %lu\n", mode.BitsPerPixel());
80}
81
82
83static void
84usage(int status)
85{
86	fprintf(stderr,
87		"Usage: %s [options] <mode>\n"
88		"Sets the specified screen mode. When no screen mode has been chosen,\n"
89		"the current one is printed. <mode> takes the form: <width> <height>\n"
90		"<depth> <refresh-rate>, or <width>x<height>, etc.\n"
91		"      --fall-back\tchanges to the standard fallback mode, and "
92			"displays a\n"
93		"\t\t\tnotification requester.\n"
94		"  -s  --short\t\twhen no mode is given the current screen mode is\n"
95			"\t\t\tprinted in short form.\n"
96		"  -l  --list\t\tdisplay a list of the available modes.\n"
97		"  -q  --dont-confirm\tdo not confirm the mode after setting it.\n"
98		"  -m  --modeline\taccept and print X-style modeline modes:\n"
99		"\t\t\t  <pclk> <h-display> <h-sync-start> <h-sync-end> <h-total>\n"
100		"\t\t\t  <v-disp> <v-sync-start> <v-sync-end> <v-total> [flags] "
101			"[depth]\n"
102		"\t\t\t(supported flags are: +/-HSync, +/-VSync, Interlace)\n",
103		kProgramName);
104
105	exit(status);
106}
107
108
109int
110main(int argc, char** argv)
111{
112	bool fallbackMode = false;
113	bool setMode = false;
114	bool shortOutput = false;
115	bool listModes = false;
116	bool modeLine = false;
117	bool confirm = true;
118	int width = -1;
119	int height = -1;
120	int depth = -1;
121	float refresh = -1;
122	display_mode mode;
123
124	// TODO: add a possibility to set a virtual screen size in addition to
125	// the display resolution!
126
127	int c;
128	while ((c = getopt_long(argc, argv, "shlfqm", kLongOptions, NULL)) != -1) {
129		switch (c) {
130			case 0:
131				break;
132			case 'f':
133				fallbackMode = true;
134				setMode = true;
135				confirm = false;
136				break;
137			case 's':
138				shortOutput = true;
139				break;
140			case 'l':
141				listModes = true;
142				break;
143			case 'm':
144				modeLine = true;
145				break;
146			case 'q':
147				confirm = false;
148				break;
149			case 'h':
150				usage(0);
151				break;
152			default:
153				usage(1);
154				break;
155		}
156	}
157
158	if (argc - optind > 0) {
159		int depthIndex = -1;
160
161		// arguments to specify the mode are following
162
163		if (!modeLine) {
164			int parsed = sscanf(argv[optind], "%dx%dx%d", &width, &height,
165				&depth);
166			if (parsed == 2)
167				depthIndex = optind + 1;
168			else if (parsed == 1) {
169				if (argc - optind > 1) {
170					height = strtol(argv[optind + 1], NULL, 0);
171					depthIndex = optind + 2;
172				} else
173					usage(1);
174			} else if (parsed != 3)
175				usage(1);
176
177			if (depthIndex > 0 && depthIndex < argc)
178				depth = strtol(argv[depthIndex], NULL, 0);
179			if (depthIndex + 1 < argc)
180				refresh = strtod(argv[depthIndex + 1], NULL);
181		} else {
182			// parse mode line
183			if (argc - optind < 9)
184				usage(1);
185
186			mode.timing.pixel_clock = strtol(argv[optind], NULL, 0) * 1000;
187			mode.timing.h_display = strtol(argv[optind + 1], NULL, 0);
188			mode.timing.h_sync_start = strtol(argv[optind + 2], NULL, 0);
189			mode.timing.h_sync_end = strtol(argv[optind + 3], NULL, 0);
190			mode.timing.h_total = strtol(argv[optind + 4], NULL, 0);
191			mode.timing.h_display = strtol(argv[optind + 5], NULL, 0);
192			mode.timing.h_sync_start = strtol(argv[optind + 6], NULL, 0);
193			mode.timing.h_sync_end = strtol(argv[optind + 7], NULL, 0);
194			mode.timing.h_total = strtol(argv[optind + 8], NULL, 0);
195			mode.timing.flags = 0;
196			mode.space = B_RGB32;
197
198			int i = optind + 9;
199			while (i < argc) {
200				if (!strcasecmp(argv[i], "+HSync"))
201					mode.timing.flags |= B_POSITIVE_HSYNC;
202				else if (!strcasecmp(argv[i], "+VSync"))
203					mode.timing.flags |= B_POSITIVE_VSYNC;
204				else if (!strcasecmp(argv[i], "Interlace"))
205					mode.timing.flags |= B_TIMING_INTERLACED;
206				else if (!strcasecmp(argv[i], "-VSync")
207					|| !strcasecmp(argv[i], "-HSync")) {
208					// okay, but nothing to do
209				} else if (isdigit(argv[i][0]) && i + 1 == argc) {
210					// bits per pixel
211					mode.space
212						= color_space_for_depth(strtoul(argv[i], NULL, 0));
213				} else {
214					fprintf(stderr, "Unknown flag: %s\n", argv[i]);
215					exit(1);
216				}
217
218				i++;
219			}
220
221			mode.virtual_width = mode.timing.h_display;
222			mode.virtual_height = mode.timing.v_display;
223			mode.h_display_start = 0;
224			mode.v_display_start = 0;
225		}
226
227		setMode = true;
228	}
229
230	BApplication application("application/x-vnd.Haiku-screenmode");
231
232	ScreenMode screenMode(NULL);
233	screen_mode currentMode;
234	screenMode.Get(currentMode);
235
236	if (listModes) {
237		// List all reported modes
238		if (!shortOutput)
239			printf("Available screen modes:\n");
240
241		for (int index = 0; index < screenMode.CountModes(); index++) {
242			if (modeLine) {
243				print_mode(screenMode.DisplayModeAt(index),
244					screenMode.ModeAt(index));
245			} else
246				print_mode(screenMode.ModeAt(index), shortOutput);
247		}
248
249		return 0;
250	}
251
252	if (!setMode) {
253		// Just print the current mode
254		if (modeLine) {
255			display_mode mode;
256			screenMode.Get(mode);
257			print_mode(mode, currentMode);
258		} else {
259			if (!shortOutput)
260				printf("Resolution: ");
261			print_mode(currentMode, shortOutput);
262		}
263		return 0;
264	}
265
266	screen_mode newMode = currentMode;
267
268	if (fallbackMode) {
269		if (currentMode.width == 800 && currentMode.height == 600) {
270			newMode.width = 640;
271			newMode.height = 480;
272			newMode.space = B_CMAP8;
273			newMode.refresh = 60;
274		} else {
275			newMode.width = 800;
276			newMode.height = 600;
277			newMode.space = B_RGB16;
278			newMode.refresh = 60;
279		}
280	} else if (modeLine) {
281		display_mode currentDisplayMode;
282		if (screenMode.Get(currentDisplayMode) == B_OK)
283			mode.flags = currentDisplayMode.flags;
284	} else {
285		newMode.width = width;
286		newMode.height = height;
287
288		if (depth != -1)
289			newMode.space = color_space_for_depth(depth);
290		else
291			newMode.space = B_RGB32;
292
293		if (refresh > 0)
294			newMode.refresh = refresh;
295		else
296			newMode.refresh = 60;
297	}
298
299	status_t status;
300	if (modeLine)
301		status = screenMode.Set(mode);
302	else
303		status = screenMode.Set(newMode);
304
305	if (status == B_OK) {
306		if (confirm) {
307			printf("Is this mode okay (Y/n - will revert after 10 seconds)? ");
308			fflush(stdout);
309
310			int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
311			fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
312
313			bigtime_t end = system_time() + 10000000LL;
314			int c = 'n';
315			while (system_time() < end) {
316				c = getchar();
317				if (c != -1)
318					break;
319
320				snooze(10000);
321			}
322
323			if (c != '\n' && tolower(c) != 'y')
324				screenMode.Revert();
325		}
326	} else {
327		fprintf(stderr, "%s: Could not set screen mode %ldx%ldx%ld: %s\n",
328			kProgramName, newMode.width, newMode.height, newMode.BitsPerPixel(),
329			strerror(status));
330		return 1;
331	}
332
333	if (fallbackMode) {
334		// display notification requester
335		BAlert* alert = new BAlert("screenmode",
336			"You have used the shortcut <Command><Ctrl><Escape> to reset the "
337			"screen mode to a safe fallback.", "Keep", "Revert");
338		alert->SetShortcut(1, B_ESCAPE);
339		if (alert->Go() == 1)
340			screenMode.Revert();
341	}
342
343	return 0;
344}
345