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