├── maim.1.gz
├── .gitignore
├── license.txt
├── modules
├── FindXFixes.cmake
├── FindXRandr.cmake
├── FindXComposite.cmake
├── FindSLOP.cmake
├── FindGLX.cmake
└── FindXRender.cmake
├── src
├── x.hpp
├── image.hpp
├── image.cpp
├── x.cpp
├── main.cpp
└── cxxopts.hpp
├── CMakeLists.txt
├── README.md
├── maim.1
└── COPYING
/maim.1.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimoralea/maim/master/maim.1.gz
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # These files are ignored since cmake generates them from cmdline.in
2 | src/cmdline.h
3 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (C) 2014 Dalton Nell, Maim Contributors (https://github.com/naelstrof/maim/graphs/contributors)
2 |
3 | This program is free software: you can redistribute it and/or modify
4 | it under the terms of the GNU General Public License as published by
5 | the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU General Public License for more details.
12 |
13 | You should have received a copy of the GNU General Public License
14 | along with this program. If not, see .
15 |
--------------------------------------------------------------------------------
/modules/FindXFixes.cmake:
--------------------------------------------------------------------------------
1 | # - Find XFixes
2 | # Find the XFixes libraries
3 | #
4 | # This module defines the following variables:
5 | # XFIXES_FOUND - 1 if XFIXES_INCLUDE_DIR & XFIXES_LIBRARY are found, 0 otherwise
6 | # XFIXES_INCLUDE_DIR - where to find Xlib.h, etc.
7 | # XFIXES_LIBRARY - the X11 library
8 | #
9 |
10 | find_path( XFIXES_INCLUDE_DIR
11 | NAMES X11/extensions/Xfixes.h
12 | PATH_SUFFIXES X11/extensions
13 | DOC "The XFixes include directory" )
14 |
15 | find_library( XFIXES_LIBRARY
16 | NAMES Xfixes
17 | PATHS /usr/lib /lib
18 | DOC "The XFixes library" )
19 |
20 | if( XFIXES_INCLUDE_DIR AND XFIXES_LIBRARY )
21 | set( XFIXES_FOUND 1 )
22 | else()
23 | set( XFIXES_FOUND 0 )
24 | endif()
25 |
26 | mark_as_advanced( XFIXES_INCLUDE_DIR XFIXES_LIBRARY )
27 |
--------------------------------------------------------------------------------
/modules/FindXRandr.cmake:
--------------------------------------------------------------------------------
1 | # - Find XRandr
2 | # Find the XRandr libraries
3 | #
4 | # This module defines the following variables:
5 | # XRANDR_FOUND - 1 if XRANDR_INCLUDE_DIR & XRANDR_LIBRARY are found, 0 otherwise
6 | # XRANDR_INCLUDE_DIR - where to find Xlib.h, etc.
7 | # XRANDR_LIBRARY - the X11 library
8 | #
9 |
10 | find_path( XRANDR_INCLUDE_DIR
11 | NAMES X11/extensions/Xrandr.h
12 | PATH_SUFFIXES X11/extensions
13 | DOC "The XRandr include directory" )
14 |
15 | find_library( XRANDR_LIBRARY
16 | NAMES Xrandr
17 | PATHS /usr/lib /lib
18 | DOC "The XRandr library" )
19 |
20 | if( XRANDR_INCLUDE_DIR AND XRANDR_LIBRARY )
21 | set( XRANDR_FOUND 1 )
22 | else()
23 | set( XRANDR_FOUND 0 )
24 | endif()
25 |
26 | mark_as_advanced( XRANDR_INCLUDE_DIR XRANDR_LIBRARY )
27 |
--------------------------------------------------------------------------------
/modules/FindXComposite.cmake:
--------------------------------------------------------------------------------
1 | # - Find XComposite
2 | # Find the XComposite libraries
3 | #
4 | # This module defines the following variables:
5 | # XCOMPOSITE_FOUND - 1 if XCOMPOSITE_INCLUDE_DIR & XCOMPOSITE_LIBRARY are found, 0 otherwise
6 | # XCOMPOSITE_INCLUDE_DIR - where to find Xlib.h, etc.
7 | # XCOMPOSITE_LIBRARY - the X11 library
8 | #
9 |
10 | find_path( XCOMPOSITE_INCLUDE_DIR
11 | NAMES X11/extensions/Xcomposite.h
12 | PATH_SUFFIXES X11/extensions
13 | DOC "The XComposite include directory" )
14 |
15 | find_library( XCOMPOSITE_LIBRARY
16 | NAMES Xcomposite
17 | PATHS /usr/lib /lib
18 | DOC "The XComposite library" )
19 |
20 | if( XCOMPOSITE_INCLUDE_DIR AND XCOMPOSITE_LIBRARY )
21 | set( XCOMPOSITE_FOUND 1 )
22 | else()
23 | set( XCOMPOSITE_FOUND 0 )
24 | endif()
25 |
26 | mark_as_advanced( XCOMPOSITE_INCLUDE_DIR XCOMPOSITE_LIBRARY )
27 |
--------------------------------------------------------------------------------
/modules/FindSLOP.cmake:
--------------------------------------------------------------------------------
1 | # - Find SLOP
2 | # Find the SLOP libraries
3 | #
4 | # This module defines the following variables:
5 | # SLOP_FOUND - 1 if SLOP_INCLUDE_DIR & SLOP_LIBRARY are found, 0 otherwise
6 | # SLOP_INCLUDE_DIR - where to find Xlib.h, etc.
7 | # SLOP_LIBRARY - the X11 library
8 | #
9 |
10 |
11 | find_path( SLOP_INCLUDE_DIRS
12 | NAMES slop.hpp
13 | PATH_SUFFIXES /usr/include /include
14 | DOC "The SLOP include directory" )
15 |
16 | find_library( SLOP_LIBRARIES
17 | NAMES slopy slopy.so slop slop.so
18 | PATHS /usr/lib /lib
19 | DOC "The SLOP library" )
20 |
21 | FIND_PACKAGE(X11 REQUIRED)
22 | FIND_PACKAGE(GLX REQUIRED)
23 | list(APPEND SLOP_LIBRARIES
24 | ${X11_LIBRARIES}
25 | ${GLX_LIBRARY}
26 | )
27 |
28 | if( SLOP_INCLUDE_DIR AND SLOP_LIBRARY )
29 | set( SLOP_FOUND 1 )
30 | else()
31 | set( SLOP_FOUND 0 )
32 | endif()
33 |
34 | mark_as_advanced( SLOP_INCLUDE_DIR SLOP_LIBRARY )
35 |
--------------------------------------------------------------------------------
/modules/FindGLX.cmake:
--------------------------------------------------------------------------------
1 | # Try to find GLX. Once done, this will define:
2 | #
3 | # GLX_FOUND - variable which returns the result of the search
4 | # GLX_INCLUDE_DIRS - list of include directories
5 | # GLX_LIBRARIES - options for the linker
6 |
7 | #=============================================================================
8 | # Copyright 2012 Benjamin Eikel
9 | #
10 | # Distributed under the OSI-approved BSD License (the "License");
11 | # see accompanying file Copyright.txt for details.
12 | #
13 | # This software is distributed WITHOUT ANY WARRANTY; without even the
14 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 | # See the License for more information.
16 | #=============================================================================
17 | # (To distribute this file outside of CMake, substitute the full
18 | # License text for the above reference.)
19 |
20 | find_package(PkgConfig)
21 | pkg_check_modules(PC_GLX QUIET glx)
22 |
23 | find_path(GLX_INCLUDE_DIR
24 | GL/glx.h
25 | HINTS ${PC_GLX_INCLUDEDIR} ${PC_GLX_INCLUDE_DIRS}
26 | )
27 | find_library(GLX_LIBRARY
28 | GL
29 | HINTS ${PC_GLX_LIBDIR} ${PC_GLX_LIBRARY_DIRS}
30 | )
31 |
32 | set(GLX_INCLUDE_DIRS ${GLX_INCLUDE_DIR})
33 | set(GLX_LIBRARIES ${GLX_LIBRARY})
34 |
35 | include(FindPackageHandleStandardArgs)
36 | find_package_handle_standard_args(GLX DEFAULT_MSG
37 | GLX_INCLUDE_DIR
38 | GLX_LIBRARY
39 | )
40 |
41 | mark_as_advanced(
42 | GLX_INCLUDE_DIR
43 | GLX_LIBRARY
44 | )
45 |
--------------------------------------------------------------------------------
/modules/FindXRender.cmake:
--------------------------------------------------------------------------------
1 | # - Find XRender
2 | # Find the XRender libraries
3 | #
4 | # This module defines the following variables:
5 | # XRENDER_FOUND - true if XRENDER_INCLUDE_DIR & XRENDER_LIBRARY are found
6 | # XRENDER_LIBRARIES - Set when Xrender_LIBRARY is found
7 | # XRENDER_INCLUDE_DIRS - Set when Xrender_INCLUDE_DIR is found
8 | #
9 | # XRENDER_INCLUDE_DIR - where to find Xrender.h, etc.
10 | # XRENDER_LIBRARY - the Xrender library
11 | #
12 |
13 | #=============================================================================
14 | # Copyright 2013 Corey Clayton
15 | #
16 | # Licensed under the Apache License, Version 2.0 (the "License");
17 | # you may not use this file except in compliance with the License.
18 | # You may obtain a copy of the License at
19 | #
20 | # http://www.apache.org/licenses/LICENSE-2.0
21 | #
22 | # Unless required by applicable law or agreed to in writing, software
23 | # distributed under the License is distributed on an "AS IS" BASIS,
24 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 | # See the License for the specific language governing permissions and
26 | # limitations under the License.
27 | #=============================================================================
28 |
29 | find_path(XRENDER_INCLUDE_DIR NAMES X11/extensions/Xrender.h
30 | PATHS /opt/X11/include
31 | DOC "The Xrender include directory")
32 |
33 | find_library(XRENDER_LIBRARY NAMES Xrender
34 | PATHS /opt/X11/lib
35 | DOC "The Xrender library")
36 |
37 | include(FindPackageHandleStandardArgs)
38 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Xrender DEFAULT_MSG XRENDER_LIBRARY XRENDER_INCLUDE_DIR)
39 |
40 | if(XRENDER_FOUND)
41 |
42 | set(XRENDER_LIBRARIES ${XRENDER_LIBRARY})
43 | set(XRENDER_INCLUDE_DIRS ${XRENDER_INCLUDE_DIR})
44 |
45 | endif()
46 |
47 | mark_as_advanced(XRENDER_INCLUDE_DIR XRENDER_LIBRARY)
48 |
--------------------------------------------------------------------------------
/src/x.hpp:
--------------------------------------------------------------------------------
1 | /* x.hpp: initializes x11
2 | *
3 | * Copyright (C) 2014: Dalton Nell, Maim Contributors (https://github.com/naelstrof/slop/graphs/contributors).
4 | *
5 | * This file is part of Maim.
6 | *
7 | * Maim is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * Maim is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with Maim. If not, see .
19 | */
20 |
21 | #ifndef N_X_H_
22 | #define N_X_H_
23 |
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | //#include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 |
39 | class X11 {
40 | private:
41 | bool hasClipping( Window d );
42 | XserverRegion findRegion( Window d );
43 | void unionClippingRegions( XserverRegion rootRegion, Window child );
44 | void unionBorderRegions( XserverRegion rootRegion, Window d );
45 | XImage* getImageUsingXRender( Window draw, int localx, int localy, int w, int h );
46 | XImage* getImageUsingXShm( Window draw, int localx, int localy, int w, int h );
47 | public:
48 | bool haveXComposite;
49 | bool haveXRender;
50 | bool haveXShm;
51 | bool haveXFixes;
52 | bool haveXRR;
53 | X11( std::string displayName );
54 | ~X11();
55 | Display* display;
56 | Visual* visual;
57 | Screen* screen;
58 | Window root;
59 | XImage* getImage( Window d, int x, int y, int w, int h, glm::ivec2& imageloc );
60 | XRRScreenResources* res;
61 | std::vector getCRTCS();
62 | void freeCRTCS( std::vector monitors );
63 | };
64 |
65 | glm::ivec4 getWindowGeometry( X11* x11, Window win );
66 |
67 | #endif
68 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required( VERSION 3.1.3 )
2 |
3 | project( "maim" )
4 |
5 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build (Debug or Release)")
6 |
7 | if ( NOT CMAKE_INSTALL_PREFIX )
8 | set(CMAKE_INSTALL_PREFIX "/usr")
9 | endif()
10 |
11 | set( CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Directory where man pages reside. (/usr/share/man, /usr/local/share/man, etc.)" )
12 |
13 | set( CMAKE_COMPRESS_MAN TRUE CACHE BOOL "Whether or not to compress the man pages for install." )
14 |
15 | if ( CMAKE_COMPRESS_MAN )
16 | set( MANTARGET "maim.1.gz" )
17 | else()
18 | set( MANTARGET "maim.1" )
19 | endif()
20 |
21 | add_definitions(-DMAIM_VERSION="v5.4.68")
22 |
23 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/")
24 |
25 | # Sources
26 | set( source
27 | src/x.cpp
28 | src/image.cpp
29 | src/main.cpp )
30 |
31 | set( BIN_TARGET "${PROJECT_NAME}" )
32 |
33 | # Executable
34 | add_executable( "${BIN_TARGET}" ${source} )
35 |
36 | # Obtain library paths and make sure they exist.
37 | set( CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_SOURCE_DIR}/modules" )
38 | find_package( PNG REQUIRED )
39 | find_package( JPEG REQUIRED )
40 | find_package( XRandr REQUIRED )
41 | find_package( XRender REQUIRED )
42 | find_package( XFixes REQUIRED )
43 | find_package( XComposite REQUIRED )
44 | find_package( X11 REQUIRED )
45 | find_package( SLOP REQUIRED )
46 | find_package( Threads REQUIRED )
47 |
48 | set_property(TARGET ${BIN_TARGET} PROPERTY CXX_STANDARD_REQUIRED ON)
49 | set_property(TARGET ${BIN_TARGET} PROPERTY CXX_STANDARD 11)
50 |
51 | # Includes
52 | include_directories( ${XRANDR_INCLUDE_DIR}
53 | ${X11_INCLUDE_DIR}
54 | ${SLOP_INCLUDE_DIR}
55 | ${XFIXES_INCLUDE_DIR}
56 | ${XCOMPOSITE_INCLUDE_DIR}
57 | ${JPEG_INCLUDE_DIR}
58 | ${XRANDR_INCLUDE_DIR}
59 | ${XRENDER_INCLUDE_DIR}
60 | ${PNG_INCLUDE_DIRS} )
61 |
62 | # Libraries
63 | target_link_libraries( ${BIN_TARGET}
64 | ${CMAKE_THREAD_LIBS_INIT}
65 | ${X11_LIBRARIES}
66 | ${PNG_LIBRARIES}
67 | ${XFIXES_LIBRARY}
68 | ${XCOMPOSITE_LIBRARY}
69 | ${XRANDR_LIBRARY}
70 | ${JPEG_LIBRARIES}
71 | ${XRENDER_LIBRARY}
72 | ${SLOP_LIBRARIES} )
73 |
74 | if( CMAKE<3.7 )
75 | message( WARNING "CMake version is below 3.7, CMake version >= 3.7 is required for unicode support." )
76 | else()
77 | find_package(ICU COMPONENTS uc)
78 | set( MAIM_UNICODE TRUE CACHE BOOL "To enable or disable unicode support." )
79 | if ( MAIM_UNICODE AND ICU_FOUND )
80 | # ICU is required for old nvidia drivers to work for whatever reason.
81 | add_definitions(-DCXXOPTS_USE_UNICODE)
82 | include_directories( ${ICU_INCLUDE_DIR} )
83 | target_link_libraries( ${BIN_TARGET} ${ICU_UC_LIBRARIES} )
84 | endif()
85 | endif()
86 |
87 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
88 |
89 | install( TARGETS ${BIN_TARGET} DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" )
90 | install( FILES "${CMAKE_SOURCE_DIR}/${MANTARGET}" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" COMPONENT doc )
91 |
--------------------------------------------------------------------------------
/src/image.hpp:
--------------------------------------------------------------------------------
1 | /* image.hpp: image helper
2 | *
3 | * Copyright (C) 2014: Dalton Nell, Maim Contributors (https://github.com/naelstrof/slop/graphs/contributors).
4 | *
5 | * This file is part of Maim.
6 | *
7 | * Maim is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * Maim is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * along with Maim. If not, see .
19 | */
20 |
21 | #ifndef N_IMAGE_H_
22 | #define N_IMAGE_H_
23 |
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #include "x.hpp"
33 |
34 | static inline unsigned char computeRGBPixel(unsigned char* data, XImage* image, int x, int y, int roffset, int goffset, int boffset, int width, glm::ivec2 offset ) {
35 | int curpixel = ((y-offset.y)*width+((x-offset.x)))*3;
36 | unsigned int real = XGetPixel(image, x, y);
37 | data[curpixel] = (unsigned char)((real & image->red_mask) >> roffset);
38 | data[curpixel+1] = (unsigned char)((real & image->green_mask) >> goffset);
39 | data[curpixel+2] = (unsigned char)((real & image->blue_mask) >> boffset);
40 | }
41 |
42 | static inline unsigned char computeRGBAPixel(unsigned char* data, XImage* image, int x, int y, int roffset, int goffset, int boffset, int aoffset, int width, glm::ivec2 offset ) {
43 | int curpixel = ((y-offset.y)*width+(x-offset.x))*4;
44 | //unsigned int real = ((unsigned int*)image->data)[curpixel/4];
45 | unsigned int real = XGetPixel(image, x, y);
46 | data[curpixel] = (unsigned char)((real & image->red_mask) >> roffset);
47 | data[curpixel+1] = (unsigned char)((real & image->green_mask) >> goffset);
48 | data[curpixel+2] = (unsigned char)((real & image->blue_mask) >> boffset);
49 | data[curpixel+3] = (unsigned char)(real >> aoffset);
50 | }
51 |
52 | static inline unsigned char computeRGBAPixel(unsigned char* data, XImage* image, int x, int y, int roffset, int goffset, int boffset, int width, glm::ivec2 offset ) {
53 | int curpixel = ((y-offset.y)*width+((x-offset.x)))*4;
54 | //unsigned int real = ((unsigned int*)image->data)[curpixel/4];
55 | unsigned int real = XGetPixel(image, x, y);
56 | data[curpixel] = (unsigned char)((real & image->red_mask) >> roffset);
57 | data[curpixel+1] = (unsigned char)((real & image->green_mask) >> goffset);
58 | data[curpixel+2] = (unsigned char)((real & image->blue_mask) >> boffset);
59 | data[curpixel+3] = 255;
60 | }
61 |
62 | static inline int get_shift (int mask) {
63 | int shift = 0;
64 | while (mask) {
65 | if (mask & 1) { break; }
66 | shift++;
67 | mask >>= 1;
68 | }
69 | return shift;
70 | }
71 |
72 | class ARGBImage {
73 | private:
74 | unsigned char* data;
75 | unsigned int width;
76 | unsigned int height;
77 | unsigned int channels;
78 | int imagex, imagey;
79 | glm::ivec2 offset;
80 | bool intersect( XRRCrtcInfo* a, glm::vec4 b );
81 | bool containsCompletely( XRRCrtcInfo* a, glm::vec4 b );
82 | public:
83 | void blendCursor( X11* x11 );
84 | void mask(X11* x11);
85 | ARGBImage( XImage* image, glm::ivec2 imageloc, glm::ivec4 selectionrect, int channels, X11* x11 );
86 | ~ARGBImage();
87 | void writePNG( std::ostream& streamout, int quality );
88 | void writeJPEG( std::ostream& streamout, int quality );
89 | };
90 |
91 | #endif
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # maim
2 | maim (Make Image) is a utility that takes screenshots of your desktop. It's meant to overcome shortcomings of scrot and performs better in several ways.
3 |
4 | ## Features
5 | * Takes screenshots of your desktop, and saves it in png or jpg format.
6 | * Takes screenshots predetermined regions or windows, useful for automation.
7 | * Allows a users to select a region, or window, before taking a screenshot on the fly.
8 |
9 | 
10 | * Blends the system cursor to the screenshot.
11 | 
12 | * Masks off-screen pixels to be transparent or black.
13 |
14 | 
15 | * Maim cleanly pipes screenshots directly to standard output (unless otherwise specified). Allowing for command chaining.
16 | * Maim supports anything slop does, even selection [shaders](https://github.com/naelstrof/slop#shaders)!
17 |
18 | 
19 |
20 |
21 | ## Installation
22 |
23 | ### Install using your Package Manager (Preferred)
24 | * [Arch Linux: community/maim](https://www.archlinux.org/packages/community/x86_64/maim/)
25 | * [Debian: maim](https://tracker.debian.org/pkg/maim)
26 | * [Void Linux: maim](https://github.com/voidlinux/void-packages/blob/24ac22af44018e2598047e5ef7fd3522efa79db5/srcpkgs/maim/template)
27 | * [FreeBSD: graphics/maim](http://www.freshports.org/graphics/maim/)
28 | * [OpenBSD: graphics/maim](http://openports.se/graphics/maim)
29 | * [CRUX: 6c37/maim](https://github.com/6c37/crux-ports/tree/3.2/maim)
30 | * [Gentoo: media-gfx/maim](https://packages.gentoo.org/packages/media-gfx/maim)
31 | * [NixOS: maim](https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/graphics/maim/default.nix)
32 | * [GNU Guix: maim](https://www.gnu.org/software/guix/packages/#maim)
33 | * Please make a package for maim on your favorite system, and make a pull request to add it to this list.
34 |
35 | ### Install using CMake (Requires CMake, git, libXrander, libXfixes, libGLM)
36 | ```bash
37 | git clone https://github.com/naelstrof/slop.git
38 | cd slop
39 | cmake -DCMAKE_INSTALL_PREFIX="/usr" ./
40 | make && sudo make install
41 | cd ..
42 | git clone https://github.com/naelstrof/maim.git
43 | cd maim
44 | cmake -DCMAKE_INSTALL_PREFIX="/usr" ./
45 | make && sudo make install
46 | ```
47 |
48 | ## Examples
49 | Maim allows for a lot of unique and interesting functionalities. Here's an example of a few interactions.
50 |
51 | * This command will allow you to select an area on your screen, then copy the selection to your clipboard. This can be used to easily post images in mumble, discord, gimp-- or any other image supporting application.
52 | ```bash
53 | $ maim -s | xclip -selection clipboard -t image/png
54 | ```
55 |
56 | * This messy command forces a user to select a window to screenshot, then applies a shadow effect using *imagemagick*, then saves it to shadow.png. It looks really nice on windows that support an alpha channel.
57 | ```bash
58 | $ maim -st 9999999 | convert - \( +clone -background black -shadow 80x3+5+5 \) +swap -background none -layers merge +repage shadow.png
59 | ```
60 |
61 | * This command is a particular favorite of mine, invented by a friend. It simply prints the RGB values of the selected pixel. A basic color picker that has the additional ability to average out the pixel values of an area. If used cleverly with the geometry and window flag, the return color might warn you of a found counter-strike match...
62 | ```bash
63 | $ maim -st 0 | convert - -resize 1x1\! -format '%[pixel:p{0,0}]' info:-
64 | ```
65 |
66 | * This is a basic, but useful command that simply screenshots the current active window.
67 | ```bash
68 | $ maim -i $(xdotool getactivewindow) ~/mypicture.jpg
69 | ```
70 |
71 | * This is another basic command, but I find it necessary to describe the usefulness of date. This particular command creates a full screenshot, and names it as the number of seconds that passed since 1970. Guaranteed unique, already sorted, and easily read.
72 | ```bash
73 | $ maim ~/Pictures/$(date +%s).png
74 | ```
75 |
76 | * This one overlays a still of your desktop, then allows you to crop it. Doesn't play well with multiple monitors, but I'm sure if it did it wouldn't look this pretty and simple.
77 | ```bash
78 | $ maim | feh - -x & maim -s cropped.png
79 | ```
80 |
81 | * Finally with the [help your friendly neighborhood scripter](https://github.com/tremby/imgur.sh), pictures can automatically be uploaded and their URLs copied to the clipboard with this basic command.
82 | ```bash
83 | $ maim -s /tmp/screenshot.png; imgurbash.sh /tmp/screenshot.png
84 | ```
85 |
--------------------------------------------------------------------------------
/maim.1:
--------------------------------------------------------------------------------
1 | .\" Manpage for maim.
2 | .\" Contact naelstrof@gmail.com to correct errors or typos.
3 | .TH maim 1 2017-03-21 Linux "maim man page"
4 | .SH NAME
5 | maim \- make image
6 | .SH SYNOPSIS
7 | maim [OPTIONS] [FILEPATH]
8 | .SH DESCRIPTION
9 | maim (make image) is an utility that takes a screenshot of your desktop, and encodes a png or jpg image of it. By default it outputs the encoded image data directly to standard output.
10 | .SH OPTIONS
11 | .TP
12 | .BR \-h ", " \-\-help
13 | Print help and exit.
14 | .TP
15 | .BR \-v ", " \-\-version
16 | Print version and exit.
17 | .TP
18 | .BR \-x ", " \-\-xdisplay=\fIhostname:number.screen_number\fR
19 | Sets the xdisplay to use.
20 | .TP
21 | .BR \-f ", " \-\-format=\fISTRING\fR
22 | Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Currently only supports `png` or `jpg`.
23 | .TP
24 | .BR \-i ", " \-\-window=\fIWINDOW\fR
25 | Sets the desired window to capture, defaults to the root window. Allows for an integer, hex, or `root` for input.
26 | .TP
27 | .BR \-g ", " \-\-geometry=\fIGEOMETRY\fR
28 | Sets the region to capture, uses local coordinates from the given window. So -g10x30-5+0 would represent the rectangle wxh+x+y where w=10, h=30, x=-5, and y=0. x and y are the upper left location of this rectangle.
29 | .TP
30 | .BR \-w ", " \-\-parent=\fIWINDOW\fR
31 | By default, maim assumes the --geometry values are in respect to the provided --window (or root if not provided). This parameter overrides this behavior by making the geometry be in respect to whatever window you provide to --parent. Allows for an integer, hex, or `root` for input.
32 | .TP
33 | .BR \-d ", " \-\-delay=\fIFLOAT\fR
34 | Sets the time in seconds to wait before taking a screenshot. Prints a simple message to show how many seconds are left before a screenshot is taken. See \-\-quiet for muting this message.
35 | .TP
36 | .BR \-u ", " \-\-hidecursor
37 | By default maim super-imposes the cursor onto the image, you can disable that behavior with this flag.
38 | .TP
39 | .BR \-m ", " \-\-quality
40 | An integer from 1 to 10 that determines the compression quality. 1 is the highest (and lossiest) compression available for the provided format. For example a setting of `1` with png (a lossless format) would increase filesize and decrease decoding time. While a setting of `1` on a jpeg would create a pixel mush.
41 | .TP
42 | .BR \-s ", " \-\-select
43 | Enables an interactive selection mode where you may select the desired region or window before a screenshot is captured. Uses the settings below to determine the visuals and settings of slop.
44 | .SH SLOP OPTIONS
45 | .TP
46 | .BR \-b ", " \-\-bordersize=\fIFLOAT\fR
47 | Sets the selection rectangle's thickness.
48 | .TP
49 | .BR \-p ", " \-\-padding=\fIFLOAT\fR
50 | Sets the padding size for the selection, this can be negative.
51 | .TP
52 | .BR \-t ", " \-\-tolerance=\fIFLOAT\fR
53 | How far in pixels the mouse can move after clicking, and still be detected as a normal click instead of a click-and-drag. Setting this to 0 will disable window selections. Alternatively setting it to 9999999 would force a window selection.
54 | .TP
55 | .BR \-c ", " \-\-color=\fIFLOAT,FLOAT,FLOAT,FLOAT\fR
56 | Sets the selection rectangle's color. Supports RGB or RGBA input. Depending on the system's window manager/OpenGL support, the opacity may be ignored.
57 | .TP
58 | .BR \-r ", " \-\-shader=\fISTRING\fR
59 | This sets the vertex shader, and fragment shader combo to use when drawing the final framebuffer to the screen. This obviously only works when OpenGL is enabled. The shaders are loaded from ~/.config/maim. See https://github.com/naelstrof/slop for more information on how to create your own shaders.
60 | .TP
61 | .BR \-n ", " \-\-nodecorations=\fIINT\fR
62 | Sets the level of aggressiveness when trying to remove window decorations. `0' is off, `1' will try lightly to remove decorations, and `2' will recursively descend into the root tree until it gets the deepest available visible child under the mouse. Defaults to `0'.
63 | .TP
64 | .BR \-l ", " \-\-highlight
65 | Instead of outlining a selection, maim will highlight it instead. This is particularly useful if the color is set to an opacity lower than 1.
66 | .TP
67 | .BR \-q ", " \-\-quiet
68 | Disable any unnecessary cerr output. Any warnings or info simply won't print.
69 | .TP
70 | .BR \-k ", " \-\-nokeyboard
71 | Disables the ability to cancel selections with the keyboard.
72 | .TP
73 | .BR \-o ", " \-\-noopengl
74 | Disables graphics hardware acceleration.
75 | .SH EXAMPLES
76 | Screenshot the active window and save it to the clipboard for quick pasting.
77 | .PP
78 | .nf
79 | .RS
80 | maim -i $(xdotool getactivewindow) | xclip -selection clipboard -t image/png
81 | .RE
82 | .fi
83 | .PP
84 | Save a desktop screenshot with a unique ordered timestamp in the Pictures folder.
85 | .PP
86 | .nf
87 | .RS
88 | maim ~/Pictures/$(date +%s).png
89 | .RE
90 | .fi
91 | .PP
92 | Prompt for a region to screenshot. Add a fancy shadow to it, then save it to shadow.png.
93 | .PP
94 | .nf
95 | .RS
96 | maim -s | convert - \\( +clone -background black -shadow 80x3+5+5 \\) +swap -background none -layers merge +repage shadow.png
97 | .RE
98 | .fi
99 | .PP
100 | .SH SEE ALSO
101 | .BR slop(1)
102 | .SH BUGS
103 | No known bugs.
104 | .SH AUTHOR
105 | Dalton Nell (naelstrof@gmail.com)
106 |
--------------------------------------------------------------------------------
/src/image.cpp:
--------------------------------------------------------------------------------
1 | #include "image.hpp"
2 |
3 | ARGBImage::~ARGBImage() {
4 | delete[] data;
5 | }
6 |
7 | ARGBImage::ARGBImage( XImage* image, glm::ivec2 iloc, glm::ivec4 selectionrect, int channels, X11* x11 ) {
8 | this->imagex = iloc.x;
9 | this->imagey = iloc.y;
10 | this->channels = channels;
11 | glm::ivec2 spos = glm::ivec2( selectionrect.x, selectionrect.y );
12 | offset = spos-iloc;
13 | long long int alpha_mask = ~(image->red_mask|image->green_mask|image->blue_mask);
14 | long long int roffset = get_shift(image->red_mask);
15 | long long int goffset = get_shift(image->green_mask);
16 | long long int boffset = get_shift(image->blue_mask);
17 | long long int aoffset = get_shift(alpha_mask);
18 | width = selectionrect.z;
19 | height = selectionrect.w;
20 | data = new unsigned char[width*height*channels];
21 | // Clear necessary stuff
22 | // Top rect
23 | for ( int y = 0; y < glm::min(-offset.y,(int)height);y++ ) {
24 | for ( int x = 0; x < width;x++ ) {
25 | for ( int c = 0; c < channels;c++ ) {
26 | data[(y*width+x)*channels+c] = 0;
27 | }
28 | }
29 | }
30 | // Left rect
31 | for ( int y = 0; y < height;y++ ) {
32 | for ( int x = 0; x < glm::min(-offset.x,(int)width);x++ ) {
33 | for ( int c = 0; c < channels;c++ ) {
34 | data[(y*width+x)*channels+c] = 0;
35 | }
36 | }
37 | }
38 | // Bot rect
39 | for ( int y=-offset.y+image->height; ywidth; xwidth);
59 | int minh = glm::min( (int)(offset.y+height), image->height );
60 |
61 | // Loop only through the intersecting parts, copying everything.
62 | // Also check if we have any useful alpha data.
63 | switch( channels ) {
64 | case 4:
65 | if ( aoffset >= image->depth ) {
66 | for(int y = maxy; y < minh; y++) {
67 | for(int x = maxx; x < minw; x++) {
68 | // This is where we just have RGB but require an RGBA image.
69 | computeRGBAPixel( data, image, x, y, roffset, goffset, boffset, width, offset );
70 | }
71 | }
72 | } else {
73 | for(int y = maxy; y < minh; y++) {
74 | for(int x = maxx; x < minw; x++) {
75 | computeRGBAPixel( data, image, x, y, roffset, goffset, boffset, aoffset, width, offset );
76 | }
77 | }
78 | }
79 | break;
80 | case 3:
81 | for(int y = maxy; y < minh; y++) {
82 | for(int x = maxx; x < minw; x++) {
83 | computeRGBPixel( data, image, x, y, roffset, goffset, boffset, width, offset );
84 | }
85 | }
86 | break;
87 | default:
88 | throw new std::invalid_argument("Invalid number of channels provided to image.");
89 | }
90 | }
91 |
92 | void png_write_ostream(png_structp png_ptr, png_bytep data, png_size_t length)
93 | {
94 | std::ostream *stream = (std::ostream*)png_get_io_ptr(png_ptr); //Get pointer to ostream
95 | stream->write((char*)data,length); //Write requested amount of data
96 | }
97 |
98 | void png_flush_ostream(png_structp png_ptr)
99 | {
100 | std::ostream *stream = (std::ostream*)png_get_io_ptr(png_ptr); //Get pointer to ostream
101 | stream->flush();
102 | }
103 |
104 | void user_error_fn(png_structp png_ptr, png_const_charp error_msg)
105 | {
106 | throw new std::runtime_error(error_msg);
107 | }
108 |
109 | void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg)
110 | {
111 | std::cerr << warning_msg << "\n";
112 | }
113 |
114 | void ARGBImage::writePNG( std::ostream& streamout, int quality ) {
115 | if ( quality > 10 || quality < 1 ) {
116 | throw new std::invalid_argument("Quality argument must be between 1 and 10");
117 | }
118 | png_structp png = NULL;
119 | png_infop info = NULL;
120 | png_bytep *rows = new png_bytep[height];
121 |
122 | png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
123 | if(!png) throw new std::runtime_error( "Failed to write png image" );
124 | info = png_create_info_struct(png);
125 | if(!info) throw new std::runtime_error( "Failed to write png image" );
126 | png_set_error_fn(png, png_get_error_ptr(png), user_error_fn, user_warning_fn);
127 | png_set_write_fn(png, &streamout, png_write_ostream, png_flush_ostream);
128 | png_set_compression_level(png, quality-1);
129 | if ( channels == 4 ) {
130 | png_set_IHDR(png, info, width, height,
131 | 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
132 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
133 | } else {
134 | png_set_IHDR(png, info, width, height,
135 | 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
136 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
137 | }
138 | for( int i=0;inext_output_byte - out_buffer );
204 | delete[] out_buffer;
205 | }
206 |
207 | bool ARGBImage::intersect( XRRCrtcInfo* a, glm::vec4 b ) {
208 | if (a->x < b.x + b.z &&
209 | a->x + a->width > b.x &&
210 | a->y < b.y + b.w &&
211 | a->height + a->y > b.y) {
212 | return true;
213 | }
214 | return false;
215 | }
216 |
217 | bool ARGBImage::containsCompletely( XRRCrtcInfo* a, glm::vec4 b ) {
218 | if ( b.x >= a->x && b.y >= a->y && b.x+b.z <= a->x+a->width && b.y+b.w <= a->y+a->height ) {
219 | return true;
220 | }
221 | return false;
222 | }
223 |
224 | void ARGBImage::mask(X11* x11) {
225 | if ( !x11->haveXRR ) {
226 | return;
227 | }
228 | std::vector physicalMonitors = x11->getCRTCS();
229 | // Make sure a masking needs to happen, it's not a perfect detection,
230 | // but will detect most situations where a masking actually needs to happen.
231 | for ( int i=0;ifreeCRTCS(physicalMonitors);
236 | return;
237 | }
238 | }
239 | }
240 | unsigned char* copy = new unsigned char[width*height*channels];
241 | // Zero out our copy
242 | for ( int y = 0; y < height;y++ ) {
243 | for ( int x = 0; x < width;x++ ) {
244 | for ( int c = 0; c < channels;c++ ) {
245 | copy[(y*width+x)*channels+c] = 0;
246 | }
247 | }
248 | }
249 | for ( int i=0;iy-imagey); yy+m->height-imagey);y++ ) {
257 | for ( int x = glm::max(0,m->x-imagex); x < glm::min(width,m->x+m->width-imagex);x++ ) {
258 | for ( int c = 0; c < channels;c++ ) {
259 | copy[(y*width+x)*channels+c] = data[(y*width+x)*channels+c];
260 | }
261 | }
262 | }
263 | }
264 | x11->freeCRTCS(physicalMonitors);
265 | delete[] data;
266 | data = copy;
267 | }
268 |
269 | void ARGBImage::blendCursor( X11* x11 ) {
270 | if ( !x11->haveXFixes ) {
271 | return;
272 | }
273 | XFixesCursorImage* xcursor = XFixesGetCursorImage( x11->display );
274 | if ( !xcursor ) {
275 | return;
276 | }
277 | // 64bit -> 32bit conversion
278 | unsigned char pixels[xcursor->width * xcursor->height * 4];
279 | for ( unsigned int i=0;iwidth*xcursor->height;i++ ) {
280 | ((unsigned int*)pixels)[ i ] = (unsigned int)xcursor->pixels[ i ];
281 | }
282 | xcursor->y -= xcursor->yhot + offset.x;
283 | xcursor->x -= xcursor->xhot + offset.y;
284 | for ( int y = glm::max(0,xcursor->y-imagey); yy+xcursor->height-imagey);y++ ) {
285 | for ( int x = glm::max(0,xcursor->x-imagex); x < glm::min((int)width,xcursor->x+xcursor->width-imagex);x++ ) {
286 | int cx = x-(xcursor->x-imagex);
287 | int cy = y-(xcursor->y-imagey);
288 | float alpha = (float)pixels[(cy*xcursor->width+cx)*4+3]/255.f;
289 | data[(y*width+x)*channels] = data[(y*width+x)*channels]*(1-alpha) + pixels[(cy*xcursor->width+cx)*4+2]*alpha;
290 | data[(y*width+x)*channels+1] = data[(y*width+x)*channels+1]*(1-alpha) + pixels[(cy*xcursor->width+cx)*4+1]*alpha;
291 | data[(y*width+x)*channels+2] = data[(y*width+x)*channels+2]*(1-alpha) + pixels[(cy*xcursor->width+cx)*4]*alpha;
292 | // If the original image has alpha, we need to override it.
293 | if ( channels == 4 ) {
294 | data[(y*width+x)*channels+3] = glm::min(data[(y*width+x)*channels+3]+pixels[(cy*xcursor->width+cx)*4+3],255);
295 | }
296 | }
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/x.cpp:
--------------------------------------------------------------------------------
1 | #include "x.hpp"
2 |
3 | static char _x_err = 0;
4 | static int
5 | TmpXError(Display * d, XErrorEvent * ev) {
6 | _x_err = 1;
7 | return 0;
8 | }
9 |
10 | glm::ivec4 getWindowGeometry( X11* x11, Window win ) {
11 | // First lets check for if we're a window manager frame.
12 | Window root, parent;
13 | Window* children;
14 | unsigned int num_children;
15 | XQueryTree( x11->display, win, &root, &parent, &children, &num_children);
16 |
17 | // To do that, we check if our top level child happens to have the _NET_FRAME_EXTENTS atom.
18 | unsigned char *data;
19 | Atom type_return;
20 | unsigned long nitems_return;
21 | unsigned long bytes_after_return;
22 | int format_return;
23 | bool window_frame = false;
24 | Window actualWindow = win;
25 | if ( num_children > 0 && XGetWindowProperty( x11->display, children[num_children-1],
26 | XInternAtom( x11->display, "_NET_FRAME_EXTENTS", False),
27 | 0, LONG_MAX, False, XA_CARDINAL, &type_return,
28 | &format_return, &nitems_return, &bytes_after_return,
29 | &data) == Success ) {
30 | if ((type_return == XA_CARDINAL) && (format_return == 32) && (nitems_return == 4) && (data)) {
31 | actualWindow = children[num_children-1];
32 | window_frame = true;
33 | }
34 | }
35 | XFree( children );
36 |
37 | // If we're a window frame, we actually get the dimensions of the child window, then add the _NET_FRAME_EXTENTS to it.
38 | // (then add the border width of the window frame after that.)
39 | if ( window_frame ) {
40 | // First lets grab the border width.
41 | XWindowAttributes frameattr;
42 | XGetWindowAttributes( x11->display, win, &frameattr );
43 | // Then lets grab the dims of the child window.
44 | XWindowAttributes attr;
45 | XGetWindowAttributes( x11->display, actualWindow, &attr );
46 | unsigned int width = attr.width;
47 | unsigned int height = attr.height;
48 | // We combine both border widths.
49 | unsigned int border = attr.border_width+frameattr.border_width;
50 | int x, y;
51 | // Gotta translate them into root coords, we can adjust for the border width here.
52 | Window junk;
53 | XTranslateCoordinates( x11->display, actualWindow, attr.root, -border, -border, &x, &y, &junk );
54 | width += border*2;
55 | height += border*2;
56 | // Now uh, remember that _NET_FRAME_EXTENTS stuff? That's the window frame information.
57 | // We HAVE to do this because mutter likes to mess with window sizes with shadows and stuff.
58 | unsigned long* ldata = (unsigned long*)data;
59 | width += ldata[0] + ldata[1];
60 | height += ldata[2] + ldata[3];
61 | x -= ldata[0];
62 | y -= ldata[2];
63 | XFree( data );
64 | return glm::vec4( x, y, width, height );
65 | } else {
66 | // Either the WM is malfunctioning, or the window secified isn't a window manager frame.
67 | // so we just rely on X.
68 | XWindowAttributes attr;
69 | XGetWindowAttributes( x11->display, win, &attr );
70 | unsigned int width = attr.width;
71 | unsigned int height = attr.height;
72 | // We combine both border widths.
73 | unsigned int border = attr.border_width;
74 | int x, y;
75 | // Gotta translate them into root coords, we can adjust for the border width here.
76 | Window junk;
77 | XTranslateCoordinates( x11->display, win, attr.root, -border, -border, &x, &y, &junk );
78 | width += border*2;
79 | height += border*2;
80 | return glm::vec4( x, y, width, height );
81 | }
82 | }
83 |
84 | std::vector X11::getCRTCS() {
85 | std::vector monitors;
86 | if ( !res ) {
87 | return monitors;
88 | }
89 | for ( int i=0;incrtc;i++ ) {
90 | monitors.push_back( XRRGetCrtcInfo( display, res, res->crtcs[ i ] ) );
91 | }
92 | return monitors;
93 | }
94 |
95 | void X11::freeCRTCS( std::vector monitors ) {
96 | for ( unsigned int i=0;idisplay, this->root, draw, x, y, &localx, &localy, &junk);
156 |
157 | if ( haveXComposite ) {
158 | // We redirect all the pixmaps offscreen, so that they won't be corrupted if obscured.
159 | for ( int i = 0; i < ScreenCount( display ); i++ ) {
160 | XCompositeRedirectSubwindows( display, RootWindow( display, i ), CompositeRedirectAutomatic );
161 | }
162 | // We don't have to worry about undoing the redirect, since as soon as maim closes X knows to undo it.
163 | }
164 | if ( haveXRender && haveXFixes ) {
165 | return getImageUsingXRender( draw, localx, localy, w, h );
166 | }
167 | // This stuff doesn't work very well...
168 | //if ( haveXShm ) {
169 | //XErrorHandler ph = XSetErrorHandler(TmpXError);
170 | //XImage* check = getImageUsingXShm( draw, localx, localy, w, h );
171 | //XSetErrorHandler(ph);
172 | //if ( !_x_err && check != None ) {
173 | //return check;
174 | //}
175 | //}
176 | return XGetImage( display, draw, localx, localy, w, h, AllPlanes, ZPixmap );
177 | }
178 |
179 | XImage* X11::getImageUsingXRender( Window draw, int localx, int localy, int w, int h ) {
180 | // We use XRender to grab the drawable, since it'll save it in a format we like.
181 | XWindowAttributes attr;
182 | XGetWindowAttributes( display, draw, &attr );
183 | XRenderPictFormat *format = XRenderFindVisualFormat( display, attr.visual );
184 | bool hasAlpha = ( format->type == PictTypeDirect && format->direct.alphaMask );
185 | XRenderPictureAttributes pa;
186 | pa.subwindow_mode = IncludeInferiors;
187 | Picture picture = XRenderCreatePicture( display, draw, format, CPSubwindowMode, &pa );
188 | if ( draw != root ) {
189 | XserverRegion region = findRegion( draw );
190 | // Also we use XRender because of this neato function here.
191 | XFixesSetPictureClipRegion( display, picture, 0, 0, region );
192 | XFixesDestroyRegion( display, region );
193 | }
194 |
195 | Pixmap pixmap = XCreatePixmap(display, root, w, h, 32);
196 | XRenderPictureAttributes pa2;
197 |
198 | XRenderPictFormat *format2 = XRenderFindStandardFormat(display, PictStandardARGB32);
199 | Picture pixmapPicture = XRenderCreatePicture( display, pixmap, format2, 0, &pa2 );
200 | XRenderColor c;
201 | c.red = 0x0000;
202 | c.green = 0x0000;
203 | c.blue = 0x0000;
204 | c.alpha = 0x0000;
205 | XRenderFillRectangle (display, PictOpSrc, pixmapPicture, &c, 0, 0, w, h);
206 | XRenderComposite(display, hasAlpha ? PictOpOver : PictOpSrc, picture, 0,
207 | pixmapPicture, localx, localy, 0, 0, 0, 0,
208 | w, h);
209 | XImage* temp = XGetImage( display, pixmap, 0, 0, w, h, AllPlanes, ZPixmap );
210 | temp->red_mask = format2->direct.redMask << format2->direct.red;
211 | temp->green_mask = format2->direct.greenMask << format2->direct.green;
212 | temp->blue_mask = format2->direct.blueMask << format2->direct.blue;
213 | temp->depth = format2->depth;
214 | return temp;
215 | }
216 |
217 | bool X11::hasClipping( Window d ) {
218 | int bShaped, xbs, ybs, cShaped, xcs, ycs;
219 | unsigned int wbs, hbs, wcs, hcs;
220 | XShapeQueryExtents ( display, d, &bShaped, &xbs, &ybs, &wbs, &hbs, &cShaped, &xcs, &ycs, &wcs, &hcs );
221 | return bShaped;
222 | }
223 |
224 | XserverRegion X11::findRegion( Window d ) {
225 | XserverRegion rootRegion = XFixesCreateRegionFromWindow( display, d, WindowRegionBounding );
226 | glm::vec4 rootgeo = getWindowGeometry( this, d );
227 | XFixesTranslateRegion( display, rootRegion, rootgeo.x, rootgeo.y ); // Regions are in respect to the root window by default.
228 | unionClippingRegions( rootRegion, d );
229 | unionBorderRegions( rootRegion, d );
230 | return rootRegion;
231 | }
232 |
233 | void X11::unionBorderRegions( XserverRegion rootRegion, Window d ) {
234 | glm::vec4 bordergeo = getWindowGeometry( this, d );
235 | XRectangle* rects = new XRectangle[1];
236 | rects[0].x = bordergeo.x;
237 | rects[0].y = bordergeo.y;
238 | rects[0].width = bordergeo.z;
239 | rects[0].height = bordergeo.w;
240 | XserverRegion borderRegionRect = XFixesCreateRegion( display, rects, 1 );
241 | XWindowAttributes attr;
242 | XGetWindowAttributes( display, d, &attr );
243 | rects[0].x += attr.border_width;
244 | rects[0].y += attr.border_width;
245 | rects[0].width -= attr.border_width*2;
246 | rects[0].height -= attr.border_width*2;
247 | XserverRegion regionRect = XFixesCreateRegion( display, rects, 1 );
248 | XFixesSubtractRegion( display, regionRect, borderRegionRect, regionRect );
249 | delete[] rects;
250 | XFixesUnionRegion( display, rootRegion, rootRegion, regionRect );
251 | XFixesDestroyRegion( display, regionRect );
252 | XFixesDestroyRegion( display, borderRegionRect );
253 | }
254 |
255 | void X11::unionClippingRegions( XserverRegion rootRegion, Window child ) {
256 | Window root, parent;
257 | Window* children;
258 | unsigned int num_children;
259 | XQueryTree( display, child, &root, &parent, &children, &num_children);
260 | for ( unsigned int i=0;ibytes_per_line * xim->height, IPC_CREAT | 0777);
303 | /* if the get succeeds */
304 | if (thing.shmid != -1) {
305 | /* set the params for the shm segment */
306 | thing.readOnly = False;
307 | thing.shmaddr = xim->data = (char*)shmat(thing.shmid, 0, 0);
308 | /* get the shm addr for this data chunk */
309 | if (xim->data != (char *)-1) {
310 | XShmAttach(display, &thing);
311 | XShmGetImage(display, draw, xim, localx, localy, AllPlanes);
312 | return xim;
313 | //shmdt(thing.shmaddr);
314 | }
315 | /* get failed - out of shm id's or shm segment too big ? */
316 | /* remove the shm id we created */
317 | shmctl(thing.shmid, IPC_RMID, 0);
318 | shmdt(thing.shmaddr);
319 | }
320 | return None;
321 | }
322 |
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "cxxopts.hpp"
10 | #include "x.hpp"
11 | #include "image.hpp"
12 |
13 | template
14 | static void split(const std::string &s, char delim, Out result) {
15 | std::stringstream ss;
16 | ss.str(s);
17 | std::string item;
18 | while (std::getline(ss, item, delim)) {
19 | *(result++) = item;
20 | }
21 | }
22 | static std::vector split(const std::string &s, char delim) {
23 | std::vector elems;
24 | split(s, delim, std::back_inserter(elems));
25 | return elems;
26 | }
27 |
28 | class MaimOptions {
29 | public:
30 | MaimOptions();
31 | std::string savepath;
32 | std::string format;
33 | Window window;
34 | Window parent;
35 | glm::vec4 geometry;
36 | float delay;
37 | int quality;
38 | bool select;
39 | bool hideCursor;
40 | bool geometryGiven;
41 | bool quiet;
42 | bool windowGiven;
43 | bool parentGiven;
44 | bool formatGiven;
45 | bool version;
46 | bool help;
47 | bool savepathGiven;
48 | };
49 |
50 | MaimOptions::MaimOptions() {
51 | savepath = "";
52 | window = None;
53 | parent = None;
54 | quality = 7;
55 | quiet = false;
56 | delay = 0;
57 | format = "png";
58 | version = false;
59 | help = false;
60 | select = false;
61 | parentGiven = false;
62 | hideCursor = false;
63 | geometryGiven = false;
64 | savepathGiven = false;
65 | windowGiven = false;
66 | formatGiven = false;
67 | }
68 |
69 | Window parseWindow( std::string win, X11* x11 ) {
70 | if ( win == "root" ) {
71 | return x11->root;
72 | }
73 | Window retwin;
74 | std::string::size_type sz;
75 | try {
76 | retwin = std::stoi(win,&sz);
77 | } catch ( ... ) {
78 | try {
79 | retwin = std::stoul(win,&sz,16);
80 | } catch ( ... ) {
81 | throw new std::invalid_argument("Unable to parse value " + win + " as a window. Expecting integer, hex, or `root`.");
82 | }
83 | }
84 | return retwin;
85 | }
86 |
87 | glm::vec4 parseColor( std::string value ) {
88 | std::string valuecopy = value;
89 | glm::vec4 found;
90 | std::string::size_type sz;
91 | try {
92 | found[0] = std::stof(value,&sz);
93 | value = value.substr(sz+1);
94 | found[1] = std::stof(value,&sz);
95 | value = value.substr(sz+1);
96 | found[2] = std::stof(value,&sz);
97 | if ( value.size() != sz ) {
98 | value = value.substr(sz+1);
99 | found[3] = std::stof(value,&sz);
100 | if ( value.size() != sz ) {
101 | throw "dur";
102 | }
103 | } else {
104 | found[3] = 1;
105 | }
106 | } catch ( ... ) {
107 | throw new std::invalid_argument("Unable to parse value `" + valuecopy + "` as a color. Should be in the format r,g,b or r,g,b,a. Like 1,1,1,1.");
108 | }
109 | return found;
110 | }
111 |
112 | glm::vec4 parseGeometry( std::string value ) {
113 | glm::vec4 found;
114 | std::string valuecopy = value;
115 | std::string::size_type sz = 0;
116 | glm::vec2 dim(0,0);
117 | int curpos = 0;
118 | glm::vec2 pos(0,0);
119 | try {
120 | if ( std::count(value.begin(), value.end(), '+') > 2 ) {
121 | throw "dur";
122 | }
123 | if ( std::count(value.begin(), value.end(), '-') > 2 ) {
124 | throw "dur";
125 | }
126 | if ( std::count(value.begin(), value.end(), 'x') > 1 ) {
127 | throw "dur";
128 | }
129 | while( value != "" ) {
130 | switch( value[0] ) {
131 | case 'x':
132 | dim.y = std::stof(value.substr(1),&sz);
133 | sz++;
134 | break;
135 | case '+':
136 | pos[curpos++] = std::stof(value.substr(1), &sz);
137 | sz++;
138 | break;
139 | case '-':
140 | pos[curpos++] = -std::stof(value.substr(1), &sz);
141 | sz++;
142 | break;
143 | default:
144 | dim.x = std::stof(value,&sz);
145 | break;
146 | }
147 | value = value.substr(sz);
148 | }
149 | } catch ( ... ) {
150 | throw new std::invalid_argument("Unable to parse value `" + valuecopy + "` as a geometry. Should be in the format wxh+x+y, +x+y, or wxh. Like 600x400+10+20.");
151 | }
152 | found.x = pos.x;
153 | found.y = pos.y;
154 | found.z = dim.x;
155 | found.w = dim.y;
156 | return found;
157 | }
158 |
159 | MaimOptions* getMaimOptions( cxxopts::Options& options, X11* x11 ) {
160 | MaimOptions* foo = new MaimOptions();
161 | foo->parentGiven = options.count("parent") > 0;
162 | if ( foo->parentGiven ) {
163 | foo->parent = parseWindow( options["parent"].as(), x11 );
164 | }
165 | foo->windowGiven = options.count("window") > 0;
166 | if ( foo->windowGiven ) {
167 | foo->window = parseWindow( options["window"].as(), x11 );
168 | }
169 | foo->geometryGiven = options.count("geometry") > 0;
170 | if ( foo->geometryGiven ) {
171 | foo->geometry = parseGeometry( options["geometry"].as() );
172 | }
173 | if ( options.count( "delay" ) > 0 ) {
174 | foo->delay = options["delay"].as();
175 | }
176 | if ( options.count( "hidecursor" ) > 0 ) {
177 | foo->hideCursor = options["hidecursor"].as();
178 | }
179 | if ( options.count( "select" ) > 0 ) {
180 | foo->select = options["select"].as();
181 | }
182 | if ( options.count( "version" ) > 0 ) {
183 | foo->version = options["version"].as();
184 | }
185 | if ( options.count( "help" ) > 0 ) {
186 | foo->help = options["help"].as();
187 | }
188 | if ( options.count( "quiet" ) > 0 ) {
189 | foo->quiet = options["quiet"].as();
190 | }
191 | if ( options.count( "format" ) > 0 ) {
192 | foo->quiet = options["quiet"].as();
193 | }
194 | foo->formatGiven = options.count("format") > 0;
195 | if ( foo->formatGiven ) {
196 | foo->format = options["format"].as();
197 | if ( foo->format != "png" && foo->format != "jpg" && foo->format != "jpeg" ) {
198 | throw new std::invalid_argument("Unknown format type: `" + foo->format + "`, only `png` or `jpg` is allowed." );
199 | }
200 | }
201 | if ( options.count( "quality" ) > 0 ) {
202 | foo->quality = options["quality"].as();
203 | if ( foo->quality > 10 || foo->quality < 1 ) {
204 | throw new std::invalid_argument("Quality argument must be between 1 and 10");
205 | }
206 | }
207 | auto& positional = options["positional"].as>();
208 | foo->savepathGiven = positional.size() > 0;
209 | //std::cerr << positional[0] << "\n";
210 | if ( foo->savepathGiven ) {
211 | foo->savepath = positional[0];
212 | }
213 | return foo;
214 | }
215 |
216 | slop::SlopOptions* getSlopOptions( cxxopts::Options& options ) {
217 | slop::SlopOptions* foo = new slop::SlopOptions();
218 | if ( options.count( "bordersize" ) > 0 ) {
219 | foo->border = options["bordersize"].as();
220 | }
221 | if ( options.count( "padding" ) > 0 ) {
222 | foo->padding = options["padding"].as();
223 | }
224 | if ( options.count( "tolerance" ) > 0 ) {
225 | foo->tolerance = options["tolerance"].as();
226 | }
227 | glm::vec4 color = glm::vec4( foo->r, foo->g, foo->b, foo->a );
228 | if ( options.count( "color" ) > 0 ) {
229 | color = parseColor( options["color"].as() );
230 | }
231 | foo->r = color.r;
232 | foo->g = color.g;
233 | foo->b = color.b;
234 | foo->a = color.a;
235 | if ( options.count( "nokeyboard" ) > 0 ) {
236 | foo->nokeyboard = options["nokeyboard"].as();
237 | }
238 | if ( options.count( "noopengl" ) > 0 ) {
239 | foo->noopengl = options["noopengl"].as();
240 | }
241 | if ( options.count( "xdisplay" ) > 0 ) {
242 | std::string xdisplay = options["xdisplay"].as();
243 | char* cxdisplay = new char[xdisplay.length()+1];
244 | memcpy( cxdisplay, xdisplay.c_str(), xdisplay.length() );
245 | cxdisplay[xdisplay.length()]='\0';
246 | foo->xdisplay = cxdisplay;
247 | }
248 | if ( options.count( "shader" ) > 0 ) {
249 | std::string shaders = options["shader"].as();
250 | char* cshaders = new char[shaders.length()+1];
251 | memcpy( cshaders, shaders.c_str(), shaders.length() );
252 | cshaders[shaders.length()]='\0';
253 | foo->shaders = cshaders;
254 | }
255 | if ( options.count( "quiet" ) > 0 ) {
256 | foo->quiet = options["quiet"].as();
257 | }
258 | if ( options.count( "highlight" ) > 0 ) {
259 | foo->highlight = options["highlight"].as();
260 | }
261 | if ( options.count( "nodecorations" ) > 0 ) {
262 | foo->nodecorations = options["nodecorations"].as();
263 | if ( foo->nodecorations < 0 || foo->nodecorations > 2 ) {
264 | throw new std::invalid_argument( "--nodecorations must be between 0 and 2. Or be used as a flag." );
265 | }
266 | }
267 | return foo;
268 | }
269 |
270 | void help() {
271 | std::cout << "maim - make image\n";
272 | std::cout << "\n";
273 | std::cout << "SYNOPSIS\n";
274 | std::cout << " maim [OPTIONS] [FILEPATH]\n";
275 | std::cout << "\n";
276 | std::cout << "DESCRIPTION\n";
277 | std::cout << " maim (make image) is an utility that takes a screenshot of your desktop,\n";
278 | std::cout << " and encodes a png or jpg image of it. By default it outputs the encoded\n";
279 | std::cout << " image data directly to standard output.\n";
280 | std::cout << "\n";
281 | std::cout << "OPTIONS\n";
282 | std::cout << " -h, --help\n";
283 | std::cout << " Print help and exit.\n";
284 | std::cout << "\n";
285 | std::cout << " -v, --version\n";
286 | std::cout << " Print version and exit.\n";
287 | std::cout << "\n";
288 | std::cout << " -x, --xdisplay=hostname:number.screen_number\n";
289 | std::cout << " Sets the xdisplay to use.\n";
290 | std::cout << "\n";
291 | std::cout << " -f, --format=STRING\n";
292 | std::cout << " Sets the desired output format, by default maim will attempt to\n";
293 | std::cout << " determine the desired output format automatically from the output\n";
294 | std::cout << " file. If that fails it defaults to a lossless png format. Cur‐\n";
295 | std::cout << " rently only supports `png` or `jpg`.\n";
296 | std::cout << "\n";
297 | std::cout << " -i, --window=INT\n";
298 | std::cout << " Sets the desired window to capture, defaults to the root window.\n";
299 | std::cout << "\n";
300 | std::cout << " -g, --geometry=GEOMETRY\n";
301 | std::cout << " Sets the region to capture, uses local coordinates from the given\n";
302 | std::cout << " window. So -g10x30-5+0 would represent the rectangle wxh+x+y where\n";
303 | std::cout << " w=10, h=30, x=-5, and y=0. x and y are the upper left location of\n";
304 | std::cout << " this rectangle.\n";
305 | std::cout << "\n";
306 | std::cout << " -d, --delay=FLOAT\n";
307 | std::cout << " Sets the time in seconds to wait before taking a screenshot.\n";
308 | std::cout << " Prints a simple message to show how many seconds are left before a\n";
309 | std::cout << " screenshot is taken. See --quiet for muting this message.\n";
310 | std::cout << "\n";
311 | std::cout << " -u, --hidecursor\n";
312 | std::cout << " By default maim super-imposes the cursor onto the image, you can\n";
313 | std::cout << " disable that behavior with this flag.\n";
314 | std::cout << "\n";
315 | std::cout << " -m, --quality\n";
316 | std::cout << " An integer from 1 to 10 that determines the compression quality. 1\n";
317 | std::cout << " is the highest (and lossiest) compression available for the pro‐\n";
318 | std::cout << " vided format. For example a setting of `1` with png (a lossless\n";
319 | std::cout << " format) would increase filesize and speed up encoding dramatical-\n";
320 | std::cout << " ly. While a setting of `1` on a jpeg would create a pixel mush.\n";
321 | std::cout << "\n";
322 | std::cout << " -s, --select\n";
323 | std::cout << " Enables an interactive selection mode where you may select the\n";
324 | std::cout << " desired region or window before a screenshot is captured. Uses the\n";
325 | std::cout << " settings below to determine the visuals and settings of slop.\n";
326 | std::cout << "\n";
327 | std::cout << " -w, --parent=WINDOW\n";
328 | std::cout << " By default, maim assumes the --geometry values are in respect to\n";
329 | std::cout << " the provided --window (or root if not provided). This parameter\n";
330 | std::cout << " overrides this behavior by making the geometry be in respect to\n";
331 | std::cout << " whatever window you provide to --parent. Allows for an integer,\n";
332 | std::cout << " hex, or `root` for input.\n";
333 | std::cout << "\n";
334 | std::cout << "SLOP OPTIONS\n";
335 | std::cout << " -b, --bordersize=FLOAT\n";
336 | std::cout << " Sets the selection rectangle's thickness.\n";
337 | std::cout << "\n";
338 | std::cout << " -p, --padding=FLOAT\n";
339 | std::cout << " Sets the padding size for the selection, this can be negative.\n";
340 | std::cout << "\n";
341 | std::cout << " -t, --tolerance=FLOAT\n";
342 | std::cout << " How far in pixels the mouse can move after clicking, and still be\n";
343 | std::cout << " detected as a normal click instead of a click-and-drag. Setting\n";
344 | std::cout << " this to 0 will disable window selections. Alternatively setting it\n";
345 | std::cout << " to 9999999 would force a window selection.\n";
346 | std::cout << "\n";
347 | std::cout << " -c, --color=FLOAT,FLOAT,FLOAT,FLOAT\n";
348 | std::cout << " Sets the selection rectangle's color. Supports RGB or RGBA input.\n";
349 | std::cout << " Depending on the system's window manager/OpenGL support, the opac‐\n";
350 | std::cout << " ity may be ignored.\n";
351 | std::cout << "\n";
352 | std::cout << " -r, --shader=STRING\n";
353 | std::cout << " This sets the vertex shader, and fragment shader combo to use when\n";
354 | std::cout << " drawing the final framebuffer to the screen. This obviously only\n";
355 | std::cout << " works when OpenGL is enabled. The shaders are loaded from ~/.con‐\n";
356 | std::cout << " fig/maim. See https://github.com/naelstrof/slop for more informa‐\n";
357 | std::cout << " tion on how to create your own shaders.\n";
358 | std::cout << "\n";
359 | std::cout << " -n, --nodecorations=INT\n";
360 | std::cout << " Sets the level of aggressiveness when trying to remove window\n";
361 | std::cout << " decorations. `0' is off, `1' will try lightly to remove decora‐\n";
362 | std::cout << " tions, and `2' will recursively descend into the root tree until\n";
363 | std::cout << " it gets the deepest available visible child under the mouse.\n";
364 | std::cout << " Defaults to `0'.\n";
365 | std::cout << "\n";
366 | std::cout << " -l, --highlight\n";
367 | std::cout << " Instead of outlining a selection, maim will highlight it instead.\n";
368 | std::cout << " This is particularly useful if the color is set to an opacity\n";
369 | std::cout << " lower than 1.\n";
370 | std::cout << "\n";
371 | std::cout << " -q, --quiet\n";
372 | std::cout << " Disable any unnecessary cerr output. Any warnings or info simply\n";
373 | std::cout << " won't print.\n";
374 | std::cout << "\n";
375 | std::cout << " -k, --nokeyboard\n";
376 | std::cout << " Disables the ability to cancel selections with the keyboard.\n";
377 | std::cout << "\n";
378 | std::cout << " -o, --noopengl\n";
379 | std::cout << " Disables graphics hardware acceleration.\n";
380 | std::cout << "\n";
381 | std::cout << "EXAMPLES\n";
382 | std::cout << " Screenshot the active window and save it to the clipboard for quick past‐\n";
383 | std::cout << " ing.\n";
384 | std::cout << "\n";
385 | std::cout << " maim -i $(xdotool getactivewindow) | xclip -selection clipboard -t image/png\n";
386 | std::cout << "\n";
387 | std::cout << " Save a desktop screenshot with a unique ordered timestamp in the Pictures\n";
388 | std::cout << " folder.\n";
389 | std::cout << "\n";
390 | std::cout << " maim ~/Pictures/$(date +%s).png\n";
391 | std::cout << "\n";
392 | std::cout << " Prompt for a region to screenshot. Add a fancy shadow to it, then save it\n";
393 | std::cout << " to shadow.png.\n";
394 | std::cout << "\n";
395 | std::cout << " maim -s | convert - \\( +clone -background black -shadow 80x3+5+5 \\) +swap \\\n";
396 | std::cout << " -background none -layers merge +repage shadow.png\n";
397 | }
398 |
399 | int app( int argc, char** argv ) {
400 | // Use cxxopts to parse options, we pass them into a MaimOptions and SlopOptions object so we can swap out cxxopts if it's bad or whatever.
401 | cxxopts::Options options("maim", "Screenshot application.");
402 | options.add_options()
403 | ("h,help", "Print help and exit.")
404 | ("v,version", "Print version and exit.")
405 | ("x,xdisplay", "Sets the xdisplay to use", cxxopts::value())
406 | ("f,format", "Sets the desired output format, by default maim will attempt to determine the desired output format automatically from the output file. If that fails it defaults to a lossless png format. Currently only supports `png` or `jpg`.", cxxopts::value())
407 | ("i,window", "Sets the desired window to capture, defaults to the root window. Allows for an integer, hex, or `root` for input.", cxxopts::value())
408 | ("g,geometry", "Sets the region to capture, uses local coordinates from the given window. So -g10x30-5+0 would represent the rectangle wxh+x+y where w=10, h=30, x=-5, and y=0. x and y are the upper left location of this rectangle.", cxxopts::value())
409 | ("w,parent", "By default, maim assumes the --geometry values are in respect to the provided --window (or root if not provided). This parameter overrides this behavior by making the geometry be in respect to whatever window you provide to --parent. Allows for an integer, hex, or `root` for input.", cxxopts::value())
410 | ("d,delay", "Sets the time in seconds to wait before taking a screenshot. Prints a simple message to show how many seconds are left before a screenshot is taken. See --quiet for muting this message.", cxxopts::value()->implicit_value("5"))
411 | ("u,hidecursor", "By default maim super-imposes the cursor onto the image, you can disable that behavior with this flag.")
412 | ("m,quality", "An integer from 1 to 10 that determines the compression quality. 1 is the highest (and lossiest) compression available for the provided format. For example a setting of `1` with png (a loss‐ less format) would increase filesize and decrease decoding time. While a setting of `1` on a jpeg would create a pixel mush.", cxxopts::value())
413 | ("s,select", "Enables an interactive selection mode where you may select the desired region or window before a screenshot is captured. Uses the settings below to determine the visuals and settings of slop.")
414 | ("b,bordersize", "Sets the selection rectangle's thickness.", cxxopts::value())
415 | ("p,padding", "Sets the padding size for the selection, this can be negative.", cxxopts::value())
416 | ("t,tolerance", "How far in pixels the mouse can move after clicking, and still be detected as a normal click instead of a click-and-drag. Setting this to 0 will disable window selections. Alternatively setting it to 9999999 would force a window selection.", cxxopts::value())
417 | ("c,color", "Sets the selection rectangle's color. Supports RGB or RGBA input. Depending on the system's window manager/OpenGL support, the opacity may be ignored.", cxxopts::value())
418 | ("r,shader", "This sets the vertex shader, and fragment shader combo to use when drawing the final framebuffer to the screen. This obviously only works when OpenGL is enabled. The shaders are loaded from ~/.config/maim. See https://github.com/naelstrof/slop for more information on how to create your own shaders.", cxxopts::value())
419 | ("n,nodecorations", "Sets the level of aggressiveness when trying to remove window decroations. `0' is off, `1' will try lightly to remove decorations, and `2' will recursively descend into the root tree until it gets the deepest available visible child under the mouse. Defaults to `0'.", cxxopts::value()->implicit_value("1"))
420 | ("l,highlight", "Instead of outlining a selection, maim will highlight it instead. This is particularly useful if the color is set to an opacity lower than 1.")
421 | ("q,quiet", "Disable any unnecessary cerr output. Any warnings or info simply won't print.")
422 | ("k,nokeyboard", "Disables the ability to cancel selections with the keyboard.")
423 | ("o,noopengl", "Disables graphics hardware acceleration.")
424 | ("positional", "Positional parameters", cxxopts::value>())
425 | ;
426 | options.parse_positional("positional");
427 | options.parse(argc, argv);
428 |
429 | slop::SlopOptions* slopOptions = getSlopOptions( options );
430 | // Boot up x11
431 | X11* x11 = new X11(slopOptions->xdisplay);
432 | MaimOptions* maimOptions = getMaimOptions( options, x11 );
433 | if ( maimOptions->version ) {
434 | std::cout << MAIM_VERSION << "\n";
435 | return 0;
436 | }
437 | if ( maimOptions->help ) {
438 | help();
439 | return 0;
440 | }
441 | slop::SlopSelection selection(0,0,0,0,0,true);
442 |
443 | if ( maimOptions->select ) {
444 | if ( maimOptions->windowGiven || maimOptions->parentGiven || maimOptions->geometryGiven ) {
445 | throw new std::invalid_argument( "Interactive mode (--select) doesn't support the following parameters: --window, --parent, --geometry." );
446 | }
447 | selection = SlopSelect(slopOptions);
448 | if ( selection.cancelled ) {
449 | if ( !maimOptions->quiet ) {
450 | std::cerr << "Selection was cancelled by keystroke or right-click.\n";
451 | }
452 | return 1;
453 | }
454 | }
455 |
456 | if ( !maimOptions->formatGiven && maimOptions->savepathGiven && maimOptions->savepath.find_last_of(".") != std::string::npos ) {
457 | maimOptions->format = maimOptions->savepath.substr(maimOptions->savepath.find_last_of(".")+1);
458 | if ( maimOptions->format != "png" && maimOptions->format != "jpg" && maimOptions->format != "jpeg" ) {
459 | throw new std::invalid_argument("Unknown format type: `" + maimOptions->format + "`, only `png` or `jpg` is allowed." );
460 | }
461 | }
462 | if ( !maimOptions->windowGiven ) {
463 | maimOptions->window = x11->root;
464 | } else {
465 | XWindowAttributes attr;
466 | XGetWindowAttributes(x11->display, maimOptions->window, &attr);
467 | if (attr.backing_store == NotUseful && attr.width == 1 && attr.height == 1) {
468 | Window root, parent;
469 | parent = None;
470 | Window* children;
471 | unsigned int nchildren;
472 | Window selectedWindow;
473 | XQueryTree( x11->display, maimOptions->window, &root, &parent, &children, &nchildren );
474 | if ( parent != None ) {
475 | maimOptions->window = parent;
476 | }
477 | }
478 | }
479 | if ( !maimOptions->parentGiven ) {
480 | maimOptions->parent = maimOptions->window;
481 | } else if ( !maimOptions->geometryGiven ) {
482 | throw new std::invalid_argument( "Relative mode (--parent) requires --geometry." );
483 | }
484 | if ( !maimOptions->geometryGiven ) {
485 | Window junk;
486 | glm::ivec4 geometry = getWindowGeometry( x11, maimOptions->window );
487 | XTranslateCoordinates(x11->display, x11->root, maimOptions->window, geometry.x, geometry.y, &geometry.x, &geometry.y, &junk);
488 | maimOptions->geometry = geometry;
489 | }
490 |
491 | if ( !maimOptions->select ) {
492 | selection.x = maimOptions->geometry.x;
493 | selection.y = maimOptions->geometry.y;
494 | selection.w = maimOptions->geometry.z;
495 | selection.h = maimOptions->geometry.w;
496 | selection.id = maimOptions->window;
497 | }
498 | std::ostream* out;
499 | if ( maimOptions->savepathGiven ) {
500 | std::ofstream* file = new std::ofstream();
501 | file->open(maimOptions->savepath.c_str());
502 | if ( !file->is_open() ) {
503 | throw new std::runtime_error( "Failed to open file for writing: `" + maimOptions->savepath + "`." );
504 | }
505 | out = file;
506 | } else {
507 | out = &std::cout;
508 | }
509 |
510 | // Then we grab the pixel buffer of the provided window/selection.
511 | if ( maimOptions->delay ) {
512 | if ( !maimOptions->quiet ) {
513 | std::cerr << "Snapshotting in...";
514 | }
515 | while ( maimOptions->delay > 0 ) {
516 | std::this_thread::sleep_for(std::chrono::milliseconds(glm::clamp(glm::min(1000,(int)(maimOptions->delay*1000)),0,1000)));
517 | maimOptions->delay-=1;
518 | if ( !maimOptions->quiet ) {
519 | if ( maimOptions->delay <= 0 ) {
520 | std::cerr << "☺";
521 | } else {
522 | std::cerr << maimOptions->delay << " ";
523 | }
524 | }
525 | }
526 | if ( !maimOptions->quiet ) {
527 | std::cerr << "\n";
528 | }
529 | }
530 | // Localize to our parent
531 | int px, py;
532 | Window junk;
533 | XTranslateCoordinates( x11->display, maimOptions->parent, x11->root, (int)selection.x, (int)selection.y, &px, &py, &junk);
534 | glm::ivec2 imageloc;
535 | // Snapshot the image
536 | XImage* image = x11->getImage( selection.id, px, py, selection.w, selection.h, imageloc);
537 | if ( maimOptions->format == "png" ) {
538 | // Convert it to an ARGB format, clipping it to the selection.
539 | ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), 4, x11 );
540 | if ( !maimOptions->hideCursor ) {
541 | convert.blendCursor( x11 );
542 | }
543 | // Mask it if we're taking a picture of root
544 | if ( selection.id == x11->root ) {
545 | convert.mask(x11);
546 | }
547 | // Then output it in the desired format.
548 | convert.writePNG(*out, maimOptions->quality );
549 | } else if ( maimOptions->format == "jpg" || maimOptions->format == "jpeg" ) {
550 | // Convert it to a RGB format, clipping it to the selection.
551 | ARGBImage convert(image, imageloc, glm::vec4(px, py, selection.w, selection.h), 3, x11 );
552 | if ( !maimOptions->hideCursor ) {
553 | convert.blendCursor( x11 );
554 | }
555 | // Mask it if we're taking a picture of root
556 | if ( selection.id == x11->root ) {
557 | convert.mask(x11);
558 | }
559 | // Then output it in the desired format.
560 | convert.writeJPEG(*out, maimOptions->quality );
561 | }
562 | XDestroyImage( image );
563 |
564 | if ( maimOptions->savepathGiven ) {
565 | std::ofstream* file = (std::ofstream*)out;
566 | file->close();
567 | delete (std::ofstream*)out;
568 | }
569 | delete x11;
570 | delete maimOptions;
571 | if ( options.count( "xdisplay" ) > 0 ) {
572 | delete slopOptions->xdisplay;
573 | }
574 | if ( options.count( "shader" ) > 0 ) {
575 | delete slopOptions->shaders;
576 | }
577 | delete slopOptions;
578 |
579 | return 0;
580 | }
581 |
582 | int main( int argc, char** argv ) {
583 | try {
584 | return app( argc, argv );
585 | } catch( std::exception* e ) {
586 | std::cerr << "Maim encountered an error:\n" << e->what() << "\n";
587 | return 1;
588 | } // let the operating system handle any other kind of exception.
589 | return 1;
590 | }
591 |
--------------------------------------------------------------------------------
/src/cxxopts.hpp:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2014, 2015, 2016 Jarryd Beck
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 | */
24 |
25 | #ifndef CXX_OPTS_HPP
26 | #define CXX_OPTS_HPP
27 |
28 | #if defined(__GNUC__)
29 | #pragma GCC diagnostic push
30 | #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
31 | #endif
32 |
33 | #include
34 | #include
35 | #include
36 | #include