2 Code based on wmppp/wmifs
6 This code was mainly put together by looking at the
10 A neat piece of equip, used to display the date
11 and time on the screen.
12 Comes with every AfterStep installation.
15 How do I create a not so solid window?
16 How do I open a window?
19 ------------------------------------------------------------
21 Authors: Martijn Pieterse (pieterse@xs4all.nl)
22 Antoine Nulle (warp@xs4all.nl)
24 This program is distributed under the GPL license.
25 (as were asclock and pppstats)
31 17/10/2009 (Romuald Delavergne, romuald.delavergne@free.fr)
32 * Support SMP processors in realtime CPU stress meter
33 15/05/2004 (Simon Law, sfllaw@debian.org)
34 * Support disabling of mode-cycling
35 23/10/2003 (Simon Law, sfllaw@debian.org)
36 * Eliminated exploitable static buffers
37 * Added -geometry support.
38 * /proc/meminfo support for Linux 2.6
39 18/05/1998 (Antoine Nulle, warp@xs4all.nl)
40 * MEM/SWAP/UPTIME only updated when visible
41 * Using global file descriptors to reduce file
42 system overhead, both updates are based on a diff
43 supplied by Dave Harden (dharden@wisewire.com)
44 15/05/1998 (Antoine Nulle, warp@xs4all.nl)
45 * Fixed memory overflow in the MEM gaugebar
46 * MEM gauge displays now real used mem
47 (buffered + cached mem removed)
48 14/05/1998 (Antoine Nulle, warp@xs4all.nl)
49 * Added -i & -s kludge for selecting startupmode,
50 tijno, don't hate me for this :)
51 12/05/1998 (Antoine Nulle, warp@xs4all.nl)
52 * Finetuned master-xpm, tijno don't worry, no
53 reprogramming needed this time ;-)
54 07/05/1998 (Martijn Pieterse, pieterse@xs4all.nl)
56 03/05/1998 (Antoine Nulle, warp@xs4all.nl)
57 * Added new master-xpm which contains the gfx
58 for the upcoming SysInfo part :P
59 02/05/1998 (Martijn Pieterse, pieterse@xs4all.nl)
60 * Removed a lot of code, that was put in wmgeneral
61 23/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
62 * Added zombie destroying code (aka wait :) )
63 18/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
64 * Added CPU-on-screen.
65 * Added -display command line
66 15/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
67 * Fixed a bug in the stats routine
68 (Top 3 bright pixels were not shown when 100% loaded)
69 * Changed xpm to a no-title one.
70 This included the reprogramming of all positions.
71 warpstah, i hate you! ;)
72 05/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
73 * First Working Version
85 #include <sys/param.h>
86 #include <sys/types.h>
90 #include <X11/extensions/shape.h>
92 #include "../wmgeneral/wmgeneral.h"
93 #include "../wmgeneral/misc.h"
95 #include "wmmon-master.xpm"
96 #include "wmmon-mask.xbm"
102 #define LEFT_ACTION (NULL)
103 #define RIGHT_ACTION (NULL)
104 #define MIDDLE_ACTION (NULL)
106 #define WMMON_VERSION "1.0.b2"
108 /********************/
109 /* Global Variables */
110 /********************/
112 int stat_current
= 0; /* now global */
113 int mode_cycling
= 1; /* Allow mode-cycling */
114 int cpu_avg_max
= 0; /* CPU stress meter with average and max for SMP */
121 void printversion(void);
122 void DrawStats(int *, int, int, int, int);
123 void DrawStats_io(int *, int, int, int, int);
125 void wmmon_routine(int, char **);
127 int main(int argc
, char *argv
[]) {
130 char *name
= argv
[0];
133 /* Parse Command Line */
135 for (i
=1; i
<argc
; i
++) {
141 if (strcmp(arg
+1, "display")) {
147 if (strcmp(arg
+1, "geometry")) {
173 wmmon_routine(argc
, argv
);
179 /*******************************************************************************\
181 \*******************************************************************************/
183 #define MAX_CPU (10) /* depends on graph height */
186 char name
[5]; /* "cpu0..cpuz", eventually.. :) */
193 /* Processors stats */
201 #define MAX_STAT_DEVICES (4)
202 stat_dev stat_device
[MAX_STAT_DEVICES
];
211 unsigned long getWidth(long, long);
212 int checksysdevs(void);
213 void get_statistics(char *, long *, long *, long *, long *, long *);
214 void DrawActive(char *);
216 void update_stat_cpu(stat_dev
*, long *, long *);
217 void update_stat_io(stat_dev
*);
218 void update_stat_mem(stat_dev
*st
, stat_dev
*st2
);
219 void update_stat_swp(stat_dev
*);
221 void wmmon_routine(int argc
, char **argv
) {
223 rckeys wmmon_keys
[] = {
224 { "left", &left_action
},
225 { "right", &right_action
},
226 { "middle", &middle_action
},
247 char *conffile
= NULL
;
249 int xpm_X
= 0, xpm_Y
= 0;
251 long online_time
= 0;
256 fp
= fopen("/proc/uptime", "r");
257 fp_meminfo
= fopen("/proc/meminfo", "r");
258 fp_loadavg
= fopen("/proc/loadavg", "r");
259 fp_stat
= fopen("/proc/stat", "r");
262 fscanf(fp
, "%ld", &online_time
);
267 for (i
=0; i
<MAX_STAT_DEVICES
; i
++) {
268 for (j
=0; j
<55; j
++) {
269 stat_device
[i
].his
[j
] = 0;
271 stat_device
[i
].hisaddcnt
= 0;
274 if (LEFT_ACTION
) left_action
= strdup(LEFT_ACTION
);
275 if (RIGHT_ACTION
) right_action
= strdup(RIGHT_ACTION
);
276 if (MIDDLE_ACTION
) middle_action
= strdup(MIDDLE_ACTION
);
278 /* Scan through the .rc files */
279 if (asprintf(&conffile
, "/etc/wmmonrc") >= 0) {
280 parse_rcfile(conffile
, wmmon_keys
);
284 if (asprintf(&conffile
, "%s/.wmmonrc", getenv("HOME")) >= 0) {
285 parse_rcfile(conffile
, wmmon_keys
);
289 if (asprintf(&conffile
, "/etc/wmmonrc.fixed") >= 0) {
290 parse_rcfile(conffile
, wmmon_keys
);
294 stat_online
= checksysdevs();
297 stat_device
[0].cpu_stat
= calloc(nb_cpu
, sizeof(long));
298 stat_device
[0].cpu_last
= calloc(nb_cpu
, sizeof(long));
299 stat_device
[0].idle_stat
= calloc(nb_cpu
, sizeof(long));
300 stat_device
[0].idle_last
= calloc(nb_cpu
, sizeof(long));
301 if (!stat_device
[0].cpu_stat
|| !stat_device
[0].cpu_last
|| !stat_device
[0].idle_stat
|| !stat_device
[0].idle_last
) {
302 fprintf(stderr
, "%s: Unable to alloc memory !\n", argv
[0]);
305 istat2
= calloc(nb_cpu
, sizeof(long));
306 idle2
= calloc(nb_cpu
, sizeof(long));
307 if (!istat2
|| !idle2
) {
308 fprintf(stderr
, "%s: Unable to alloc memory !!\n", argv
[0]);
312 openXwindow(argc
, argv
, wmmon_master_xpm
, wmmon_mask_bits
, wmmon_mask_width
, wmmon_mask_height
);
314 /* add mouse region */
315 AddMouseRegion(0, 12, 13, 58, 57);
316 AddMouseRegion(1, 5, 5, 24, 14);
319 nexttime
= starttime
+ 10;
321 /* Collect information on each panel */
322 for (i
=0; i
<stat_online
; i
++) {
323 get_statistics(stat_device
[i
].name
, &k
, &istat
, &idle
, istat2
, idle2
);
324 stat_device
[i
].statlast
= istat
;
325 stat_device
[i
].idlelast
= idle
;
326 if (i
== 0 && nb_cpu
> 1) {
328 for (cpu
= 0; cpu
< nb_cpu
; cpu
++) {
329 stat_device
[i
].cpu_last
[cpu
] = istat2
[cpu
];
330 stat_device
[i
].idle_last
[cpu
] = idle2
[cpu
];
335 /* Set the mask for the current window */
336 switch (stat_current
) {
349 /* Draw statistics */
350 if (stat_current
== 0)
351 DrawStats(stat_device
[stat_current
].his
, 54, 40, 5, 58);
352 if (stat_current
== 1)
353 DrawStats_io(stat_device
[stat_current
].his
, 54, 40, 5, 58);
354 DrawActive(stat_device
[stat_current
].name
);
357 curtime
= time(NULL
);
359 waitpid(0, NULL
, WNOHANG
);
362 update_stat_cpu(&stat_device
[0], istat2
, idle2
);
363 update_stat_io(&stat_device
[1]);
365 if(stat_current
== 2) {
366 update_stat_mem(&stat_device
[2], &stat_device
[3]);
367 // update_stat_swp(&stat_device[3]);
370 if (stat_current
< 2) {
373 /* Load ding is 45 pixels hoog */
374 copyXPMArea(0, 64, 32, 12, 28, 4);
376 if (i
== 0 && nb_cpu
> 1) {
377 if (nb_cpu
> MAX_CPU
|| cpu_avg_max
) {
378 /* show average CPU */
379 j
= getWidth(stat_device
[i
].rt_stat
, stat_device
[i
].rt_idle
);
380 copyXPMArea(32, 64, j
, 6, 28, 4);
382 j
= getWidth(stat_device
[i
].cpu_stat
[cpu_max
], stat_device
[i
].idle_stat
[cpu_max
]);
383 copyXPMArea(32, 70, j
, 6, 28, 10);
386 for (cpu
= 0; cpu
< nb_cpu
; cpu
++) {
387 j
= getWidth(stat_device
[i
].cpu_stat
[cpu
], stat_device
[i
].idle_stat
[cpu
]);
388 copyXPMArea(32, 65, j
, MAX_CPU
/nb_cpu
, 28, 5+(MAX_CPU
/nb_cpu
)*cpu
);
393 j
= getWidth(stat_device
[i
].rt_stat
, stat_device
[i
].rt_idle
);
394 copyXPMArea(32, 64, j
, 12, 28, 4);
397 /* Nu zal ie wel 3 zijn. */
399 copyXPMArea(0, 64, 32, 12, 28+64, 4);
400 copyXPMArea(0, 64, 32, 12, 28+64, 18);
402 j
= stat_device
[2].rt_idle
;
404 j
= (stat_device
[2].rt_stat
* 100) / j
;
408 copyXPMArea(32, 64, j
, 12, 28+64, 4);
409 /*--------------------- ------------------*/
410 j
= stat_device
[3].rt_idle
;
412 j
= (stat_device
[3].rt_stat
* 100) / j
;
416 copyXPMArea(32, 64, j
, 12, 28+64, 18);
418 /*----------- online tijd neerzetten! ----------*/
420 cnt_time
= time(0) - ref_time
+ online_time
;
422 /* cnt_time = uptime in seconden */
427 digits = 40,78, 6breed, 9hoog
431 copyXPMArea(40 + (i
% 10)*7, 78, 6, 9, 115, 47);
432 copyXPMArea(40 + (i
/ 10)*7, 78, 6, 9, 108, 47);
436 copyXPMArea(40 + (i
% 10)*7, 78, 6, 9, 96, 47);
437 copyXPMArea(40 + (i
/ 10)*7, 78, 6, 9, 89, 47);
441 copyXPMArea(40 + (i
% 10)*7, 78, 6, 9, 77, 47);
442 copyXPMArea(40 + (i
/ 10)*7, 78, 6, 9, 70, 47);
444 /* De rest is dagen! 5x7*/
447 copyXPMArea(66 + (i
% 10)*6, 66, 5, 7, 88, 35);
449 copyXPMArea(66 + (i
% 10)*6, 66, 5, 7, 82, 35);
451 copyXPMArea(66 + (i
% 10)*6, 66, 5, 7, 76, 35);
453 copyXPMArea(66 + (i
% 10)*6, 66, 5, 7, 70, 35);
456 if (curtime
>= nexttime
) {
459 if (curtime
> nexttime
) /* dont let APM suspends make this crazy */
462 for (i
=0; i
<stat_online
; i
++) {
463 if (stat_device
[i
].his
[54])
464 stat_device
[i
].his
[54] /= stat_device
[i
].hisaddcnt
;
466 for (j
=1; j
<55; j
++) {
467 stat_device
[i
].his
[j
-1] = stat_device
[i
].his
[j
];
470 if (i
== stat_current
) {
471 if (i
== 0) DrawStats(stat_device
[i
].his
, 54, 40, 5, 58);
472 if (i
== 1) DrawStats_io(stat_device
[i
].his
, 54, 40, 5, 58);
474 stat_device
[i
].his
[54] = 0;
475 stat_device
[i
].hisaddcnt
= 0;
479 RedrawWindowXY(xpm_X
, xpm_Y
);
481 while (XPending(display
)) {
482 XNextEvent(display
, &Event
);
483 switch (Event
.type
) {
485 RedrawWindowXY(xpm_X
, xpm_Y
);
488 XCloseDisplay(display
);
492 but_stat
= CheckMouseRegion(Event
.xbutton
.x
, Event
.xbutton
.y
);
495 i
= CheckMouseRegion(Event
.xbutton
.x
, Event
.xbutton
.y
);
496 if (but_stat
== i
&& but_stat
>= 0 && mode_cycling
) {
499 switch (Event
.xbutton
.button
) {
502 execCommand(left_action
);
506 execCommand(middle_action
);
510 execCommand(right_action
);
515 if (stat_current
== stat_online
)
518 DrawActive(stat_device
[stat_current
].name
);
519 if (stat_current
== 0) DrawStats(stat_device
[stat_current
].his
, 54, 40, 5, 58);
520 if (stat_current
== 1) {
521 DrawStats_io(stat_device
[stat_current
].his
, 54, 40, 5, 58);
523 if (stat_current
== 2) {
530 RedrawWindowXY(xpm_X
, xpm_Y
);
542 void update_stat_cpu(stat_dev
*st
, long *istat2
, long *idle2
) {
545 get_statistics(st
->name
, &k
, &istat
, &idle
, istat2
, idle2
);
547 st
->rt_idle
= idle
- st
->idlelast
;
550 st
->rt_stat
= istat
- st
->statlast
;
551 st
->statlast
= istat
;
555 unsigned long max
, j
;
556 cpu_max
= 0; max
= 0;
557 for (cpu
= 0; cpu
< nb_cpu
; cpu
++) {
558 st
->idle_stat
[cpu
] = idle2
[cpu
] - st
->idle_last
[cpu
];
559 st
->idle_last
[cpu
] = idle2
[cpu
];
561 st
->cpu_stat
[cpu
] = istat2
[cpu
] - st
->cpu_last
[cpu
];
562 st
->cpu_last
[cpu
] = istat2
[cpu
];
564 j
= st
->cpu_stat
[cpu
] + st
->idle_stat
[cpu
];
565 if (j
!= 0) j
= (st
->cpu_stat
[cpu
] << 7) / j
;
577 void update_stat_io(stat_dev
*st
) {
579 long j
, k
, istat
, idle
;
580 static long maxdiskio
= 0;
582 get_statistics(st
->name
, &k
, &istat
, &idle
, NULL
, NULL
);
584 st
->rt_idle
= idle
- st
->idlelast
;
587 st
->rt_stat
= istat
- st
->statlast
;
588 st
->statlast
= istat
;
594 st
->rt_idle
= maxdiskio
- j
;
596 st
->his
[54] += st
->rt_stat
;
600 void update_stat_mem(stat_dev
*st
, stat_dev
*st2
) {
602 static char *line
= NULL
;
603 static size_t line_size
= 0;
605 unsigned long swapfree
;
606 unsigned long free
, shared
, buffers
, cached
;
608 freopen("/proc/meminfo", "r", fp_meminfo
);
609 while ((getline(&line
, &line_size
, fp_meminfo
)) > 0) {
610 /* The original format for the first two lines of /proc/meminfo was
611 * Mem: total used free shared buffers cached
612 * Swap: total used free
614 * As of at least 2.5.47 these two lines were removed, so that the
615 * required information has to come from the rest of the lines.
616 * On top of that, used is no longer recorded - you have to work
617 * this out yourself, from total - free.
619 * So, these changes below should work. They should also work with
620 * older kernels, too, since the new format has been available for
623 if (strstr(line
, "MemTotal:")) {
624 sscanf(line
, "MemTotal: %ld", &st
->rt_idle
);
626 else if (strstr(line
, "MemFree:")) {
627 sscanf(line
, "MemFree: %ld", &free
);
629 else if (strstr(line
, "MemShared:")) {
630 sscanf(line
, "MemShared: %ld", &shared
);
632 else if (strstr(line
, "Buffers:")) {
633 sscanf(line
, "Buffers: %ld", &buffers
);
635 else if (strstr(line
, "Cached:")) {
636 sscanf(line
, "Cached: %ld", &cached
);
638 else if (strstr(line
, "SwapTotal:")) {
639 sscanf(line
, "SwapTotal: %ld", &st2
->rt_idle
);
641 else if (strstr(line
, "SwapFree:")) {
642 sscanf(line
, "SwapFree: %ld", &swapfree
);
646 /* memory use - rt_stat is the amount used, it seems, and this isn't
647 * recorded in current version of /proc/meminfo (as of 2.5.47), so we
648 * calculate it from MemTotal - MemFree
650 st
->rt_stat
= st
->rt_idle
- free
;
651 st
->rt_stat
-= buffers
+cached
;
652 /* As with the amount of memory used, it's not recorded any more, so
653 * we have to calculate it ourselves.
655 st2
->rt_stat
= st2
->rt_idle
- swapfree
;
658 void update_stat_swp(stat_dev
*st
) {
660 static char *line
= NULL
;
661 static size_t line_size
= 0;
662 unsigned long swapfree
;
664 fseek(fp_meminfo
, 0, SEEK_SET
);
665 while ((getline(&line
, &line_size
, fp_meminfo
)) > 0) {
666 /* As with update_stat_mem(), the format change to /proc/meminfo has
667 * forced some changes here. */
668 if (strstr(line
, "SwapTotal:")) {
669 sscanf(line
, "SwapTotal: %ld", &st
->rt_idle
);
671 else if (strstr(line
, "SwapFree:")) {
672 sscanf(line
, "SwapFree: %ld", &swapfree
);
675 st
->rt_stat
= st
->rt_idle
- swapfree
;
678 /*******************************************************************************\
680 \*******************************************************************************/
682 void get_statistics(char *devname
, long *is
, long *ds
, long *idle
, long *ds2
, long *idle2
) {
685 static char *line
= NULL
;
686 static size_t line_size
= 0;
688 char *tokens
= " \t\n";
695 if (!strncmp(devname
, "cpu", 3)) {
696 fseek(fp_stat
, 0, SEEK_SET
);
697 while ((getline(&line
, &line_size
, fp_stat
)) > 0) {
698 if (strstr(line
, "cpu")) {
699 int cpu
= -1; /* by default, cumul stats => average */
700 if (!strstr(line
, "cpu ")) {
701 sscanf(line
, "cpu%d", &cpu
);
705 p
= strtok(line
, tokens
);
706 /* 1..3, 4 == idle, we don't want idle! */
707 for (i
=0; i
<3; i
++) {
708 p
= strtok(NULL
, tokens
);
714 p
= strtok(NULL
, tokens
);
718 idle2
[cpu
] = atol(p
);
721 fp_loadavg
= freopen("/proc/loadavg", "r", fp_loadavg
);
722 fscanf(fp_loadavg
, "%f", &f
);
723 *is
= (long) (100 * f
);
726 if (!strncmp(devname
, "i/o", 3)) {
728 fseek(fp_stat
, 0, SEEK_SET
);
729 while ((getline(&line
, &line_size
, fp_stat
)) > 0) {
730 if (strstr(line
, "disk_rio") || strstr(line
, "disk_wio")) {
731 p
= strtok(line
, tokens
);
733 for (i
=0; i
<4; i
++) {
734 p
= strtok(NULL
, tokens
);
738 else if (strstr(line
, "disk_io")) {
740 unsigned int a
, b
, c
, d
, e
, h
, g
;
742 p
= strtok(line
, tokens
);
744 while ((p
= strtok(NULL
, tokens
))) {
746 "(%d,%d):(%d,%d,%d,%d,%d)",
747 &a
, &b
, &c
, &d
, &e
, &h
,
761 /*******************************************************************************\
763 \*******************************************************************************/
765 unsigned long getWidth(long actif
, long idle
) {
770 j
= (actif
* 100) / j
;
779 /*******************************************************************************\
781 \*******************************************************************************/
784 static char *line
= NULL
;
785 static size_t line_size
= 0;
788 fseek(fp_stat
, 0, SEEK_SET
);
789 while ((getline(&line
, &line_size
, fp_stat
)) > 0) {
790 if (strstr(line
, "cpu") && !strstr(line
, "cpu "))
791 sscanf(line
, "cpu%d", &cpu
);
798 /*******************************************************************************\
800 \*******************************************************************************/
802 int checksysdevs(void) {
804 strcpy(stat_device
[0].name
, "cpu0");
805 strcpy(stat_device
[1].name
, "i/o");
806 strcpy(stat_device
[2].name
, "sys");
812 /*******************************************************************************\
813 |* void DrawActive(char *) *|
814 \*******************************************************************************/
816 void DrawActive(char *name
) {
826 if (name
[0] == 'c') {
827 copyXPMArea(0, 77, 19, 10, 5, 5);
828 } else if (name
[0] == 'i') {
829 copyXPMArea(19, 77, 19, 10, 5, 5);
834 /*******************************************************************************\
836 \*******************************************************************************/
838 void DrawStats(int *his
, int num
, int size
, int x_left
, int y_bottom
) {
845 pixels_per_byte
= 100;
847 for (j
=0; j
<num
; j
++) {
848 if (p
[0] > pixels_per_byte
)
849 pixels_per_byte
+= 100;
855 for (k
=0; k
<num
; k
++) {
856 d
= (1.0 * p
[0] / pixels_per_byte
) * size
;
858 for (j
=0; j
<size
; j
++) {
861 copyXPMArea(2, 88, 1, 1, k
+x_left
, y_bottom
-j
);
863 copyXPMArea(2, 89, 1, 1, k
+x_left
, y_bottom
-j
);
865 copyXPMArea(2, 90, 1, 1, k
+x_left
, y_bottom
-j
);
870 /* Nu horizontaal op 100/200/300 etc lijntje trekken! */
871 for (j
= pixels_per_byte
-100; j
> 0; j
-=100) {
872 for (k
=0; k
<num
; k
++) {
873 d
= (40.0 / pixels_per_byte
) * j
;
875 copyXPMArea(2, 91, 1, 1, k
+x_left
, y_bottom
-d
);
880 /*******************************************************************************\
882 \*******************************************************************************/
884 void DrawStats_io(int *his
, int num
, int size
, int x_left
, int y_bottom
) {
886 float pixels_per_byte
;
891 static int global_io_scale
= 1;
894 for (j
=0; j
<num
; j
++) {
895 if (p
[j
] > global_io_scale
) global_io_scale
= p
[j
];
898 pixels_per_byte
= 1.0 * global_io_scale
/ size
;
899 if (pixels_per_byte
== 0) pixels_per_byte
= 1;
901 for (k
=0; k
<num
; k
++) {
902 d
= (1.0 * p
[0] / pixels_per_byte
);
904 for (j
=0; j
<size
; j
++) {
907 copyXPMArea(2, 88, 1, 1, k
+x_left
, y_bottom
-j
);
909 copyXPMArea(2, 89, 1, 1, k
+x_left
, y_bottom
-j
);
911 copyXPMArea(2, 90, 1, 1, k
+x_left
, y_bottom
-j
);
918 /*******************************************************************************\
920 \*******************************************************************************/
922 void usage(char *name
) {
923 printf("Usage: %s [OPTION]...\n", name
);
924 printf("WindowMaker dockapp that displays system information.\n");
926 printf(" -display DISPLAY contact the DISPLAY X server\n");
927 printf(" -geometry GEOMETRY position the clock at GEOMETRY\n");
928 printf(" -l locked view - cannot cycle modes\n");
929 printf(" -c show average and max CPU for SMP machine.\n");
930 printf(" default if there is more than %d processors\n", MAX_CPU
);
931 printf(" -i start in Disk I/O mode\n");
932 printf(" -s start in System Info mode\n");
933 printf(" -h display this help and exit\n");
934 printf(" -v output version information and exit\n");
937 /*******************************************************************************\
939 \*******************************************************************************/
941 void printversion(void) {
943 printf("WMMon version %s\n", WMMON_VERSION
);
945 /* vim: sw=4 ts=4 columns=82