pax_global_header00006660000000000000000000000064142034343140014510gustar00rootroot0000000000000052 comment=14abea742ab0c1f4536ecc9ebd739cc97893ee55 grim-1.4.0+ds/000077500000000000000000000000001420343431400130525ustar00rootroot00000000000000grim-1.4.0+ds/.build.yml000066400000000000000000000004011420343431400147450ustar00rootroot00000000000000image: archlinux packages: - meson - wayland - wayland-protocols - pixman - libpng - libjpeg-turbo sources: - https://github.com/emerison/grim tasks: - setup: | cd grim meson build - build: | cd grim ninja -C build grim-1.4.0+ds/.editorconfig000066400000000000000000000002011420343431400155200ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true grim-1.4.0+ds/.gitignore000066400000000000000000000006651420343431400150510ustar00rootroot00000000000000# Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf /buildgrim-1.4.0+ds/LICENSE000066400000000000000000000020511420343431400140550ustar00rootroot00000000000000MIT License Copyright (c) 2018 emersion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. grim-1.4.0+ds/README.md000066400000000000000000000031411420343431400143300ustar00rootroot00000000000000# grim Grab images from a Wayland compositor. Works great with [slurp] and with [sway]. ## Example usage Screenshoot all outputs: ```sh grim ``` Screenshoot a specific output: ```sh grim -o DP-1 ``` Screenshoot a region: ```sh grim -g "10,20 300x400" ``` Select a region and screenshoot it: ```sh grim -g "$(slurp)" ``` Use a custom filename: ```sh grim $(xdg-user-dir PICTURES)/$(date +'%s_grim.png') ``` Screenshoot and copy to clipboard: ```sh grim - | wl-copy ``` Grab a screenshot from the focused monitor under Sway, using `swaymsg` and `jq`: ```sh grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') ``` Grab a screenshot from the focused window under Sway, using `swaymsg` and `jq`: ```sh grim -g "$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"')" ``` Pick a color, using ImageMagick: ```sh grim -g "$(slurp -p)" -t ppm - | convert - -format '%[pixel:p{0,0}]' txt:- ``` ## Building from source Install dependencies: * meson * wayland * pixman * libpng * libjpeg (optional) Then run: ```sh meson build ninja -C build ``` To run directly, use `build/grim`, or if you would like to do a system installation (in `/usr/local` by default), run `ninja -C build install`. ## Contributing Either [send GitHub pull requests][github] or [send patches on the mailing list][ml]. Join the IRC channel: #emersion on Libera Chat. ## License MIT [slurp]: https://github.com/emersion/slurp [sway]: https://github.com/swaywm/sway [github]: https://github.com/emersion/grim [ml]: https://lists.sr.ht/%7Eemersion/public-inbox grim-1.4.0+ds/box.c000066400000000000000000000020131420343431400140020ustar00rootroot00000000000000#include #include #include "box.h" #include bool parse_box(struct grim_box *box, const char *str) { char *end = NULL; box->x = strtol(str, &end, 10); if (end[0] != ',') { return false; } char *next = end + 1; box->y = strtol(next, &end, 10); if (end[0] != ' ') { return false; } next = end + 1; box->width = strtol(next, &end, 10); if (end[0] != 'x') { return false; } next = end + 1; box->height = strtol(next, &end, 10); if (end[0] != '\0') { return false; } return true; } bool is_empty_box(struct grim_box *box) { return box->width <= 0 || box->height <= 0; } bool intersect_box(struct grim_box *a, struct grim_box *b) { if (is_empty_box(a) || is_empty_box(b)) { return false; } int x1 = fmax(a->x, b->x); int y1 = fmax(a->y, b->y); int x2 = fmin(a->x + a->width, b->x + b->width); int y2 = fmin(a->y + a->height, b->y + b->height); struct grim_box box = { .x = x1, .y = y1, .width = x2 - x1, .height = y2 - y1, }; return !is_empty_box(&box); } grim-1.4.0+ds/buffer.c000066400000000000000000000036661420343431400145020ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include "buffer.h" static void randname(char *buf) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; for (int i = 0; i < 6; ++i) { buf[i] = 'A'+(r&15)+(r&16)*2; r >>= 5; } } static int anonymous_shm_open(void) { char name[] = "/grim-XXXXXX"; int retries = 100; do { randname(name + strlen(name) - 6); --retries; // shm_open guarantees that O_CLOEXEC is set int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } } while (retries > 0 && errno == EEXIST); return -1; } static int create_shm_file(off_t size) { int fd = anonymous_shm_open(); if (fd < 0) { return fd; } if (ftruncate(fd, size) < 0) { close(fd); return -1; } return fd; } struct grim_buffer *create_buffer(struct wl_shm *shm, enum wl_shm_format format, int32_t width, int32_t height, int32_t stride) { size_t size = stride * height; int fd = create_shm_file(size); if (fd == -1) { return NULL; } void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { close(fd); return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_shm_pool_destroy(pool); close(fd); struct grim_buffer *buffer = calloc(1, sizeof(struct grim_buffer)); buffer->wl_buffer = wl_buffer; buffer->data = data; buffer->width = width; buffer->height = height; buffer->stride = stride; buffer->size = size; buffer->format = format; return buffer; } void destroy_buffer(struct grim_buffer *buffer) { if (buffer == NULL) { return; } munmap(buffer->data, buffer->size); wl_buffer_destroy(buffer->wl_buffer); free(buffer); } grim-1.4.0+ds/contrib/000077500000000000000000000000001420343431400145125ustar00rootroot00000000000000grim-1.4.0+ds/contrib/completions/000077500000000000000000000000001420343431400170465ustar00rootroot00000000000000grim-1.4.0+ds/contrib/completions/bash/000077500000000000000000000000001420343431400177635ustar00rootroot00000000000000grim-1.4.0+ds/contrib/completions/bash/grim.bash000066400000000000000000000011421420343431400215560ustar00rootroot00000000000000_grim() { _init_completion || return CUR="${COMP_WORDS[COMP_CWORD]}" PREV="${COMP_WORDS[COMP_CWORD-1]}" if [[ "$PREV" == "-t" ]]; then COMPREPLY=($(compgen -W "png ppm jpeg" -- "$CUR")) return elif [[ "$PREV" == "-o" ]]; then OUTPUTS="$(swaymsg -t get_outputs 2>/dev/null | \ jq -r '.[] | select(.active) | "\(.name)\t\(.make) \(.model)"' 2>/dev/null)" COMPREPLY=($(compgen -W "$OUTPUTS" -- "$CUR")) return fi if [[ "$CUR" == -* ]]; then COMPREPLY=($(compgen -W "-h -s -g -t -q -o -c" -- "$CUR")) return fi # fall back to completing filenames _filedir } complete -F _grim grim grim-1.4.0+ds/contrib/completions/fish/000077500000000000000000000000001420343431400177775ustar00rootroot00000000000000grim-1.4.0+ds/contrib/completions/fish/grim.fish000066400000000000000000000013141420343431400216070ustar00rootroot00000000000000function complete_outputs if string length -q "$SWAYSOCK"; and command -sq jq swaymsg -t get_outputs | jq -r '.[] | select(.active) | "\(.name)\t\(.make) \(.model)"' else return 1 end end complete -c grim -s t --exclusive --arguments 'png ppm jpeg' -d 'Output image format' complete -c grim -s q --exclusive -d 'Output jpeg quality (default 80)' complete -c grim -s g --exclusive -d 'Region to capture: , x' complete -c grim -s s --exclusive -d 'Output image scale factor' complete -c grim -s c -d 'Include cursors in the screenshot' complete -c grim -s h -d 'Show help and exit' complete -c grim -s o --exclusive --arguments '(complete_outputs)' -d 'Output name to capture' grim-1.4.0+ds/contrib/completions/meson.build000066400000000000000000000011661420343431400212140ustar00rootroot00000000000000if get_option('fish-completions') fish_files = files('fish/grim.fish') fish_comp = dependency('fish', required: false) if fish_comp.found() fish_install_dir = fish_comp.get_variable('completionsdir') else datadir = get_option('datadir') fish_install_dir = join_paths(datadir, 'fish', 'vendor_completions.d') endif install_data(fish_files, install_dir: fish_install_dir) endif if get_option('bash-completions') bash_comp = dependency('bash-completion') bash_files = files('bash/grim.bash') bash_install_dir = bash_comp.get_variable('completionsdir') install_data(bash_files, install_dir: bash_install_dir) endif grim-1.4.0+ds/grim.1.scd000066400000000000000000000036731420343431400146530ustar00rootroot00000000000000grim(1) # NAME grim - grab images from a Wayland compositor # SYNOPSIS *grim* [options...] [output-file] # DESCRIPTION grim is a command-line utility to take screenshots of Wayland desktops. For now it requires support for the screencopy protocol to work. Support for the xdg-output protocol is optional, but improves fractional scaling support. grim will write an image to _output-file_, or to a timestamped file name in *$GRIM_DEFAULT_DIR* if not specified. If *$GRIM_DEFAULT_DIR* is not set, it falls back first to *$XDG_PICTURES_DIR* and then to the current working directory. If _output-file_ is *-*, grim will write the image to the standard output instead. # OPTIONS *-h* Show help message and quit. *-s* Set the output image's scale factor to _factor_. By default, the scale factor is set to the highest of all outputs. *-g* ", x" Set the region to capture, in layout coordinates. If set to *-*, read the region from the standard input instead. *-t* Set the output image's file format to _type_. By default, the filetype is set to *png*, valid values are *png*, *jpeg* or *ppm*. *-q* Set the output jpeg's filetype compression rate to _quality_. By default, the jpeg quality is *80*, valid values are between 0-100. *-l* Set the output PNG's filetype compression level to _level_. By default, the PNG compression level is 6 on a scale from 0 to 9. Level 9 gives the highest compression ratio, but may be slow; level 1 gives a lower compression ratio, but is faster. Level 0 does no compression at all, and produces very large files; it can be useful when grim is used in a pipeline with other commands. *-o* Set the output name to capture. *-c* Include cursors in the screenshot. # AUTHORS Maintained by Simon Ser , who is assisted by other open-source contributors. For more information about grim development, see https://github.com/emersion/grim. grim-1.4.0+ds/include/000077500000000000000000000000001420343431400144755ustar00rootroot00000000000000grim-1.4.0+ds/include/box.h000066400000000000000000000004521420343431400154370ustar00rootroot00000000000000#ifndef _BOX_H #define _BOX_H #include #include struct grim_box { int32_t x, y; int32_t width, height; }; bool parse_box(struct grim_box *box, const char *str); bool is_empty_box(struct grim_box *box); bool intersect_box(struct grim_box *a, struct grim_box *b); #endif grim-1.4.0+ds/include/buffer.h000066400000000000000000000006131420343431400161170ustar00rootroot00000000000000#ifndef _BUFFER_H #define _BUFFER_H #include struct grim_buffer { struct wl_buffer *wl_buffer; void *data; int32_t width, height, stride; size_t size; enum wl_shm_format format; }; struct grim_buffer *create_buffer(struct wl_shm *shm, enum wl_shm_format format, int32_t width, int32_t height, int32_t stride); void destroy_buffer(struct grim_buffer *buffer); #endif grim-1.4.0+ds/include/grim.h000066400000000000000000000020161420343431400156030ustar00rootroot00000000000000#ifndef _GRIM_H #define _GRIM_H #include #include "box.h" #include "wlr-screencopy-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" enum grim_filetype { GRIM_FILETYPE_PNG, GRIM_FILETYPE_PPM, GRIM_FILETYPE_JPEG, }; struct grim_state { struct wl_display *display; struct wl_registry *registry; struct wl_shm *shm; struct zxdg_output_manager_v1 *xdg_output_manager; struct zwlr_screencopy_manager_v1 *screencopy_manager; struct wl_list outputs; size_t n_done; }; struct grim_buffer; struct grim_output { struct grim_state *state; struct wl_output *wl_output; struct zxdg_output_v1 *xdg_output; struct wl_list link; struct grim_box geometry; enum wl_output_transform transform; int32_t scale; struct grim_box logical_geometry; double logical_scale; // guessed from the logical size char *name; struct grim_buffer *buffer; struct zwlr_screencopy_frame_v1 *screencopy_frame; uint32_t screencopy_frame_flags; // enum zwlr_screencopy_frame_v1_flags }; #endif grim-1.4.0+ds/include/output-layout.h000066400000000000000000000007321420343431400175230ustar00rootroot00000000000000#ifndef _OUTPUT_LAYOUT_H #define _OUTPUT_LAYOUT_H #include #include "grim.h" void get_output_layout_extents(struct grim_state *state, struct grim_box *box); void apply_output_transform(enum wl_output_transform transform, int32_t *width, int32_t *height); double get_output_rotation(enum wl_output_transform transform); int get_output_flipped(enum wl_output_transform transform); void guess_output_logical_geometry(struct grim_output *output); #endif grim-1.4.0+ds/include/render.h000066400000000000000000000002611420343431400161240ustar00rootroot00000000000000#ifndef _RENDER_H #define _RENDER_H #include #include "grim.h" pixman_image_t *render(struct grim_state *state, struct grim_box *geometry, double scale); #endif grim-1.4.0+ds/include/write_png.h000066400000000000000000000002511420343431400166420ustar00rootroot00000000000000#ifndef _WRITE_PNG_H #define _WRITE_PNG_H #include #include int write_to_png_stream(pixman_image_t *image, FILE *stream, int comp_level); #endif grim-1.4.0+ds/include/write_ppm.h000066400000000000000000000002311420343431400166500ustar00rootroot00000000000000#ifndef _WRITE_PPM_H #define _WRITE_PPM_H #include #include int write_to_ppm_stream(pixman_image_t *image, FILE *stream); #endif grim-1.4.0+ds/main.c000066400000000000000000000417251420343431400141530ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include "buffer.h" #include "grim.h" #include "output-layout.h" #include "render.h" #include "write_ppm.h" #ifdef HAVE_JPEG #include "write_jpg.h" #endif #include "write_png.h" static void screencopy_frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { struct grim_output *output = data; output->buffer = create_buffer(output->state->shm, format, width, height, stride); if (output->buffer == NULL) { fprintf(stderr, "failed to create buffer\n"); exit(EXIT_FAILURE); } zwlr_screencopy_frame_v1_copy(frame, output->buffer->wl_buffer); } static void screencopy_frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) { struct grim_output *output = data; output->screencopy_frame_flags = flags; } static void screencopy_frame_handle_ready(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { struct grim_output *output = data; ++output->state->n_done; } static void screencopy_frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) { struct grim_output *output = data; fprintf(stderr, "failed to copy output %s\n", output->name); exit(EXIT_FAILURE); } static const struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { .buffer = screencopy_frame_handle_buffer, .flags = screencopy_frame_handle_flags, .ready = screencopy_frame_handle_ready, .failed = screencopy_frame_handle_failed, }; static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { struct grim_output *output = data; output->logical_geometry.x = x; output->logical_geometry.y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { struct grim_output *output = data; output->logical_geometry.width = width; output->logical_geometry.height = height; } static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { struct grim_output *output = data; // Guess the output scale from the logical size int32_t width = output->geometry.width; int32_t height = output->geometry.height; apply_output_transform(output->transform, &width, &height); output->logical_scale = (double)width / output->logical_geometry.width; } static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { struct grim_output *output = data; output->name = strdup(name); } static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { // No-op } static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = xdg_output_handle_logical_position, .logical_size = xdg_output_handle_logical_size, .done = xdg_output_handle_done, .name = xdg_output_handle_name, .description = xdg_output_handle_description, }; static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct grim_output *output = data; output->geometry.x = x; output->geometry.y = y; output->transform = transform; } static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct grim_output *output = data; if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) { output->geometry.width = width; output->geometry.height = height; } } static void output_handle_done(void *data, struct wl_output *wl_output) { // No-op } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct grim_output *output = data; output->scale = factor; } static const struct wl_output_listener output_listener = { .geometry = output_handle_geometry, .mode = output_handle_mode, .done = output_handle_done, .scale = output_handle_scale, }; static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct grim_state *state = data; if (strcmp(interface, wl_shm_interface.name) == 0) { state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { uint32_t bind_version = (version > 2) ? 2 : version; state->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, bind_version); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct grim_output *output = calloc(1, sizeof(struct grim_output)); output->state = state; output->scale = 1; output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 3); wl_output_add_listener(output->wl_output, &output_listener, output); wl_list_insert(&state->outputs, &output->link); } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { state->screencopy_manager = wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1); } } static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { // who cares } static const struct wl_registry_listener registry_listener = { .global = handle_global, .global_remove = handle_global_remove, }; static bool default_filename(char *filename, size_t n, int filetype) { time_t time_epoch = time(NULL); struct tm *time = localtime(&time_epoch); if (time == NULL) { perror("localtime"); return false; } char *format_str; const char *ext = NULL; switch (filetype) { case GRIM_FILETYPE_PNG: ext = "png"; break; case GRIM_FILETYPE_PPM: ext = "ppm"; break; case GRIM_FILETYPE_JPEG: #if HAVE_JPEG ext = "jpeg"; break; #else abort(); #endif } assert(ext != NULL); char tmpstr[32]; sprintf(tmpstr, "%%Y%%m%%d_%%Hh%%Mm%%Ss_grim.%s", ext); format_str = tmpstr; if (strftime(filename, n, format_str, time) == 0) { fprintf(stderr, "failed to format datetime with strftime(3)\n"); return false; } return true; } static bool path_exists(const char *path) { return path && access(path, R_OK) != -1; } char *get_xdg_pictures_dir(void) { const char *home_dir = getenv("HOME"); if (home_dir == NULL) { return NULL; } char *config_file; const char user_dirs_file[] = "user-dirs.dirs"; const char config_home_fallback[] = ".config"; const char *config_home = getenv("XDG_CONFIG_HOME"); if (config_home == NULL || config_home[0] == 0) { size_t size = strlen(home_dir) + strlen("/") + strlen(config_home_fallback) + strlen("/") + strlen(user_dirs_file) + 1; config_file = malloc(size); if (config_file == NULL) { return NULL; } snprintf(config_file, size, "%s/%s/%s", home_dir, config_home_fallback, user_dirs_file); } else { size_t size = strlen(config_home) + strlen("/") + strlen(user_dirs_file) + 1; config_file = malloc(size); if (config_file == NULL) { return NULL; } snprintf(config_file, size, "%s/%s", config_home, user_dirs_file); } FILE *file = fopen(config_file, "r"); free(config_file); if (file == NULL) { return NULL; } char *line = NULL; size_t line_size = 0; ssize_t nread; char *pictures_dir = NULL; while ((nread = getline(&line, &line_size, file)) != -1) { if (nread > 0 && line[nread - 1] == '\n') { line[nread - 1] = '\0'; } if (strlen(line) == 0 || line[0] == '#') { continue; } size_t i = 0; while (line[i] == ' ') { i++; } const char prefix[] = "XDG_PICTURES_DIR="; if (strncmp(&line[i], prefix, strlen(prefix)) == 0) { const char *line_remaining = &line[i] + strlen(prefix); wordexp_t p; if (wordexp(line_remaining, &p, WRDE_UNDEF) == 0) { free(pictures_dir); pictures_dir = strdup(p.we_wordv[0]); wordfree(&p); } } } free(line); fclose(file); return pictures_dir; } char *get_output_dir(void) { const char *grim_default_dir = getenv("GRIM_DEFAULT_DIR"); if (path_exists(grim_default_dir)) { return strdup(grim_default_dir); } char *xdg_fallback_dir = get_xdg_pictures_dir(); if (path_exists(xdg_fallback_dir)) { return xdg_fallback_dir; } else { free(xdg_fallback_dir); } return strdup("."); } static const char usage[] = "Usage: grim [options...] [output-file]\n" "\n" " -h Show help message and quit.\n" " -s Set the output image scale factor. Defaults to the\n" " greatest output scale factor.\n" " -g Set the region to capture.\n" " -t png|ppm|jpeg Set the output filetype. Defaults to png.\n" " -q Set the JPEG filetype quality 0-100. Defaults to 80.\n" " -l Set the PNG filetype compression level 0-9. Defaults to 6.\n" " -o Set the output name to capture.\n" " -c Include cursors in the screenshot.\n"; int main(int argc, char *argv[]) { double scale = 1.0; bool use_greatest_scale = true; struct grim_box *geometry = NULL; char *geometry_output = NULL; enum grim_filetype output_filetype = GRIM_FILETYPE_PNG; int jpeg_quality = 80; int png_level = 6; // current default png/zlib compression level bool with_cursor = false; int opt; while ((opt = getopt(argc, argv, "hs:g:t:q:l:o:c")) != -1) { switch (opt) { case 'h': printf("%s", usage); return EXIT_SUCCESS; case 's': use_greatest_scale = false; scale = strtod(optarg, NULL); break; case 'g':; char *geometry_str = NULL; if (strcmp(optarg, "-") == 0) { size_t n = 0; ssize_t nread = getline(&geometry_str, &n, stdin); if (nread < 0) { free(geometry_str); fprintf(stderr, "failed to read a line from stdin\n"); return EXIT_FAILURE; } if (nread > 0 && geometry_str[nread - 1] == '\n') { geometry_str[nread - 1] = '\0'; } } else { geometry_str = strdup(optarg); } free(geometry); geometry = calloc(1, sizeof(struct grim_box)); if (!parse_box(geometry, geometry_str)) { fprintf(stderr, "invalid geometry\n"); return EXIT_FAILURE; } free(geometry_str); break; case 't': if (strcmp(optarg, "png") == 0) { output_filetype = GRIM_FILETYPE_PNG; } else if (strcmp(optarg, "ppm") == 0) { output_filetype = GRIM_FILETYPE_PPM; } else if (strcmp(optarg, "jpeg") == 0) { #ifdef HAVE_JPEG output_filetype = GRIM_FILETYPE_JPEG; #else fprintf(stderr, "jpeg support disabled\n"); return EXIT_FAILURE; #endif } else { fprintf(stderr, "invalid filetype\n"); return EXIT_FAILURE; } break; case 'q': if (output_filetype != GRIM_FILETYPE_JPEG) { fprintf(stderr, "quality is used only for jpeg files\n"); return EXIT_FAILURE; } else { char *endptr = NULL; errno = 0; jpeg_quality = strtol(optarg, &endptr, 10); if (*endptr != '\0' || errno) { fprintf(stderr, "quality must be a integer\n"); return EXIT_FAILURE; } if (jpeg_quality < 0 || jpeg_quality > 100) { fprintf(stderr, "quality valid values are between 0-100\n"); return EXIT_FAILURE; } } break; case 'l': if (output_filetype != GRIM_FILETYPE_PNG) { fprintf(stderr, "compression level is used only for png files\n"); return EXIT_FAILURE; } else { char *endptr = NULL; errno = 0; png_level = strtol(optarg, &endptr, 10); if (*endptr != '\0' || errno) { fprintf(stderr, "level must be a integer\n"); return EXIT_FAILURE; } if (png_level < 0 || png_level > 9) { fprintf(stderr, "compression level valid values are between 0-9\n"); return EXIT_FAILURE; } } break; case 'o': free(geometry_output); geometry_output = strdup(optarg); break; case 'c': with_cursor = true; break; default: return EXIT_FAILURE; } } const char *output_filename; char *output_filepath; char tmp[64]; if (optind >= argc) { if (!default_filename(tmp, sizeof(tmp), output_filetype)) { fprintf(stderr, "failed to generate default filename\n"); return EXIT_FAILURE; } output_filename = tmp; char *output_dir = get_output_dir(); int len = snprintf(NULL, 0, "%s/%s", output_dir, output_filename); if (len < 0) { perror("snprintf failed"); return EXIT_FAILURE; } output_filepath = malloc(len + 1); snprintf(output_filepath, len + 1, "%s/%s", output_dir, output_filename); free(output_dir); } else { output_filename = argv[optind]; output_filepath = strdup(output_filename); } struct grim_state state = {0}; wl_list_init(&state.outputs); state.display = wl_display_connect(NULL); if (state.display == NULL) { fprintf(stderr, "failed to create display\n"); return EXIT_FAILURE; } state.registry = wl_display_get_registry(state.display); wl_registry_add_listener(state.registry, ®istry_listener, &state); wl_display_roundtrip(state.display); if (state.shm == NULL) { fprintf(stderr, "compositor doesn't support wl_shm\n"); return EXIT_FAILURE; } if (wl_list_empty(&state.outputs)) { fprintf(stderr, "no wl_output\n"); return EXIT_FAILURE; } if (state.xdg_output_manager != NULL) { struct grim_output *output; wl_list_for_each(output, &state.outputs, link) { output->xdg_output = zxdg_output_manager_v1_get_xdg_output( state.xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); } wl_display_roundtrip(state.display); } else { fprintf(stderr, "warning: zxdg_output_manager_v1 isn't available, " "guessing the output layout\n"); struct grim_output *output; wl_list_for_each(output, &state.outputs, link) { guess_output_logical_geometry(output); } } if (state.screencopy_manager == NULL) { fprintf(stderr, "compositor doesn't support wlr-screencopy-unstable-v1\n"); return EXIT_FAILURE; } if (geometry_output != NULL) { struct grim_output *output; wl_list_for_each(output, &state.outputs, link) { if (output->name != NULL && strcmp(output->name, geometry_output) == 0) { geometry = calloc(1, sizeof(struct grim_box)); memcpy(geometry, &output->logical_geometry, sizeof(struct grim_box)); } } if (geometry == NULL) { fprintf(stderr, "unknown output '%s'", geometry_output); return EXIT_FAILURE; } } size_t n_pending = 0; struct grim_output *output; wl_list_for_each(output, &state.outputs, link) { if (geometry != NULL && !intersect_box(geometry, &output->logical_geometry)) { continue; } if (use_greatest_scale && output->logical_scale > scale) { scale = output->logical_scale; } output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( state.screencopy_manager, with_cursor, output->wl_output); zwlr_screencopy_frame_v1_add_listener(output->screencopy_frame, &screencopy_frame_listener, output); ++n_pending; } if (n_pending == 0) { fprintf(stderr, "supplied geometry did not intersect with any outputs\n"); return EXIT_FAILURE; } bool done = false; while (!done && wl_display_dispatch(state.display) != -1) { done = (state.n_done == n_pending); } if (!done) { fprintf(stderr, "failed to screenshoot all outputs\n"); return EXIT_FAILURE; } if (geometry == NULL) { geometry = calloc(1, sizeof(struct grim_box)); get_output_layout_extents(&state, geometry); } pixman_image_t *image = render(&state, geometry, scale); if (image == NULL) { return EXIT_FAILURE; } FILE *file; if (strcmp(output_filename, "-") == 0) { file = stdout; } else { file = fopen(output_filepath, "w"); if (!file) { fprintf(stderr, "Failed to open file '%s' for writing: %s\n", output_filepath, strerror(errno)); return EXIT_FAILURE; } } int ret = 0; switch (output_filetype) { case GRIM_FILETYPE_PPM: ret = write_to_ppm_stream(image, file); break; case GRIM_FILETYPE_PNG: ret = write_to_png_stream(image, file, png_level); break; case GRIM_FILETYPE_JPEG: #if HAVE_JPEG ret = write_to_jpeg_stream(image, file, jpeg_quality); break; #else abort(); #endif } if (ret == -1) { // Error messages will be printed at the source return EXIT_FAILURE; } if (strcmp(output_filename, "-") != 0) { fclose(file); } free(output_filepath); pixman_image_unref(image); struct grim_output *output_tmp; wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { wl_list_remove(&output->link); free(output->name); if (output->screencopy_frame != NULL) { zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); } destroy_buffer(output->buffer); if (output->xdg_output != NULL) { zxdg_output_v1_destroy(output->xdg_output); } wl_output_release(output->wl_output); free(output); } zwlr_screencopy_manager_v1_destroy(state.screencopy_manager); if (state.xdg_output_manager != NULL) { zxdg_output_manager_v1_destroy(state.xdg_output_manager); } wl_shm_destroy(state.shm); wl_registry_destroy(state.registry); wl_display_disconnect(state.display); free(geometry); free(geometry_output); return EXIT_SUCCESS; } grim-1.4.0+ds/meson.build000066400000000000000000000032461420343431400152210ustar00rootroot00000000000000project( 'grim', 'c', version: '1.4.0', license: 'MIT', meson_version: '>=0.59.0', default_options: ['c_std=c11', 'warning_level=3', 'werror=true'], ) add_project_arguments('-Wno-unused-parameter', language: 'c') grim_inc = include_directories('include') cc = meson.get_compiler('c') png = dependency('libpng') jpeg = dependency('libjpeg', required: get_option('jpeg')) math = cc.find_library('m') pixman = dependency('pixman-1') realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') if jpeg.found() add_project_arguments('-DHAVE_JPEG', language: 'c') endif is_le = host_machine.endian() == 'little' add_project_arguments('-DGRIM_LITTLE_ENDIAN=@0@'.format(is_le.to_int()), language: 'c') subdir('contrib/completions') subdir('protocol') grim_files = [ 'box.c', 'buffer.c', 'main.c', 'output-layout.c', 'render.c', 'write_ppm.c', 'write_png.c', ] grim_deps = [ client_protos, math, pixman, png, realtime, wayland_client, ] if jpeg.found() grim_files += ['write_jpg.c'] grim_deps += [jpeg] endif executable( 'grim', files(grim_files), dependencies: grim_deps, include_directories: [grim_inc], install: true, ) scdoc = find_program('scdoc', required: get_option('man-pages')) if scdoc.found() man_pages = ['grim.1.scd'] foreach src : man_pages topic = src.split('.')[0] section = src.split('.')[1] output = '@0@.@1@'.format(topic, section) custom_target( output, input: src, output: output, command: scdoc, feed: true, capture: true, install: true, install_dir: '@0@/man@1@'.format(get_option('mandir'), section), ) endforeach endif grim-1.4.0+ds/meson_options.txt000066400000000000000000000005741420343431400165150ustar00rootroot00000000000000option('jpeg', type: 'feature', value: 'auto', description: 'Enable JPEG support') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('fish-completions', type: 'boolean', value: false, description: 'Install fish completions') option('bash-completions', type: 'boolean', value: false, description: 'Install bash completions') grim-1.4.0+ds/output-layout.c000066400000000000000000000036701420343431400160770ustar00rootroot00000000000000#define _XOPEN_SOURCE 500 #include #include #include "output-layout.h" #include "grim.h" void get_output_layout_extents(struct grim_state *state, struct grim_box *box) { int32_t x1 = INT_MAX, y1 = INT_MAX; int32_t x2 = INT_MIN, y2 = INT_MIN; struct grim_output *output; wl_list_for_each(output, &state->outputs, link) { if (output->logical_geometry.x < x1) { x1 = output->logical_geometry.x; } if (output->logical_geometry.y < y1) { y1 = output->logical_geometry.y; } if (output->logical_geometry.x + output->logical_geometry.width > x2) { x2 = output->logical_geometry.x + output->logical_geometry.width; } if (output->logical_geometry.y + output->logical_geometry.height > y2) { y2 = output->logical_geometry.y + output->logical_geometry.height; } } box->x = x1; box->y = y1; box->width = x2 - x1; box->height = y2 - y1; } void apply_output_transform(enum wl_output_transform transform, int32_t *width, int32_t *height) { if (transform & WL_OUTPUT_TRANSFORM_90) { int32_t tmp = *width; *width = *height; *height = tmp; } } double get_output_rotation(enum wl_output_transform transform) { switch (transform & ~WL_OUTPUT_TRANSFORM_FLIPPED) { case WL_OUTPUT_TRANSFORM_90: return M_PI / 2; case WL_OUTPUT_TRANSFORM_180: return M_PI; case WL_OUTPUT_TRANSFORM_270: return 3 * M_PI / 2; } return 0; } int get_output_flipped(enum wl_output_transform transform) { return transform & WL_OUTPUT_TRANSFORM_FLIPPED ? -1 : 1; } void guess_output_logical_geometry(struct grim_output *output) { output->logical_geometry.x = output->geometry.x; output->logical_geometry.y = output->geometry.y; output->logical_geometry.width = output->geometry.width / output->scale; output->logical_geometry.height = output->geometry.height / output->scale; apply_output_transform(output->transform, &output->logical_geometry.width, &output->logical_geometry.height); output->logical_scale = output->scale; } grim-1.4.0+ds/protocol/000077500000000000000000000000001420343431400147135ustar00rootroot00000000000000grim-1.4.0+ds/protocol/meson.build000066400000000000000000000022671420343431400170640ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') # should check wayland_scanner's version, but it is hard to get if wayland_client.version().version_compare('>=1.14.91') code_type = 'private-code' else code_type = 'code' endif wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: [code_type, '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], ['wlr-screencopy-unstable-v1.xml'], ] client_protos_src = [] client_protos_headers = [] foreach p : client_protocols xml = join_paths(p) client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [wayland_client] ) # for the include directory client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) grim-1.4.0+ds/protocol/wlr-screencopy-unstable-v1.xml000066400000000000000000000167511420343431400225620ustar00rootroot00000000000000 Copyright © 2018 Simon Ser Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This protocol allows clients to ask the compositor to copy part of the screen content to a client buffer. Warning! The protocol described in this file is experimental and backward incompatible changes may be made. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes are done by bumping the version number in the protocol and interface names and resetting the interface version. Once the protocol is to be declared stable, the 'z' prefix and the version number in the protocol and interface names are removed and the interface version number is reset. This object is a manager which offers requests to start capturing from a source. Capture the next frame of an entire output. Capture the next frame of an output's region. The region is given in output logical coordinates, see xdg_output.logical_size. The region will be clipped to the output's extents. All objects created by the manager will still remain valid, until their appropriate destroy request has been called. This object represents a single frame. When created, a "buffer" event will be sent. The client will then be able to send a "copy" request. If the capture is successful, the compositor will send a "flags" followed by a "ready" event. If the capture failed, the "failed" event is sent. This can happen anytime before the "ready" event. Once either a "ready" or a "failed" event is received, the client should destroy the frame. Provides information about the frame's buffer. This event is sent once as soon as the frame is created. The client should then create a buffer with the provided attributes, and send a "copy" request. Copy the frame to the supplied buffer. The buffer must have a the correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to have a supported format. If the frame is successfully copied, a "flags" and a "ready" events are sent. Otherwise, a "failed" event is sent. Provides flags about the frame. This event is sent once before the "ready" event. Called as soon as the frame is copied, indicating it is available for reading. This event includes the time at which presentation happened at. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, and the additional fractional part in tv_nsec as nanoseconds. Hence, for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part may have an arbitrary offset at start. After receiving this event, the client should destroy the object. This event indicates that the attempted frame copy has failed. After receiving this event, the client should destroy the object. Destroys the frame. This request can be sent at any time by the client. grim-1.4.0+ds/render.c000066400000000000000000000205311420343431400144760ustar00rootroot00000000000000#include #include #include #include #include #include #include "buffer.h" #include "output-layout.h" #include "render.h" static pixman_format_code_t get_pixman_format(enum wl_shm_format wl_fmt) { switch (wl_fmt) { #if GRIM_LITTLE_ENDIAN case WL_SHM_FORMAT_RGB332: return PIXMAN_r3g3b2; case WL_SHM_FORMAT_BGR233: return PIXMAN_b2g3r3; case WL_SHM_FORMAT_ARGB4444: return PIXMAN_a4r4g4b4; case WL_SHM_FORMAT_XRGB4444: return PIXMAN_x4r4g4b4; case WL_SHM_FORMAT_ABGR4444: return PIXMAN_a4b4g4r4; case WL_SHM_FORMAT_XBGR4444: return PIXMAN_x4b4g4r4; case WL_SHM_FORMAT_ARGB1555: return PIXMAN_a1r5g5b5; case WL_SHM_FORMAT_XRGB1555: return PIXMAN_x1r5g5b5; case WL_SHM_FORMAT_ABGR1555: return PIXMAN_a1b5g5r5; case WL_SHM_FORMAT_XBGR1555: return PIXMAN_x1b5g5r5; case WL_SHM_FORMAT_RGB565: return PIXMAN_r5g6b5; case WL_SHM_FORMAT_BGR565: return PIXMAN_b5g6r5; case WL_SHM_FORMAT_RGB888: return PIXMAN_r8g8b8; case WL_SHM_FORMAT_BGR888: return PIXMAN_b8g8r8; case WL_SHM_FORMAT_ARGB8888: return PIXMAN_a8r8g8b8; case WL_SHM_FORMAT_XRGB8888: return PIXMAN_x8r8g8b8; case WL_SHM_FORMAT_ABGR8888: return PIXMAN_a8b8g8r8; case WL_SHM_FORMAT_XBGR8888: return PIXMAN_x8b8g8r8; case WL_SHM_FORMAT_BGRA8888: return PIXMAN_b8g8r8a8; case WL_SHM_FORMAT_BGRX8888: return PIXMAN_b8g8r8x8; case WL_SHM_FORMAT_RGBA8888: return PIXMAN_r8g8b8a8; case WL_SHM_FORMAT_RGBX8888: return PIXMAN_r8g8b8x8; case WL_SHM_FORMAT_ARGB2101010: return PIXMAN_a2r10g10b10; case WL_SHM_FORMAT_ABGR2101010: return PIXMAN_a2b10g10r10; case WL_SHM_FORMAT_XRGB2101010: return PIXMAN_x2r10g10b10; case WL_SHM_FORMAT_XBGR2101010: return PIXMAN_x2b10g10r10; #else case WL_SHM_FORMAT_ARGB8888: return PIXMAN_b8g8r8a8; case WL_SHM_FORMAT_XRGB8888: return PIXMAN_b8g8r8x8; case WL_SHM_FORMAT_ABGR8888: return PIXMAN_r8g8b8a8; case WL_SHM_FORMAT_XBGR8888: return PIXMAN_r8g8b8x8; case WL_SHM_FORMAT_BGRA8888: return PIXMAN_a8r8g8b8; case WL_SHM_FORMAT_BGRX8888: return PIXMAN_x8r8g8b8; case WL_SHM_FORMAT_RGBA8888: return PIXMAN_a8b8g8r8; case WL_SHM_FORMAT_RGBX8888: return PIXMAN_x8b8g8r8; #endif default: return 0; } } static void compute_composite_region(const struct pixman_f_transform *out2com, int output_width, int output_height, struct grim_box *dest, bool *grid_aligned) { struct pixman_transform o2c_fixedpt; pixman_transform_from_pixman_f_transform(&o2c_fixedpt, out2com); pixman_fixed_t w = pixman_int_to_fixed(output_width); pixman_fixed_t h = pixman_int_to_fixed(output_height); struct pixman_vector corners[4] = { {{0, 0, pixman_fixed_1}}, {{w, 0, pixman_fixed_1}}, {{0, h, pixman_fixed_1}}, {{w, h, pixman_fixed_1}}, }; pixman_fixed_t x_min = INT32_MAX, x_max = INT32_MIN, y_min = INT32_MAX, y_max = INT32_MIN; for (int i = 0; i < 4; i++) { pixman_transform_point(&o2c_fixedpt, &corners[i]); x_min = corners[i].vector[0] < x_min ? corners[i].vector[0] : x_min; x_max = corners[i].vector[0] > x_max ? corners[i].vector[0] : x_max; y_min = corners[i].vector[1] < y_min ? corners[i].vector[1] : y_min; y_max = corners[i].vector[1] > y_max ? corners[i].vector[1] : y_max; } *grid_aligned = pixman_fixed_frac(x_min) == 0 && pixman_fixed_frac(x_max) == 0 && pixman_fixed_frac(y_min) == 0 && pixman_fixed_frac(y_max) == 0; int32_t x1 = pixman_fixed_to_int(pixman_fixed_floor(x_min)); int32_t x2 = pixman_fixed_to_int(pixman_fixed_ceil(x_max)); int32_t y1 = pixman_fixed_to_int(pixman_fixed_floor(y_min)); int32_t y2 = pixman_fixed_to_int(pixman_fixed_ceil(y_max)); *dest = (struct grim_box) { .x = x1, .y = y1, .width = x2 - x1, .height = y2 - y1 }; } pixman_image_t *render(struct grim_state *state, struct grim_box *geometry, double scale) { pixman_image_t *common_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, geometry->width * scale, geometry->height * scale, NULL, 0); struct grim_output *output; wl_list_for_each(output, &state->outputs, link) { struct grim_buffer *buffer = output->buffer; if (buffer == NULL) { continue; } pixman_format_code_t pixman_fmt = get_pixman_format(buffer->format); if (!pixman_fmt) { fprintf(stderr, "unsupported format %d = 0x%08x\n", buffer->format, buffer->format); return NULL; } int32_t output_x = output->logical_geometry.x - geometry->x; int32_t output_y = output->logical_geometry.y - geometry->y; int32_t output_width = output->logical_geometry.width; int32_t output_height = output->logical_geometry.height; int32_t raw_output_width = output->geometry.width; int32_t raw_output_height = output->geometry.height; apply_output_transform(output->transform, &raw_output_width, &raw_output_height); int output_flipped_x = get_output_flipped(output->transform); int output_flipped_y = output->screencopy_frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT ? -1 : 1; pixman_image_t *output_image = pixman_image_create_bits( pixman_fmt, buffer->width, buffer->height, buffer->data, buffer->stride); if (!output_image) { fprintf(stderr, "Failed to create image\n"); return NULL; } // The transformation `out2com` will send a pixel in the output_image // to one in the common_image struct pixman_f_transform out2com; pixman_f_transform_init_identity(&out2com); pixman_f_transform_translate(&out2com, NULL, -(double)output->geometry.width / 2, -(double)output->geometry.height / 2); pixman_f_transform_scale(&out2com, NULL, (double)output_width / raw_output_width, (double)output_height * output_flipped_y / raw_output_height); pixman_f_transform_rotate(&out2com, NULL, round(cos(get_output_rotation(output->transform))), round(sin(get_output_rotation(output->transform)))); pixman_f_transform_scale(&out2com, NULL, output_flipped_x, 1); pixman_f_transform_translate(&out2com, NULL, (double)output_width / 2, (double)output_height / 2); pixman_f_transform_translate(&out2com, NULL, output_x, output_y); pixman_f_transform_scale(&out2com, NULL, scale, scale); struct grim_box composite_dest; bool grid_aligned; compute_composite_region(&out2com, buffer->width, buffer->height, &composite_dest, &grid_aligned); pixman_f_transform_translate(&out2com, NULL, -composite_dest.x, -composite_dest.y); struct pixman_f_transform com2out; pixman_f_transform_invert(&com2out, &out2com); struct pixman_transform c2o_fixedpt; pixman_transform_from_pixman_f_transform(&c2o_fixedpt, &com2out); pixman_image_set_transform(output_image, &c2o_fixedpt); double x_scale = fmax(fabs(out2com.m[0][0]), fabs(out2com.m[0][1])); double y_scale = fmax(fabs(out2com.m[1][0]), fabs(out2com.m[1][1])); if (x_scale >= 0.75 && y_scale >= 0.75) { // Bilinear scaling is relatively fast and gives decent // results for upscaling and light downscaling pixman_image_set_filter(output_image, PIXMAN_FILTER_BILINEAR, NULL, 0); } else { // When downscaling, convolve the output_image so that each // pixel in the common_image collects colors from a region // of size roughly 1/x_scale*1/y_scale in the output_image int n_values = 0; pixman_fixed_t *conv = pixman_filter_create_separable_convolution( &n_values, pixman_double_to_fixed(fmax(1., 1. / x_scale)), pixman_double_to_fixed(fmax(1., 1. / y_scale)), PIXMAN_KERNEL_IMPULSE, PIXMAN_KERNEL_IMPULSE, PIXMAN_KERNEL_LANCZOS2, PIXMAN_KERNEL_LANCZOS2, 2, 2); pixman_image_set_filter(output_image, PIXMAN_FILTER_SEPARABLE_CONVOLUTION, conv, n_values); free(conv); } bool overlapping = false; struct grim_output *other_output; wl_list_for_each(other_output, &state->outputs, link) { if (output != other_output && intersect_box(&output->logical_geometry, &other_output->logical_geometry)) { overlapping = true; } } /* OP_SRC copies the image instead of blending it, and is much * faster, but this a) is incorrect in the weird case where * logical outputs overlap and are partially transparent b) * can draw the edge between two outputs incorrectly if that * edge is not exactly grid aligned in the common image */ pixman_op_t op = (grid_aligned && !overlapping) ? PIXMAN_OP_SRC : PIXMAN_OP_OVER; pixman_image_composite32(op, output_image, NULL, common_image, 0, 0, 0, 0, composite_dest.x, composite_dest.y, composite_dest.width, composite_dest.height); pixman_image_unref(output_image); } return common_image; } grim-1.4.0+ds/write_png.c000066400000000000000000000062031420343431400152150ustar00rootroot00000000000000#include #include #include #include #include #include "write_png.h" static void pack_row32(uint8_t *restrict row_out, const uint32_t *restrict row_in, size_t width, bool fully_opaque) { for (size_t x = 0; x < width; x++) { uint8_t b = (row_in[x] >> 0) & 0xff; uint8_t g = (row_in[x] >> 8) & 0xff; uint8_t r = (row_in[x] >> 16) & 0xff; uint8_t a = (row_in[x] >> 24) & 0xff; // Unpremultiply pixels, if necessary. In practice, few images // made by grim will have many pixels with fractional alpha if (!fully_opaque && (a != 0 && a != 255)) { uint32_t inv = (0xff << 16) / a; uint32_t sr = r * inv; r = sr > (0xff << 16) ? 0xff : (sr >> 16); uint32_t sg = g * inv; g = sg > (0xff << 16) ? 0xff : (sg >> 16); uint32_t sb = b * inv; b = sb > (0xff << 16) ? 0xff : (sb >> 16); } *row_out++ = r; *row_out++ = g; *row_out++ = b; if (!fully_opaque) { *row_out++ = a; } } } int write_to_png_stream(pixman_image_t *image, FILE *stream, int comp_level) { pixman_format_code_t format = pixman_image_get_format(image); assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8); int width = pixman_image_get_width(image); int height = pixman_image_get_height(image); int stride = pixman_image_get_stride(image); const unsigned char *data = (unsigned char *)pixman_image_get_data(image); bool fully_opaque = true; if (format == PIXMAN_a8r8g8b8) { for (int y = 0; y < height; y++) { const uint32_t *row = (const uint32_t *)(data + y * stride); for (int x = 0; x < width; x++) { if ((row[x] >> 24) != 0xff) { fully_opaque = false; } } } } int color_type = fully_opaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA; int bit_depth = 8; uint8_t *tmp_row = calloc(width, 4); if (!tmp_row) { fprintf(stderr, "failed to allocate temp row\n"); return -1; } int ret = 0; png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_info *info = NULL; if (!png) { fprintf(stderr, "failed to allocate png struct\n"); ret = -1; goto cleanup; } info = png_create_info_struct(png); if (!info) { fprintf(stderr, "failed to allocate png write struct\n"); ret = -1; goto cleanup; } #ifdef PNG_SETJMP_SUPPORTED if (setjmp(png_jmpbuf(png))) { fprintf(stderr, "failed to write png\n"); ret = -1; goto cleanup; } #endif png_init_io(png, stream); png_set_IHDR(png, info, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png, info); // If the level is zero (no compression), filtering will be unnecessary png_set_compression_level(png, comp_level); if (comp_level == 0) { png_set_filter(png, 0, PNG_NO_FILTERS); } else { png_set_filter(png, 0, PNG_ALL_FILTERS); } for (int y = 0; y < height; y++) { const uint32_t *row = (const uint32_t *)(data + y * stride); pack_row32(tmp_row, row, width, fully_opaque); png_write_row(png, tmp_row); } png_write_end(png, NULL); cleanup: if (info) { png_destroy_info_struct(png, &info); } if (png) { png_destroy_write_struct(&png, NULL); } free(tmp_row); return ret; } grim-1.4.0+ds/write_ppm.c000066400000000000000000000026641420343431400152340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "write_ppm.h" int write_to_ppm_stream(pixman_image_t *image, FILE *stream) { // 256 bytes ought to be enough for everyone char header[256]; int width = pixman_image_get_width(image); int height = pixman_image_get_height(image); int header_len = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", width, height); assert(header_len <= (int)sizeof(header)); size_t len = header_len + width * height * 3; unsigned char *data = malloc(len); unsigned char *buffer = data; // We _do_not_ include the null byte memcpy(buffer, header, header_len); buffer += header_len; pixman_format_code_t format = pixman_image_get_format(image); assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8); // Both formats are native-endian 32-bit ints uint32_t *pixels = pixman_image_get_data(image); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { uint32_t p = *pixels++; // RGB order *buffer++ = (p >> 16) & 0xff; *buffer++ = (p >> 8) & 0xff; *buffer++ = (p >> 0) & 0xff; } } size_t written = fwrite(data, 1, len, stream); if (written < len) { free(data); fprintf(stderr, "Failed to write ppm; only %zu of %zu bytes written\n", written, len); return -1; } free(data); return 0; }