1 /* main.cpp: parses options, runs slop, prints results.
2  *
3  * Copyright (C) 2014: Dalton Nell, Slop Contributors (https://github.com/naelstrof/slop/graphs/contributors).
4  *
5  * This file is part of Slop.
6  *
7  * Slop 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  * Slop 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 Slop.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <iostream>
22 #include <sstream>
23 #include <string>
24 #include <vector>
25 #include <glm/glm.hpp>
26 
27 #include "slop.hpp"
28 #include "cxxopts.hpp"
29 
30 using namespace slop;
31 
parseColor(std::string value)32 glm::vec4 parseColor( std::string value ) {
33     std::string valuecopy = value;
34     glm::vec4 found;
35     std::string::size_type sz;
36     try {
37         found[0] = std::stof(value,&sz);
38         value = value.substr(sz+1);
39         found[1] = std::stof(value,&sz);
40         value = value.substr(sz+1);
41         found[2] = std::stof(value,&sz);
42         if ( value.size() != sz ) {
43             value = value.substr(sz+1);
44             found[3] = std::stof(value,&sz);
45             if ( value.size() != sz ) {
46                 throw std::runtime_error("dur");
47             }
48         } else {
49             found[3] = 1;
50         }
51     } catch ( ... ) {
52         throw 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.");
53     }
54     return found;
55 }
56 
getOptions(cxxopts::Options & options)57 SlopOptions* getOptions( cxxopts::Options& options ) {
58     slop::SlopOptions* foo = new slop::SlopOptions();
59     if ( options.count( "bordersize" ) > 0 ) {
60         foo->border = options["bordersize"].as<float>();
61     }
62     if ( options.count( "padding" ) > 0 ) {
63         foo->padding = options["padding"].as<float>();
64     }
65     if ( options.count( "tolerance" ) > 0 ) {
66         foo->tolerance = options["tolerance"].as<float>();
67     }
68     glm::vec4 color = glm::vec4( foo->r, foo->g, foo->b, foo->a );
69     if ( options.count( "color" ) > 0 ) {
70         color = parseColor( options["color"].as<std::string>() );
71     }
72     foo->r = color.r;
73     foo->g = color.g;
74     foo->b = color.b;
75     foo->a = color.a;
76     if ( options.count( "nokeyboard" ) > 0 ) {
77         foo->nokeyboard = options["nokeyboard"].as<bool>();
78     }
79     if ( options.count( "xdisplay" ) > 0 ) {
80         std::string xdisplay = options["xdisplay"].as<std::string>();
81         char* cxdisplay = new char[xdisplay.length()+1];
82         memcpy( cxdisplay, xdisplay.c_str(), xdisplay.length() );
83         cxdisplay[xdisplay.length()]='\0';
84         foo->xdisplay = cxdisplay;
85     }
86     if ( options.count( "shader" ) > 0 ) {
87         std::string shaders = options["shader"].as<std::string>();
88         char* cshaders = new char[shaders.length()+1];
89         memcpy( cshaders, shaders.c_str(), shaders.length() );
90         cshaders[shaders.length()]='\0';
91         foo->shaders = cshaders;
92     }
93     if ( options.count( "noopengl" ) > 0 ) {
94         foo->noopengl = options["noopengl"].as<bool>();
95     }
96     if ( options.count( "highlight" ) > 0 ) {
97         foo->highlight = options["highlight"].as<bool>();
98     }
99     if ( options.count( "nodrag" ) > 0 ) {
100         foo->nodrag = options["nodrag"].as<bool>();
101     }
102     if ( options.count( "quiet" ) > 0 ) {
103         foo->quiet = options["quiet"].as<bool>();
104     }
105     if ( options.count( "nodecorations" ) > 0 ) {
106         foo->nodecorations = options["nodecorations"].as<int>();
107         if ( foo->nodecorations < 0 || foo->nodecorations > 2 ) {
108             throw std::invalid_argument( "--nodecorations must be between 0 and 2. Or be used as a flag." );
109         }
110     }
111     return foo;
112 }
113 
formatOutput(std::string input,SlopSelection selection)114 std::string formatOutput( std::string input, SlopSelection selection ) {
115     std::stringstream output;
116     for( unsigned int i=0;i<input.length();i++) {
117         if ( input[i] == '%' ) {
118             if ( input.length() <= i+1 ) {
119                 throw std::invalid_argument( "Expected character after `%`, got END." );
120             }
121             switch( input[i+1] ) {
122                 case 'x':
123                 case 'X': output << round(selection.x); break;
124                 case 'y':
125                 case 'Y': output << round(selection.y); break;
126                 case 'w':
127                 case 'W': output << round(selection.w); break;
128                 case 'c':
129                 case 'C': output << selection.cancelled; break;
130                 case 'h':
131                 case 'H': output << round(selection.h); break;
132                 case 'g':
133                 case 'G': output << round(selection.w) << "x" << round(selection.h)
134                           << "+" << round(selection.x) << "+" << round(selection.y); break;
135                 case 'i':
136                 case 'I': output << selection.id; break;
137                 case '%': output << "%"; break;
138                 default: throw std::invalid_argument( std::string()+"Expected x, y, w, h, g, i, c, or % after % in format. Got `" + input[i+1] + "`." );
139              }
140             i++;
141             continue;
142         }
143         output << input[i];
144     }
145     return output.str();
146 }
147 
printHelp()148 void printHelp() {
149     std::cout << "slop " << SLOP_VERSION << "\n";
150     std::cout << "\n";
151     std::cout << "Copyright (C) 2017 Dalton Nell, Slop Contributors\n";
152     std::cout << "(https://github.com/naelstrof/slop/graphs/contributors)\n";
153     std::cout << "Usage: slop [options]\n";
154     std::cout << "\n";
155     std::cout << "slop (Select Operation) is an application that queries for a selection from the\n";
156     std::cout << "user and prints the region to stdout.\n";
157     std::cout << "\n";
158     std::cout << "-h, --help                    Print help and exit\n";
159     std::cout << "-v, --version                 Print version and exit\n";
160     std::cout << "Options\n";
161     std::cout << "  -x, --xdisplay=hostname:number.screen_number\n";
162     std::cout << "                                Sets the x display.\n";
163     std::cout << "  -k, --nokeyboard              Disables the ability to cancel selections with\n";
164     std::cout << "                                  the keyboard.  (default=off)\n";
165     std::cout << "  -b, --bordersize=FLOAT        Set the selection rectangle's thickness.\n";
166     std::cout << "                                  (default=`1')\n";
167     std::cout << "  -p, --padding=FLOAT           Set the padding size of the selection. Can be\n";
168     std::cout << "                                  negative.  (default=`0')\n";
169     std::cout << "  -t, --tolerance=FLOAT         How far in pixels the mouse can move after\n";
170     std::cout << "                                  clicking and still be detected as a normal\n";
171     std::cout << "                                  click instead of a click and drag. Setting\n";
172     std::cout << "                                  this to 0 will disable window selections.\n";
173     std::cout << "                                  Alternatively setting it to 999999 would.\n";
174     std::cout << "                                  only allow for window selections.\n";
175     std::cout << "                                  (default=`2')\n";
176     std::cout << "  -D, --nodrag                  Select region with two clicks instead of\n";
177     std::cout << "                                  click and drag\n";
178     std::cout << "  -c, --color=FLOAT,FLOAT,FLOAT,FLOAT\n";
179     std::cout << "                                Set the selection rectangle's color. Supports\n";
180     std::cout << "                                  RGB or RGBA values.\n";
181     std::cout << "                                  (default=`0.5,0.5,0.5,1')\n";
182     std::cout << "  -n, --nodecorations=INT       Attempt to select child windows in order to\n";
183     std::cout << "                                  avoid window decorations. Setting this to\n";
184     std::cout << "                                  1 will enable a light attempt to\n";
185     std::cout << "                                  remove decorations. Setting this to 2 will\n";
186     std::cout << "                                  enable aggressive decoration removal.\n";
187     std::cout << "                                  Supplying slop with just `-n` is\n";
188     std::cout << "                                  equivalent to supplying `-n1`.\n";
189     std::cout << "                                  (default=`0')\n";
190     std::cout << "  -D, --nodrag       	          Allows you to click twice to indicate a \n";
191     std::cout << "                                  selection, rather than click-dragging.\n";
192     std::cout << "  -q, --quiet                   Disable any unnecessary cerr output. Any\n";
193     std::cout << "                                  warnings simply won't print.\n";
194     std::cout << "  -l, --highlight               Instead of outlining selections, slop\n";
195     std::cout << "                                  highlights it. This is only useful when\n";
196     std::cout << "                                  --color is set to a transparent color.\n";
197     std::cout << "                                  (default=off)\n";
198     std::cout << "  -r, --shader=STRING           Sets the shader to load and use from\n";
199     std::cout << "                                  ~/.config/slop/\n";
200     std::cout << "  -f, --format=STRING           Set the output format string. Format specifiers\n";
201     std::cout << "                                  are %x (x offset), %y (y offset), %w (width),\n";
202     std::cout << "                                  %h (height), %i (window id),\n";
203     std::cout << "                                  %g (geometry - `%wx%h+%x+%y\'),\n";
204     std::cout << "                                  %c (1 if cancelled, 0 otherwise),\n";
205     std::cout << "                                  and %% for a literal percent sign.\n";
206     std::cout << "                                  (default=`%g')\n";
207     std::cout << "  -o, --noopengl                Disable graphics acceleration.\n";
208     std::cout << "Examples\n";
209     std::cout << "    $ # Gray, thick, transparent border for maximum visiblity.\n";
210     std::cout << "    $ slop -b 20 -c 0.5,0.5,0.5,0.8\n";
211     std::cout << "\n";
212     std::cout << "    $ # Remove window decorations.\n";
213     std::cout << "    $ slop --nodecorations\n";
214     std::cout << "\n";
215     std::cout << "    $ # Disable window selections. Useful for selecting individual pixels.\n";
216     std::cout << "    $ slop -t 0\n";
217     std::cout << "\n";
218     std::cout << "    $ # Classic Windows XP selection.\n";
219     std::cout << "    $ slop -l -c 0.3,0.4,0.6,0.4\n";
220     std::cout << "\n";
221     std::cout << "    $ # Read slop output for use in scripts.\n";
222     std::cout << "    $ read -r X Y W H G ID < <(slop -f '%x %y %w %h %g %i')\n";
223     std::cout << "\n";
224     std::cout << "Tips\n";
225     std::cout << "    * If you don't like a selection: you can cancel it by right-clicking\n";
226     std::cout << "regardless of which options are enabled or disabled for slop.\n";
227     std::cout << "    * You can move the selection by holding down the space bar!\n";
228     std::cout << "    * If slop doesn't seem to select a window accurately, the problem could be\n";
229     std::cout << "because of decorations getting in the way. Try enabling the --nodecorations\n";
230     std::cout << "flag.\n";
231 }
232 
app(int argc,char ** argv)233 int app( int argc, char** argv ) {
234     cxxopts::Options options("maim", "Screenshot application.");
235     options.add_options()
236     ("h,help", "Print help and exit.")
237     ("v,version", "Print version and exit.")
238     ("x,xdisplay", "Sets the xdisplay to use", cxxopts::value<std::string>())
239     ("f,format", "Sets the output format for slop. Format specifiers are  %x, %y, %w, %h, %i, %c, and %g. If actual percentage signs are desired in output, use a double percentage sign like so `%%`.", cxxopts::value<std::string>())
240     ("b,bordersize", "Sets the selection rectangle's thickness.", cxxopts::value<float>())
241     ("p,padding", "Sets the padding size for the selection, this can be negative.", cxxopts::value<float>())
242     ("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<float>())
243     ("D,nodrag", "Select region with two clicks instead of click and drag")
244     ("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<std::string>())
245     ("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<std::string>())
246     ("n,nodecorations", "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'.", cxxopts::value<int>()->implicit_value("1"))
247     ("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.")
248     ("q,quiet", "Disable any unnecessary cerr output. Any warnings or info simply won't print.")
249     ("k,nokeyboard", "Disables the ability to cancel selections with the keyboard.")
250     ("o,noopengl", "Disables graphics hardware acceleration.")
251     ("positional", "Positional parameters", cxxopts::value<std::vector<std::string>>())
252     ;
253     options.parse_positional("positional");
254     options.parse(argc, argv);
255     // Options just validates all of our input from argv
256     auto& positional = options["positional"].as<std::vector<std::string>>();
257     if ( positional.size() > 0 ) {
258         throw std::invalid_argument("Unexpected positional argument: " + positional[0]);
259     }
260     bool help = false;
261     if ( options.count( "help" ) > 0 ) {
262         help = options["help"].as<bool>();
263     }
264     if ( help ) {
265         printHelp();
266         return 0;
267     }
268     bool version = false;
269     if ( options.count( "version" ) > 0 ) {
270         version = options["version"].as<bool>();
271     }
272     if ( version ) {
273         std::cout << SLOP_VERSION << "\n";
274         return 0;
275     }
276     // We then parse the options into something slop can understand.
277     SlopOptions* parsedOptions = getOptions( options );
278 
279     // We want to validate our format option if we got one, we do that by just doing a dry run
280     // on a fake selection.
281     SlopSelection selection(0,0,0,0,0,true);
282     std::string format;
283     bool gotFormat = options.count( "format" ) > 0;
284     if ( gotFormat ) {
285         format = options["format"].as<std::string>();
286         formatOutput( format, selection );
287     }
288 
289     // Finally we do the real selection.
290     selection = SlopSelect(parsedOptions);
291 
292     bool quiet = parsedOptions->quiet;
293     // Here we're done with the parsed option data.
294     delete parsedOptions;
295     // We know if we cancelled or not
296     if ( selection.cancelled ) {
297         if ( !quiet ) {
298             std::cerr << "Selection was cancelled by keystroke or right-click.\n";
299         }
300         return 1;
301     }
302     // If we recieved a format option, we output the specified output.
303     if ( gotFormat ) {
304         std::cout << formatOutput( format, selection );
305         return 0;
306     }
307     std::cout << formatOutput( "%g\n", selection );
308     return 0;
309 }
310 
main(int argc,char ** argv)311 int main( int argc, char** argv ) {
312     try {
313         return app( argc, argv );
314     } catch( std::exception& e ) {
315         std::cerr << "Slop encountered an error:\n" << e.what() << "\n";
316         return 1;
317     } // let the operating system handle any other kind of exception.
318     return 1;
319 }
320