├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── cmake ├── FindGDI32.c └── FindGDI32.cmake ├── docs ├── help.txt └── shot.1 └── src ├── .gitignore ├── bitmap.c ├── bitmap.h ├── bitmap_win.c ├── bitmap_x11.c ├── grab.h ├── grab_win.c ├── grab_x11.c ├── monitor.c ├── monitor.h ├── monitor_mgr.c ├── monitor_mgr.h ├── monitor_mgr_win.c ├── monitor_mgr_x11.c ├── region.h ├── region_picker ├── active_monitor.c ├── active_monitor.h ├── active_window.h ├── active_window_win.c ├── active_window_x11.c ├── errors.h ├── interactive.h ├── interactive_common.c ├── interactive_common.h ├── interactive_win.c ├── interactive_x11.c ├── monitor.c ├── monitor.h ├── string.c ├── string.h ├── window.h ├── window_win.c └── window_x11.c └── shot.c /.gitignore: -------------------------------------------------------------------------------- 1 | waf 2 | build 3 | .lock* 4 | .waf* 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6 FATAL_ERROR) 2 | 3 | # ------------------- 4 | # Version and project 5 | # ------------------- 6 | find_package(Git) 7 | if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.git" AND Git_FOUND) 8 | execute_process(COMMAND 9 | "${GIT_EXECUTABLE}" describe --always --dirty --long --tags 10 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 11 | OUTPUT_VARIABLE SHOT_VERSION_LONG OUTPUT_STRIP_TRAILING_WHITESPACE) 12 | endif() 13 | if(NOT SHOT_VERSION_LONG) 14 | set(SHOT_VERSION_LONG "0.0") 15 | endif() 16 | string(REGEX REPLACE "-.*" "" SHOT_VERSION_SHORT "${SHOT_VERSION_LONG}") 17 | project(shot VERSION ${SHOT_VERSION_SHORT} LANGUAGES C) 18 | 19 | # ------------ 20 | # Dependencies 21 | # ------------ 22 | find_package(PNG REQUIRED) 23 | 24 | if(WIN32) 25 | set(target "win") 26 | else() 27 | find_package(PkgConfig REQUIRED) 28 | pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11 xrandr) 29 | set(target "x11") 30 | endif() 31 | 32 | # --------------------------- 33 | # Source files and executable 34 | # --------------------------- 35 | file(READ docs/help.txt HELP HEX) 36 | string(REGEX REPLACE "(..)" "\\\\x\\1" HELP "${HELP}") 37 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/help.h" 38 | "#ifndef HELP_H\n" 39 | "#define HELP_H\n" 40 | "static const char *help_str = \"${HELP}\";\n" 41 | "#endif\n") 42 | 43 | add_executable(shot WIN32 # disable console window on windows 44 | src/bitmap.c src/bitmap.h 45 | src/bitmap_${target}.c 46 | src/grab.h 47 | src/grab_${target}.c 48 | src/monitor.c src/monitor.h 49 | src/monitor_mgr.c src/monitor_mgr.h 50 | src/monitor_mgr_${target}.c 51 | src/region.h 52 | src/shot.c 53 | src/region_picker/active_monitor.c src/region_picker/active_monitor.h 54 | src/region_picker/active_window_${target}.c 55 | src/region_picker/active_window.h 56 | src/region_picker/errors.h 57 | src/region_picker/interactive_${target}.c 58 | src/region_picker/interactive.h 59 | src/region_picker/interactive_common.c 60 | src/region_picker/interactive_common.h 61 | src/region_picker/monitor.c src/region_picker/monitor.h 62 | src/region_picker/string.c src/region_picker/string.h 63 | src/region_picker/window_${target}.c 64 | src/region_picker/window.h 65 | "${CMAKE_CURRENT_BINARY_DIR}/help.h") 66 | 67 | # ------------- 68 | # Build options 69 | # ------------- 70 | target_include_directories(shot PRIVATE src "${CMAKE_CURRENT_BINARY_DIR}") 71 | set_target_properties(shot PROPERTIES 72 | C_STANDARD 99 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF) 73 | target_compile_definitions(shot PRIVATE SHOT_VERSION="${SHOT_VERSION_LONG}" 74 | $<$>:_POSIX_C_SOURCE=200809L>) # needed for nanosleep() 75 | include(CheckCCompilerFlag) 76 | foreach(FLAG IN ITEMS Wall Wextra pedantic-errors) 77 | string(TOUPPER "${FLAG}" VARIABLE) 78 | string(REPLACE "-" "_" VARIABLE "${VARIABLE}") 79 | check_c_compiler_flag("-${FLAG}" "${VARIABLE}") 80 | if("${${VARIABLE}}") 81 | target_compile_options(shot PRIVATE "-${FLAG}") 82 | endif() 83 | endforeach() 84 | 85 | # ------------------- 86 | # Linking definitions 87 | # ------------------- 88 | target_link_libraries(shot PRIVATE PNG::PNG 89 | $<$>:PkgConfig::X11>) 90 | 91 | # ------------ 92 | # Installation 93 | # ------------ 94 | include(GNUInstallDirs) 95 | install(TARGETS shot DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}") 96 | install(FILES docs/shot.1 DESTINATION "${CMAKE_INSTALL_FULL_MANDIR}/man1") 97 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 98 | "file(REMOVE \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/shot\" 99 | \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_MANDIR}/man1/shot.1\")") 100 | add_custom_target(uninstall COMMAND "${CMAKE_COMMAND}" -P cmake_uninstall.cmake) 101 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Marcin Kurczewski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shot 2 | ==== 3 | 4 | Make screenshots with friendly CLI. 5 | 6 | Download for Windows: [*Github Releases*](https://github.com/rr-/shot/releases) 7 | Detailed usage: [*click*](https://raw.githubusercontent.com/rr-/shot/master/docs/help.txt) 8 | 9 | ### Features 10 | 11 | - Cross platform: 12 | - Windows 13 | - GNU/Linux (X11 + XRandR) 14 | - Versatile region selection methods: 15 | - the whole desktop 16 | - currently focused window 17 | - currently focused monitor (established by focused window) 18 | - specific monitor 19 | - specific window 20 | - specific rectangle (passed as string) 21 | - interactive selection with a special window 22 | - Ideal to put under a hotkey 23 | 24 | ### Why not scrot? 25 | 26 | To have consistent user experience between platforms I use, I wanted it 27 | to work on Windows as well. Additionally, I wanted manual region selection to 28 | be more precise - in scrot, once you select a rectangle, that's it, you can't 29 | correct it. shot's region picker makes it easy to correct offsets even by 1px 30 | before actually taking the screenshot and provides visual feedback of the 31 | screen area. 32 | 33 | ### Interactive selection in action 34 | 35 | ![--interactive at its 36 | best](https://cloud.githubusercontent.com/assets/1045476/8808860/5908945e-2fe5-11e5-93bf-ecad1500c35b.png) 37 | 38 | --- 39 | 40 | ### Compiling for GNU/Linux 41 | 42 | 1. Install `libpng`. 43 | 2. Run following: 44 | 45 | mkdir build && cd build 46 | cmake .. 47 | make 48 | 49 | 3. If you wish to install it globally: 50 | 51 | sudo make install 52 | 53 | ### Cross compiling for Windows 54 | 55 | 1. Install [`mxe`](https://github.com/mxe/mxe) and compile `libpng` and `cmake`: 56 | 57 | git clone https://github.com/mxe/mxe.git 58 | cd mxe 59 | make libpng cmake 60 | 61 | 2. Compile with `mxe`: 62 | 63 | mkdir build && cd build 64 | ~/path/to/mxe/usr/bin/i686-w64-mingw32.static-cmake .. 65 | make 66 | -------------------------------------------------------------------------------- /cmake/FindGDI32.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | HDC hDC=NULL; 6 | RECT clientRect; 7 | DPtoLP(hDC, (LPPOINT)&clientRect, 2); 8 | } 9 | -------------------------------------------------------------------------------- /cmake/FindGDI32.cmake: -------------------------------------------------------------------------------- 1 | # Find gdi32 header and library for wingcc driver 2 | # 3 | 4 | # This module defines the following uncached variables: 5 | # GDI32_FOUND, if false, do not try to use gdi32. 6 | # GDI32_LIBRARIES, the libraries to link against to use gdi32 7 | 8 | # Borland compiler doesn't know the gdi32 library 9 | IF(NOT BORLAND) 10 | set(GDI32_LIBRARY gdi32) 11 | ENDIF(NOT BORLAND) 12 | 13 | try_compile(TESTGDI32 14 | ${CMAKE_BINARY_DIR} 15 | ${CMAKE_SOURCE_DIR}/cmake/FindGDI32.c 16 | CMAKE_FLAGS -DLINK_LIBRARIES=${GDI32_LIBRARY} 17 | OUTPUT_VARIABLE OUTPUT) 18 | if(TESTGDI32) 19 | set(GDI32_FOUND ON) 20 | set(GDI32_LIBRARIES ${GDI32_LIBRARY}) 21 | file(APPEND ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeOutput.log 22 | "Determining if gdi32 is available passed with " 23 | "the following output:\n${OUTPUT}\n\n") 24 | else(TESTGDI32) 25 | set(GDI32_FOUND OFF) 26 | set(GDI32_LIBRARIES "") 27 | file(APPEND ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeError.log 28 | "Determining if gdi32 is available failed with " 29 | "the following output:\n${OUTPUT}\n\n") 30 | endif(TESTGDI32) 31 | 32 | if(GDI32_FOUND) 33 | if(NOT GDI32_FIND_QUIETLY) 34 | message(STATUS "FindGDI32: Found gdi32 header file and library") 35 | endif(NOT GDI32_FIND_QUIETLY) 36 | else(GDI32_FOUND) 37 | if(GDI32_FIND_REQUIRED) 38 | message(FATAL_ERROR "FindGDI32: Could not find gdi32 header file and/or library") 39 | endif(GDI32_FIND_REQUIRED) 40 | endif(GDI32_FOUND) 41 | 42 | if(GDI32_FOUND AND NOT TARGET GDI32::GDI32) 43 | add_library(GDI32::GDI32 UNKNOWN IMPORTED) 44 | set_target_properties(GDI32::GDI32 PROPERTIES 45 | IMPORTED_LOCATION "${GDI32_LIBRARY}" 46 | INTERFACE_COMPILE_OPTIONS -mwindows) # disable console window 47 | endif() 48 | -------------------------------------------------------------------------------- /docs/help.txt: -------------------------------------------------------------------------------- 1 | Usage: shot [options] 2 | Takes screenshots of your desktop. 3 | 4 | Basic options 5 | 6 | -h, --help Shows this help. 7 | -o, --output PATH Saves the target image in PATH. The default file name 8 | is of the form "yyyymmdd_hhiiss_rrr.png", where "r" is 9 | a random character (example: 20150721_103014_ukd.png), 10 | and the file is placed in current working directory. 11 | If the PATH ends with /, it will be appended with 12 | default file name. Target directory needs to exist 13 | prior to invocation. 14 | Currently, only PNG format is supported. 15 | -c, --clipboard Puts the target image in the system clipboard. 16 | Overrides --output. Available only on Windows. 17 | --list Lists all monitors and exits. 18 | -v, --version Shows shot's version and exists. 19 | 20 | Options specific to screenshot regions 21 | 22 | By default, the region is equivalent to --desktop. 23 | 24 | -d, --desktop Takes region from the area bounding all monitors. 25 | -r, --region REGION Takes region from string. REGION needs to be of form 26 | WIDTHxHEIGHT+X+Y or WIDTHxHEIGHT. 27 | -m, --monitor NUM Takes region from NUM-th monitor. 28 | -w, --window NUM Takes region from window with id NUM. 29 | -M, --active-monitor Takes region from the currently active monitor. 30 | -W, --active-window Takes region from the currently active window. 31 | -i, --interactive Creates a resizable and movable window, whose final 32 | position and size will be used to derive the region. 33 | 34 | Controlling interactive region picker 35 | 36 | The interactive region picker's window can be moved with left mouse button 37 | and resized with right mouse button. Following keyboard shortcuts are also 38 | available: 39 | 40 | h, left Moves left by DELTA. 41 | k, up Moves up by DELTA. 42 | j, down Moves down by DELTA. 43 | l, right Moves right by DELTA. 44 | 45 | ctrl+h, ctrl+left Shrinks horizontally by DELTA. 46 | ctrl+k, ctrl+up Shrinks vertically by DELTA. 47 | ctrl+j, ctrl+down Grows horizontally by DELTA. 48 | ctrl+l, ctrl+right Grows vertically by DELTA. 49 | 50 | esc, q Cancels (exits without taking a screenshot). 51 | enter Confirms selection and takes a screenshot. 52 | 53 | DELTA is equal to 25px by default, and 1px when shift key is pressed. 54 | 55 | By default, the interactive region picker starts with a 640x480 rectangle 56 | centered on the primary monitor. This can be controlled by prepending other 57 | region arguments before -i. For example, "shot -W -i" will create 58 | interactive region picker whose initial location and size is set to that of 59 | the currently focused window. 60 | 61 | Examples 62 | 63 | shot 64 | 65 | Takes a screenshot of the area bounding all available monitors. 66 | 67 | shot --list 68 | 69 | Doesn't take a screenshot and lists monitors instead. 70 | 71 | shot -W --output=/home/sseagal/ 72 | 73 | Takes a screenshot of focused window and saves it to /home/sseagal/. 74 | -------------------------------------------------------------------------------- /docs/shot.1: -------------------------------------------------------------------------------- 1 | .TH SHOT "1" "July 2015" "shot" "User Commands" 2 | .SH NAME 3 | shot \- manual page for shot 4 | .SH SYNOPSIS 5 | .B shot 6 | [\fI\,options\/\fR] 7 | .PP 8 | .SH DESCRIPTION 9 | Takes screenshots of your desktop. 10 | .SH BASIC OPTIONS 11 | .TP 12 | \fB\-h\fR, \fB\-\-help\fR 13 | Shows this help. 14 | .TP 15 | \fB\-o\fR, \fB\-\-output\fR PATH 16 | Saves the target image in PATH. The default file name is of the form 17 | "\fByyyymmdd_hhiiss_rrr.png\fR", where "\fBr\fR" is a random character (example: 18 | \fB20150721_103014_ukd.png\fR), and the file is placed in current working 19 | directory. If the PATH ends with \fB/\fR, it will be appended with default 20 | file name. Target directory needs to exist prior to invocation. 21 | Currently, only PNG format is supported. 22 | .TP 23 | \fB\-c\fR, \fB\-\-clipboard\fR 24 | Puts the target image in the system clipboard. Overrides \fB--output\fR. 25 | Available only on Windows. 26 | .TP 27 | \fB\-\-list\fR 28 | Lists all monitors and exits. 29 | .TP 30 | \fB\-v\fR, \fB\-\-version\fR 31 | Shows shot's version and exits. 32 | .SH REGION OPTIONS 33 | .PP 34 | By default, the region is equivalent to \fB\-\-desktop\fR. 35 | .TP 36 | \fB\-d\fR, \fB\-\-desktop\fR 37 | Takes region from the area bounding all monitors. 38 | .TP 39 | \fB\-r\fR, \fB\-\-region REGION\fR 40 | Takes region from string. \fBREGION\fR needs 41 | to be of form \fBWIDTHxHEIGHT+X+Y\fR or \fBWIDTHxHEIGHT\fR. 42 | .TP 43 | \fB\-m\fR, \fB\-\-monitor NUM\fR 44 | Takes region from \fBNUM\fR\-th monitor. 45 | .TP 46 | \fB\-w\fR, \fB\-\-window NUM\fR 47 | Takes region from window with id \fBNUM\fR. 48 | .TP 49 | \fB\-M\fR, \fB\-\-active\-monitor\fR 50 | Takes region from the currently active monitor. 51 | .TP 52 | \fB\-W\fR, \fB\-\-active\-window\fR 53 | Takes region from the currently active window. 54 | .TP 55 | \fB\-i\fR, \fB\-\-interactive\fR 56 | Creates a resizable and movable window, whose final position and size will be 57 | used to derive the region. 58 | .SH 59 | INTERACTIVE REGION PICKER CONTROL 60 | .PP 61 | The interactive region picker's window can be moved with left mouse button 62 | and resized with right mouse button. Following keyboard shortcuts are also 63 | available: 64 | .TP 65 | \fBH\fR or \fBLEFT\fR 66 | Moves left by \fIDELTA\fR. 67 | .TP 68 | \fBJ\fR or \fBDOWN\fR 69 | Moves down by \fIDELTA\fR. 70 | .TP 71 | \fBK\fR or \fBUP\fR 72 | Moves up by \fIDELTA\fR. 73 | .TP 74 | \fBL\fR or \fBRIGHT\fR 75 | Moves right by \fIDELTA\fR. 76 | .TP 77 | \fBCTRL+H\fR or \fBCTRL+LEFT\fR 78 | Shrinks horizontally by \fIDELTA\fR. 79 | .TP 80 | \fBCTRL+J\fR or \fBCTRL+DOWN\fR 81 | Grows horizontally by \fIDELTA\fR. 82 | .TP 83 | \fBCTRL+K\fR or \fBCTRL+UP\fR 84 | Shrinks vertically by \fIDELTA\fR. 85 | .TP 86 | \fBCTRL+L\fR or \fBCTRL+RIGHT\fR 87 | Grows vertically by \fIDELTA\fR. 88 | .TP 89 | \fBQ\fR or \fBESCAPE\fR 90 | Cancels (exits without taking a screenshot). 91 | .TP 92 | \fBENTER\fR 93 | Confirms selection and takes a screenshot. 94 | .PP 95 | \fIDELTA\fR is equal to 25px by default, and 1px when \fBSHIFT\fR key is 96 | pressed. 97 | .PP 98 | By default, the interactive region picker starts with a 640x480 rectangle 99 | centered on the primary monitor. This can be controlled by prepending other 100 | region arguments before \fB\-i\fR. For example, "\fBshot \-W \-i\fR" will 101 | create interactive region picker whose initial location and size is set to that 102 | of the currently focused window. 103 | .SH EXAMPLES 104 | .B shot 105 | .IP 106 | Takes a screenshot of the area bounding all available monitors. 107 | .TP 108 | .B shot \-\-list 109 | .IP 110 | Doesn't take a screenshot and lists monitors instead. 111 | .P 112 | .B shot \-W \-\-output=/home/sseagal/ 113 | .IP 114 | Takes a screenshot of focused window and saves it to \fB/home/sseagal/\fR. 115 | .SH BUG REPORTS 116 | All bug reports and feature requests should go to the GitHub page which is 117 | available at \fIhttps://github.com/rr-/shot\fR. 118 | .SH AUTHORS 119 | Written by rr- (rr-@sakuya.pl). 120 | .SH COPYRIGHT 121 | MIT. 122 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | help.h 2 | version.h 3 | -------------------------------------------------------------------------------- /src/bitmap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "bitmap.h" 5 | 6 | ShotBitmap *bitmap_create(unsigned int width, unsigned int height) 7 | { 8 | ShotBitmap *bitmap = malloc(sizeof(ShotBitmap)); 9 | bitmap->width = width; 10 | bitmap->height = height; 11 | bitmap->pixels = calloc(sizeof(ShotPixel), width * height); 12 | return bitmap; 13 | } 14 | 15 | void bitmap_destroy(ShotBitmap *bitmap) 16 | { 17 | assert(bitmap); 18 | free(bitmap->pixels); 19 | free(bitmap); 20 | } 21 | 22 | ShotPixel *bitmap_get_pixel(ShotBitmap *bitmap, unsigned int x, unsigned int y) 23 | { 24 | return &bitmap->pixels[bitmap->width * y + x]; 25 | } 26 | 27 | int bitmap_save_to_png(ShotBitmap *bitmap, const char *path) 28 | { 29 | png_structp png_ptr = NULL; 30 | png_infop info_ptr = NULL; 31 | int ret = 1; 32 | 33 | FILE *fp = fopen(path, "wb"); 34 | if (!fp) 35 | { 36 | fprintf(stderr, "Can't open %s for writing.\n", path); 37 | goto end; 38 | } 39 | 40 | png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 41 | if (!png_ptr) 42 | { 43 | fprintf(stderr, "Can't intialize PNG writer.\n"); 44 | goto end; 45 | } 46 | 47 | info_ptr = png_create_info_struct(png_ptr); 48 | if (!info_ptr) 49 | { 50 | fprintf(stderr, "Can't intialize PNG writer.\n"); 51 | goto end; 52 | } 53 | 54 | if (setjmp(png_jmpbuf(png_ptr))) 55 | { 56 | fprintf(stderr, "Can't intialize PNG writer.\n"); 57 | goto end; 58 | } 59 | 60 | png_set_IHDR(png_ptr, 61 | info_ptr, 62 | bitmap->width, 63 | bitmap->height, 64 | 8, //depth 65 | PNG_COLOR_TYPE_RGB, 66 | PNG_INTERLACE_NONE, 67 | PNG_COMPRESSION_TYPE_DEFAULT, 68 | PNG_FILTER_TYPE_DEFAULT); 69 | 70 | png_byte **row_pointers = png_malloc( 71 | png_ptr, bitmap->height * sizeof(png_byte*)); 72 | for (unsigned int y = 0; y < bitmap->height; y++) 73 | { 74 | png_byte *row = png_malloc(png_ptr, bitmap->width * 3); //channels 75 | row_pointers[y] = row; 76 | for (unsigned int x = 0; x < bitmap->width; x++) 77 | { 78 | ShotPixel *pixel = bitmap_get_pixel(bitmap, x, y); 79 | *row++ = pixel->red; 80 | *row++ = pixel->green; 81 | *row++ = pixel->blue; 82 | } 83 | } 84 | 85 | png_init_io(png_ptr, fp); 86 | png_set_rows(png_ptr, info_ptr, row_pointers); 87 | png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); 88 | 89 | ret = 0; 90 | for (unsigned int y = 0; y < bitmap->height; y++) 91 | png_free(png_ptr, row_pointers[y]); 92 | png_free(png_ptr, row_pointers); 93 | 94 | end: 95 | if (png_ptr && info_ptr) 96 | png_destroy_write_struct (&png_ptr, &info_ptr); 97 | if (fp) 98 | fclose(fp); 99 | return ret; 100 | } 101 | -------------------------------------------------------------------------------- /src/bitmap.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAP_H 2 | #define BITMAP_H 3 | #include 4 | 5 | typedef struct 6 | { 7 | uint8_t red; 8 | uint8_t green; 9 | uint8_t blue; 10 | } ShotPixel; 11 | 12 | typedef struct 13 | { 14 | ShotPixel *pixels; 15 | unsigned int width; 16 | unsigned int height; 17 | } ShotBitmap; 18 | 19 | ShotBitmap *bitmap_create(unsigned int width, unsigned int height); 20 | void bitmap_destroy(ShotBitmap *bitmap); 21 | ShotPixel *bitmap_get_pixel(ShotBitmap *bitmap, unsigned int x, unsigned int y); 22 | int bitmap_save_to_png(ShotBitmap *bitmap, const char *path); 23 | int bitmap_save_to_clipboard(ShotBitmap *bitmap); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/bitmap_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bitmap.h" 4 | 5 | int bitmap_save_to_clipboard(ShotBitmap *bitmap) 6 | { 7 | int ret = 1; 8 | size_t stride = ((bitmap->width * 32 + 31) / 32) * 3; 9 | size_t bitmap_size = sizeof(BITMAPINFOHEADER) + bitmap->height * stride; 10 | 11 | HGLOBAL memory = GlobalAlloc(GHND | GMEM_SHARE, bitmap_size); 12 | if (!memory) 13 | { 14 | fprintf(stderr, "Error allocating memory for bitmap data.\n"); 15 | goto end; 16 | } 17 | 18 | char *dest = (char*) GlobalLock(memory); 19 | if (!dest) 20 | { 21 | fprintf(stderr, "Error accessing memory for bitmap data.\n"); 22 | goto end; 23 | } 24 | 25 | BITMAPINFOHEADER header = {0}; 26 | header.biSize = sizeof(BITMAPINFOHEADER); 27 | header.biWidth = bitmap->width; 28 | header.biHeight = -bitmap->height; 29 | header.biPlanes = 1; 30 | header.biBitCount = 24; 31 | header.biCompression = BI_RGB; 32 | header.biSizeImage = bitmap->height * stride; 33 | header.biXPelsPerMeter = 0; 34 | header.biYPelsPerMeter = 0; 35 | header.biClrUsed = 0; 36 | header.biClrImportant = 0; 37 | 38 | *((BITMAPINFOHEADER*)dest) = header; 39 | dest += sizeof(BITMAPINFOHEADER); 40 | ShotPixel *src = bitmap->pixels; 41 | for (int y = 0; y < bitmap->height; y++) 42 | for (int x = 0; x < bitmap->width; x++) 43 | { 44 | dest[y * stride + x * 3 + 0] = src->blue; 45 | dest[y * stride + x * 3 + 1] = src->green; 46 | dest[y * stride + x * 3 + 2] = src->red; 47 | src++; 48 | } 49 | 50 | GlobalUnlock(memory); 51 | 52 | if (!OpenClipboard(NULL)) 53 | { 54 | fprintf(stderr, "Error opening clipboard.\n"); 55 | goto end; 56 | } 57 | if (!EmptyClipboard()) 58 | { 59 | fprintf(stderr, "Error emptying clipboard.\n"); 60 | goto end; 61 | } 62 | if (!SetClipboardData(CF_DIB, memory)) 63 | { 64 | fprintf(stderr, "Error setting bitmap on clipboard.\n"); 65 | goto end; 66 | } 67 | 68 | memory = NULL; 69 | CloseClipboard(); 70 | ret = 0; 71 | 72 | end: 73 | if (memory) 74 | GlobalFree(memory); 75 | return ret; 76 | } 77 | -------------------------------------------------------------------------------- /src/bitmap_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bitmap.h" 3 | 4 | int bitmap_save_to_clipboard(ShotBitmap *bitmap) 5 | { 6 | fprintf(stderr, "Not implemented, sorry!\n"); 7 | return 1; 8 | } 9 | -------------------------------------------------------------------------------- /src/grab.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAB_H 2 | #define GRAB_H 3 | #include "region.h" 4 | #include "bitmap.h" 5 | 6 | extern ShotBitmap *grab_screenshot(ShotRegion *region); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/grab_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "grab.h" 4 | 5 | ShotBitmap *grab_screenshot(ShotRegion *region) 6 | { 7 | ShotBitmap *bitmap_out = NULL; 8 | char *data = NULL; 9 | assert(region); 10 | 11 | HDC hdc_screen = GetDC(0); 12 | HDC hdc = CreateCompatibleDC(hdc_screen); 13 | HBITMAP hbitmap = CreateCompatibleBitmap( 14 | hdc_screen, region->width, region->height); 15 | 16 | SelectObject(hdc, hbitmap); 17 | 18 | BitBlt(hdc, 0, 0, region->width, region->height, 19 | hdc_screen, region->x, region->y, SRCCOPY | CAPTUREBLT); 20 | 21 | BITMAP bitmap; 22 | GetObject(hbitmap, sizeof(BITMAP), &bitmap); 23 | assert(bitmap.bmBitsPixel == 32); 24 | 25 | BITMAPINFO bitmap_info; 26 | bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 27 | bitmap_info.bmiHeader.biWidth = bitmap.bmWidth; 28 | bitmap_info.bmiHeader.biHeight = bitmap.bmHeight; 29 | bitmap_info.bmiHeader.biPlanes = 1; 30 | bitmap_info.bmiHeader.biBitCount = 24; 31 | bitmap_info.bmiHeader.biCompression = BI_RGB; 32 | 33 | int scanline_size = region->width * 3; 34 | while (scanline_size % 4 != 0) 35 | scanline_size++; 36 | data = malloc(scanline_size * bitmap.bmHeight); 37 | if (!data) 38 | goto err; 39 | 40 | int result = GetDIBits(hdc, hbitmap, 0, bitmap.bmHeight, 41 | data, &bitmap_info, DIB_RGB_COLORS); 42 | if (!result) 43 | goto err; 44 | 45 | assert(bitmap_info.bmiHeader.biBitCount == 24); 46 | 47 | bitmap_out = bitmap_create(region->width, region->height); 48 | assert(bitmap_out); 49 | 50 | for (unsigned int y = 0; y < region->height; y++) 51 | { 52 | char *data_ptr = data + y * scanline_size; 53 | for (unsigned int x = 0; x < region->width; x++) 54 | { 55 | ShotPixel *pixel = bitmap_get_pixel( 56 | bitmap_out, x, region->height - 1 - y); 57 | pixel->blue = *data_ptr++; 58 | pixel->green = *data_ptr++; 59 | pixel->red = *data_ptr++; 60 | } 61 | } 62 | 63 | err: 64 | free(data); 65 | DeleteObject(hbitmap); 66 | 67 | return bitmap_out; 68 | } 69 | -------------------------------------------------------------------------------- /src/grab_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "grab.h" 5 | 6 | ShotBitmap *grab_screenshot(ShotRegion *region) 7 | { 8 | assert(region); 9 | 10 | Display *display = XOpenDisplay(NULL); 11 | assert(display); 12 | 13 | XImage *image = XGetImage( 14 | display, RootWindow(display, DefaultScreen(display)), 15 | region->x, region->y, region->width, region->height, 16 | AllPlanes, ZPixmap); 17 | assert(image); 18 | 19 | ShotBitmap *bitmap = bitmap_create(region->width, region->height); 20 | assert(bitmap); 21 | 22 | for (unsigned int y = 0; y < region->height; y++) 23 | { 24 | for (unsigned int x = 0; x < region->width; x++) 25 | { 26 | uint32_t color = XGetPixel(image, x, y); 27 | ShotPixel *pixel_out = bitmap_get_pixel(bitmap, x, y); 28 | assert(pixel_out); 29 | 30 | pixel_out->red = (color >> 16) & 0xFF; 31 | pixel_out->green = (color >> 8) & 0xFF; 32 | pixel_out->blue = color & 0xFF; 33 | } 34 | } 35 | 36 | XDestroyImage(image); 37 | XCloseDisplay(display); 38 | return bitmap; 39 | } 40 | -------------------------------------------------------------------------------- /src/monitor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "monitor.h" 4 | 5 | Monitor *monitor_create( 6 | int primary, 7 | int x, int y, 8 | unsigned int width, unsigned int height) 9 | { 10 | Monitor *monitor = malloc(sizeof(Monitor)); 11 | assert(monitor); 12 | monitor->primary = primary; 13 | monitor->x = x; 14 | monitor->y = y; 15 | monitor->width = width; 16 | monitor->height = height; 17 | return monitor; 18 | } 19 | 20 | void monitor_destroy(Monitor *monitor) 21 | { 22 | if (monitor) 23 | free(monitor); 24 | } 25 | -------------------------------------------------------------------------------- /src/monitor.h: -------------------------------------------------------------------------------- 1 | #ifndef MONITOR_H 2 | #define MONITOR_H 3 | #include 4 | 5 | typedef struct 6 | { 7 | int primary; 8 | int x, y; 9 | unsigned int width, height; 10 | } Monitor; 11 | 12 | Monitor *monitor_create( 13 | int primary, 14 | int x, int y, 15 | unsigned int width, unsigned int height); 16 | 17 | void monitor_destroy(Monitor *monitor); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/monitor_mgr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "monitor_mgr.h" 4 | 5 | void monitor_mgr_destroy(MonitorManager *mgr) 6 | { 7 | if (!mgr) 8 | return; 9 | if (mgr->monitors) 10 | { 11 | for (size_t i = 0; i < mgr->monitor_count; i++) 12 | monitor_destroy(mgr->monitors[i]); 13 | free(mgr->monitors); 14 | } 15 | free(mgr); 16 | } 17 | 18 | void monitor_mgr_add(MonitorManager *mgr, Monitor *monitor) 19 | { 20 | assert(monitor); 21 | Monitor **new_monitors = realloc( 22 | mgr->monitors, mgr->monitor_count * sizeof(Monitor**)); 23 | assert(new_monitors); 24 | mgr->monitors = new_monitors; 25 | mgr->monitors[mgr->monitor_count] = monitor; 26 | ++mgr->monitor_count; 27 | } 28 | -------------------------------------------------------------------------------- /src/monitor_mgr.h: -------------------------------------------------------------------------------- 1 | #ifndef MONITOR_MGR_H 2 | #define MONITOR_MGR_H 3 | #include "monitor.h" 4 | 5 | typedef struct 6 | { 7 | unsigned int monitor_count; 8 | Monitor **monitors; 9 | } MonitorManager; 10 | 11 | MonitorManager *monitor_mgr_create(); 12 | void monitor_mgr_destroy(MonitorManager *mgr); 13 | 14 | void monitor_mgr_add(MonitorManager *mgr, Monitor *monitor); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/monitor_mgr_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "monitor_mgr.h" 4 | 5 | #define UNUSED(x) (void)(x) 6 | 7 | static BOOL CALLBACK callback( 8 | HMONITOR hmonitor, HDC hdc, LPRECT rect, LPARAM data) 9 | { 10 | UNUSED(hmonitor); 11 | UNUSED(hdc); 12 | 13 | MonitorManager *mgr = (MonitorManager*)data; 14 | assert(mgr); 15 | 16 | Monitor *monitor = monitor_create( 17 | mgr->monitor_count == 0, 18 | rect->left, 19 | rect->top, 20 | rect->right - rect->left, 21 | rect->bottom - rect->top); 22 | assert(monitor); 23 | 24 | monitor_mgr_add(mgr, monitor); 25 | return TRUE; 26 | } 27 | 28 | MonitorManager *monitor_mgr_create() 29 | { 30 | MonitorManager *mgr = malloc(sizeof(MonitorManager)); 31 | mgr->monitor_count = 0; 32 | mgr->monitors = NULL; 33 | 34 | EnumDisplayMonitors(0, NULL, callback, (LPARAM)mgr); 35 | 36 | return mgr; 37 | } 38 | -------------------------------------------------------------------------------- /src/monitor_mgr_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "monitor_mgr.h" 7 | 8 | MonitorManager *monitor_mgr_create() 9 | { 10 | Display *display = XOpenDisplay(NULL); 11 | if (!display) 12 | { 13 | fprintf(stderr, "Cannot open display.\n"); 14 | return NULL; 15 | } 16 | 17 | MonitorManager *mgr = malloc(sizeof(MonitorManager)); 18 | mgr->monitor_count = 0; 19 | mgr->monitors = NULL; 20 | 21 | Window root = DefaultRootWindow(display); 22 | RROutput primary_output = XRRGetOutputPrimary(display, root); 23 | 24 | XRRScreenResources *screen = XRRGetScreenResources(display, root); 25 | assert(screen); 26 | 27 | for (int i = 0; i < screen->noutput; i++) 28 | { 29 | XRROutputInfo *output_info = XRRGetOutputInfo( 30 | display, screen, screen->outputs[i]); 31 | assert(output_info); 32 | 33 | if (output_info->connection == RR_Connected && output_info->crtc) 34 | { 35 | XRRCrtcInfo *crtc_info = XRRGetCrtcInfo( 36 | display, screen, output_info->crtc); 37 | assert(crtc_info); 38 | 39 | int primary = 0; 40 | for (int j = 0; j < crtc_info->noutput; j++) 41 | if (crtc_info->outputs[j] == primary_output) 42 | primary = 1; 43 | 44 | Monitor *monitor = monitor_create( 45 | primary, 46 | crtc_info->x, 47 | crtc_info->y, 48 | crtc_info->width, 49 | crtc_info->height); 50 | assert(monitor); 51 | 52 | monitor_mgr_add(mgr, monitor); 53 | 54 | XRRFreeCrtcInfo(crtc_info); 55 | } 56 | 57 | XRRFreeOutputInfo(output_info); 58 | } 59 | 60 | XRRFreeScreenResources(screen); 61 | XCloseDisplay(display); 62 | return mgr; 63 | } 64 | -------------------------------------------------------------------------------- /src/region.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_H 2 | #define REGION_H 3 | #include 4 | 5 | typedef struct 6 | { 7 | int x, y; 8 | unsigned int width, height; 9 | } ShotRegion; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/region_picker/active_monitor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "region_picker/errors.h" 3 | #include "region_picker/active_monitor.h" 4 | #include "region_picker/active_window.h" 5 | #include "region_picker/monitor.h" 6 | 7 | static int contains(const Monitor *monitor, const int x, const int y) 8 | { 9 | return x >= monitor->x 10 | && y >= monitor->y 11 | && x < monitor->x + (int)monitor->width 12 | && y < monitor->y + (int)monitor->height; 13 | } 14 | 15 | int update_region_from_active_monitor( 16 | ShotRegion *region, const MonitorManager *mgr) 17 | { 18 | ShotRegion window_region; 19 | assert(!update_region_from_active_window(&window_region)); 20 | const int gravity_x = window_region.x + window_region.width / 2; 21 | const int gravity_y = window_region.y + window_region.height / 2; 22 | for (size_t i = 0; i < mgr->monitor_count; i++) 23 | { 24 | if (contains(mgr->monitors[i], gravity_x, gravity_y)) 25 | return update_region_from_monitor(region, mgr->monitors[i]); 26 | } 27 | return ERR_OTHER; 28 | } 29 | -------------------------------------------------------------------------------- /src/region_picker/active_monitor.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_ACTIVE_MONITOR_H 2 | #define REGION_PICKER_ACTIVE_MONITOR_H 3 | #include "monitor_mgr.h" 4 | #include "region.h" 5 | 6 | int update_region_from_active_monitor( 7 | ShotRegion *region, const MonitorManager *mgr); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/region_picker/active_window.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_ACTIVE_WINDOW_H 2 | #define REGION_PICKER_ACTIVE_WINDOW_H 3 | #include "region.h" 4 | 5 | int update_region_from_active_window(ShotRegion *region); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/region_picker/active_window_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "region_picker/errors.h" 3 | #include "region_picker/active_window.h" 4 | 5 | int update_region_from_active_window(ShotRegion *region) 6 | { 7 | RECT rect; 8 | HWND hwnd = GetForegroundWindow(); 9 | GetWindowRect(hwnd, &rect); 10 | region->x = rect.left; 11 | region->y = rect.top; 12 | region->width = rect.right - rect.left; 13 | region->height = rect.bottom - rect.top; 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/region_picker/active_window_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "region_picker/errors.h" 5 | #include "region_picker/active_window.h" 6 | 7 | int update_region_from_active_window(ShotRegion *region) 8 | { 9 | int ret; 10 | Display *display = XOpenDisplay(NULL); 11 | assert(display); 12 | 13 | Window root = RootWindow(display, DefaultScreen(display)); 14 | Window window; 15 | 16 | { 17 | Atom key = XInternAtom(display, "_NET_ACTIVE_WINDOW", False); 18 | Atom value; 19 | int format; 20 | unsigned long extra, n; 21 | unsigned char *data; 22 | XGetWindowProperty( 23 | display, root, key, 0, ~0, False, AnyPropertyType, 24 | &value, &format, &n, &extra, &data); 25 | if (data) 26 | window = *(Window*)data; 27 | } 28 | 29 | if (window == None) 30 | { 31 | int revert_to; 32 | XGetInputFocus(display, &window, &revert_to); 33 | } 34 | 35 | if (window == None) 36 | { 37 | fprintf(stderr, "No focused window?\n"); 38 | ret = ERR_OTHER; 39 | goto end; 40 | } 41 | 42 | unsigned int border_size, depth; 43 | XGetGeometry(display, window, &root, 44 | ®ion->x, ®ion->y, ®ion->width, ®ion->height, 45 | &border_size, &depth); 46 | 47 | Window child; 48 | XTranslateCoordinates(display, window, root, 49 | 0, 0, ®ion->x, ®ion->y, &child); 50 | 51 | region->x -= border_size; 52 | region->y -= border_size; 53 | region->width += 2 * border_size; 54 | region->height += 2 * border_size; 55 | 56 | ret = 0; 57 | 58 | end: 59 | XCloseDisplay(display); 60 | return ret; 61 | } 62 | -------------------------------------------------------------------------------- /src/region_picker/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_ERRORS_H 2 | #define REGION_PICKER_ERRORS_H 3 | 4 | #define ERR_INVALID_ARGUMENT 1 5 | #define ERR_NOT_IMPLEMENTED 2 6 | #define ERR_CANCELED 3 7 | #define ERR_OTHER 4 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/region_picker/interactive.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_INTERACTIVE_H 2 | #define REGION_PICKER_INTERACTIVE_H 3 | #include "region.h" 4 | 5 | int update_region_interactively( 6 | ShotRegion *region, const ShotRegion *working_area); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /src/region_picker/interactive_common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "region_picker/interactive_common.h" 4 | 5 | #define MIN_SIZE 30 6 | 7 | static void _min(int *a, int b) { if (*a > b) *a = b; } 8 | static void _max(int *a, int b) { if (*a < b) *a = b; } 9 | 10 | void ip_handle_key_down(ShotInteractivePicker *ip, int key) 11 | { 12 | assert(ip); 13 | int delta[2] = { 0, 0 }; 14 | 15 | if (key == IP_KEY_LSHIFT || key == IP_KEY_RSHIFT) 16 | { 17 | ip->keyboard_state.shift = 1; 18 | } 19 | else if (key == IP_KEY_LCONTROL || key == IP_KEY_RCONTROL) 20 | { 21 | ip->keyboard_state.ctrl = 1; 22 | } 23 | else if (key == IP_KEY_ESCAPE || key == IP_KEY_Q) 24 | { 25 | ip->canceled = 1; 26 | } 27 | else if (key == IP_KEY_RETURN) 28 | { 29 | ip->canceled = -1; 30 | } 31 | else if (key == IP_KEY_LEFT || key == IP_KEY_H) 32 | { 33 | delta[0] = -1; 34 | } 35 | else if (key == IP_KEY_RIGHT || key == IP_KEY_L) 36 | { 37 | delta[0] = 1; 38 | } 39 | else if (key == IP_KEY_DOWN || key == IP_KEY_J) 40 | { 41 | delta[1] = 1; 42 | } 43 | else if (key == IP_KEY_UP || key == IP_KEY_K) 44 | { 45 | delta[1] = -1; 46 | } 47 | 48 | if (delta[0] || delta[1]) 49 | { 50 | ip_pull_window_rect(ip); 51 | 52 | const struct Rectangle *wa = &ip->workarea; 53 | struct Rectangle *r = &ip->rect; 54 | 55 | if (!ip->keyboard_state.shift) 56 | { 57 | for (int i = 0; i < 2; i++) 58 | delta[i] *= 25; 59 | } 60 | 61 | if (ip->keyboard_state.ctrl) 62 | { 63 | for (int i = 0; i < 2; i++) 64 | { 65 | r->size[i] += delta[i]; 66 | _max(&r->size[i], MIN_SIZE); 67 | _min(&r->size[i], wa->pos[i] + wa->size[i] - r->pos[i]); 68 | } 69 | } 70 | else 71 | { 72 | for (int i = 0; i < 2; i++) 73 | { 74 | r->pos[i] += delta[i]; 75 | _max(&r->pos[i], wa->pos[i]); 76 | _min(&r->pos[i], wa->pos[i] + wa->size[i] - r->size[i]); 77 | } 78 | } 79 | 80 | ip_sync_window_rect(ip); 81 | ip_update_text(ip); 82 | } 83 | } 84 | 85 | void ip_handle_key_up(ShotInteractivePicker *ip, int key) 86 | { 87 | assert(ip); 88 | if (key == IP_KEY_LSHIFT || key == IP_KEY_RSHIFT) 89 | { 90 | ip->keyboard_state.shift = 0; 91 | } 92 | else if (key == IP_KEY_LCONTROL || key == IP_KEY_RCONTROL) 93 | { 94 | ip->keyboard_state.ctrl = 0; 95 | } 96 | } 97 | 98 | void ip_handle_mouse_down( 99 | ShotInteractivePicker *ip, int button, int mouse_x, int mouse_y) 100 | { 101 | assert(ip); 102 | ip_pull_window_rect(ip); 103 | ip->last_mouse_pos[0] = mouse_x; 104 | ip->last_mouse_pos[1] = mouse_y; 105 | if (button == IP_MOUSE_LEFT) 106 | { 107 | ip->window_state.resizing[0] = 0; 108 | ip->window_state.resizing[1] = 0; 109 | ip->window_state.moving = 1; 110 | } 111 | else if (button == IP_MOUSE_RIGHT) 112 | { 113 | ip->window_state.moving = 0; 114 | int mouse_pos[2] = { mouse_x, mouse_y }; 115 | for (int i = 0; i < 2; i++) 116 | { 117 | ip->window_state.resizing[i] = 0; 118 | if (mouse_pos[i] < ip->rect.size[i] / 3) 119 | ip->window_state.resizing[i] = -1; 120 | else if (mouse_pos[i] > ip->rect.size[i] * 2/3) 121 | ip->window_state.resizing[i] = 1; 122 | } 123 | } 124 | } 125 | 126 | void ip_handle_mouse_up(ShotInteractivePicker *ip) 127 | { 128 | assert(ip); 129 | ip->window_state.resizing[0] = 0; 130 | ip->window_state.resizing[1] = 0; 131 | ip->window_state.moving = 0; 132 | } 133 | 134 | void ip_handle_mouse_move( 135 | ShotInteractivePicker *ip, int mouse_x, int mouse_y) 136 | { 137 | assert(ip); 138 | 139 | const struct Rectangle *wa = &ip->workarea; 140 | struct Rectangle *r = &ip->rect; 141 | int mouse_pos[2] = { mouse_x, mouse_y }; 142 | int old_pos[2] = { r->pos[0], r->pos[1] }; 143 | 144 | if (ip->window_state.moving) 145 | { 146 | for (int i = 0; i < 2; i++) 147 | { 148 | r->pos[i] += mouse_pos[i] - ip->last_mouse_pos[i]; 149 | _max(&r->pos[i], wa->pos[i]); 150 | _min(&r->pos[i], wa->pos[i] + wa->size[i] - r->size[i]); 151 | } 152 | ip_sync_window_rect(ip); 153 | ip_update_text(ip); 154 | } 155 | 156 | else if (ip->window_state.resizing[0] || ip->window_state.resizing[1]) 157 | { 158 | int mouse_delta[2] = { 159 | mouse_x - ip->last_mouse_pos[0], 160 | mouse_y - ip->last_mouse_pos[1] 161 | }; 162 | 163 | for (int i = 0; i < 2; i++) 164 | { 165 | if (ip->window_state.resizing[i] == -1) 166 | { 167 | int npos = r->pos[i] + mouse_delta[i]; 168 | _max(&npos, wa->pos[i]); 169 | _min(&npos, wa->pos[i] + wa->size[i] - r->size[i]); 170 | int nsize = r->size[i] + old_pos[i] - npos; 171 | _max(&nsize, MIN_SIZE); 172 | _min(&nsize, wa->pos[i] + wa->size[i] - r->pos[i]); 173 | npos = r->size[i] + old_pos[i] - nsize; 174 | r->size[i] = nsize; 175 | r->pos[i] = npos; 176 | } 177 | else if (ip->window_state.resizing[i] == 1) 178 | { 179 | r->size[i] += mouse_delta[i]; 180 | _max(&r->size[i], MIN_SIZE); 181 | _min(&r->size[i], wa->pos[i] + wa->size[i] - r->pos[i]); 182 | } 183 | } 184 | 185 | ip_sync_window_rect(ip); 186 | ip_update_text(ip); 187 | } 188 | 189 | for (int i = 0; i < 2; i++) 190 | ip->last_mouse_pos[i] = mouse_pos[i] + old_pos[i] - r->pos[i]; 191 | } 192 | 193 | void ip_init( 194 | ShotInteractivePicker *ip, ShotRegion *region, const ShotRegion *workarea) 195 | { 196 | ip->workarea.pos[0] = workarea->x; 197 | ip->workarea.pos[1] = workarea->y; 198 | ip->workarea.size[0] = workarea->width; 199 | ip->workarea.size[1] = workarea->height; 200 | ip->keyboard_state.ctrl = 0; 201 | ip->keyboard_state.shift = 0; 202 | ip->window_state.moving = 0; 203 | ip->window_state.resizing[0] = 0; 204 | ip->window_state.resizing[1] = 0; 205 | ip->canceled = 0; 206 | 207 | const struct Rectangle *wa = &ip->workarea; 208 | struct Rectangle *r = &ip->rect; 209 | r->pos[0] = region->x; 210 | r->pos[1] = region->y; 211 | r->size[0] = region->width; 212 | r->size[1] = region->height; 213 | for (int i = 0; i < 2; i++) 214 | { 215 | if (r->pos[i] < wa->pos[i]) 216 | { 217 | r->size[i] = r->size[i] - wa->pos[i] + r->pos[i]; 218 | r->pos[i] = wa->pos[i]; 219 | } 220 | _max(&r->size[i], MIN_SIZE); 221 | _min(&r->size[i], wa->pos[i] + wa->size[i] - r->pos[i]); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/region_picker/interactive_common.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIVE_COMMON_H 2 | #define INTERACTIVE_COMMON_H 3 | #include "region.h" 4 | 5 | extern int IP_MOUSE_LEFT; 6 | extern int IP_MOUSE_RIGHT; 7 | extern int IP_KEY_LSHIFT; 8 | extern int IP_KEY_RSHIFT; 9 | extern int IP_KEY_LCONTROL; 10 | extern int IP_KEY_RCONTROL; 11 | extern int IP_KEY_ESCAPE; 12 | extern int IP_KEY_Q; 13 | extern int IP_KEY_H; 14 | extern int IP_KEY_J; 15 | extern int IP_KEY_K; 16 | extern int IP_KEY_L; 17 | extern int IP_KEY_LEFT; 18 | extern int IP_KEY_DOWN; 19 | extern int IP_KEY_UP; 20 | extern int IP_KEY_RIGHT; 21 | extern int IP_KEY_RETURN; 22 | 23 | struct private; 24 | 25 | struct Rectangle 26 | { 27 | int pos[2]; 28 | int size[2]; 29 | }; 30 | 31 | typedef struct 32 | { 33 | struct private *priv; 34 | 35 | struct 36 | { 37 | int ctrl; 38 | int shift; 39 | } keyboard_state; 40 | 41 | struct 42 | { 43 | int moving; 44 | int resizing[2]; 45 | } window_state; 46 | 47 | int last_mouse_pos[2]; 48 | 49 | struct Rectangle rect; 50 | struct Rectangle workarea; 51 | 52 | int canceled; 53 | } ShotInteractivePicker; 54 | 55 | void ip_init( 56 | ShotInteractivePicker *ip, ShotRegion *region, const ShotRegion *workarea); 57 | void ip_handle_key_down(ShotInteractivePicker *ip, int key); 58 | void ip_handle_key_up(ShotInteractivePicker *ip, int key); 59 | void ip_handle_mouse_down( 60 | ShotInteractivePicker *p, int button, int mouse_x, int mouse_y); 61 | void ip_handle_mouse_up(ShotInteractivePicker *ip); 62 | void ip_handle_mouse_move( 63 | ShotInteractivePicker *ip, int mouse_x, int mouse_y); 64 | 65 | void ip_pull_window_rect(ShotInteractivePicker *ip); 66 | void ip_sync_window_rect(ShotInteractivePicker *ip); 67 | void ip_update_text(ShotInteractivePicker *ip); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /src/region_picker/interactive_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "region_picker/errors.h" 5 | #include "region_picker/interactive.h" 6 | #include "region_picker/interactive_common.h" 7 | 8 | int IP_MOUSE_LEFT = VK_LBUTTON; 9 | int IP_MOUSE_RIGHT = VK_RBUTTON; 10 | int IP_KEY_LSHIFT = VK_SHIFT; 11 | int IP_KEY_RSHIFT = VK_SHIFT; 12 | int IP_KEY_LCONTROL = VK_CONTROL; 13 | int IP_KEY_RCONTROL = VK_CONTROL; 14 | int IP_KEY_ESCAPE = VK_ESCAPE; 15 | int IP_KEY_Q = 'Q'; 16 | int IP_KEY_H = 'H'; 17 | int IP_KEY_J = 'J'; 18 | int IP_KEY_K = 'K'; 19 | int IP_KEY_L = 'L'; 20 | int IP_KEY_LEFT = VK_LEFT; 21 | int IP_KEY_DOWN = VK_DOWN; 22 | int IP_KEY_UP = VK_UP; 23 | int IP_KEY_RIGHT = VK_RIGHT; 24 | int IP_KEY_RETURN = VK_RETURN; 25 | 26 | struct private 27 | { 28 | int mouse_captured; 29 | HWND hwnd; 30 | ShotInteractivePicker ip; 31 | unsigned int border_size; 32 | }; 33 | 34 | void ip_pull_window_rect(ShotInteractivePicker *ip) 35 | { 36 | assert(ip); 37 | 38 | struct private *priv = ip->priv; 39 | RECT rect; 40 | GetWindowRect(priv->hwnd, &rect); 41 | ip->rect.pos[0] = rect.left + priv->border_size; 42 | ip->rect.pos[1] = rect.top + priv->border_size; 43 | ip->rect.size[0] = rect.right - rect.left - 2 * priv->border_size; 44 | ip->rect.size[1] = rect.bottom - rect.top - 2 * priv->border_size; 45 | } 46 | 47 | void ip_sync_window_rect(ShotInteractivePicker *ip) 48 | { 49 | assert(ip); 50 | struct private *priv = ip->priv; 51 | MoveWindow( 52 | priv->hwnd, 53 | ip->rect.pos[0] - priv->border_size, 54 | ip->rect.pos[1] - priv->border_size, 55 | ip->rect.size[0] + 2 * priv->border_size, 56 | ip->rect.size[1] + 2 * priv->border_size, 57 | TRUE); 58 | } 59 | 60 | void ip_update_text(ShotInteractivePicker *ip) 61 | { 62 | struct private *priv = ip->priv; 63 | RECT rect; 64 | GetClientRect(priv->hwnd, &rect); 65 | InvalidateRect(priv->hwnd, &rect, TRUE); 66 | } 67 | 68 | static void draw_text(struct private *p) 69 | { 70 | char msg[100]; 71 | sprintf(msg, 72 | "%dx%d+%d+%d", 73 | p->ip.rect.size[0], p->ip.rect.size[1], 74 | p->ip.rect.pos[0], p->ip.rect.pos[1]); 75 | 76 | RECT rect; 77 | GetClientRect(p->hwnd, &rect); 78 | HDC wdc = GetWindowDC(p->hwnd); 79 | SetTextColor(wdc, 0x00000000); 80 | SetBkMode(wdc,TRANSPARENT); 81 | DrawText(wdc, msg, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); 82 | DeleteDC(wdc); 83 | } 84 | 85 | static LRESULT CALLBACK wnd_proc( 86 | HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) 87 | { 88 | struct private *p = GetWindowLongPtr(hwnd, GWLP_USERDATA); 89 | 90 | switch (msg) 91 | { 92 | case WM_ERASEBKGND: 93 | { 94 | RECT rect; 95 | GetClientRect(hwnd, &rect); 96 | FillRect((HDC)wparam, &rect, CreateSolidBrush(RGB(255, 200, 0))); 97 | break; 98 | } 99 | 100 | case WM_PAINT: 101 | draw_text(p); 102 | break; 103 | 104 | case WM_CLOSE: 105 | DestroyWindow(hwnd); 106 | break; 107 | 108 | case WM_DESTROY: 109 | PostQuitMessage(0); 110 | break; 111 | 112 | case WM_KEYDOWN: 113 | ip_handle_key_down(&p->ip, wparam); 114 | break; 115 | 116 | case WM_KEYUP: 117 | ip_handle_key_up(&p->ip, wparam); 118 | break; 119 | 120 | case WM_LBUTTONDOWN: 121 | ip_handle_mouse_down( 122 | &p->ip, IP_MOUSE_LEFT, LOWORD(lparam), HIWORD(lparam)); 123 | p->mouse_captured = 1; 124 | SetCapture(p->hwnd); 125 | break; 126 | 127 | case WM_RBUTTONDOWN: 128 | ip_handle_mouse_down( 129 | &p->ip, IP_MOUSE_RIGHT, LOWORD(lparam), HIWORD(lparam)); 130 | p->mouse_captured = 1; 131 | SetCapture(p->hwnd); 132 | break; 133 | 134 | case WM_LBUTTONUP: 135 | case WM_RBUTTONUP: 136 | ip_handle_mouse_up(&p->ip); 137 | if (p->mouse_captured) 138 | { 139 | ReleaseCapture(); 140 | p->mouse_captured = 0; 141 | } 142 | break; 143 | 144 | case WM_MOUSEMOVE: 145 | ip_handle_mouse_move( 146 | &p->ip, 147 | (short)LOWORD(lparam), 148 | (short)HIWORD(lparam)); 149 | break; 150 | 151 | default: 152 | return DefWindowProc(hwnd, msg, wparam, lparam); 153 | } 154 | 155 | if (p->ip.canceled) 156 | { 157 | ShowWindow(p->hwnd, SW_HIDE); 158 | PostQuitMessage(0); 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | static int register_class( 165 | const char *class_name, 166 | LRESULT CALLBACK (*wnd_proc)(HWND, UINT, WPARAM, LPARAM)) 167 | { 168 | assert(class_name); 169 | assert(wnd_proc); 170 | WNDCLASSEX wc = { 171 | .cbSize = sizeof(WNDCLASSEX), 172 | .style = 0, 173 | .lpfnWndProc = wnd_proc, 174 | .cbClsExtra = 0, 175 | .cbWndExtra = 0, 176 | .hInstance = 0, 177 | .hIcon = LoadIcon(NULL, IDI_APPLICATION), 178 | .hCursor = LoadCursor(NULL, IDC_ARROW), 179 | .hbrBackground = 0, 180 | .lpszMenuName = NULL, 181 | .lpszClassName = class_name, 182 | .hIconSm = LoadIcon(NULL, IDI_APPLICATION), 183 | }; 184 | if (!RegisterClassEx(&wc)) 185 | { 186 | fprintf(stderr, "Failed to register window class\n"); 187 | return -1; 188 | } 189 | return 0; 190 | } 191 | 192 | static int init_window( 193 | const char *class_name, const char *title, struct private *p) 194 | { 195 | assert(class_name); 196 | assert(title); 197 | assert(p); 198 | 199 | p->hwnd = CreateWindowEx( 200 | WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_APPWINDOW, 201 | class_name, 202 | title, 203 | WS_POPUPWINDOW, 204 | p->ip.rect.pos[0] - p->border_size, 205 | p->ip.rect.pos[1] - p->border_size, 206 | p->ip.rect.size[0] + 2 * p->border_size, 207 | p->ip.rect.size[1] + 2 * p->border_size, 208 | NULL, NULL, 0, NULL); 209 | 210 | if (!p->hwnd) 211 | { 212 | fprintf(stderr, "Failed to create a window\n"); 213 | return -1; 214 | } 215 | 216 | SetLayeredWindowAttributes(p->hwnd, 0, 0.5f * 255, LWA_ALPHA); 217 | SetWindowLongPtr(p->hwnd, GWLP_USERDATA, (long)p); 218 | ShowWindow(p->hwnd, SW_SHOWNORMAL); 219 | UpdateWindow(p->hwnd); 220 | return 0; 221 | } 222 | 223 | static void run_event_loop(struct private *p) 224 | { 225 | MSG Msg; 226 | while (!p->ip.canceled && GetMessage(&Msg, NULL, 0, 0) > 0) 227 | { 228 | TranslateMessage(&Msg); 229 | DispatchMessage(&Msg); 230 | } 231 | } 232 | 233 | int update_region_interactively(ShotRegion *region, const ShotRegion *workarea) 234 | { 235 | const char *class_name = "shot"; 236 | if (register_class(class_name, &wnd_proc)) 237 | return ERR_OTHER; 238 | 239 | struct private p = { 240 | .mouse_captured = 0, 241 | .border_size = 1, 242 | }; 243 | ip_init(&p.ip, region, workarea); 244 | p.ip.priv = &p; 245 | 246 | if (init_window(class_name, "shot", &p)) 247 | return ERR_OTHER; 248 | 249 | run_event_loop(&p); 250 | 251 | if (p.ip.canceled == 1) 252 | return ERR_CANCELED; 253 | 254 | //wait for window close, vsync and other blows and whistles 255 | Sleep(100); 256 | 257 | region->x = p.ip.rect.pos[0]; 258 | region->y = p.ip.rect.pos[1]; 259 | region->width = p.ip.rect.size[0]; 260 | region->height = p.ip.rect.size[1]; 261 | return 0; 262 | } 263 | -------------------------------------------------------------------------------- /src/region_picker/interactive_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "region_picker/errors.h" 9 | #include "region_picker/interactive.h" 10 | #include "region_picker/interactive_common.h" 11 | 12 | int IP_MOUSE_LEFT = Button1; 13 | int IP_MOUSE_RIGHT = Button3; 14 | int IP_KEY_LSHIFT = XK_Shift_L; 15 | int IP_KEY_RSHIFT = XK_Shift_R; 16 | int IP_KEY_LCONTROL = XK_Control_L; 17 | int IP_KEY_RCONTROL = XK_Control_R; 18 | int IP_KEY_ESCAPE = XK_Escape; 19 | int IP_KEY_Q = XK_q; 20 | int IP_KEY_H = XK_h; 21 | int IP_KEY_J = XK_j; 22 | int IP_KEY_K = XK_k; 23 | int IP_KEY_L = XK_l; 24 | int IP_KEY_LEFT = XK_Left; 25 | int IP_KEY_DOWN = XK_Down; 26 | int IP_KEY_UP = XK_Up; 27 | int IP_KEY_RIGHT = XK_Right; 28 | int IP_KEY_RETURN = XK_Return; 29 | 30 | struct private 31 | { 32 | Display *display; 33 | Window window; 34 | Window root; 35 | GC gc; 36 | Colormap colormap; 37 | 38 | ShotInteractivePicker ip; 39 | unsigned int border_size; 40 | }; 41 | 42 | void ip_pull_window_rect(ShotInteractivePicker *ip) 43 | { 44 | assert(ip); 45 | 46 | unsigned int depth; 47 | struct private *priv = ip->priv; 48 | 49 | XGetGeometry( 50 | priv->display, priv->window, &priv->root, 51 | &ip->rect.pos[0], 52 | &ip->rect.pos[1], 53 | (unsigned int*)&ip->rect.size[0], 54 | (unsigned int*)&ip->rect.size[1], 55 | &priv->border_size, &depth); 56 | 57 | Window child; 58 | XTranslateCoordinates( 59 | priv->display, priv->window, priv->root, 60 | 0, 0, &ip->rect.pos[0], &ip->rect.pos[1], &child); 61 | } 62 | 63 | void ip_sync_window_rect(ShotInteractivePicker *ip) 64 | { 65 | assert(ip); 66 | 67 | struct private *priv = ip->priv; 68 | XMoveWindow( 69 | priv->display, 70 | priv->window, 71 | ip->rect.pos[0] - priv->border_size, 72 | ip->rect.pos[1] - priv->border_size); 73 | XResizeWindow( 74 | priv->display, 75 | priv->window, 76 | ip->rect.size[0], 77 | ip->rect.size[1]); 78 | } 79 | 80 | void ip_update_text(ShotInteractivePicker *ip) 81 | { 82 | assert(ip); 83 | 84 | struct private *priv = ip->priv; 85 | XClearWindow(priv->display, priv->window); 86 | XFlush(priv->display); 87 | 88 | char msg[100]; 89 | sprintf(msg, 90 | "%dx%d+%d+%d", 91 | ip->rect.size[0], ip->rect.size[1], 92 | ip->rect.pos[0], ip->rect.pos[1]); 93 | 94 | const int char_width = 6; 95 | const int char_height = 9; 96 | int text_x = (ip->rect.size[0] - strlen(msg) * char_width) / 2; 97 | int text_y = (ip->rect.size[1] - char_height) / 2 + char_height; 98 | XSetForeground(priv->display, priv->gc, 0xff000000); 99 | XDrawString(priv->display, priv->window, priv->gc, 100 | text_x, text_y, msg, strlen(msg)); 101 | } 102 | 103 | static void run_event_loop(struct private *p) 104 | { 105 | assert(p); 106 | XSelectInput(p->display, p->window, ExposureMask 107 | | KeyPressMask | KeyReleaseMask 108 | | ButtonPress | ButtonReleaseMask | PointerMotionMask); 109 | 110 | static int border_fixed = 0; 111 | 112 | while (!p->ip.canceled) 113 | { 114 | XEvent e; 115 | XNextEvent(p->display, &e); 116 | 117 | switch (e.type) 118 | { 119 | case Expose: 120 | //fix offset due to own border on first draw 121 | if (!border_fixed) 122 | { 123 | XWindowAttributes attrs; 124 | XGetWindowAttributes( 125 | p->display, p->window, &attrs); 126 | p->border_size = attrs.border_width; 127 | ip_sync_window_rect(&p->ip); 128 | border_fixed = 1; 129 | } 130 | ip_update_text(&p->ip); 131 | break; 132 | 133 | case KeyPress: 134 | ip_handle_key_down(&p->ip, XLookupKeysym(&e.xkey, 0)); 135 | break; 136 | 137 | case KeyRelease: 138 | ip_handle_key_up(&p->ip, XLookupKeysym(&e.xkey, 0)); 139 | break; 140 | 141 | case ButtonPress: 142 | ip_handle_mouse_down( 143 | &p->ip, e.xbutton.button, e.xbutton.x, e.xbutton.y); 144 | break; 145 | 146 | case ButtonRelease: 147 | ip_handle_mouse_up(&p->ip); 148 | break; 149 | 150 | case MotionNotify: 151 | ip_handle_mouse_move(&p->ip, e.xbutton.x, e.xbutton.y); 152 | break; 153 | } 154 | } 155 | } 156 | 157 | static int init_window(struct private *p) 158 | { 159 | assert(p); 160 | int screen = DefaultScreen(p->display); 161 | XVisualInfo visual; 162 | if (!XMatchVisualInfo(p->display, screen, 32, TrueColor, &visual)) 163 | { 164 | fprintf(stderr, "Couldn\'t find matching visual\n"); 165 | return -1; 166 | } 167 | 168 | p->root = RootWindow(p->display, screen); 169 | 170 | p->colormap = XCreateColormap( 171 | p->display, p->root, visual.visual, AllocNone); 172 | XSetWindowAttributes attrs; 173 | attrs.colormap = p->colormap; 174 | attrs.override_redirect = True; 175 | attrs.background_pixel = 0x80ff8000; 176 | attrs.border_pixel = 0; 177 | 178 | p->window = XCreateWindow( 179 | p->display, 180 | p->root, 181 | p->ip.rect.pos[0], 182 | p->ip.rect.pos[1], 183 | p->ip.rect.size[0], 184 | p->ip.rect.size[1], 185 | 0, visual.depth, InputOutput, visual.visual, 186 | CWBackPixel | CWColormap | CWBorderPixel, &attrs); 187 | 188 | if (!p->window) 189 | { 190 | fprintf(stderr, "Couldn\'t create window\n"); 191 | return -1; 192 | } 193 | 194 | XStoreName(p->display, p->window, "shot"); 195 | 196 | // make floating 197 | { 198 | Atom type = XInternAtom(p->display, "_NET_WM_WINDOW_TYPE", False); 199 | Atom value = XInternAtom( 200 | p->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); 201 | XChangeProperty( 202 | p->display, p->window, type, XA_ATOM, 32, 203 | PropModeReplace, (unsigned char*)&value, 1); 204 | } 205 | 206 | // disable borders 207 | { 208 | Atom type = XInternAtom(p->display, "_MOTIF_WM_HINTS", False); 209 | struct MotifHints 210 | { 211 | unsigned long flags; 212 | unsigned long functions; 213 | unsigned long decorations; 214 | long inputMode; 215 | unsigned long status; 216 | }; 217 | struct MotifHints value = { 218 | .flags = 2, 219 | .decorations = 0, 220 | }; 221 | XChangeProperty(p->display, p->window, type, type, 32, 222 | PropModeReplace, (unsigned char*)&value, 5); 223 | } 224 | 225 | p->gc = XCreateGC(p->display, p->window, 0L, NULL); 226 | 227 | XSetWindowColormap(p->display, p->window, attrs.colormap); 228 | XMapWindow(p->display, p->window); 229 | return 0; 230 | } 231 | 232 | static void destroy_window(struct private *p) 233 | { 234 | assert(p); 235 | XFreeColormap(p->display, p->colormap); 236 | XFreeGC(p->display, p->gc); 237 | XDestroyWindow(p->display, p->window); 238 | XSync(p->display, True); 239 | XCloseDisplay(p->display); 240 | } 241 | 242 | int update_region_interactively(ShotRegion *region, const ShotRegion *workarea) 243 | { 244 | Display *display = XOpenDisplay(NULL); 245 | assert(display); 246 | 247 | struct private p = { 248 | .display = display, 249 | }; 250 | ip_init(&p.ip, region, workarea); 251 | p.ip.priv = &p; 252 | 253 | if (init_window(&p)) 254 | return ERR_OTHER; 255 | run_event_loop(&p); 256 | destroy_window(&p); 257 | 258 | if (p.ip.canceled == 1) 259 | return ERR_CANCELED; 260 | 261 | // wait til window is actually hidden, vsync redraws things, and 262 | // hypothetical compiz or other eyecandy draw their fadeout effects 263 | struct timespec tim, tim2; 264 | tim.tv_sec = 0; 265 | tim.tv_nsec = 5e8; 266 | nanosleep(&tim , &tim2); 267 | 268 | region->x = p.ip.rect.pos[0]; 269 | region->y = p.ip.rect.pos[1]; 270 | region->width = p.ip.rect.size[0]; 271 | region->height = p.ip.rect.size[1]; 272 | return 0; 273 | } 274 | -------------------------------------------------------------------------------- /src/region_picker/monitor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "region_picker/errors.h" 3 | #include "region_picker/monitor.h" 4 | 5 | int update_region_from_all_monitors( 6 | ShotRegion *region, const MonitorManager *mgr) 7 | { 8 | assert(region); 9 | assert(mgr); 10 | int min_x = mgr->monitors[0]->x; 11 | int min_y = mgr->monitors[0]->y; 12 | for (unsigned int i = 1; i < mgr->monitor_count; i++) 13 | { 14 | if (!i || mgr->monitors[i]->x < min_x) min_x = mgr->monitors[i]->x; 15 | if (!i || mgr->monitors[i]->y < min_y) min_y = mgr->monitors[i]->y; 16 | } 17 | 18 | for (unsigned int i = 0; i < mgr->monitor_count; i++) 19 | { 20 | ShotRegion pr = { 21 | .x = mgr->monitors[i]->x, 22 | .y = mgr->monitors[i]->y, 23 | .width = mgr->monitors[i]->width + mgr->monitors[i]->x - min_x, 24 | .height = mgr->monitors[i]->height + mgr->monitors[i]->y - min_y, 25 | }; 26 | if (!i || pr.x < region->x) region->x = pr.x; 27 | if (!i || pr.y < region->y) region->y = pr.y; 28 | if (!i || pr.width > region->width) region->width = pr.width; 29 | if (!i || pr.height > region->height) region->height = pr.height; 30 | } 31 | return 0; 32 | } 33 | 34 | int update_region_from_monitor(ShotRegion *region, const Monitor *monitor) 35 | { 36 | assert(region); 37 | assert(monitor); 38 | region->x = monitor->x; 39 | region->y = monitor->y; 40 | region->width = monitor->width; 41 | region->height = monitor->height; 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/region_picker/monitor.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_MONITOR_H 2 | #define REGION_PICKER_MONITOR_H 3 | #include "monitor_mgr.h" 4 | #include "region.h" 5 | 6 | int update_region_from_all_monitors( 7 | ShotRegion *region, const MonitorManager *mgr); 8 | 9 | int update_region_from_monitor( 10 | ShotRegion *region, const Monitor *monitor); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/region_picker/string.c: -------------------------------------------------------------------------------- 1 | #include "region_picker/errors.h" 2 | #include "region.h" 3 | 4 | static int int_from_string(const char **str) 5 | { 6 | int ret = 0; 7 | while (**str >= '0' && **str <= '9') 8 | { 9 | ret *= 10; 10 | ret += **str - '0'; 11 | (*str)++; 12 | } 13 | return ret; 14 | } 15 | 16 | int update_region_from_string(ShotRegion *r, const char *str) 17 | { 18 | ShotRegion tmp; 19 | tmp.width = 100; 20 | tmp.height = 100; 21 | tmp.x = 0; 22 | tmp.y = 0; 23 | 24 | const char *ptr = str; 25 | tmp.width = int_from_string(&ptr); 26 | if (*ptr != 'x' && *ptr != 'X') 27 | return ERR_INVALID_ARGUMENT; 28 | ptr++; 29 | 30 | tmp.height = int_from_string(&ptr); 31 | if (*ptr != '\0') 32 | { 33 | if (*ptr != '+') 34 | return ERR_INVALID_ARGUMENT; 35 | ptr++; 36 | tmp.x = int_from_string(&ptr); 37 | if (*ptr != '+') 38 | return ERR_INVALID_ARGUMENT; 39 | ptr++; 40 | tmp.y = int_from_string(&ptr); 41 | if (*ptr != '\0') 42 | return ERR_INVALID_ARGUMENT; 43 | } 44 | 45 | r->x = tmp.x; 46 | r->y = tmp.y; 47 | r->width = tmp.width; 48 | r->height = tmp.height; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /src/region_picker/string.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_STRING_H 2 | #define REGION_PICKER_STRING_H 3 | #include "region.h" 4 | 5 | int update_region_from_string(ShotRegion *r, const char *str); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/region_picker/window.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_PICKER_WINDOW_H 2 | #define REGION_PICKER_WINDOW_H 3 | #include "region.h" 4 | 5 | int update_region_from_window(ShotRegion *region, const long int window_id); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/region_picker/window_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "region_picker/errors.h" 4 | #include "region_picker/window.h" 5 | 6 | int update_region_from_window(ShotRegion *region, const long int window_id) 7 | { 8 | HWND hwnd = (HWND)window_id; 9 | if (!IsWindow(hwnd)) 10 | { 11 | fprintf(stderr, "Window %ld does not exist?\n", window_id); 12 | return ERR_OTHER; 13 | } 14 | RECT rect; 15 | GetWindowRect(hwnd, &rect); 16 | region->x = rect.left; 17 | region->y = rect.top; 18 | region->width = rect.right - rect.left; 19 | region->height = rect.bottom - rect.top; 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/region_picker/window_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "region_picker/errors.h" 5 | #include "region_picker/window.h" 6 | 7 | static int window_found = 1; 8 | 9 | static int error_handler(Display *display, XErrorEvent *event) 10 | { 11 | (void)display; 12 | if (event->error_code == BadWindow) 13 | window_found = 0; 14 | return 0; 15 | } 16 | 17 | int update_region_from_window(ShotRegion *region, const long int window_id) 18 | { 19 | XSetErrorHandler(error_handler); 20 | window_found = 1; 21 | 22 | int ret; 23 | Display *display = XOpenDisplay(NULL); 24 | assert(display); 25 | 26 | Window root = RootWindow(display, DefaultScreen(display)); 27 | Window window = window_id; 28 | 29 | XWindowAttributes xwa; 30 | XGetWindowAttributes(display, window, &xwa); 31 | 32 | if (!window_found) 33 | { 34 | fprintf(stderr, "Window %ld does not exist?\n", window_id); 35 | ret = ERR_OTHER; 36 | goto end; 37 | } 38 | 39 | unsigned int border_size, depth; 40 | XGetGeometry(display, window, &root, 41 | ®ion->x, ®ion->y, ®ion->width, ®ion->height, 42 | &border_size, &depth); 43 | 44 | Window child; 45 | XTranslateCoordinates(display, window, root, 46 | 0, 0, ®ion->x, ®ion->y, &child); 47 | 48 | region->x -= border_size; 49 | region->y -= border_size; 50 | region->width += 2 * border_size; 51 | region->height += 2 * border_size; 52 | 53 | ret = 0; 54 | 55 | end: 56 | XSetErrorHandler(NULL); 57 | XCloseDisplay(display); 58 | return ret; 59 | } 60 | -------------------------------------------------------------------------------- /src/shot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "grab.h" 8 | #include "help.h" 9 | #include "monitor.h" 10 | #include "monitor_mgr.h" 11 | #include "region_picker/active_monitor.h" 12 | #include "region_picker/active_window.h" 13 | #include "region_picker/errors.h" 14 | #include "region_picker/interactive.h" 15 | #include "region_picker/monitor.h" 16 | #include "region_picker/string.h" 17 | #include "region_picker/window.h" 18 | 19 | #define STATUS_CONTINUE 0 20 | #define STATUS_EXIT_ERROR 1 21 | #define STATUS_EXIT_NO_ERROR 2 22 | 23 | struct ShotOptions 24 | { 25 | int status; 26 | int clipboard; 27 | const char *output_path; 28 | ShotRegion region; 29 | }; 30 | 31 | static void show_usage(void) 32 | { 33 | printf("%s", help_str); 34 | } 35 | 36 | static void show_version(void) 37 | { 38 | printf("shot v%s\n", SHOT_VERSION); 39 | } 40 | 41 | static void show_usage_hint(const char *program_name) 42 | { 43 | fprintf(stderr, "Try '%s --help' for more information.\n", program_name); 44 | } 45 | 46 | static void show_monitors(const MonitorManager *monitor_mgr) 47 | { 48 | printf("Available monitors: %d\n", monitor_mgr->monitor_count); 49 | for (unsigned int i = 0; i < monitor_mgr->monitor_count; i++) 50 | { 51 | Monitor *m = monitor_mgr->monitors[i]; 52 | printf("%d - %dx%d+%d+%d", i, m->width, m->height, m->x, m->y); 53 | if (m->primary) 54 | printf(" (primary)"); 55 | puts(""); 56 | } 57 | } 58 | 59 | static void init_region(ShotRegion *region, const MonitorManager *monitor_mgr) 60 | { 61 | //if user calls -i, it should be initialized with a 640x480 window 62 | region->width = 640; 63 | region->height = 480; 64 | region->x = 40; 65 | region->y = 40; 66 | 67 | //...and that window should be centered on the default screen 68 | for (unsigned int i = 0; i < monitor_mgr->monitor_count; i++) 69 | { 70 | const Monitor *m = monitor_mgr->monitors[i]; 71 | if (m->primary) 72 | { 73 | region->x = m->x + (m->width - region->width) / 2; 74 | region->y = m->y + (m->height - region->height) / 2; 75 | } 76 | } 77 | } 78 | 79 | static struct ShotOptions parse_options( 80 | int argc, char **argv, const MonitorManager *monitor_mgr) 81 | { 82 | assert(argv); 83 | assert(monitor_mgr); 84 | assert(monitor_mgr->monitor_count); 85 | 86 | struct ShotOptions options = { 87 | .clipboard = 0, 88 | .status = STATUS_CONTINUE, 89 | .output_path = NULL, 90 | }; 91 | 92 | init_region(&options.region, monitor_mgr); 93 | 94 | int region_result = -1; 95 | 96 | const char *short_opt = "ho:r:diw:Wvm:Mlc"; 97 | struct option long_opt[] = { 98 | {"list", no_argument, NULL, 'l'}, 99 | {"help", no_argument, NULL, 'h'}, 100 | {"output", required_argument, NULL, 'o'}, 101 | {"clipboard", no_argument, NULL, 'c'}, 102 | {"region", required_argument, NULL, 'r'}, 103 | {"monitor", required_argument, NULL, 'm'}, 104 | {"window", required_argument, NULL, 'w'}, 105 | {"desktop", no_argument, NULL, 'd'}, 106 | {"interactive", no_argument, NULL, 'i'}, 107 | {"active-monitor", no_argument, NULL, 'M'}, 108 | {"active-window", no_argument, NULL, 'W'}, 109 | {"version", no_argument, NULL, 'v'}, 110 | {NULL, 0, NULL, 0} 111 | }; 112 | 113 | while (options.status == STATUS_CONTINUE) 114 | { 115 | int c = getopt_long(argc, argv, short_opt, long_opt, NULL); 116 | if (c == -1) 117 | break; 118 | 119 | switch (c) 120 | { 121 | case -1: 122 | case 0: 123 | break; 124 | 125 | case 'v': 126 | show_version(); 127 | options.status = STATUS_EXIT_NO_ERROR; 128 | break; 129 | 130 | case 'h': 131 | show_usage(); 132 | options.status = STATUS_EXIT_NO_ERROR; 133 | break; 134 | 135 | case ':': 136 | case '?': 137 | show_usage_hint(argv[0]); 138 | options.status = STATUS_EXIT_ERROR; 139 | break; 140 | 141 | case 'l': 142 | show_monitors(monitor_mgr); 143 | options.status = STATUS_EXIT_NO_ERROR; 144 | break; 145 | 146 | case 'c': 147 | options.clipboard = 1; 148 | break; 149 | 150 | case 'o': 151 | options.output_path = optarg; 152 | break; 153 | 154 | case 'd': 155 | region_result = update_region_from_all_monitors( 156 | &options.region, monitor_mgr); 157 | break; 158 | 159 | case 'm': 160 | { 161 | size_t n = atoi(optarg); 162 | if (n >= monitor_mgr->monitor_count) 163 | { 164 | fprintf( 165 | stderr, 166 | "Invalid monitor number. " 167 | "Valid monitor numbers = 0..%d\n", 168 | monitor_mgr->monitor_count - 1); 169 | region_result = ERR_INVALID_ARGUMENT; 170 | } 171 | else 172 | { 173 | region_result = update_region_from_monitor( 174 | &options.region, monitor_mgr->monitors[n]); 175 | } 176 | break; 177 | } 178 | 179 | case 'M': 180 | { 181 | region_result = update_region_from_active_monitor( 182 | &options.region, monitor_mgr); 183 | break; 184 | } 185 | 186 | case 'r': 187 | region_result = update_region_from_string( 188 | &options.region, optarg); 189 | break; 190 | 191 | case 'i': 192 | { 193 | ShotRegion working_area; 194 | region_result = update_region_from_all_monitors( 195 | &working_area, monitor_mgr); 196 | assert(!region_result); 197 | region_result = update_region_interactively( 198 | &options.region, &working_area); 199 | break; 200 | } 201 | 202 | case 'w': 203 | { 204 | long int n = atoi(optarg); 205 | region_result = update_region_from_window(&options.region, n); 206 | break; 207 | } 208 | 209 | case 'W': 210 | region_result = update_region_from_active_window( 211 | &options.region); 212 | break; 213 | 214 | default: 215 | fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c); 216 | show_usage_hint(argv[0]); 217 | options.status = STATUS_EXIT_ERROR; 218 | break; 219 | } 220 | } 221 | 222 | //if no region was chosen in the arguments 223 | if (region_result == -1) 224 | { 225 | //take the whole desktop 226 | region_result = update_region_from_all_monitors( 227 | &options.region, monitor_mgr); 228 | } 229 | 230 | if (region_result) 231 | { 232 | if (region_result == ERR_CANCELED) 233 | { 234 | fprintf(stderr, "Canceled due to user input.\n"); 235 | } 236 | else if (region_result == ERR_NOT_IMPLEMENTED) 237 | { 238 | fprintf(stderr, "Not implemented. Sorry...\n"); 239 | } 240 | else if (region_result == ERR_INVALID_ARGUMENT) 241 | { 242 | fprintf(stderr, "Invalid argument, aborting.\n"); 243 | show_usage_hint(argv[0]); 244 | } 245 | else if (region_result == ERR_OTHER) 246 | { 247 | fprintf(stderr, "An error occurred, aborting.\n"); 248 | } 249 | options.status = STATUS_EXIT_ERROR; 250 | } 251 | 252 | return options; 253 | } 254 | 255 | static char *get_random_name() 256 | { 257 | time_t t = time(NULL); 258 | struct tm *tmp = localtime(&t); 259 | assert(tmp); 260 | 261 | char time_str[20]; 262 | strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", tmp); 263 | 264 | char *name = malloc(30); 265 | strcpy(name, time_str); 266 | strcat(name, "_"); 267 | 268 | srand(t); 269 | for (int i = 0; i < 3; i++) 270 | { 271 | char random_char = 'a' + rand() % ('z' + 1 - 'a'); 272 | strncat(name, &random_char, 1); 273 | } 274 | 275 | strcat(name, ".png"); 276 | return name; 277 | } 278 | 279 | static char *get_output_path(const char *path) 280 | { 281 | if (!path || !*path) 282 | return get_random_name(); 283 | 284 | const char *end = &path[strlen(path) - 1]; 285 | if (*end == '/' || *end == '\\') 286 | { 287 | char *random_name = get_random_name(); 288 | assert(random_name); 289 | char *path_copy = malloc(strlen(path) + strlen(random_name) + 1); 290 | assert(path_copy); 291 | strcpy(path_copy, path); 292 | strcat(path_copy, random_name); 293 | free(random_name); 294 | return path_copy; 295 | } 296 | else 297 | { 298 | char *path_copy = malloc(strlen(path) + 1); 299 | assert(path_copy); 300 | strcpy(path_copy, path); 301 | return path_copy; 302 | } 303 | } 304 | 305 | int main(int argc, char **argv) 306 | { 307 | char *output_path = NULL; 308 | int exit_code = 1; 309 | 310 | ShotBitmap *bitmap = NULL; 311 | 312 | MonitorManager *monitor_mgr = monitor_mgr_create(); 313 | if (!monitor_mgr) 314 | { 315 | fprintf(stderr, "Failed to initialize monitor manager, aborting.\n"); 316 | goto end; 317 | } 318 | 319 | struct ShotOptions options = parse_options(argc, argv, monitor_mgr); 320 | 321 | if (options.status == STATUS_EXIT_ERROR) 322 | { 323 | goto end; 324 | } 325 | else if (options.status == STATUS_EXIT_NO_ERROR) 326 | { 327 | exit_code = 0; 328 | goto end; 329 | } 330 | 331 | if (!options.clipboard) 332 | { 333 | output_path = get_output_path(options.output_path); 334 | assert(output_path); 335 | } 336 | 337 | if (options.region.width <= 0 || options.region.height <= 0) 338 | { 339 | fprintf(stderr, 340 | "Cannot take screenshot with non-positive width or height.\n"); 341 | goto end; 342 | } 343 | 344 | bitmap = grab_screenshot(&options.region); 345 | assert(bitmap); 346 | if (options.clipboard) 347 | { 348 | if (bitmap_save_to_clipboard(bitmap)) 349 | goto end; 350 | } 351 | else 352 | { 353 | if (bitmap_save_to_png(bitmap, output_path)) 354 | goto end; 355 | } 356 | exit_code = 0; 357 | 358 | end: 359 | if (output_path) 360 | free(output_path); 361 | if (bitmap) 362 | bitmap_destroy(bitmap); 363 | monitor_mgr_destroy(monitor_mgr); 364 | return exit_code; 365 | } 366 | --------------------------------------------------------------------------------