1 /*-
2  * wmQStat - WindowMaker/AfterStep/BB/FB/Waimea dockable front-end to
3  * Steve Jankowski's qstat (http://www.qstat.org/).
4  *
5  * Copyright (c) 2003-2005 Alexey Dokuchaev.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
21  * BE 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
24  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * $Id: wmqstat.c,v 1.16 2005/02/21 10:59:40 danfe Exp $
30  */
31 
32 #include <X11/Xlib.h>
33 #include <X11/xpm.h>
34 #include <sys/time.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #include "../wmgeneral/wmgeneral.h"
43 #include "../wmgeneral/misc.h"
44 
45 #include "wmqstat.xpm"
46 
47 #define _WMQSTAT
48 #include "proto.h"
49 
50 #define PROG_NAME	"wmqstat"
51 #define PROG_TITLE	"wmQStat"
52 #define PROG_VER	"0.0.4"
53 #define COPYRIGHT	"Copyright (c) 2003-2005 Alexey Dokuchaev"
54 
55 #define RC_FILE		"/." PROG_NAME "rc"
56 #define MAX_PATH	254
57 
58 #define TIME_FRAQ	5
59 
60 #define MAX_TMP_STR	128
61 
62 #define CHAR_WIDTH	6
63 #define CHAR_HEIGHT	8
64 
65 char mask_bits[64 * 64];
66 int mask_width = 64;
67 int mask_height = 64;
68 
69 int offset = 0;
70 int what = FRAGS;
71 
72 struct slist *cursrv;	/* must be global to be visible to signal() */
73 
74 int
main(int argc,char ** argv)75 main(int argc, char **argv)
76 {
77 	XEvent event;
78 	int bpr = -1, brl = -1;
79 	struct itimerval itv;
80 	char *rcfile;
81 	struct slist *servers;
82 
83 	if (!(rcfile = parse_cmd_line(argc, argv))) {
84 		fprintf(stderr, "Unable to read configuration file.\n");
85 		return (1);
86 	}
87 
88 	servers = spawn_servers(rcfile);
89 	free(rcfile);			/* we don't need it anymore */
90 
91 	if (!(cursrv = servers)) {
92 		fprintf(stderr, "Could not load any servers.\n");
93 		return (1);
94 	}
95 
96 	createXBMfromXPM(mask_bits, wmqstat_xpm, mask_width, mask_height);
97 	openXwindow(argc, argv, wmqstat_xpm, mask_bits, mask_width, mask_height);
98 
99 	AddMouseRegion(0, 5, 5, 58, 58);	/* entire active area */
100 
101 	itv.it_value.tv_sec =
102 	itv.it_value.tv_usec = 1;	/* start immediately */
103 	itv.it_interval.tv_sec = TIME_FRAQ;
104 	itv.it_interval.tv_usec = 0;
105 
106 	signal(SIGALRM, (void (*)(int))update_stats);
107 	setitimer(ITIMER_REAL, &itv, NULL);
108 
109 	for (;;) {
110 		while (XPending(display)) {
111 			XNextEvent(display, &event);
112 
113 			switch (event.type) {
114 			case Expose:
115 				RedrawWindow();
116 				break;
117 			case DestroyNotify:
118 				XCloseDisplay(display);
119 				clean_up(servers);
120 				return (0);		/* bye-bye! */
121 				break;
122 			case ButtonPress:
123 				bpr = CheckMouseRegion(event.xbutton.x,
124 					event.xbutton.y);
125 				break;
126 			case ButtonRelease:
127 				brl = CheckMouseRegion(event.xbutton.x,
128 					event.xbutton.y);
129 				if (bpr == brl && brl >= 0) {
130 					switch (event.xbutton.button) {
131 					case 1:
132 						if (!servers->next)
133 							break;
134 						cursrv = cursrv->next ?
135 							cursrv->next :
136 							servers;
137 						update_stats();
138 						break;
139 					case 3:
140 						if (!what--)
141 							what = FRAGS;
142 						display_stats(cursrv->srv);
143 						break;
144 					case 4:
145 						if (offset)
146 							offset--, display_stats(cursrv->srv);
147 						break;
148 					case 5:
149 						if (cursrv->srv->npl - offset > 4)
150 							offset++, display_stats(cursrv->srv);
151 						break;
152 					}
153 				}
154 				brl = -1;
155 				RedrawWindow();
156 				break;
157 			}
158 		}
159 		usleep(100000l);
160 	}
161 	clean_up(servers);
162 	return (1);
163 }
164 
165 /*
166  * Re-fetch data from current server and display them.
167  * Called once in a while normally (every TIME_FRAQ seconds), and when
168  * user had selected next server to monitor.
169  */
170 static void
update_stats(void)171 update_stats(void)
172 {
173 	static int lock = 0;
174 
175 	if (lock)
176 		return;
177 
178 	lock++;				/* grab the lock */
179 	obtain_stats(cursrv->srv);
180 	draw_header(cursrv->srv);
181 	display_stats(cursrv->srv);
182 	RedrawWindow();
183 	lock--;				/* release lock */
184 }
185 
186 /*
187  * For now, the only who things that can be specified as program
188  * arguments (except X ones) are alternate config file and what to
189  * display by default (frags, ping, or time).
190  * Don't forget to free() the allocated memory afterwards.
191  * Unknown options are ignored (instead of aborting and/or calling
192  * usage(), due to no desire to take special care of -display/geometry
193  * ones that `wmgeneral' does for us already (no matter how screwy it
194  * might seem).
195  */
196 static char *
parse_cmd_line(int ac,char ** av)197 parse_cmd_line(int ac, char **av)
198 {
199 	char *res = NULL;
200 
201 	while (--ac) {
202 		if (!strncmp(av[ac - 1], "-c", 2) && ac) {
203 			res = strdup(av[ac]);	/* to shup up free() */
204 			continue;
205 		}
206 
207 		if (!strncmp(av[ac], "-p", 2)) {
208 			what = PING;
209 			continue;
210 		}
211 
212 		if (!strncmp(av[ac], "-t", 2)) {
213 			what = TIME;
214 			continue;
215 		}
216 
217 		if (!strncmp(av[ac], "-h", 2))
218 			usage();
219 
220 		if (!strncmp(av[ac], "-v", 2)) {
221 			fprintf(stderr, PROG_TITLE " version " PROG_VER
222 				"\n" COPYRIGHT "\n");
223 			exit(1);
224 		}
225 	}
226 
227 	if (!res) if (res = malloc(MAX_PATH + 1)) {
228 		strncpy(res, getenv("HOME"), MAX_PATH - sizeof(RC_FILE) + 1);
229 		strncat(res, RC_FILE, sizeof(RC_FILE) - 1);
230 	}
231 	return (res);
232 }
233 
234 /*
235  * Read configuration file, and spawn initial server monitors
236  * from it (that is, allocate memory and obtain some preliminary
237  * server information.
238  */
239 static struct slist *
spawn_servers(const char * rcfile)240 spawn_servers(const char *rcfile)
241 {
242 	FILE *fp;
243 	char type[MAX_TMP_STR], addr[MAX_TMP_STR];
244 	struct server *srv;
245 	struct slist *servers = NULL;
246 
247 	if (fp = fopen(rcfile, "r")) {
248 		while (fscanf(fp, "%64s%64s", type, addr) > 1) {
249 			/*
250 			 * We always want to read 2 tokens; 1 or EOF (-1)
251 			 * simply aren't good enough for us.
252 			 */
253 			if (*type == '#' || *addr == '#')
254 				/* ignore comments (token starting with #) */
255 				continue;
256 
257 			if (!(srv = add_server(type, addr)))
258 				continue;
259 
260 			if (servers) {
261 				struct slist *tmp;
262 
263 				if (tmp = malloc(sizeof(struct slist))) {
264 					tmp->srv = srv;
265 					tmp->next = servers;
266 					servers = tmp;
267 				} else
268 					kill_server(srv);
269 			} else {
270 				if (servers = malloc(sizeof(struct slist))) {
271 					servers->srv = srv;
272 					servers->next = NULL;
273 				} else
274 					kill_server(srv);
275 			}
276 		}
277 	}
278 	return (servers);
279 }
280 
281 /*
282  * Free all memory allocated for servers and clients, and destroy linked
283  * list of servers.  We do not immediately exit() here since this
284  * might be called not necessarily at the end of dockapp's life.
285  */
286 static void
clean_up(struct slist * servers)287 clean_up(struct slist *servers)
288 {
289 	struct slist *tmp;
290 
291 	while (servers) {
292 		kill_server(servers->srv);
293 		tmp = servers->next;
294 		free(servers);
295 		servers = tmp;
296 	}
297 }
298 
299 /*
300  * Display current map and total number of players.
301  * I might consider adding that nifty marquee thing like in XMMS.
302  * And tinting, probably, too.
303  */
304 static void
draw_header(const struct server * srv)305 draw_header(const struct server *srv)
306 {
307 
308 	wm_printf(5, 5, "%-6.6s", (strlen(srv->map) > 6) ?
309 			(srv->map + strlen(srv->map) - 6) :
310 			srv->map);
311 	wm_printf(45, 5, "%2d", srv->npl);
312 }
313 
314 /*
315  * Display 4 players, skipping `offset' number from top.
316  * `offset' is automagically adjusted when needed: there should not
317  * left any blank lines if it's possible to display something in
318  * there, and we always vertically align to top.
319  * Display either players' frags, ping, or time (each if available),
320  * depending on global `what' variable.
321  */
322 static void
display_stats(const struct server * srv)323 display_stats(const struct server *srv)
324 {
325 	int i = 4;
326 
327 	if (srv->npl > 4) {
328 		if (srv->npl - offset < 4)
329 			offset = srv->npl - 4;
330 		if (offset < 0)
331 			offset = 0;	/* valign always = top */
332 	}
333 	else
334 		while (i > srv->npl)
335 			copyXPMArea(72, 50, 51, 7, 6, 17 + --i * 11);
336 
337 	while (i--) {	/* dump the `for' loop and thus save variable */
338 		//copyXPMArea(124, 52, 1, 5, 37, 19 + i * 11);	/* `:' */
339 		wm_printf(5, 16 + i * 11, "%-5.5s", srv->players[i +
340 			offset].name);
341 		wm_printf(39, 16 + i * 11, "%3d", (what == FRAGS) ?
342 			srv->players[i + offset].frags :
343 			(what == PING) ? srv->players[i + offset].ping :
344 			(srv->players[i + offset].time > -1) ?
345 			srv->players[i + offset].time / 60 : -1);
346 	}
347 }
348 
349 /*
350  * Print something according to format string, on the dockapp
351  * virtual LCD display.
352  */
353 static void
wm_printf(int x,const int y,const char * fmt,...)354 wm_printf(int x, const int y, const char *fmt, ...)
355 {
356 	char p[MAX_TMP_STR];
357 	va_list ap;
358 	int i;
359 
360 	va_start(ap, fmt);
361 	vsnprintf(p, MAX_TMP_STR, fmt, ap);
362 	va_end(ap);
363 
364 	for (i = 0; p[i]; i++) {
365 		copyXPMArea(((p[i] & 0x7f) - 32) * CHAR_WIDTH, 64,
366 				CHAR_WIDTH, CHAR_HEIGHT, x, y);
367 		x += CHAR_WIDTH;
368 	}
369 }
370 
371 /*
372  * This one is self-explanatory, I hope. ;-)
373  */
374 static void
usage(void)375 usage(void)
376 {
377 
378 	fprintf(stderr, "\nUsage: " PROG_NAME " [option(s) ...]\n\n"
379 		"Available options:\n"
380 		"  -p \t\t\tdisplay ping by default instead of frags\n"
381 		"  -t \t\t\tdisplay time by default instead of frags\n"
382 		"  -c filename\t\tuse alternate configuration file\n"
383 		"  -v\t\t\tprint the version number\n"
384 		"  -h\t\t\tdisplay this usage information\n"
385 		"  -display a:b\t\tspecify the X server to contact\n"
386 		"  -geometry +x+y\tset preferred dockapp geometry\n\n");
387 	exit(1);
388 }
389