/* SPDX-License-Identifier: GPL-2.0-only */ /* * fbgrab - takes screenshots using the framebuffer. * * (C) Gunnar Monell 2002-2021 * * This program is free Software, see the COPYING file * and is based on Stephan Beyer's FBShot * (C) 2000. * * For features and differences, read the manual page. * * This program has been checked with "splint +posixlib" without * warnings. Splint is available from http://www.splint.org/ . * Patches and enhancements of fbgrab have to fulfill this too. */ #include #include #include #include #include #include #include #include /* to handle vt changing */ #ifdef S_SPLINT_S #define Z_BEST_COMPRESSION 1 #define Z_DEFAULT_COMPRESSION 0 #define png_int_32 int #define png_byte char #define png_uint_32 unsigned #define png_int_16 short #define png_uint_16 unsigned #define __signed__ signed #else #include #endif #include /* PNG lib */ #include /* to handle framebuffer ioctls */ #define VERSION "1.5" #define DEFAULT_FB "/dev/fb0" #define MAX_LEN 512 #define UNDEFINED -1 static int srcBlue = 0; static int srcGreen = 1; static int srcRed = 2; static int srcAlpha = 3; static const __u32 Blue = 0; static const __u32 Green = 1; static const __u32 Red = 2; static const __u32 Alpha = 3; /*@noreturn@*/ static void fatal_error(char *message) { fprintf(stderr, "%s\n", message); exit(EXIT_FAILURE); } static void usage(char *binary) { fprintf(stderr, "Usage: %s\t[-hi] [-{C|c} vt] [-d dev] [-s n] [-z n]\n" "\t\t[-f fromfile -w n -h n -b n] filename.png\n", binary); } static void help(char *binary) { fprintf(stderr, "fbgrab - takes screenshots using the framebuffer, v%s\n", VERSION); usage(binary); fprintf(stderr, "\nPossible options:\n"); /* please keep this list alphabetical */ fprintf(stderr, "\t-a \tignore the alpha channel, to support pixel formats like BGR32\n"); fprintf(stderr, "\t-b n \tforce use of n bits/pixel, required when reading from file\n"); fprintf(stderr, "\t-C n \tgrab from console n, for slower framebuffers\n"); fprintf(stderr, "\t-c n \tgrab from console n\n"); fprintf(stderr, "\t-d dev\tuse framebuffer device dev instead of default\n"); fprintf(stderr, "\t-f file\t read from file instead of framebuffer\n"); fprintf(stderr, "\t-h n \tset height to n pixels, required when reading from file\n" "\t\tcan be used to force height when reading from framebuffer\n"); fprintf(stderr, "\t-i \tturns on interlacing in PNG\n"); fprintf(stderr, "\t-s n \tsleep n seconds before making screenshot\n"); fprintf(stderr, "\t-v \tverbose, print debug information.\n"); fprintf(stderr, "\t-w n \tset width to n pixels, required when reading from file\n" "\t\tcan be used to force width when reading from framebuffer\n"); fprintf(stderr, "\t-l n \tset line length, stride, to n pixels, required when reading from file\n"); fprintf(stderr, "\t-z n \tPNG compression level: 0 (fast) .. 9 (best)\n"); fprintf(stderr, "\t-? \tprint this usage information\n"); } static void chvt(int num) { int fd; if(-1 == (fd = open("/dev/console", O_RDWR))) fatal_error("Cannot open /dev/console"); if (ioctl(fd, VT_ACTIVATE, num) != 0) fatal_error("ioctl VT_ACTIVATE"); if (ioctl(fd, VT_WAITACTIVE, num) != 0) fatal_error("ioctl VT_WAITACTIVE"); (void) close(fd); } static unsigned short int change_to_vt(unsigned short int vt_num) { int fd; unsigned short int old_vt; struct vt_stat vt_info; memset(&vt_info, 0, sizeof(struct vt_stat)); if(-1 == (fd=open("/dev/console", O_RDONLY))) fatal_error("Couldn't open /dev/console"); if (ioctl(fd, VT_GETSTATE, &vt_info) != 0) fatal_error("ioctl VT_GETSTATE"); (void) close (fd); old_vt = vt_info.v_active; chvt((int) vt_num); /* go there for information */ return old_vt; } static void get_framebufferdata(char *device, struct fb_var_screeninfo *fb_varinfo_p, struct fb_fix_screeninfo *fb_fixedinfo, int verbose) { int fd; /* now open framebuffer device */ if(-1 == (fd=open(device, O_RDONLY))) { fprintf(stderr, "Error: Couldn't open %s.\n", device); exit(EXIT_FAILURE); } if (ioctl(fd, FBIOGET_VSCREENINFO, fb_varinfo_p) != 0) fatal_error("ioctl FBIOGET_VSCREENINFO"); if (ioctl(fd, FBIOGET_FSCREENINFO, fb_fixedinfo) != 0) fatal_error("ioctl FBIOGET_FSCREENINFO"); if (0!=verbose) { fprintf(stderr, "frame buffer fixed info:\n"); fprintf(stderr, "id: \"%s\"\n", fb_fixedinfo->id); switch (fb_fixedinfo->type) { case FB_TYPE_PACKED_PIXELS: fprintf(stderr, "type: packed pixels\n"); break; case FB_TYPE_PLANES: fprintf(stderr, "type: non interleaved planes\n"); break; case FB_TYPE_INTERLEAVED_PLANES: fprintf(stderr, "type: interleaved planes\n"); break; case FB_TYPE_TEXT: fprintf(stderr, "type: text/attributes\n"); break; case FB_TYPE_VGA_PLANES: fprintf(stderr, "type: EGA/VGA planes\n"); break; default: fprintf(stderr, "type: undefined!\n"); break; } fprintf(stderr, "line length: %i bytes (%i pixels)\n", (int) fb_fixedinfo->line_length, (int) fb_fixedinfo->line_length/(fb_varinfo_p->bits_per_pixel/8)); fprintf(stderr, "\nframe buffer variable info:\n"); fprintf(stderr, "resolution: %ix%i\n", (int) fb_varinfo_p->xres, (int) fb_varinfo_p->yres); fprintf(stderr, "virtual resolution: %ix%i\n", (int) fb_varinfo_p->xres_virtual, (int) fb_varinfo_p->yres_virtual); fprintf(stderr, "offset: %ix%i\n", (int) fb_varinfo_p->xoffset, (int) fb_varinfo_p->yoffset); fprintf(stderr, "bits_per_pixel: %i\n", (int) fb_varinfo_p->bits_per_pixel); fprintf(stderr, "grayscale: %s\n", fb_varinfo_p->grayscale!=0 ? "true" : "false"); fprintf(stderr, "red: offset: %i, length: %i, msb_right: %i\n", (int) fb_varinfo_p->red.offset, (int) fb_varinfo_p->red.length, (int) fb_varinfo_p->red.msb_right); fprintf(stderr, "green: offset: %i, length: %i, msb_right: %i\n", (int) fb_varinfo_p->green.offset, (int) fb_varinfo_p->green.length, (int) fb_varinfo_p->green.msb_right); fprintf(stderr, "blue: offset: %i, length: %i, msb_right: %i\n", (int) fb_varinfo_p->blue.offset, (int) fb_varinfo_p->blue.length, (int) fb_varinfo_p->blue.msb_right); fprintf(stderr, "alpha: offset: %i, length: %i, msb_right: %i\n", (int) fb_varinfo_p->transp.offset, (int) fb_varinfo_p->transp.length, (int) fb_varinfo_p->transp.msb_right); fprintf(stderr, "pixel format: %s\n", fb_varinfo_p->nonstd == 0 ? "standard" : "non-standard"); } srcBlue = (int) (fb_varinfo_p->blue.offset >> 3); srcGreen = (int) (fb_varinfo_p->green.offset >> 3); srcRed = (int) (fb_varinfo_p->red.offset >> 3); (void) close(fd); } static void read_framebuffer(char *device, size_t bytes, unsigned char *buf_p, int skip_bytes) { int fd; if(-1 == (fd=open(device, O_RDONLY))) { fprintf(stderr, "Error: Couldn't open %s.\n", device); exit(EXIT_FAILURE); } if (skip_bytes!=0) { if (-1 == lseek(fd, skip_bytes, SEEK_SET)) fatal_error("Error: Could not seek to framebuffer start position.\n"); } if (buf_p == NULL || read(fd, buf_p, bytes) != (ssize_t) bytes) fatal_error("Error: Not enough memory or data\n"); } static void convert1555to32(unsigned int width, unsigned int height, int line_length, unsigned char *inbuffer, unsigned char *outbuffer) { unsigned int row; unsigned int col; for (row=0; row < height; row++) for (col=0; col < width; col++) { unsigned int srcidx = 2 * (row * line_length + col); unsigned int dstidx = 4 * (row * width + col); /* BLUE = 0 */ outbuffer[dstidx+Blue] = (inbuffer[srcidx+1] & 0x7C) << 1; /* GREEN = 1 */ outbuffer[dstidx+Green] = (((inbuffer[srcidx+1] & 0x3) << 3) | ((inbuffer[srcidx] & 0xE0) >> 5)) << 3; /* RED = 2 */ outbuffer[dstidx+Red] = (inbuffer[srcidx] & 0x1f) << 3; /* ALPHA = 3 */ outbuffer[dstidx+Alpha] = '\0'; } } static void convert565to32(unsigned int width, unsigned int height, int line_length, unsigned char *inbuffer, unsigned char *outbuffer) { unsigned int row; unsigned int col; for (row=0; row < height; row++) for (col=0; col < width; col++) { unsigned int srcidx = 2 * (row * line_length + col); unsigned int dstidx = 4 * (row * width + col); /* BLUE = 0 */ outbuffer[dstidx+Blue] = (inbuffer[srcidx] & 0x1f) << 3; /* GREEN = 1 */ outbuffer[dstidx+Green] = (((inbuffer[srcidx+1] & 0x7) << 3) | (inbuffer[srcidx] & 0xE0) >> 5) << 2; /* RED = 2 */ outbuffer[dstidx+Red] = (inbuffer[srcidx+1] & 0xF8); /* ALPHA = 3 */ outbuffer[dstidx+Alpha] = '\0'; } } static void convert888to32(unsigned int width, unsigned int height, int line_length, unsigned char *inbuffer, unsigned char *outbuffer) { unsigned int row; unsigned int col; for (row=0; row= 0 ? inbuffer[srcidx+srcAlpha] : '\0'; } } static void write_PNG(unsigned char *outbuffer, char *filename, unsigned int width, unsigned int height, int interlace, int compression) { unsigned int i; int bit_depth=0, color_type; png_bytep row_pointers[height]; png_structp png_ptr; png_infop info_ptr; FILE *outfile; memset((void*) row_pointers, (int) sizeof(png_bytep), (size_t) height); if (strcmp(filename, "-") == 0) outfile = stdout; else { outfile = fopen(filename, "wb"); if (!outfile) { fprintf(stderr, "Error: Couldn't fopen %s.\n", filename); exit(EXIT_FAILURE); } } for (i=0; i 0) { if (UNDEFINED == bitdepth || UNDEFINED == width || UNDEFINED == height) { fprintf(stderr, "Width, height and bitdepth are mandatory when reading from file\n"); exit(EXIT_FAILURE); } } else { if (NULL == device) { device = getenv("FRAMEBUFFER"); if (NULL == device) { device = DEFAULT_FB; } } get_framebufferdata(device, &fb_varinfo, &fb_fixedinfo, verbose); if ((UNDEFINED == ignore_alpha) && (fb_varinfo.transp.length > 0)) { srcAlpha = (int) (fb_varinfo.transp.offset >> 3); } else { srcAlpha = -1; // not used } if (UNDEFINED == bitdepth) bitdepth = (int) fb_varinfo.bits_per_pixel; if (UNDEFINED == width) width = (int) fb_varinfo.xres; if (UNDEFINED == height) height = (int) fb_varinfo.yres; if (UNDEFINED == line_length) line_length = (int) fb_fixedinfo.line_length/(fb_varinfo.bits_per_pixel>>3); skip_bytes = (int) ((fb_varinfo.yoffset * fb_varinfo.xres) * (fb_varinfo.bits_per_pixel >> 3)); fprintf(stderr, "Resolution: %ix%i depth %i\n", width, height, bitdepth); strncpy(infile, device, MAX_LEN - 1); } buf_size = (size_t) (line_length * height * (((unsigned int) bitdepth + 7) >> 3)); buf_p = (unsigned char*) malloc(buf_size); if(buf_p == NULL) fatal_error("Not enough memory"); if(line_length < width) fatal_error("Line length cannot be smaller than width"); memset(buf_p, 0, buf_size); read_framebuffer(infile, buf_size, buf_p, skip_bytes); if (UNDEFINED != old_vt) (void) change_to_vt((unsigned short int) old_vt); convert_and_write(buf_p, outfile, (unsigned int) width, (unsigned int) height, line_length, bitdepth, interlace, png_compression); (void) free(buf_p); return 0; }