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