wmtop: Add version 0.84 to repository.
[dockapps.git] / wmtop / wmtop.c
blob1c0895ff2b793e78abfef87708d172b7c7cb6e5d
1 /******************************************/
2 /* WMTOP - Mini top in a dock app */
3 /******************************************/
5 /*
6 * wmtop.c -- WindowMaker process view dock app
7 * Derived by Dan Piponi dan@tanelorn.demon.co.uk
8 * http://www.tanelorn.demon.co.uk
9 * http://wmtop.sourceforge.net
10 * from code originally contained in wmsysmon by Dave Clark (clarkd@skynet.ca)
11 * This software is licensed through the GNU General Public License.
15 * Ensure there's an operating system defined. There is *no* default
16 * because every OS has it's own way of revealing CPU/memory usage.
18 #if defined(FREEBSD)
19 #define OS_DEFINED
20 #endif /* defined(FREEBSD) */
22 #if defined(LINUX)
23 #define OS_DEFINED
24 #endif /* defined(LINUX) */
26 #if !defined(OS_DEFINED)
27 #error No operating system selected
28 #endif /* !defined(OS_DEFINED) */
30 /******************************************/
31 /* Includes */
32 /******************************************/
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <time.h>
37 #include <dirent.h>
38 #include <string.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41 #include <ctype.h>
42 #include <math.h>
43 #include <limits.h>
44 #include <errno.h>
45 #include <signal.h>
47 #if defined(PARANOID)
48 #include <assert.h>
49 #endif /* defined(PARANOID) */
51 #include <sys/wait.h>
52 #include <sys/stat.h>
53 #include <sys/param.h>
54 #include <sys/types.h>
55 #include <sys/ioctl.h>
56 #include <sys/time.h>
58 #include <X11/Xlib.h>
59 #include <X11/xpm.h>
60 #include <X11/extensions/shape.h>
61 #include <X11/keysym.h>
63 #include <regex.h>
65 #include "wmgeneral/wmgeneral.h"
66 #include "wmgeneral/misc.h"
67 #include "xpm/wmtop-default.xpm"
68 #include "xpm/wmtop-lcd.xpm"
69 #include "xpm/wmtop-neon1.xpm"
70 #include "xpm/wmtop-neon2.xpm"
71 #include "xpm/wmtop-rainbow.xpm"
73 /******************************************/
74 /* Defines */
75 /******************************************/
77 #define WMTOP_VERSION "0.9"
80 * XXX: I shouldn't really use this WMTOP_BUFLENGTH variable but scanf is so
81 * lame and it'll take me a while to write a replacement.
83 #define WMTOP_BUFLENGTH 1024
85 #if defined(LINUX)
86 #define PROCFS_TEMPLATE "/proc/%d/stat"
87 #define PROCFS_CMDLINE_TEMPLATE "/proc/%d/cmdline"
88 #endif /* defined(LINUX) */
90 #if defined(FREEBSD)
91 #define PROCFS_TEMPLATE "/proc/%d/status"
92 #endif /* defined(FREEBSD) */
94 /******************************************/
95 /* Globals */
96 /******************************************/
98 regex_t *exclusion_expression = 0;
99 int user = -1;
100 char *process_command = 0;
102 * Default mode: zero=cpu one=memory
104 int mode = 0;
107 * Number and default artistic styles.
109 int nstyles = 5;
110 int style = 0;
112 char wmtop_mask_bits[64*64];
113 int wmtop_mask_width = 64;
114 int wmtop_mask_height = 64;
116 int update_rate = 1000000;
117 int refresh_rate = 100000;
119 extern char **environ;
121 char *ProgName;
123 /******************************************/
124 /* Debug */
125 /******************************************/
127 #if defined(DEBUG)
129 * Memory handler
131 int g_malloced = 0;
133 void *wmtop_malloc(int n) {
134 int *p = (int *)malloc(sizeof(int)+n);
135 p[0] = n;
136 g_malloced += n;
137 return (void *)(p+1);
140 void wmtop_free(void *n) {
141 int *p = (int *)n;
142 g_malloced -= p[-1];
143 free(p-1);
146 void show_memory() {
147 fprintf(stderr,"%d bytes allocated\n",g_malloced);
149 #else /* defined(DEBUG) */
150 #define wmtop_malloc malloc
151 #define wmtop_free free
152 #endif /* defined(DEBUG) */
154 char *wmtop_strdup(const char *s) {
155 return strcpy((char *)wmtop_malloc(strlen(s)+1),s);
158 /******************************************/
159 /* Structures */
160 /******************************************/
162 struct {
163 char **pixmap;
164 char *description;
165 } styles[] = {
166 { wmtop_default_xpm, "Light emitting diode (default)" },
167 { wmtop_lcd_xpm, "Liquid crystal display" },
168 { wmtop_rainbow_xpm, "Rainbow display" },
169 { wmtop_neon1_xpm, "Neon lights" },
170 { wmtop_neon2_xpm, "More neon lights" },
173 struct process {
174 #if defined(PARANOID)
175 long id;
176 #endif /* defined(PARANOID) */
178 * Store processes in a doubly linked list
180 struct process *next;
181 struct process *previous;
183 pid_t pid;
184 char *name;
185 float amount;
186 int user_time;
187 int kernel_time;
188 int previous_user_time;
189 int previous_kernel_time;
190 int vsize;
191 int rss;
192 int time_stamp;
193 int counted;
196 /******************************************/
197 /* Process class */
198 /******************************************/
201 * Global pointer to head of process list
203 struct process *first_process = 0;
205 int g_time = 0;
207 struct process *find_process(pid_t pid) {
208 struct process *p = first_process;
209 while (p) {
210 if (p->pid==pid)
211 return p;
212 p = p->next;
214 return 0;
218 * Create a new process object and insert it into the process list
220 struct process *new_process(int p) {
221 struct process *process;
222 process = wmtop_malloc(sizeof(struct process));
224 #if defined(PARANOID)
225 process->id = 0x0badfeed;
226 #endif /* defined(PARANOID) */
229 * Do stitching necessary for doubly linked list
231 process->name = 0;
232 process->previous = 0;
233 process->next = first_process;
234 if (process->next)
235 process->next->previous = process;
236 first_process = process;
238 process->pid = p;
239 process->time_stamp = 0;
240 process->previous_user_time = INT_MAX;
241 process->previous_kernel_time = INT_MAX;
242 process->counted = 1;
244 /* process_find_name(process);*/
246 return process;
249 /******************************************/
250 /* Functions */
251 /******************************************/
253 void wmtop_routine(int, char **);
254 int process_parse_procfs(struct process *);
255 int update_process_table(void);
256 int calculate_cpu(struct process *);
257 void process_cleanup(void);
258 void delete_process(struct process *);
259 inline void draw_processes(void);
260 int calc_cpu_total(void);
261 void calc_cpu_each(int);
262 #if defined(LINUX)
263 int calc_mem_total(void);
264 void calc_mem_each(int);
265 #endif
266 int process_find_top_three(struct process **);
267 void draw_bar(int, int, int, int, float, int, int);
268 inline void blit_string(char *, int, int);
269 void usage(void);
270 inline void printversion(void);
272 /******************************************/
273 /* Main */
274 /******************************************/
276 int main(int argc, char *argv[]) {
277 int i;
278 struct stat sbuf;
281 * Make sure we have a /proc filesystem. No point in continuing if we
282 * haven't!
284 if (stat("/proc",&sbuf)<0) {
285 fprintf(stderr,
286 "No /proc filesystem present. Unable to obtain processor info.\n");
287 exit(1);
291 * Parse Command Line
294 ProgName = argv[0];
295 if (strlen(ProgName) >= 5)
296 ProgName += strlen(ProgName) - 5;
298 for (i = 1; i<argc; i++) {
299 char *arg = argv[i];
301 if (*arg=='-') {
302 switch (arg[1]) {
303 case 'x' :
304 if (argc>i+1) {
305 static regex_t reg;
306 exclusion_expression = &reg;
307 regcomp(exclusion_expression,argv[i+1],REG_EXTENDED);
308 i++;
309 } else {
310 usage();
311 exit(1);
313 break;
314 case 'c' :
315 if (argc>i+1) {
316 process_command = argv[i+1];
317 i++;
318 break;
319 } else {
320 usage();
321 exit(1);
323 #if defined(LINUX)
324 case 'm':
326 * Display memory
328 mode = 1;
329 break;
330 #endif /* defined(LINUX) */
331 case 'd' :
332 if (strcmp(arg+1, "display")) {
333 usage();
334 exit(1);
336 break;
337 case 'g' :
338 if (strcmp(arg+1, "geometry")) {
339 usage();
340 exit(1);
342 break;
343 case 'v' :
344 printversion();
345 exit(0);
346 break;
347 case 'U' :
348 user = getuid();
349 break;
350 case 's':
351 if (argc > (i+1)) {
352 update_rate = (atoi(argv[i+1]) * 1000);
353 i++;
355 break;
356 case 'r':
357 if (argc > (i+1)) {
358 refresh_rate = (atoi(argv[i+1]) * 1000);
359 i++;
361 break;
362 case 'a':
363 if (argc > (i+1)) {
364 if (atoi(argv[i+1]) < 1 || atoi(argv[i+1]) > nstyles) {
365 usage();
366 exit(1);
368 style = atoi(argv[i+1]) - 1;
369 i++;
371 break;
372 default:
373 usage();
374 exit(0);
375 break;
380 wmtop_routine(argc, argv);
382 return 0;
385 /******************************************/
386 /* Main routine */
387 /******************************************/
389 void wmtop_routine(int argc, char **argv) {
390 XEvent Event;
391 struct timeval tv={0,0};
392 struct timeval last={0,0};
393 int count = update_rate;
395 createXBMfromXPM(wmtop_mask_bits, styles[style].pixmap, wmtop_mask_width, wmtop_mask_height);
397 openXwindow(argc, argv, styles[style].pixmap, wmtop_mask_bits, wmtop_mask_width, wmtop_mask_height);
400 while (1) {
402 waitpid(0, NULL, WNOHANG);
404 if (count>=update_rate) {
405 memcpy(&last,&tv,sizeof(tv));
408 * Update display
410 draw_processes();
412 RedrawWindow();
413 count = 0;
417 * X Events
419 while (XPending(display)) {
420 XNextEvent(display, &Event);
421 switch (Event.type) {
422 case Expose:
423 RedrawWindow();
424 break;
425 case DestroyNotify:
426 XCloseDisplay(display);
427 exit(0);
428 case ButtonPress:
429 #if defined(LINUX)
430 if (Event.xbutton.button==1)
431 mode = !mode;
432 #endif
433 if (Event.xbutton.button==2) {
434 if (user==-1)
435 user=getuid();
436 else
437 user=-1;
439 if (Event.xbutton.button==3 && process_command)
440 execCommand(process_command);
441 break;
444 usleep(refresh_rate);
445 count = count + refresh_rate;
449 /******************************************/
450 /* Extract information from /proc */
451 /******************************************/
454 * These are the guts that extract information out of /proc.
455 * Anyone hoping to port wmtop should look here first.
457 int process_parse_procfs(struct process *process) {
458 char line[WMTOP_BUFLENGTH],filename[WMTOP_BUFLENGTH],procname[WMTOP_BUFLENGTH];
459 int ps;
460 struct stat sbuf;
461 int user_time,kernel_time;
462 int rc;
463 #if defined(LINUX)
464 char *r,*q;
465 char deparenthesised_name[WMTOP_BUFLENGTH];
466 int endl;
467 #endif /* defined(LINUX) */
468 #if defined(FREEBSD)
469 int us,um,ks,km;
470 #endif /* defined(FREEBSD) */
472 #if defined(PARANOID)
473 assert(process->id==0x0badfeed);
474 #endif /* defined(PARANOID) */
476 sprintf(filename,PROCFS_TEMPLATE,process->pid);
479 * Permissions of /proc filesystem are permissions of process too
481 if (user>=0) {
482 stat(filename,&sbuf);
483 if (sbuf.st_uid!=user)
484 return 1;
487 ps = open(filename,O_RDONLY);
488 if (ps<0)
490 * The process must have finished in the last few jiffies!
492 return 1;
495 * Mark process as up-to-date.
497 process->time_stamp = g_time;
499 rc = read(ps,line,sizeof(line));
500 close(ps);
501 if (rc<0)
502 return 1;
504 #if defined(LINUX)
506 * Extract cpu times from data in /proc filesystem
508 rc = sscanf(line,"%*s %s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %d %d %*s %*s %*s %*s %*s %*s %*s %d %d",
509 procname,
510 &process->user_time,&process->kernel_time,
511 &process->vsize,&process->rss);
512 if (rc<5)
513 return 1;
515 * Remove parentheses from the process name stored in /proc/ under Linux...
517 r = procname+1;
518 /* remove any "kdeinit: " */
519 if (r == strstr(r, "kdeinit"))
521 sprintf(filename,PROCFS_CMDLINE_TEMPLATE,process->pid);
524 * Permissions of /proc filesystem are permissions of process too
526 if (user>=0) {
527 stat(filename,&sbuf);
528 if (sbuf.st_uid!=user)
529 return 1;
532 ps = open(filename,O_RDONLY);
533 if (ps<0)
535 * The process must have finished in the last few jiffies!
537 return 1;
539 endl = read(ps,line,sizeof(line));
540 close(ps);
542 /* null terminate the input */
543 line[endl]=0;
544 /* account for "kdeinit: " */
545 if ((char*)line == strstr(line, "kdeinit: "))
546 r = ((char*)line)+9;
547 else
548 r = (char*)line;
550 q = deparenthesised_name;
551 /* stop at space */
552 while (*r && *r!=' ')
553 *q++ = *r++;
554 *q = 0;
556 else
558 q = deparenthesised_name;
559 while (*r && *r!=')')
560 *q++ = *r++;
561 *q = 0;
564 if (process->name)
565 wmtop_free(process->name);
566 process->name = wmtop_strdup(deparenthesised_name);
567 #endif /* defined(LINUX) */
569 #if defined(FREEBSD)
571 * Extract cpu times from data in /proc/<pid>/stat
572 * XXX: Process name extractor for FreeBSD is untested right now.
574 rc = sscanf(line,"%s %*s %*s %*s %*s %*s %*s %*s %d,%d %d,%d",
575 procname,
576 &us,&um,&ks,&km);
577 if (rc<5)
578 return 1;
579 if (process->name)
580 wmtop_free(process->name);
581 process->name = wmtop_strdup(procname);
582 process->user_time = us*1000+um/1000;
583 process->kernel_time = ks*1000+km/1000;
584 #endif /* defined(FREEBSD) */
586 process->rss *= getpagesize();
588 if (process->previous_user_time==INT_MAX)
589 process->previous_user_time = process->user_time;
590 if (process->previous_kernel_time==INT_MAX)
591 process->previous_kernel_time = process->kernel_time;
593 user_time = process->user_time-process->previous_user_time;
594 kernel_time = process->kernel_time-process->previous_kernel_time;
596 process->previous_user_time = process->user_time;
597 process->previous_kernel_time = process->kernel_time;
599 process->user_time = user_time;
600 process->kernel_time = kernel_time;
602 return 0;
605 /******************************************/
606 /* Update process table */
607 /******************************************/
609 int update_process_table() {
610 DIR *dir;
611 struct dirent *entry;
613 if (!(dir = opendir("/proc")))
614 return 1;
617 * Get list of processes from /proc directory
619 while ((entry = readdir(dir))) {
620 pid_t pid;
622 if (!entry) {
624 * Problem reading list of processes
626 closedir(dir);
627 return 1;
630 if (sscanf(entry->d_name,"%d",&pid)>0) {
631 struct process *p;
632 p = find_process(pid);
633 if (!p)
634 p = new_process(pid);
636 calculate_cpu(p);
640 closedir(dir);
642 return 0;
645 /******************************************/
646 /* Get process structure for process pid */
647 /******************************************/
650 * This function seems to hog all of the CPU time. I can't figure out why - it
651 * doesn't do much.
653 int calculate_cpu(struct process *process) {
654 int rc;
656 #if defined(PARANOID)
657 assert(process->id==0x0badfeed);
658 #endif /* defined(PARANOID) */
660 rc = process_parse_procfs(process);
661 if (rc)
662 return 1;
665 * Check name against the exclusion list
667 if (process->counted && exclusion_expression && !regexec(exclusion_expression,process->name,0,0,0))
668 process->counted = 0;
670 return 0;
673 /******************************************/
674 /* Strip dead process entries */
675 /******************************************/
677 void process_cleanup() {
679 struct process *p = first_process;
680 while (p) {
681 struct process *current = p;
683 #if defined(PARANOID)
684 assert(p->id==0x0badfeed);
685 #endif /* defined(PARANOID) */
687 p = p->next;
689 * Delete processes that have died
691 if (current->time_stamp!=g_time)
692 delete_process(current);
696 /******************************************/
697 /* Destroy and remove a process */
698 /******************************************/
700 void delete_process(struct process *p) {
701 #if defined(PARANOID)
702 assert(p->id==0x0badfeed);
705 * Ensure that deleted processes aren't reused.
707 p->id = 0x007babe;
708 #endif /* defined(PARANOID) */
711 * Maintain doubly linked list.
713 if (p->next)
714 p->next->previous = p->previous;
715 if (p->previous)
716 p->previous->next = p->next;
717 else
718 first_process = p->next;
720 if (p->name)
721 wmtop_free(p->name);
722 wmtop_free(p);
725 /******************************************/
726 /* Generate display */
727 /******************************************/
729 void draw_processes() {
730 int i,n;
731 struct process *best[3] = { 0, 0, 0 };
732 int total;
735 * Invalidate time stamps
737 ++g_time;
739 update_process_table();
741 switch (mode) {
742 case 0:
743 total = calc_cpu_total();
744 calc_cpu_each(total);
745 break;
746 #if defined(LINUX)
747 case 1:
748 total = calc_mem_total();
749 calc_mem_each(total);
750 break;
751 #endif
754 process_cleanup();
757 * Find the top three!
759 n = process_find_top_three(best);
761 for (i = 0; i<3; ++i) {
762 int j;
763 char s[10];
764 strcpy(s," ");
765 if (i<n) {
766 for (j = 0; j<9; ++j) {
767 char c;
768 c = best[i]->name[j];
769 if (c)
770 s[j] = c;
771 else
772 break;
774 draw_bar(0, 97, 55, 6, best[i]->amount, 4, 13+i*20);
775 } else
776 draw_bar(0, 97, 55, 6, 0, 4, 13+i*20);
777 blit_string(s,4,4+i*20);
780 #if defined(DEBUG)
781 show_memory();
782 #endif
785 /******************************************/
786 /* Calculate cpu total */
787 /******************************************/
789 int calc_cpu_total() {
790 int total,t;
791 static int previous_total = INT_MAX;
792 #if defined(LINUX)
793 int rc;
794 int ps;
795 char line[WMTOP_BUFLENGTH];
796 int cpu,nice,system,idle;
798 ps = open("/proc/stat",O_RDONLY);
799 rc = read(ps,line,sizeof(line));
800 close(ps);
801 if (rc<0)
802 return 0;
803 sscanf(line,"%*s %d %d %d %d",&cpu,&nice,&system,&idle);
804 total = cpu+nice+system+idle;
805 #endif /* defined(LINUX) */
807 #if defined(FREEBSD)
808 struct timeval tv;
810 gettimeofday(&tv,0);
811 total = tv.tv_sec*1000+tv.tv_usec/1000;
812 #endif /* defined(FREEBSD) */
814 t = total-previous_total;
815 previous_total = total;
816 if (t<0)
817 t = 0;
819 return t;
822 /******************************************/
823 /* Calculate each processes cpu */
824 /******************************************/
826 void calc_cpu_each(int total) {
827 struct process *p = first_process;
828 while (p) {
830 #if defined(PARANOID)
831 assert(p->id==0x0badfeed);
832 #endif /* defined(PARANOID) */
834 p->amount = total ? 100*(float)(p->user_time+p->kernel_time)/total : 0;
835 p = p->next;
839 /******************************************/
840 /* Calculate total memory */
841 /******************************************/
843 #if defined(LINUX)
844 int calc_mem_total() {
845 int ps;
846 char line[512];
847 char *ptr;
848 int rc;
850 ps = open("/proc/meminfo",O_RDONLY);
851 rc = read(ps,line,sizeof(line));
852 close(ps);
853 if (rc<0)
854 return 0;
856 if ((ptr = strstr(line, "Mem:")) == NULL) {
857 return 0;
858 } else {
859 ptr += 4;
860 return atoi(ptr);
864 #endif /* defined(LINUX) */
866 /******************************************/
867 /* Calculate each processes memory */
868 /******************************************/
870 #if defined(LINUX)
871 void calc_mem_each(int total) {
872 struct process *p = first_process;
873 while (p) {
874 p->amount = 100*(float)p->rss/total;
875 p = p->next;
878 #endif /* defined(LINUX) */
880 /******************************************/
881 /* Find the top three processes */
882 /******************************************/
885 * Result is stored in decreasing order in best[0-2].
887 int process_find_top_three(struct process **best) {
888 struct process *p = first_process;
889 int n = 0;
892 * Insertion sort approach to skim top 3
894 while (p) {
895 if (p->counted && p->amount>0 && (!best[0] || p->amount>best[0]->amount)) {
896 best[2] = best[1];
897 best[1] = best[0];
898 best[0] = p;
899 ++n;
900 } else if (p->counted && p->amount>0 && (!best[1] || p->amount>best[1]->amount)) {
901 best[2] = best[1];
902 best[1] = p;
903 ++n;
904 } else if (p->counted && p->amount>0 && (!best[2] || p->amount>best[2]->amount)) {
905 ++n;
906 best[2] = p;
909 p = p->next;
912 return n>3 ? 3 : n;
915 /******************************************/
916 /* Blit bar at co-ordinates */
917 /******************************************/
919 void draw_bar(int sx, int sy, int w, int h, float percent, int dx, int dy) {
920 int tx;
922 if (percent<=100)
923 tx = w * (float)percent / 100;
924 else
925 tx = w;
927 if (tx>0)
928 copyXPMArea(sx, sy, tx, h, dx, dy);
929 if (tx<w)
930 copyXPMArea(sx+tx, sy+h, w-tx, h, dx+tx, dy);
933 /******************************************/
934 /* Blit string at co-ordinates */
935 /******************************************/
937 void blit_string(char *name, int x, int y) {
938 int i;
939 int c;
940 int k;
942 k = x;
943 for ( i = 0; name[i]; i++) {
944 c = toupper(name[i]);
945 if (c >= 'A' && c <= 'J') {
946 c -= 'A';
947 copyXPMArea(c*6,73,6,7,k,y);
948 } else if (c>='K' && c<='T') {
949 c -= 'K';
950 copyXPMArea(c*6,81,6,7,k,y);
951 } else if (c>='U' && c<='Z') {
952 c -= 'U';
953 copyXPMArea(c*6,89,6,7,k,y);
954 } else if (c>='0' && c<='9') {
955 c -= '0';
956 copyXPMArea(c*6,65,6,7,k,y);
957 } else {
958 copyXPMArea(36,89,6,7,k,y);
960 k += 6;
964 /******************************************/
965 /* Usage */
966 /******************************************/
968 void usage(void) {
969 int i;
970 fprintf(stderr,"\nWMtop - Dan Piponi <dan@tanelorn.demon.co.uk> http://www.tanelorn.demon.co.uk\n\n");
971 fprintf(stderr,"usage:\n");
972 fprintf(stderr," -display <display name>\n");
973 fprintf(stderr," -geometry +XPOS+YPOS initial window position\n");
974 fprintf(stderr," -s <...> sample rate in milliseconds (default:%d)\n", update_rate/1000);
975 fprintf(stderr," -r <...> refresh rate in milliseconds (default:%d)\n", refresh_rate/1000);
976 fprintf(stderr," -U display user processes only\n");
977 fprintf(stderr," -x <...> exclude matching processes\n");
978 fprintf(stderr," -c <...> command\n");
979 #if defined(LINUX)
980 fprintf(stderr," -m display memory usage\n");
981 #endif /* defined(LINUX) */
982 fprintf(stderr," -v print version number\n");
983 fprintf(stderr," -a <1..%d> select artistic style\n", nstyles);
984 fprintf(stderr,"\n");
985 fprintf(stderr,"The artistic style is one of:\n");
986 for (i = 0; i<nstyles; ++i)
987 fprintf(stderr," %d - %s\n",i+1,styles[i].description);
990 /******************************************/
991 /* Print version */
992 /******************************************/
994 void printversion(void) {
995 fprintf(stderr, "wmtop v%s\n",WMTOP_VERSION);