pax_global_header00006660000000000000000000000064136116334650014522gustar00rootroot0000000000000052 comment=086401e12a79848b2e7418ff8bd8a580d6fe4a04 grim-1.3.0+ds/000077500000000000000000000000001361163346500130635ustar00rootroot00000000000000grim-1.3.0+ds/.build.yml000066400000000000000000000003651361163346500147670ustar00rootroot00000000000000image: archlinux packages: - meson - wayland - wayland-protocols - cairo - libjpeg-turbo sources: - https://github.com/emerison/grim tasks: - setup: | cd grim meson build - build: | cd grim ninja -C build grim-1.3.0+ds/.editorconfig000066400000000000000000000002011361163346500155310ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true grim-1.3.0+ds/.gitignore000066400000000000000000000006651361163346500150620ustar00rootroot00000000000000# 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.3.0+ds/LICENSE000066400000000000000000000020511361163346500140660ustar00rootroot00000000000000MIT 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.3.0+ds/README.md000066400000000000000000000025751361163346500143530ustar00rootroot00000000000000# 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') ``` 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 * cairo * 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 Freenode. ## 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.3.0+ds/box.c000066400000000000000000000020131361163346500140130ustar00rootroot00000000000000#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.3.0+ds/buffer.c000066400000000000000000000036661361163346500145130ustar00rootroot00000000000000#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.3.0+ds/cairo_ppm.c000066400000000000000000000046131361163346500152040ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "cairo_ppm.h" cairo_status_t cairo_surface_write_to_ppm_mem(cairo_surface_t *sfc, unsigned char **data, unsigned long *len) { // 256 bytes ought to be enough for everyone char header[256]; int width = cairo_image_surface_get_width(sfc); int height = cairo_image_surface_get_height(sfc); int header_len = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", width, height); assert(header_len <= (int)sizeof(header)); *len = header_len + width * height * 3; unsigned char *buffer = malloc(*len); *data = buffer; // We _do_not_ include the null byte memcpy(buffer, header, header_len); buffer += header_len; cairo_format_t cformat = cairo_image_surface_get_format(sfc); assert(cformat == CAIRO_FORMAT_RGB24 || cformat == CAIRO_FORMAT_ARGB32); // Both formats are native-endian 32-bit ints uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(sfc); 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; } } return CAIRO_STATUS_SUCCESS; } static cairo_status_t cj_write(void *closure, const unsigned char *data, unsigned int length) { if (write((long) closure, data, length) < (ssize_t) length) { return CAIRO_STATUS_WRITE_ERROR; } else { return CAIRO_STATUS_SUCCESS; } } cairo_status_t cairo_surface_write_to_ppm_stream(cairo_surface_t *sfc, cairo_write_func_t write_func, void *closure) { cairo_status_t e; unsigned char *data = NULL; unsigned long len = 0; e = cairo_surface_write_to_ppm_mem(sfc, &data, &len); if (e == CAIRO_STATUS_SUCCESS) { assert(sizeof(unsigned long) <= sizeof(size_t) || !(len >> (sizeof(size_t) * CHAR_BIT))); e = write_func(closure, data, len); free(data); } return e; } cairo_status_t cairo_surface_write_to_ppm(cairo_surface_t *sfc, const char *filename) { cairo_status_t e; int outfile; outfile = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (outfile == -1) { return CAIRO_STATUS_DEVICE_ERROR; } e = cairo_surface_write_to_ppm_stream(sfc, cj_write, (void*) (long) outfile); close(outfile); return e; } grim-1.3.0+ds/grim.1.scd000066400000000000000000000027701361163346500146610ustar00rootroot00000000000000grim(1) # NAME grim - grab images from a Wayland compositor # DESCRIPTION *grim* [options...] [output-file] # SYNOPSIS 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 defaults to *$XDG_PICTURES_DIR*. If _output-file_ is *-*, grim will output 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. *-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.3.0+ds/include/000077500000000000000000000000001361163346500145065ustar00rootroot00000000000000grim-1.3.0+ds/include/box.h000066400000000000000000000004521361163346500154500ustar00rootroot00000000000000#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.3.0+ds/include/buffer.h000066400000000000000000000006131361163346500161300ustar00rootroot00000000000000#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.3.0+ds/include/cairo_ppm.h000066400000000000000000000006031361163346500166270ustar00rootroot00000000000000#ifndef _CAIRO_PPM_H #define _CAIRO_PPM_H #include cairo_status_t cairo_surface_write_to_ppm_mem(cairo_surface_t *sfc, unsigned char **data, unsigned long *len); cairo_status_t cairo_surface_write_to_ppm_stream(cairo_surface_t *sfc, cairo_write_func_t write_func, void *closure); cairo_status_t cairo_surface_write_to_ppm(cairo_surface_t *sfc, const char *filename); #endif grim-1.3.0+ds/include/grim.h000066400000000000000000000020161361163346500156140ustar00rootroot00000000000000#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.3.0+ds/include/output-layout.h000066400000000000000000000007321361163346500175340ustar00rootroot00000000000000#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.3.0+ds/include/render.h000066400000000000000000000002611361163346500161350ustar00rootroot00000000000000#ifndef _RENDER_H #define _RENDER_H #include #include "grim.h" cairo_surface_t *render(struct grim_state *state, struct grim_box *geometry, double scale); #endif grim-1.3.0+ds/main.c000066400000000000000000000362441361163346500141640ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "buffer.h" #include "grim.h" #include "output-layout.h" #include "render.h" #include "cairo_ppm.h" #ifdef HAVE_JPEG #include "cairo_jpg.h" #endif 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 cairo_status_t write_func(void *data, const unsigned char *buf, unsigned int len) { FILE *f = data; size_t written = fwrite(buf, sizeof(unsigned char), len, f); if (written < len) { return CAIRO_STATUS_WRITE_ERROR; } return CAIRO_STATUS_SUCCESS; } 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 assert(false); #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_output_dir(void) { static const char *output_dirs[] = { "GRIM_DEFAULT_DIR", "XDG_PICTURES_DIR", }; for (size_t i = 0; i < sizeof(output_dirs) / sizeof(char *); ++i) { char *path = getenv(output_dirs[i]); if (path_exists(path)) { return path; } } return "."; } void filepath(char *output_path, const char *filename) { char *output_dir = get_output_dir(); sprintf(output_path, "%s/%s", output_dir, filename); } 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" " -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; bool with_cursor = false; int opt; while ((opt = getopt(argc, argv, "hs:g:t:q: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 'o': free(geometry_output); geometry_output = strdup(optarg); break; case 'c': with_cursor = true; break; default: return EXIT_FAILURE; } } char output_filepath[PATH_MAX]; char *output_filename; char tmp[64]; if (optind >= argc) { if (default_filename(tmp, sizeof(tmp), output_filetype) != true) { fprintf(stderr, "failed to generate default filename\n"); return EXIT_FAILURE; } output_filename = tmp; filepath(output_filepath, output_filename); } else { output_filename = argv[optind]; strcpy(output_filepath, 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_dispatch(state.display); 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_dispatch(state.display); 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, "screenshot region is empty\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); } cairo_surface_t *surface = render(&state, geometry, scale); if (surface == NULL) { return EXIT_FAILURE; } cairo_status_t status; if (strcmp(output_filename, "-") == 0) { switch (output_filetype) { case GRIM_FILETYPE_PPM: status = cairo_surface_write_to_ppm_stream(surface, write_func, stdout); break; case GRIM_FILETYPE_PNG: status = cairo_surface_write_to_png_stream(surface, write_func, stdout); break; case GRIM_FILETYPE_JPEG: #if HAVE_JPEG status = cairo_surface_write_to_jpeg_stream(surface, write_func, stdout, jpeg_quality); break; #else assert(false); #endif } } else { switch (output_filetype) { case GRIM_FILETYPE_PPM: status = cairo_surface_write_to_ppm(surface, output_filepath); break; case GRIM_FILETYPE_PNG: status = cairo_surface_write_to_png(surface, output_filepath); break; case GRIM_FILETYPE_JPEG: #if HAVE_JPEG status = cairo_surface_write_to_jpeg( surface, output_filepath, jpeg_quality); break; #else assert(false); #endif } } if (status != CAIRO_STATUS_SUCCESS) { fprintf(stderr, "%s\n", cairo_status_to_string(status)); if (status == CAIRO_STATUS_WRITE_ERROR && strlen(output_filepath) > NAME_MAX) { fprintf(stderr, "Hint: Output filepath length may be too long for your filesystem."); } return EXIT_FAILURE; } cairo_surface_destroy(surface); 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.3.0+ds/meson.build000066400000000000000000000030451361163346500152270ustar00rootroot00000000000000project( 'grim', 'c', version: '1.3.0', license: 'MIT', meson_version: '>=0.48.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') cairo = dependency('cairo') jpeg = dependency('libjpeg', required: get_option('jpeg')) math = cc.find_library('m') 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 subdir('protocol') grim_files = [ 'box.c', 'buffer.c', 'main.c', 'output-layout.c', 'render.c', 'cairo_ppm.c', ] grim_deps = [ cairo, client_protos, math, realtime, wayland_client, ] if jpeg.found() grim_files += ['cairo_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() sh = find_program('sh') man_pages = ['grim.1.scd'] mandir = get_option('mandir') 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: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) ], install: true, install_dir: '@0@/man@1@'.format(mandir, section) ) endforeach endif grim-1.3.0+ds/meson_options.txt000066400000000000000000000002661361163346500165240ustar00rootroot00000000000000option('jpeg', type: 'feature', value: 'auto', description: 'Enable JPEG support') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') grim-1.3.0+ds/output-layout.c000066400000000000000000000036701361163346500161100ustar00rootroot00000000000000#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.3.0+ds/protocol/000077500000000000000000000000001361163346500147245ustar00rootroot00000000000000grim-1.3.0+ds/protocol/meson.build000066400000000000000000000023011361163346500170620ustar00rootroot00000000000000wl_protocol_dir = wayland_protos.get_pkgconfig_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.3.0+ds/protocol/wlr-screencopy-unstable-v1.xml000066400000000000000000000167511361163346500225730ustar00rootroot00000000000000 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.3.0+ds/render.c000066400000000000000000000075561361163346500145230ustar00rootroot00000000000000#include #include #include "buffer.h" #include "output-layout.h" #include "render.h" static bool convert_buffer(struct grim_buffer *buffer) { // Formats are little-endian switch (buffer->format) { case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_XRGB8888: // Natively supported by Cairo return true; case WL_SHM_FORMAT_ABGR8888: case WL_SHM_FORMAT_XBGR8888:; // ABGR -> ARGB uint8_t *data = buffer->data; for (int i = 0; i < buffer->height; ++i) { for (int j = 0; j < buffer->width; ++j) { uint32_t *px = (uint32_t *)(data + i * buffer->stride + j * 4); uint8_t a = (*px >> 24) & 0xFF; uint8_t b = (*px >> 16) & 0xFF; uint8_t g = (*px >> 8) & 0xFF; uint8_t r = *px & 0xFF; *px = (a << 24) | (r << 16) | (g << 8) | b; } } if (buffer->format == WL_SHM_FORMAT_ABGR8888) { buffer->format = WL_SHM_FORMAT_ARGB8888; } else { buffer->format = WL_SHM_FORMAT_XRGB8888; } return true; default: fprintf(stderr, "unsupported format %d\n", buffer->format); return false; } assert(false); } static cairo_format_t get_cairo_format(enum wl_shm_format wl_fmt) { switch (wl_fmt) { case WL_SHM_FORMAT_ARGB8888: return CAIRO_FORMAT_ARGB32; case WL_SHM_FORMAT_XRGB8888: return CAIRO_FORMAT_RGB24; default: return CAIRO_FORMAT_INVALID; } assert(false); } cairo_surface_t *render(struct grim_state *state, struct grim_box *geometry, double scale) { cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, geometry->width * scale, geometry->height * scale); cairo_t *cairo = cairo_create(surface); // Clear cairo_save(cairo); cairo_set_source_rgba(cairo, 0, 0, 0, 0); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_paint(cairo); cairo_restore(cairo); struct grim_output *output; wl_list_for_each(output, &state->outputs, link) { struct grim_buffer *buffer = output->buffer; if (buffer == NULL) { continue; } if (!convert_buffer(buffer)) { return NULL; } cairo_format_t cairo_fmt = get_cairo_format(buffer->format); assert(cairo_fmt != CAIRO_FORMAT_INVALID); 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; cairo_surface_t *output_surface = cairo_image_surface_create_for_data( buffer->data, cairo_fmt, buffer->width, buffer->height, buffer->stride); cairo_pattern_t *output_pattern = cairo_pattern_create_for_surface(output_surface); // All transformations are in pattern-local coordinates cairo_matrix_t matrix; cairo_matrix_init_identity(&matrix); cairo_matrix_translate(&matrix, (double)output->geometry.width / 2, (double)output->geometry.height / 2); cairo_matrix_rotate(&matrix, -get_output_rotation(output->transform)); cairo_matrix_scale(&matrix, (double)raw_output_width / output_width * output_flipped_x, (double)raw_output_height / output_height * output_flipped_y); cairo_matrix_translate(&matrix, -(double)output_width / 2, -(double)output_height / 2); cairo_matrix_translate(&matrix, -output_x, -output_y); cairo_matrix_scale(&matrix, 1 / scale, 1 / scale); cairo_pattern_set_matrix(output_pattern, &matrix); cairo_pattern_set_filter(output_pattern, CAIRO_FILTER_BEST); cairo_set_source(cairo, output_pattern); cairo_pattern_destroy(output_pattern); cairo_paint(cairo); cairo_surface_destroy(output_surface); } cairo_destroy(cairo); return surface; }