aboutsummaryrefslogtreecommitdiffstats
path: root/calibrator.c
diff options
context:
space:
mode:
Diffstat (limited to 'calibrator.c')
-rw-r--r--calibrator.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/calibrator.c b/calibrator.c
new file mode 100644
index 0000000..9456f96
--- /dev/null
+++ b/calibrator.c
@@ -0,0 +1,655 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+
+/**
+ * Draw bars in different shades of grey, red, green and blue
+ * used for calibrating the contrast and brightness
+ */
+static void
+draw_contrast_brightness(void)
+{
+ const int CONTRAST_BRIGHTNESS_LEVELS[21] = {
+ 0, 17, 27, 38, 48, 59, 70, 82, 94, 106, 119, 131,
+ 144, 158, 171, 185, 198, 212, 226, 241, 255
+ };
+ size_t f;
+ uint32_t y, x, colour;
+ framebuffer_t *restrict fb;
+ int v;
+
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = &framebuffers[f];
+ for (y = 0; y < 4; y++) {
+ for (x = 0; x < 21; x++) {
+ v = CONTRAST_BRIGHTNESS_LEVELS[x];
+ colour = fb_colour(v * ((y == 1) | (y == 0)),
+ v * ((y == 2) | (y == 0)),
+ v * ((y == 3) | (y == 0)));
+ fb_fill_rectangle(fb, colour, x * fb->width / 21, y * fb->height / 4,
+ (x + 1) * fb->width / 21 - x * fb->width / 21,
+ (y + 1) * fb->height / 4 - y * fb->height / 4);
+ }
+ }
+ }
+}
+
+
+/**
+ * Draw a seven segment display
+ *
+ * @param fb The framebuffer to draw on
+ * @param colour The intensity of the least intense colour to use
+ * @param x The X component of the top left corner of the seven segment display
+ * @param y The Y component of the top left corner of the seven segment display
+ */
+static void
+draw_digit(framebuffer_t *restrict fb, int colour, uint32_t x, uint32_t y)
+{
+ uint32_t c;
+
+ c = fb_colour(colour + 0, colour + 0, colour + 0);
+ fb_fill_rectangle(fb, c, x + 20, y, 80, 20);
+
+ c = fb_colour(colour + 1, colour + 1, colour + 1);
+ fb_fill_rectangle(fb, c, x, y + 20, 20, 80);
+
+ c = fb_colour(colour + 2, colour + 2, colour + 2);
+ fb_fill_rectangle(fb, c, x + 100, y + 20, 20, 80);
+
+ c = fb_colour(colour + 3, colour + 3, colour + 3);
+ fb_fill_rectangle(fb, c, x + 20, y + 100, 80, 20);
+
+ c = fb_colour(colour + 4, colour + 4, colour + 4);
+ fb_fill_rectangle(fb, c, x, y + 120, 20, 80);
+
+ c = fb_colour(colour + 5, colour + 5, colour + 5);
+ fb_fill_rectangle(fb, c, x + 100, y + 120, 20, 80);
+
+ c = fb_colour(colour + 6, colour + 6, colour + 6);
+ fb_fill_rectangle(fb, c, x + 20, y + 200, 80, 20);
+}
+
+
+/**
+ * Manipulate a CRT controllers gamma ramps to display a specific digit
+ * for one of the seven segment display on only that CRT controller's
+ * monitors
+ *
+ * @param crtc The CRT controller information
+ * @param colour The intensity of the least intense colour in the seven segment display
+ * @param value The valud of the digit to display
+ */
+static void
+gamma_digit(drm_crtc_t *restrict crtc, int colour, size_t value)
+{
+#define __ 0
+ const int DIGITS[11] = { 1 | 2 | 4 | __ | 16 | 32 | 64, /* (0) */
+ __ | __ | 4 | __ | __ | 32 | __, /* (1) */
+ 1 | __ | 4 | 8 | 16 | __ | 64, /* (2) */
+ 1 | __ | 4 | 8 | __ | 32 | 64, /* (3) */
+ __ | 2 | 4 | 8 | __ | 32 | __, /* (4) */
+ 1 | 2 | __ | 8 | __ | 32 | 64, /* (5) */
+ 1 | 2 | __ | 8 | 16 | 32 | 64, /* (6) */
+ 1 | __ | 4 | __ | __ | 32 | __, /* (7) */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64, /* (8) */
+ 1 | 2 | 4 | 8 | __ | 32 | 64, /* (9) */
+ __ | __ | __ | __ | __ | __ | __}; /* not visible */
+#undef __
+ int i, j, digit = DIGITS[value];
+ uint16_t c;
+
+ for (i = 0; i < 7; i++) {
+ c = (digit & (1 << i)) ? 0xFFFF : 0;
+ j = i + colour;
+ crtc->red[j] = crtc->green[j] = crtc->blue[j] = c;
+ }
+}
+
+
+/**
+ * Draw an unique index on each monitor
+ *
+ * @return Zero on success, -1 on error
+ */
+static int
+draw_id(void)
+{
+ size_t f, c, id = 0;
+ framebuffer_t *restrict fb;
+ drm_crtc_t *restrict crtc;
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = &framebuffers[f];
+ fb_fill_rectangle(fb, fb_colour(0, 0, 0), 0, 0, fb->width, fb->height);
+ draw_digit(fb, 1, 40, 40);
+ draw_digit(fb, 8, 180, 40);
+ }
+ for (c = 0; c < crtc_count; c++) {
+ crtc = &crtcs[c];
+ if (drm_get_gamma(crtc) < 0)
+ return -1;
+ gamma_digit(crtc, 1, id < 10 ? 10 : (id / 10) % 10);
+ gamma_digit(crtc, 8, (id / 1) % 10);
+ id++;
+ if (drm_set_gamma(crtc) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+/**
+ * Draw squares used as reference when tweeking the gamma correction
+ */
+static void
+draw_gamma(void)
+{
+ size_t f;
+ uint32_t x, y, background, average, high, low, xoff;
+ framebuffer_t *restrict fb;
+ int r, g, b;
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = framebuffers + f;
+ for (x = 0; x < 4; x++) {
+ r = (x == 1) || (x == 0);
+ g = (x == 2) || (x == 0);
+ b = (x == 3) || (x == 0);
+ background = fb_colour(128 * r, 128 * g, 128 * b);
+ average = fb_colour(188 * r, 188 * g, 188 * b);
+ high = fb_colour(255 * r, 255 * g, 255 * b);
+ low = fb_colour(0, 0, 0);
+ xoff = x * fb->width / 4;
+ fb_fill_rectangle(fb, background, xoff, 0, fb->width / 4, fb->height);
+ xoff += (fb->width / 4 - 200) / 2;
+ fb_fill_rectangle(fb, high, xoff, 40, 200, 200);
+ fb_fill_rectangle(fb, average, xoff + 50, 40, 100, 200);
+ fb_fill_rectangle(fb, average, xoff, 280, 200, 200);
+ for (y = 0; y < 200; y += 2) {
+ fb_draw_horizontal_line(fb, high, xoff + 50, 280 + y + 0, 100);
+ fb_draw_horizontal_line(fb, low , xoff + 50, 280 + y + 1, 100);
+ }
+ fb_fill_rectangle(fb, average, xoff, 520, 200, 200);
+ fb_fill_rectangle(fb, high, xoff + 50, 520, 100, 200);
+ }
+ }
+}
+
+
+/**
+ * Print a pattern on the screen that can be used when
+ * calibrating the convergence
+ */
+static void
+draw_convergence(void)
+{
+ uint32_t black = fb_colour(0, 0, 0);
+ uint32_t white = fb_colour(255, 255, 255);
+ uint32_t x, y;
+ size_t f;
+ framebuffer_t *restrict fb;
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = framebuffers + f;
+ fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height);
+ for (y = 0; y <= fb->height; y += 16) {
+ if (y == fb->height)
+ y = fb->height - 1;
+ for (x = 0; x <= fb->width; x += 16) {
+ if (x == fb->height)
+ x = fb->height - 1;
+ fb_draw_pixel(fb, white, x, y);
+ }
+ }
+ }
+}
+
+
+/**
+ * Print a pattern on the screen that can be used when
+ * calibrating the moiré cancellation
+ *
+ * @param gap The horizontal and vertical gap, in pixels, between the dots
+ * @param diagonal Whether to draw dots in a diagonal pattern
+ */
+static void
+draw_moire(uint32_t gap, int diagonal)
+{
+ uint32_t black = fb_colour(0, 0, 0);
+ uint32_t white = fb_colour(255, 255, 255);
+ uint32_t x, y, gap2 = gap << 1;
+ size_t f;
+ framebuffer_t *restrict fb;
+ gap += (uint32_t)!diagonal;
+ if (diagonal) {
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = framebuffers + f;
+ fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height);
+ for (y = 0; y < fb->height; y += gap)
+ for (x = (y % gap2); x < fb->width; x += gap2)
+ fb_draw_pixel(fb, white, x, y);
+ }
+ } else {
+ for (f = 0; f < framebuffer_count; f++) {
+ fb = framebuffers + f;
+ fb_fill_rectangle(fb, black, 0, 0, fb->width, fb->height);
+ for (y = 0; y < fb->height; y += gap)
+ for (x = 0; x < fb->width; x += gap)
+ fb_draw_pixel(fb, white, x, y);
+ }
+ }
+}
+
+
+/**
+ * Analyse the monitors calibrations
+ *
+ * @return Zero on success, -1 on error
+ */
+static int
+read_calibs(void)
+{
+ size_t c;
+ for (c = 0; c < crtc_count; c++) {
+ if (drm_get_gamma(&crtcs[c]) < 0)
+ return -1;
+ gamma_analyse(crtcs[c].gamma_stops, crtcs[c].red, &gammas[0][c], &contrasts[0][c], &brightnesses[0][c]);
+ gamma_analyse(crtcs[c].gamma_stops, crtcs[c].green, &gammas[1][c], &contrasts[1][c], &brightnesses[1][c]);
+ gamma_analyse(crtcs[c].gamma_stops, crtcs[c].blue, &gammas[2][c], &contrasts[2][c], &brightnesses[2][c]);
+ }
+ return 0;
+}
+
+
+/**
+ * Apply the selected calibrations to the monitors
+ *
+ * @return Zero on success, -1 on error
+ */
+static int
+apply_calibs(void)
+{
+ size_t c;
+ for (c = 0; c < crtc_count; c++) {
+ gamma_generate(crtcs[c].gamma_stops, crtcs[c].red, gammas[0][c], contrasts[0][c], brightnesses[0][c]);
+ gamma_generate(crtcs[c].gamma_stops, crtcs[c].green, gammas[1][c], contrasts[1][c], brightnesses[1][c]);
+ gamma_generate(crtcs[c].gamma_stops, crtcs[c].blue, gammas[2][c], contrasts[2][c], brightnesses[2][c]);
+ if (drm_set_gamma(&crtcs[c]) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+/**
+ * Print calibrations into a file
+ *
+ * @param fp The file
+ * @return Zero on success, -1 on error
+ */
+static int
+save_calibs(FILE *fp)
+{
+ size_t c;
+ for (c = 0; c < crtc_count; c++) {
+ if (fprintf(fp, "# index = %lu\n", c) < 0)
+ return -1;
+ if (fprintf(fp, "edid = %s\n", crtcs[c].edid) < 0)
+ return -1;
+ if (fprintf(fp, "brightness = %f:%f:%f\n", brightnesses[0][c], brightnesses[1][c], brightnesses[2][c]) < 0)
+ return -1;
+ if (fprintf(fp, "contrast = %f:%f:%f\n", contrasts[0][c], contrasts[1][c], contrasts[2][c]) < 0)
+ return -1;
+ if (fprintf(fp, "gamma = %f:%f:%f\n\n", gammas[0][c], gammas[1][c], gammas[2][c]) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ FILE *output_file = stdout;
+ int tty_configured = 0, rc = 0, in_fork = 0, status;
+ int c, b, d, at_contrast, red, green, blue;
+ struct termios stty, saved_stty;
+ uint32_t gap;
+ size_t mon;
+ pid_t pid;
+
+ if (argc > 1 && argv[1][0] == '-') {
+ if (argv[1][1] == '-' && !argv[1][2]) {
+ argv[1] = argv[2];
+ argc -= 1;
+ } else {
+ printf("usage: %s [output-file]\n", *argv);
+ return 1;
+ }
+ }
+ if (argc > 2) {
+ printf("usage: %s [output-file]\n", *argv);
+ return 0;
+ }
+
+ if ((acquire_video() < 0) ||
+ (tcgetattr(STDIN_FILENO, &saved_stty) < 0) ||
+ (tcgetattr(STDIN_FILENO, &stty) < 0))
+ goto fail;
+
+ stty.c_lflag &= (tcflag_t)~(ICANON | ECHO);
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty) < 0)
+ goto fail;
+ tty_configured = 1;
+
+ printf("\033[?25l");
+ fflush(stdout);
+
+ pid = fork();
+ if (pid > 0) {
+ while (waitpid(pid, &status, 0) < 0)
+ if (errno != EINTR)
+ perror(*argv);
+ rc = !!status;
+ goto done;
+ } else if (!pid) {
+ in_fork = 1;
+ }
+
+ printf("\033[H\033[2J");
+ printf("Please deactivate any program that dynamically\n");
+ printf("applies filters to your monitors' colours\n");
+ printf("and remove any existing filters.\n");
+ printf("In doubt, you probably do not have any.\n");
+ printf("Do not try to calibrate CRT monitors will\n");
+ printf("they are cold.\n");
+ printf("\n");
+ printf("You will be presented with an image on each\n");
+ printf("monitor. Please use the control panel on your\n");
+ printf("to calibrate the contrast and brightness of\n");
+ printf("each monitor. The contrasts adjusts the\n");
+ printf("brightness of bright colours, and the\n");
+ printf("brightness adjusts the brightness of dim\n");
+ printf("colours. All rectangles are of equals size\n");
+ printf("and they should be distrint from eachother.\n");
+ printf("There should only be a slight difference\n");
+ printf("between the two darkest colours for each\n");
+ printf("colour. The grey colour does not need to\n");
+ printf("be perfectly grey but should be close.\n");
+ printf("The brightness should be as high as\n");
+ printf("possible without the first square being\n");
+ printf("any other colour than black or the two first\n");
+ printf("square being too distinct from eachother. The\n");
+ printf("contrast should be as high as possible without\n");
+ printf("causing distortion.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ draw_contrast_brightness();
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ printf("An index will be displayed on each monitor.\n");
+ printf("It behoves you to memorise them. They will\n");
+ printf("be used in the output when descibing the\n");
+ printf("calibrations, and is the index of the monitors\n");
+ printf("that are used when changing monitor to\n");
+ printf("calibrate.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ if (read_calibs() || draw_id())
+ goto fail;
+
+ while (getchar() != '\n');
+
+ if (apply_calibs())
+ goto fail;
+
+ printf("\033[H\033[2J");
+ printf("You will not be given the opportunity to.\n");
+ printf("calibrate your monitors' brightness and\n");
+ printf("contrast using software incase your monitors\n");
+ printf("could not be sufficiently calibrated using\n");
+ printf("hardware.\n");
+ printf("\n");
+ printf("<Left> and <right> is used to change which\n");
+ printf("monitor to calibrate. <Left> switches to the\n");
+ printf("previous monitor (one lower in index) and\n");
+ printf("<right> switches to the next monitor (one\n");
+ printf("higher in index.)\n");
+ printf("<Up> and <down> is used to increase and\n");
+ printf("descrease the settings. respectively.\n");
+ printf("<Shift+b> is used to switch to changing the.\n");
+ printf("monitor's brightness and <shift+c> switches\n");
+ printf("to contrast.\n");
+ printf("<r> is used to switch to changing the red.\n");
+ printf("channel, <g> switches to the green channel,\n");
+ printf("<b> switches to the blue channel, and <a>\n");
+ printf("is used to switch to change all channels.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ draw_contrast_brightness();
+
+ b = at_contrast = 0;
+ red = green = blue = 1;
+ mon = 0;
+ while ((c = getchar()) != '\n') {
+ if (b) {
+ b = 0;
+ if (c == 'A' && at_contrast) {
+ contrasts[0][mon] += (double)red / 100;
+ contrasts[1][mon] += (double)green / 100;
+ contrasts[2][mon] += (double)blue / 100;
+ } else if (c == 'A') {
+ brightnesses[0][mon] += (double)red / 100;
+ brightnesses[1][mon] += (double)green / 100;
+ brightnesses[2][mon] += (double)blue / 100;
+ } else if (c == 'B' && at_contrast) {
+ contrasts[0][mon] -= (double)red / 100;
+ contrasts[1][mon] -= (double)green / 100;
+ contrasts[2][mon] -= (double)blue / 100;
+ } else if (c == 'B') {
+ brightnesses[0][mon] -= (double)red / 100;
+ brightnesses[1][mon] -= (double)green / 100;
+ brightnesses[2][mon] -= (double)blue / 100;
+ } else if (c == 'C') {
+ mon = (mon + 1) % crtc_count;
+ } else if (c == 'D') {
+ mon = (mon == 0 ? crtc_count : mon) - 1;
+ }
+
+ if (c == 'A' || c == 'B')
+ apply_calibs();
+ }
+ else if (c == '[') b = 1;
+ else if (c == 'B') at_contrast = 0;
+ else if (c == 'C') at_contrast = 1;
+ else if (c == 'r') red = 1, green = 0, blue = 0;
+ else if (c == 'g') red = 0, green = 1, blue = 0;
+ else if (c == 'b') red = 0, green = 0, blue = 1;
+ else if (c == 'a') red = 1, green = 1, blue = 1;
+ }
+
+ printf("\033[H\033[2J");
+ printf("You will now be presented with squares used\n");
+ printf("to calibrate the gamma correction. There will\n");
+ printf("be four stacks: grey, red, green and blue.\n");
+ printf("Each stack has three squares: the upper square\n");
+ printf("shows the characterics of how the middle square\n");
+ printf("will look if the gamma is too high, and the\n");
+ printf("lower shows how the middile will look if the\n");
+ printf("gamma is too low. The middle square should\n");
+ printf("look like it is one single colour if the gamma\n");
+ printf("correction is configured correctly. You may\n");
+ printf("have to look from a distance or not focus\n");
+ printf("your eyes on the squares to compensate for\n");
+ printf("the fact that there actually multiple colours\n");
+ printf("is the square.\n");
+ printf("The grey should look perfectly grey when you\n");
+ printf("are done.\n");
+ printf("\n");
+ printf("<Left> and <right> is used to change which\n");
+ printf("monitor to calibrate. <Left> switches to the\n");
+ printf("previous monitor (one lower in index) and\n");
+ printf("<right> switches to the next monitor (one\n");
+ printf("higher in index.)\n");
+ printf("<Up> and <down> is used to increase and\n");
+ printf("descrease the gamma. respectively.\n");
+ printf("<r> is used to switch to changing the red.\n");
+ printf("channel, <g> switches to the green channel,\n");
+ printf("<b> switches to the blue channel, and <a>\n");
+ printf("is used to switch to change all channels.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ draw_gamma();
+
+ b = 0;
+ red = green = blue = 1;
+ mon = 0;
+ while ((c = getchar()) != '\n') {
+ if (b) {
+ b = 0;
+ if (c == 'A') {
+ gammas[0][mon] += (double)red / 100;
+ gammas[1][mon] += (double)green / 100;
+ gammas[2][mon] += (double)blue / 100;
+ } else if (c == 'B') {
+ gammas[0][mon] -= (double)red / 100;
+ gammas[1][mon] -= (double)green / 100;
+ gammas[2][mon] -= (double)blue / 100;
+ if (gammas[0][mon] < 0) gammas[0][mon] = 0;
+ if (gammas[1][mon] < 0) gammas[1][mon] = 0;
+ if (gammas[2][mon] < 0) gammas[2][mon] = 0;
+ } else if (c == 'C') {
+ mon = (mon + 1) % crtc_count;
+ } else if (c == 'D') {
+ mon = (mon == 0 ? crtc_count : mon) - 1;
+ }
+
+ if (c == 'A' || c == 'B')
+ apply_calibs();
+ }
+ else if (c == '[') b = 1;
+ else if (c == 'r') red = 1, green = 0, blue = 0;
+ else if (c == 'g') red = 0, green = 1, blue = 0;
+ else if (c == 'b') red = 0, green = 0, blue = 1;
+ else if (c == 'a') red = 1, green = 1, blue = 1;
+ }
+
+ printf("\033[H\033[2J");
+ printf("The next step is to calibrate the monitors'\n");
+ printf("convergence settings using the monitors'\n");
+ printf("control panel. White dots will be printed on the\n");
+ printf("screens, you should try to get as many as\n");
+ printf("possible of them to appear as pure white dots\n");
+ printf("rather than dots splitted in the red, green and\n");
+ printf("blue dots. On most CRT monitors this is not\n");
+ printf("possible to get all corners perfect.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ draw_convergence();
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ printf("The final step is to calbirate the monitors' moiré\n");
+ printf("cancellation. This too is done on the using the\n");
+ printf("monitors' control panel.\n");
+ printf("\n");
+ printf("You can use <d> and the arrow keys to change the\n");
+ printf("dot-pattern on the screens.\n");
+ printf("\n");
+ printf("Press ENTER to continue, and ENTER again when\n");
+ printf("your are done.\n");
+ fflush(stdout);
+
+ while (getchar() != '\n');
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+ draw_moire(1, 1);
+
+ b = 0;
+ d = 1;
+ gap = 1;
+ while ((c = getchar()) != '\n') {
+ if (b) {
+ b = 0;
+ if (c == 'A' || c == 'C') {
+ draw_moire(++gap, d);
+ } else if (c == 'B' || c == 'D') {
+ if (--gap == 0)
+ gap = 1;
+ draw_moire(gap, d);
+ }
+ } else if (c == '[') {
+ b = 1;
+ } else if (c == 'd') {
+ draw_moire(gap, d ^= 1);
+ }
+ }
+
+ printf("\033[H\033[2J");
+ fflush(stdout);
+
+ if (argc == 2) {
+ output_file = fopen(argv[1], "w");
+ if (!output_file)
+ goto fail;
+ }
+
+ if (save_calibs(output_file))
+ goto fail;
+ fflush(output_file);
+
+ if (argc == 2)
+ if (fclose(output_file))
+ goto fail;
+
+done:
+ if (!in_fork) {
+ release_video();
+ if (tty_configured)
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty);
+ printf("\033[?25h");
+ fflush(stdout);
+ }
+ return rc;
+fail:
+ perror(*argv);
+ rc = 1;
+ goto done;
+}