⛏️ index : haiku.git

/*
 * Copyright 2008-2011, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include <Alert.h>
#include <Application.h>
#include <Screen.h>

#include "ScreenMode.h"


static struct option const kLongOptions[] = {
	{"fall-back", no_argument, 0, 'f'},
	{"dont-confirm", no_argument, 0, 'q'},
	{"modeline", no_argument, 0, 'm'},
	{"short", no_argument, 0, 's'},
	{"list", no_argument, 0, 'l'},
	{"help", no_argument, 0, 'h'},
	{"brightness", required_argument, 0, 'b'},
	{"get-brightness", no_argument, 0, 'B'},
	{NULL}
};

extern const char *__progname;
static const char *kProgramName = __progname;


static color_space
color_space_for_depth(int32 depth)
{
	switch (depth) {
		case 8:
			return B_CMAP8;
		case 15:
			return B_RGB15;
		case 16:
			return B_RGB16;
		case 24:
			return B_RGB24;
		case 32:
		default:
			return B_RGB32;
	}
}


static void
print_mode(const screen_mode& mode, bool shortOutput)
{
	const char* format
		= shortOutput ? "%ld %ld %ld %g\n" : "%ld %ld, %ld bits, %g Hz\n";
	printf(format, mode.width, mode.height, mode.BitsPerPixel(), mode.refresh);
}


static void
print_mode(const display_mode& displayMode, const screen_mode& mode)
{
	const display_timing& timing = displayMode.timing;

	printf("%" B_PRIu32 "  %u %u %u %u  %u %u %u %u ", timing.pixel_clock / 1000,
		timing.h_display, timing.h_sync_start, timing.h_sync_end,
		timing.h_total, timing.v_display, timing.v_sync_start,
		timing.v_sync_end, timing.v_total);

	// TODO: more flags?
	if ((timing.flags & B_POSITIVE_HSYNC) != 0)
		printf(" +HSync");
	if ((timing.flags & B_POSITIVE_VSYNC) != 0)
		printf(" +VSync");
	if ((timing.flags & B_TIMING_INTERLACED) != 0)
		printf(" Interlace");
	printf(" %" B_PRId32 "\n", mode.BitsPerPixel());
}


static void
usage(int status)
{
	fprintf(stderr,
		"Usage: %s [options] <mode>\n"
		"Sets the specified screen mode. When no screen mode has been chosen,\n"
		"the current one is printed. <mode> takes the form: <width> <height>\n"
		"<depth> <refresh-rate>, or <width>x<height>, etc.\n"
		"      --fall-back\tchanges to the standard fallback mode, and "
			"displays a\n"
		"\t\t\tnotification requester.\n"
		"  -s  --short\t\twhen no mode is given the current screen mode or\n\n"
			"\t\t\tthe screen brightness is printed in short form.\n"
		"  -l  --list\t\tdisplay a list of the available modes.\n"
		"  -q  --dont-confirm\tdo not confirm the mode after setting it.\n"
		"  -b  --brightness f\tset brightness (range 0 to 1).\n"
		"  -b  --brightness +/-f\tchange brightness by given amount.\n"
		"  -B  --get-brightness\tprint the current brightness to stdout.\n"
		"\t\t\tinstead of the screen mode\n"
		"  -m  --modeline\taccept and print X-style modeline modes:\n"
		"\t\t\t  <pclk> <h-display> <h-sync-start> <h-sync-end> <h-total>\n"
		"\t\t\t  <v-disp> <v-sync-start> <v-sync-end> <v-total> [flags] "
			"[depth]\n"
		"\t\t\t(supported flags are: +/-HSync, +/-VSync, Interlace)\n",
		kProgramName);

	exit(status);
}


int
main(int argc, char** argv)
{
	bool fallbackMode = false;
	bool setMode = false;
	bool shortOutput = false;
	bool listModes = false;
	bool modeLine = false;
	bool getBrightness = false;
	bool confirm = true;
	int width = -1;
	int height = -1;
	int depth = -1;
	float refresh = -1;
	float brightness = std::nanf("0");
	bool relativeBrightness = false;
	display_mode mode;

	// TODO: add a possibility to set a virtual screen size in addition to
	// the display resolution!

	int c;
	while ((c = getopt_long(argc, argv, "shlfqmb:B", kLongOptions, NULL)) != -1) {
		switch (c) {
			case 0:
				break;
			case 'f':
				fallbackMode = true;
				setMode = true;
				confirm = false;
				break;
			case 's':
				shortOutput = true;
				break;
			case 'l':
				listModes = true;
				break;
			case 'm':
				modeLine = true;
				break;
			case 'q':
				confirm = false;
				break;
			case 'b':
				if (optarg[0] == '+' || optarg[0] == '-')
					relativeBrightness = true;
				brightness = atof(optarg);
				break;
			case 'B':
				getBrightness = true;
				break;
			case 'h':
				usage(0);
				break;
			default:
				usage(1);
				break;
		}
	}

	if (argc - optind > 0) {
		int depthIndex = -1;

		// arguments to specify the mode are following

		if (!modeLine) {
			int parsed = sscanf(argv[optind], "%dx%dx%d", &width, &height,
				&depth);
			if (parsed == 2)
				depthIndex = optind + 1;
			else if (parsed == 1) {
				if (argc - optind > 1) {
					height = strtol(argv[optind + 1], NULL, 0);
					depthIndex = optind + 2;
				} else
					usage(1);
			} else if (parsed != 3)
				usage(1);

			if (depthIndex > 0 && depthIndex < argc)
				depth = strtol(argv[depthIndex], NULL, 0);
			if (depthIndex + 1 < argc)
				refresh = strtod(argv[depthIndex + 1], NULL);
		} else {
			// parse mode line
			if (argc - optind < 9)
				usage(1);

			mode.timing.pixel_clock = strtol(argv[optind], NULL, 0) * 1000;
			mode.timing.h_display = strtol(argv[optind + 1], NULL, 0);
			mode.timing.h_sync_start = strtol(argv[optind + 2], NULL, 0);
			mode.timing.h_sync_end = strtol(argv[optind + 3], NULL, 0);
			mode.timing.h_total = strtol(argv[optind + 4], NULL, 0);
			mode.timing.v_display = strtol(argv[optind + 5], NULL, 0);
			mode.timing.v_sync_start = strtol(argv[optind + 6], NULL, 0);
			mode.timing.v_sync_end = strtol(argv[optind + 7], NULL, 0);
			mode.timing.v_total = strtol(argv[optind + 8], NULL, 0);
			mode.timing.flags = 0;
			mode.space = B_RGB32;

			int i = optind + 9;
			while (i < argc) {
				if (!strcasecmp(argv[i], "+HSync"))
					mode.timing.flags |= B_POSITIVE_HSYNC;
				else if (!strcasecmp(argv[i], "+VSync"))
					mode.timing.flags |= B_POSITIVE_VSYNC;
				else if (!strcasecmp(argv[i], "Interlace"))
					mode.timing.flags |= B_TIMING_INTERLACED;
				else if (!strcasecmp(argv[i], "-VSync")
					|| !strcasecmp(argv[i], "-HSync")) {
					// okay, but nothing to do
				} else if (isdigit(argv[i][0]) && i + 1 == argc) {
					// bits per pixel
					mode.space
						= color_space_for_depth(strtoul(argv[i], NULL, 0));
				} else {
					fprintf(stderr, "Unknown flag: %s\n", argv[i]);
					exit(1);
				}

				i++;
			}

			mode.virtual_width = mode.timing.h_display;
			mode.virtual_height = mode.timing.v_display;
			mode.h_display_start = 0;
			mode.v_display_start = 0;
		}

		setMode = true;
	}

	BApplication application("application/x-vnd.Haiku-screenmode");

	ScreenMode screenMode(NULL);
	screen_mode currentMode;
	screenMode.Get(currentMode);
	if (!isnan(brightness)) {
		BScreen screen;
		if (relativeBrightness) {
			float previousBrightness;
			screen.GetBrightness(&previousBrightness);
			brightness = previousBrightness + brightness;

			// Clamp to min/max values
			if (brightness < 0.f)
				brightness = 0.f;

			if (brightness > 1.f)
				brightness = 1.f;
		}

		if (brightness < 0.f || brightness > 1.f)
			printf("Brightness %f is out of range\n", brightness);
		screen.SetBrightness(brightness);
	}

	if (listModes) {
		// List all reported modes
		if (!shortOutput)
			printf("Available screen modes:\n");

		for (int index = 0; index < screenMode.CountModes(); index++) {
			if (modeLine) {
				print_mode(screenMode.DisplayModeAt(index),
					screenMode.ModeAt(index));
			} else
				print_mode(screenMode.ModeAt(index), shortOutput);
		}

		return 0;
	}

	if (!setMode && !getBrightness) {
		// Just print the current mode
		if (modeLine) {
			display_mode mode;
			screenMode.Get(mode);
			print_mode(mode, currentMode);
		} else {
			if (!shortOutput)
				printf("Resolution: ");
			print_mode(currentMode, shortOutput);
		}
		return 0;
	}

	status_t status;

	if (getBrightness) {
		float brightnessToPrint = std::nanf("0");
		BScreen screen;
		status = screen.GetBrightness(&brightnessToPrint);
		if (status != B_OK) {
			fprintf(stderr, "Error retrieving brightness: %s\n", strerror(status));
			return 1;
		}
		if (!shortOutput)
			printf("Brightness: ");
		printf("%f\n", brightnessToPrint);
		return 0;
	}

	screen_mode newMode = currentMode;

	if (fallbackMode) {
		if (currentMode.width == 800 && currentMode.height == 600) {
			newMode.width = 640;
			newMode.height = 480;
			newMode.space = B_CMAP8;
			newMode.refresh = 60;
		} else {
			newMode.width = 800;
			newMode.height = 600;
			newMode.space = B_RGB16;
			newMode.refresh = 60;
		}
	} else if (modeLine) {
		display_mode currentDisplayMode;
		if (screenMode.Get(currentDisplayMode) == B_OK)
			mode.flags = currentDisplayMode.flags;
	} else {
		newMode.width = width;
		newMode.height = height;

		if (depth != -1)
			newMode.space = color_space_for_depth(depth);
		else
			newMode.space = B_RGB32;

		if (refresh > 0)
			newMode.refresh = refresh;
		else
			newMode.refresh = 60;
	}

	if (modeLine)
		status = screenMode.Set(mode);
	else
		status = screenMode.Set(newMode);

	if (status == B_OK) {
		if (confirm) {
			printf("Is this mode okay (Y/n - will revert after 10 seconds)? ");
			fflush(stdout);

			int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
			fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);

			bigtime_t end = system_time() + 10000000LL;
			int c = 'n';
			while (system_time() < end) {
				c = getchar();
				if (c != -1)
					break;

				snooze(10000);
			}

			if (c != '\n' && tolower(c) != 'y')
				screenMode.Revert();
		}
	} else {
		fprintf(stderr,
			"%s: Could not set screen mode "
			"%" B_PRId32 "x%" B_PRId32 "x%" B_PRId32 ": "
			"%s\n",
				kProgramName,
				newMode.width, newMode.height, newMode.BitsPerPixel(),
				strerror(status));
		return 1;
	}

	if (fallbackMode) {
		// display notification requester
		BAlert* alert = new BAlert("screenmode",
			"You have used the shortcut <Shift><Command><Ctrl><Escape> to "
			"reset the screen mode to a safe fallback.", "Keep", "Revert");
		alert->SetShortcut(1, B_ESCAPE);
		if (alert->Go() == 1)
			screenMode.Revert();
	}

	return 0;
}