1 /*
2  * Copyright (c) 2006 Markus Fisch
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of Markus Fisch nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 #include <sys/types.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <iostream>
35 #include <fstream>
36 
37 #include "Dock.hh"
38 #include "Render.hh"
39 
40 using namespace std;
41 using namespace bbdock;
42 
43 /**
44  * Add an Icon record to a Dock
45  *
46  * @param dock - Dock object
47  * @param record - an icon record
48  * @param delimiter - field delimiter
49  */
add(Dock & dock,char * record,const char delimiter)50 bool add( Dock &dock, char *record, const char delimiter )
51 {
52 	// cut off comments, don't use strtok because it will skip leading
53 	// delimiters
54 	{
55 		char *comment;
56 
57 		if( (comment = strchr( record, '#' )) )
58 			*comment = 0;
59 	}
60 
61 	enum
62 	{
63 		Image,
64 		Command,
65 		Title,
66 		Idle
67 	};
68 
69 	char *fields[Idle+1];
70 
71 	bzero( fields, sizeof( fields ) );
72 	fields[Image] = record;
73 
74 	for( int n = Image; n <= Idle; )
75 	{
76 		char *stop;
77 
78 		if( !(stop = strchr( fields[n], delimiter )) )
79 			break;
80 
81 		*(stop++) = 0;
82 		fields[++n] = stop;
83 	}
84 
85 	if( !fields[Image] ||
86 		!fields[Command] )
87 		return false;
88 
89 	unsigned int idletime = Icon::Slow;
90 
91 	if( fields[Idle] )
92 		if( !strcasecmp( fields[Idle], "fast" ) )
93 			idletime = Icon::Fast;
94 		else if( !strcasecmp( fields[Idle], "fastest" ) )
95 			idletime = Icon::Fastest;
96 		else if( !strcasecmp( fields[Idle], "slow" ) )
97 			idletime = Icon::Slow;
98 		else if( !strcasecmp( fields[Idle], "lame" ) )
99 			idletime = Icon::Lame;
100 		else
101 		{
102 			unsigned int t = (unsigned int)
103 				atoi( fields[Idle] );
104 
105 			if( t > 0 )
106 				idletime = t;
107 		}
108 
109 	return dock.add( new Icon( fields[Image], fields[Command],
110 		(fields[Title] ? fields[Title] : ""), idletime ) );
111 }
112 
113 /**
114  * Load icons from file
115  *
116  * @param binary - name of the currently running binary
117  * @param dock - Dock object
118  */
loadRC(char * binary,Dock & dock)119 void loadRC( char *binary, Dock &dock )
120 {
121 	string rcfilename = binary;
122 	string::size_type p;
123 
124 	if( (p = rcfilename.find_last_of( '/' )) )
125 		rcfilename = rcfilename.substr( ++p );
126 
127 	rcfilename = "/."+rcfilename+"rc";
128 	rcfilename = getenv( "HOME" )+rcfilename;
129 
130 	ifstream fin( rcfilename.c_str(), ios::in );
131 
132 	if( !fin )
133 		throw "Unable to open resource configuraton file for reading";
134 
135 	for( string buf; getline( fin, buf ); )
136 		add( dock, (char *) buf.c_str(), ':' );
137 }
138 
139 /**
140  * Entry point
141  */
main(int argc,char ** argv)142 int main( int argc, char **argv )
143 {
144 	Dock::Settings settings;
145 	char *binary;
146 
147 	// process command line arguments
148 	for( binary = *argv; --argc; )
149 		if( **(++argv) != '-' )
150 			break;
151 		else
152 			switch( *((*argv)+1) )
153 			{
154 				default:
155 					cerr << "Unknown argument \"" << *argv << "\" !" << endl
156 						<< endl;
157 					// fall through
158 				case '?':
159 				case 'h':
160 					cout << "usage: " << binary <<
161 " [-hvdmprlcix] IMAGEFILE:COMMAND[:WINDOWTITLE;...[:IDLE]]...\n\
162   -h                    print this help\n\
163   -v                    print version\n\
164   -d WIDTHxHEIGHT       outer dimensions of dock buttons\n\
165   -m TYPE               define look of mark indicating a running instance,\n\
166                         TYPE may be \"play\", \"dots\", \"corner\" or\n\
167                         \"cross\" (default)\n\
168   -p LEFT/TOP           left/top (or if negative right/bottom) padding of\n\
169                         the mark in icon image\n\
170   -r ACTION             determines what action is performed when a running\n\
171                         icon gets right-clicked, possible ACTIONs are:\n\
172                         \"nothing\" - do nothing\n\
173                         \"iconfiy\" - iconify instance (default)\n\
174                         \"lower\" - move instance to background\n\
175                         \"close\" - shut down instance\n\
176   -l ACTION             determines what action is performed when a icon\n\
177                         is clicked again when the corresponding window is\n\
178                         already activated, use the same ACTIONs like before,\n\
179                         \"nothing\" is default\n\
180   -c                    match WINDOWTITLE case-sensitive (recommended)\n\
181   -i COMMAND:IMAGEFILE  remotely exchange icon of this command\n\
182   -x COMMAND            remotely execute icon with this command\n\
183 \n\
184 IMAGEFILE   - should be path and filename of some PNG icon\n\
185 COMMAND     - a script or binary to execute\n\
186 WINDOWTITLE - is a semicolon-seperated list of case-insensitive window-titles\n\
187               of corresponding application-windows. Those strings may contain\n\
188               wildcard characters (* and/or ?) to exclusively  identify a\n\
189               window. By providing this list you make the icon exclusive to\n\
190               one instance of course. Clicking on already launched icons will\n\
191               raise the corresponding window instead of invoking a new\n\
192               instance.\n\
193 IDLE        - idle time after triggering one icon in miliseconds, instead\n\
194               of using numbers you may also use the terms \"lame\" (~ 10 s),\n\
195               \"slow\" (~ 5 s), \"fast\" (~ 500 ms) or \"fastest\" (~ 250 ms)"
196 						<< endl;
197 					return 0;
198 				case 'v':
199 					cout << "bbdock, version 0.2.9 <mf@markusfisch.de>" << endl;
200 					return 0;
201 				case 'd':
202 					{
203 						char *w;
204 						char *h;
205 
206 						if( --argc &&
207 							(w = strtok( *(++argv), "x" )) &&
208 							(h = strtok( 0, "" )) )
209 						{
210 							settings.setSlotWidth( atoi( w ) );
211 							settings.setSlotHeight( atoi( h ) );
212 						}
213 						else
214 							cerr << "Missing or invalid dimensions !" <<
215 								endl;
216 					}
217 					break;
218 				case 'm':
219 					if( !--argc )
220 						cerr << "Missing mark type !" << endl;
221 					else
222 					{
223 						char *type = *(++argv);
224 
225 						if( !(strcasecmp( type, "play" )) )
226 							settings.setMarkType( Render::PlayMark );
227 						else if( !(strcasecmp( type, "dots" )) )
228 						{
229 							settings.setMarkType( Render::DotsMark );
230 							settings.setMarkLeft( -1 );
231 							settings.setMarkTop( -1 );
232 						}
233 						else if( !(strcasecmp( type, "corner" )) )
234 						{
235 							settings.setMarkType( Render::CornerMark );
236 							settings.setMarkLeft( -1 );
237 							settings.setMarkTop( -1 );
238 						}
239 						else if( !(strcasecmp( type, "cross" )) )
240 						{
241 							settings.setMarkType( Render::CrossMark );
242 							settings.setMarkLeft( -1 );
243 							settings.setMarkTop( 0 );
244 						}
245 						else
246 							cerr << "Unknown type '" << type << "' !" << endl;
247 					}
248 					break;
249 				case 'p':
250 					{
251 						char *l;
252 						char *t;
253 
254 						if( --argc &&
255 							(l = strtok( *(++argv), "/" )) &&
256 							(t = strtok( 0, "" )) )
257 						{
258 							settings.setMarkLeft( atoi( l ) );
259 							settings.setMarkTop( atoi( t ) );
260 						}
261 						else
262 							cerr << "Missing or invalid padding !" <<
263 								endl;
264 					}
265 					break;
266 				case 'r':
267 					if( !--argc )
268 						cerr << "Missing right-click action !" << endl;
269 					else
270 					{
271 						char *action = *(++argv);
272 
273 						if( !strcasecmp( action, "nothing" ) )
274 							settings.setRightClickAction(
275 								Dock::Settings::DoNothing );
276 						else if( !strcasecmp( action, "iconify" ) )
277 							settings.setRightClickAction(
278 								Dock::Settings::IconifyApplication );
279 						else if( !strcasecmp( action, "lower" ) )
280 							settings.setRightClickAction(
281 								Dock::Settings::LowerApplication );
282 						else if( !strcasecmp( action, "close" ) )
283 							settings.setRightClickAction(
284 								Dock::Settings::CloseApplication );
285 						else
286 							cerr << "Unknown action '" << action << "' !" <<
287 								endl;
288 					}
289 					break;
290 				case 'l':
291 					if( !--argc )
292 						cerr << "Missing left-click action !" << endl;
293 					else
294 					{
295 						char *action = *(++argv);
296 
297 						if( !strcasecmp( action, "nothing" ) )
298 							settings.setLeftClickAction(
299 								Dock::Settings::DoNothing );
300 						else if( !strcasecmp( action, "iconify" ) )
301 							settings.setLeftClickAction(
302 								Dock::Settings::IconifyApplication );
303 						else if( !strcasecmp( action, "lower" ) )
304 							settings.setLeftClickAction(
305 								Dock::Settings::LowerApplication );
306 						else if( !strcasecmp( action, "close" ) )
307 							settings.setLeftClickAction(
308 								Dock::Settings::CloseApplication );
309 						else
310 							cerr << "Unknown action '" << action << "' !" <<
311 								endl;
312 					}
313 					break;
314 				case 'c':
315 					settings.setCaseSensitive( true );
316 					break;
317 				case 'i':
318 					{
319 						char *icon;
320 						char *cmd;
321 
322 						if( --argc &&
323 							(cmd = strtok( *(++argv), ":" )) &&
324 							(icon = strtok( 0, "" )) )
325 						{
326 							Dock::changeIcon( cmd, icon );
327 							return 0;
328 						}
329 						else
330 							cerr << "Missing or invalid parameter !" <<
331 								endl;
332 					}
333 					break;
334 				case 'x':
335 					{
336 						if( --argc )
337 						{
338 							Dock::executeIcon( *(++argv) );
339 							return 0;
340 						}
341 						else
342 							cerr << "Missing or invalid parameter !" <<
343 								endl;
344 					}
345 					break;
346 			}
347 
348 	// detach from shell
349 	switch( fork() )
350 	{
351 		case -1:
352 			cerr << "Can not fork !" << endl;
353 			return -1;
354 		case 0:
355 			break;
356 		default:
357 			return 0;
358 	}
359 
360 	// bring up the icons
361 	try
362 	{
363 		Dock dock( settings );
364 
365 		if( !argc )
366 		{
367 			// try to start up by configuration file
368 			loadRC( binary, dock );
369 		}
370 		else
371 		{
372 			// try to start up by command line
373 			for( ; argc--; argv++ )
374 				if( !add( dock, *argv, ':' ) )
375 					throw "Invalid argument";
376 		}
377 
378 		dock.run();
379 	}
380 	catch( const char *e )
381 	{
382 		cerr << e << endl;
383 	}
384 	catch( ... )
385 	{
386 		cerr << "Unknown error" << endl;
387 	}
388 
389 	return 0;
390 }
391