diff -urN tmp/orig/xscreensaver-5.07/driver/XScreenSaver.ad.in xscreensaver-5.07/driver/XScreenSaver.ad.in
--- tmp/orig/xscreensaver-5.07/driver/XScreenSaver.ad.in 2008-08-11 05:50:05.000000000 +0100
+++ xscreensaver-5.07/driver/XScreenSaver.ad.in 2008-11-16 00:32:52.000000000 +0000
@@ -223,6 +223,7 @@
epicycle -root \n\
flow -root \n\
@GL_KLUDGE@ GL: glplanet -root \n\
+@GL_KLUDGE@ GL: glpidstat -root \n\
interference -root \n\
jigsaw -root \n\
kumppa -root \n\
diff -urN tmp/orig/xscreensaver-5.07/hacks/config/glpidstat.xml xscreensaver-5.07/hacks/config/glpidstat.xml
--- tmp/orig/xscreensaver-5.07/hacks/config/glpidstat.xml 1970-01-01 01:00:00.000000000 +0100
+++ xscreensaver-5.07/hacks/config/glpidstat.xml 2008-11-13 17:35:17.000000000 +0000
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+ <_description>
+
+The 'pidstat' program, that is part of the systat suite, displays the state of
+processes on a Linux system. This hack displays the output of pidstat
+graphically so you can sit back and watch your machine. This hack
+requires version 8.1.6 of pidstat (or newer).
+
+Written by Dr. David Alan Gilbert; 2008.
+
+
+
diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.c xscreensaver-5.07/hacks/glx/glpidstat.c
--- tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.c 1970-01-01 01:00:00.000000000 +0100
+++ xscreensaver-5.07/hacks/glx/glpidstat.c 2008-11-16 15:53:25.000000000 +0000
@@ -0,0 +1,1251 @@
+/* -*- Mode: C; tab-width: 4 -*- */
+/* glpidstat --- Display of pidstat process data using GL. */
+
+/* Nice to do's:
+ Filter stuff for low scores and don't bother to add?
+ I took out context switches because you get way too many processes
+ Score processes that have just been swapped differently so that
+ we don't thrash as much
+ Gamma the colours so 10% cpu is noticeable
+ Look at intervals < a second in pidstat
+
+ It's difficult to associate the text with a particular stripe - especially with
+ descenders - texfont doesn't seem to tell us about descenders
+
+ Notes:
+ On the main stripe, Red is System, Green is total, user is blue (biased up)
+ On the IO stripe (right) Red is read (!), Green is written
+
+ Originally the cpu usage line drawn in each stripe was
+ stripewidth/ncpu - that's OK on a small number of cores but really
+ stinks on a 16way box where the line is very thin; so now it's always
+ stripewidth/2 with an offset that's offset within the stripe
+
+*/
+
+/*-
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind. The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof. In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ *
+ * Thanks goes to Brian Paul for making it possible and inexpensive to use
+ * OpenGL at home.
+ *
+ * Dr. David Alan Gilbert (2008-09-14)
+ *
+ * Revision History:
+ * 2008-09-14: Started by taking 'moebius' as a template
+ */
+
+#ifdef STANDALONE
+# define MODE_glpidstat
+# define refresh_glpidstat 0
+
+#define DEF_FONT "-*-times-bold-r-normal-*-240-*"
+# define DEFAULTS "*delay: 500000 \n" \
+ "*showFPS: False \n" \
+ "*font: " DEF_FONT "\n" \
+
+# include "xlockmore.h" /* from the xscreensaver distribution */
+#else /* !STANDALONE */
+# include "xlock.h" /* from the xlockmore distribution */
+#endif /* !STANDALONE */
+
+#include
+/* For FLT_MAX */
+#include
+/* For XtAppAddInput */
+#include
+
+#ifdef MODE_glpidstat
+
+#include "gltrackball.h"
+#include "rotator.h"
+#include "texfont.h"
+
+#define DEF_PIDSTATCOMMANDSTRING "pidstat -h -u -r -d 1"
+/* Hmm do I want to determine this from window size? */
+#define DEF_NUMSTRIPES "40"
+#define DEF_SAMPLESPERSTRIP "30"
+#define DEF_MAXRANGEIO "1024"
+#define LINEBUFFERSIZE 1024
+
+static char* pidstatcommandstring;
+static unsigned int numStripes; /* That's the number of processes to show accross */
+static unsigned int samplesPerStripe; /* Number of samples vertically */
+static float maxRangeIO; /* Amount of IO to be full colour */
+
+static XrmOptionDescRec opts[] =
+{
+ {"-pidstatcommandstring", ".pidstatcommandstring", XrmoptionSepArg, 0},
+ {"-numstripes", ".numstripes", XrmoptionSepArg, 0},
+ {"-samplesperstripe", ".samplesperstripe", XrmoptionSepArg, 0},
+ {"-maxrangeio", ".maxrangeio", XrmoptionSepArg, 0}
+};
+static argtype vars[] =
+{
+ {&pidstatcommandstring, "pidstatcommandstring", "Pidstatcommandstring", DEF_PIDSTATCOMMANDSTRING, t_String},
+ {&numStripes, "numstripes", "Numstripes", DEF_NUMSTRIPES, t_Int},
+ {&samplesPerStripe, "samplesperstripe", "Samplesperstripe", DEF_SAMPLESPERSTRIP, t_Int},
+ {&maxRangeIO, "maxrangeio", "Maxrangeio", DEF_MAXRANGEIO, t_Float},
+
+};
+static OptionStruct desc[] =
+{
+ {"-pidstatcommandstring", "The command to run pidstat - normally with the -h option"},
+ {"-numstripes", "Number of vertical stripes"},
+ {"-samplesperstripe", "Number of samples down"},
+ {"-maxrangeio", "The amount of IO to make the colours go to full brightness"}
+};
+
+ENTRYPOINT ModeSpecOpt glpidstat_opts =
+{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
+
+#ifdef USE_MODULES
+ModStruct glpidstat_description =
+{"glpidstat", "init_glpidstat", "draw_glpidstat", "release_glpidstat",
+ "draw_glpidstat", "change_glpidstat", (char *) NULL, &glpidstat_opts,
+ 1000, 1, 1, 1, 4, 1.0, "",
+ "Shows system state using pidstat.", 0, NULL};
+
+#endif
+
+#define sqr(A) ((A)*(A))
+
+#ifndef Pi
+#define Pi M_PI
+#endif
+
+/*************************************************************************/
+
+/* The type of the time reported by pidstat */
+typedef long pidstatTimeT;
+
+/* One sample for a process or thread - threads don't necessarily have it all right */
+typedef struct {
+ float user, system, guest, total; /* 0...1 from % cpu used */
+ unsigned int curCPU; /* Which CPU it's on */
+
+ /* Could add cancelled - not sure how I would display it */
+ float kbread, kbwritten;
+ /* Lots more to add */
+} samplet;
+
+/* Information for one process and all it's children in the main process store */
+typedef struct processinfo_s {
+ char* name;
+ pidstatTimeT lastinterest; /* Time a sample last came in */
+ unsigned int pid; /* Note not pid_t - remote machine might have larger pid_t */
+ /* Number of display cells that reference us */
+ int count;
+ samplet* samples;
+ struct processinfo_s *next;
+} processinfo;
+
+/* These hold a temporary copy of process data as they are read in but before they
+are merged into the main process list - they are a separate type because they
+contain a sample */
+typedef struct processtmp_s {
+ char* name;
+ pidstatTimeT lastinterest; /* Time a sample last came in */
+ unsigned int pid;
+ samplet onesample;
+ struct processtmp_s* next;
+
+ /* Filled in by the merging process */
+ processinfo* mainprocess;
+ float score;
+} processtmp;
+
+typedef struct {
+ GLint WindH, WindW;
+ GLXContext *glx_context;
+ rotator *rot;
+ trackball_state *trackball;
+ Bool button_down_p;
+} glpidstatstruct;
+
+/* There is one of these per screen */
+static glpidstatstruct *glpidstat = (glpidstatstruct *) NULL;
+
+typedef struct {
+ pid_t childprocess;
+ int fdtochild;
+
+ /* Process chain */
+ processinfo* processes;
+
+ /* 2D array of process pointers - one for each cell on the display */
+ processinfo** bindings;
+
+ /* The temporary list built while reading the samples in */
+ processtmp* tmplist;
+
+ int numCPUs;
+
+ /* last time of any sample we got - 'now' as far as the far pidstat knows */
+ pidstatTimeT lastsample;
+
+ /* An array of scores for the last row */
+ float* lastrowscores;
+ char tmpcommandname[LINEBUFFERSIZE];
+ int haveGotHeader;
+
+ texture_font_data *texfont;
+
+
+} glpidstatglobalstruct;
+
+/* This is global for all screens */
+static glpidstatglobalstruct *glpidstatglob = (glpidstatglobalstruct*) NULL;
+
+/*************************************************************************/
+
+static const float front_shininess[] = {0.0};
+static const float front_specular[] = {0.1, 0.1, 0.1, 1.0};
+static const float ambient[] = {0.0, 0.0, 0.0, 1.0};
+static const float diffuse[] = {1.0, 1.0, 1.0, 1.0};
+static const float position0[] = {1.0, 1.0, 1.0, 0.0};
+static const float position1[] = {-1.0, -1.0, 1.0, 0.0};
+static const float lmodel_ambient[] = {0.5, 0.5, 0.5, 1.0};
+static const float lmodel_twoside[] = {GL_FALSE};
+
+static const float MaterialBase[] = {0.0, 0.0, 0.0, 1.0};
+
+static processinfo* getProcess(int stripe, int sample)
+{
+ return glpidstatglob->bindings[sample*numStripes+stripe];
+}
+
+/* As above but where you need to modify the binding - ah for references */
+static processinfo** getProcessRef(int stripe, int sample)
+{
+ return &(glpidstatglob->bindings[sample*numStripes+stripe]);
+}
+
+static processinfo* findProcessByNameAndID(const char* name, unsigned int pid)
+{
+ processinfo* cur;
+
+ for(cur=glpidstatglob->processes;cur; cur=cur->next)
+ {
+ if ((cur->pid==pid) && (strcmp(name,cur->name)==0))
+ return cur;
+ }
+
+ return NULL;
+}
+
+/* Returns NULL if there is no process bound to the given cell */
+static samplet* getSample(int stripe, int sample)
+{
+ processinfo* p=getProcess(stripe, sample);
+
+ if (!p) return NULL;
+
+ return &(p->samples[sample]);
+}
+
+/* Where we shuffle the data up but don't have any process info this cycle assume it did nothing
+ - we might want to average something here */
+static void
+clearSample(samplet *s)
+{
+ s->user=0.0;
+ s->system=0.0;
+ s->guest=0.0;
+ s->total=0.0;
+ s->curCPU=0;
+
+ s->kbread=0.0;
+ s->kbwritten=0.0;
+}
+
+static float
+scoreSample(samplet *s)
+{
+ float res=1.0;
+ float ioscore=0.0;
+ float tmp;
+
+ /* This needs to get smarter as we add other parts of the pidstat output into the sample */
+ res=res*s->total;
+
+ tmp=s->kbread/maxRangeIO;
+ if (tmp>1.0) tmp=1.0;
+ ioscore+=tmp;
+
+ tmp=s->kbwritten/maxRangeIO;
+ if (tmp>1.0) tmp=1.0;
+ ioscore+=tmp;
+
+ return res+ioscore;
+}
+
+/*
+Walk the process list and delete any process that has no references and hasn't
+had any for a while - we don't just take out ones with no reference since they
+might have useful data just none in the last sample */
+static void
+removeOldProcesses(void)
+{
+ processinfo* curp;
+ processinfo** lastp; /* Pointer to modify if we delete curp to fix the list */
+ pidstatTimeT now=glpidstatglob->lastsample;
+
+ lastp=&(glpidstatglob->processes);
+ curp=glpidstatglob->processes;
+
+ while (curp)
+ {
+ if ((curp->count==0) && (curp->lastinterest<(now-samplesPerStripe)))
+ {
+ /* Delete */
+ processinfo *nextp=curp->next;
+
+ *lastp=nextp;
+
+ free(curp->samples);
+ free(curp->name);
+ /* TODO: Possibly an image of the name coming here */
+ free(curp);
+
+ curp=nextp;
+ /* lastp stays the same */
+
+ } else {
+ lastp=&(curp->next);
+ curp=curp->next;
+ }
+ };
+}
+
+/* Display text for a process so that the bottom left of the text is at x,y - width should
+ be the desired height of the text */
+static void
+addProcessText(char* text, float x, float y, float width)
+{
+ int textwidth, textheight;
+ textwidth=texture_string_width(glpidstatglob->texfont,text,&textheight);
+ glPushMatrix();
+ /* This should make 0,0 be the top of our sample */
+ glTranslatef(x+width,y,0);
+ /* We base our scaling off the height which should be mostly constant -
+ so x scaling is also by that */
+ glScalef(width/textheight,width/textheight,1.0);
+ /* make text point up */
+ glRotatef(90.0, 0, 0.0, 1.0);
+ glDisable (GL_DEPTH_TEST);
+ glEnable(GL_TEXTURE_2D);
+ glColor3f(1.0,1.0,1.0);
+ print_texture_string(glpidstatglob->texfont,text);
+ glDisable(GL_TEXTURE_2D);
+ glEnable (GL_DEPTH_TEST);
+ glPopMatrix();
+}
+
+static Bool
+draw_glpidstat_strip(ModeInfo * mi)
+{
+ glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)];
+ int stripe,sample;
+ float stripeWidth=2.0/numStripes;
+ float sampleHeight=2.0/samplesPerStripe;
+ float traceWidth=stripeWidth/2.0;
+ float traceStep=stripeWidth/(2*(glpidstatglob->numCPUs-1));
+
+ /* -1,1 is top left, 1,-1 is bottom right */
+ for(stripe=0;stripecurCPU:0;
+ traceLeft=stripeLeft+traceStep*cpu+traceWidth*0.75;
+ traceRight=traceLeft+traceWidth*0.25;
+
+ if ((sample!=0) && (previousProcess!=currentProcess))
+ {
+ glColor3f(0.0,0.0,0.0);
+
+ /* Something different when the process changes */
+ glVertex3f(traceLeft, sampleTop+sampleHeight/2,0);
+ glVertex3f(traceRight,sampleTop+sampleHeight/2,0);
+
+ /* Split the strip so we can do other stuff */
+ glEnd();
+
+ glBegin(GL_QUAD_STRIP);
+ }
+
+ if (cursample)
+ {
+ float colread, colwrite;
+
+ colread=cursample->kbread/maxRangeIO;
+ colwrite=cursample->kbwritten/maxRangeIO;
+ if (colread>1.0) colread=1.0;
+ if (colwrite>1.0) colwrite=1.0;
+
+ /* Bias blue on a bit so it's not all black if idleish */
+ glColor3f(colread, colwrite,0.2);
+
+ } else {
+ glColor3f(0.0,0,0.2);
+ }
+
+ glVertex3f(traceLeft, sampleTop,0);
+ glVertex3f(traceRight,sampleTop,0);
+ }
+ glEnd();
+ glBegin(GL_QUAD_STRIP);
+
+ previousProcess=NULL;
+ currentProcess=NULL;
+
+ /* 2nd pass through the samples - CPU load, stirp to the right and text for the process names */
+ for(sample=0;samplecurCPU:0;
+ traceLeft=stripeLeft+traceStep*cpu;
+ traceRight=traceLeft+traceWidth*0.75;
+
+ if ((sample!=0) && (previousProcess!=currentProcess))
+ {
+ glColor3f(0.0,0.0,0.0);
+
+ /* Something different when the process changes , need
+ to break the stripe and display the text*/
+ glVertex3f(traceLeft, sampleTop+sampleHeight/2 ,0);
+ glVertex3f(traceRight,sampleTop+sampleHeight/2,0);
+
+ /* Split the strip so we can do other stuff */
+ glEnd();
+
+ if (previousProcess!=NULL)
+ {
+ addProcessText(previousProcess->name, stripeLeft, sampleTop, stripeWidth);
+ }
+ glBegin(GL_QUAD_STRIP);
+ }
+
+ if (cursample)
+ {
+ /* Bias blue on a bit so it's not all black if idleish */
+ glColor3f(cursample->system, cursample->total,0.25+(cursample->user*.075));
+ } else {
+ glColor3f(0.2,0.2,0.2);
+ }
+
+ glVertex3f(traceLeft, sampleTop ,0);
+ glVertex3f(traceRight,sampleTop,0);
+ }
+ glEnd();
+ /* Add text to identify the process at the bottom of the strip */
+ if (currentProcess)
+ {
+ addProcessText(currentProcess->name, stripeLeft, -1.0, stripeWidth);
+ }
+
+ }
+
+ return True;
+}
+
+ENTRYPOINT void
+reshape_glpidstat (ModeInfo * mi, int width, int height)
+{
+ glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)];
+
+ glViewport(0, 0, mp->WindW = (GLint) width, mp->WindH = (GLint) height);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ /* x range, y range, z range ?*/
+ glFrustum(-1.0, 1.0, -1.0, 1.0, 5, 15.0);
+ glMatrixMode(GL_MODELVIEW);
+ if (width >= 1024) {
+ glLineWidth(3);
+ glPointSize(3);
+ } else if (width >= 512) {
+ glLineWidth(2);
+ glPointSize(2);
+ } else {
+ glLineWidth(1);
+ glPointSize(1);
+ }
+}
+
+static void
+pinit(void)
+{
+ glClearDepth(1.0);
+ glClearColor(0.0, 0.0, 0.0, 1.0);
+
+ glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
+ glLightfv(GL_LIGHT0, GL_POSITION, position0);
+ glLightfv(GL_LIGHT1, GL_AMBIENT, ambient);
+ glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse);
+ glLightfv(GL_LIGHT1, GL_POSITION, position1);
+ glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
+ glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_LIGHT1);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_COLOR_MATERIAL);
+ glFrontFace(GL_CCW);
+ glCullFace(GL_BACK);
+
+ /* glpidstat */
+ glShadeModel(GL_SMOOTH);
+ glEnable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+ clear_gl_error();
+}
+
+
+
+ENTRYPOINT void
+release_glpidstat (ModeInfo * mi)
+{
+ if (glpidstat != NULL) {
+ (void) free((void *) glpidstat);
+ glpidstat = (glpidstatstruct *) NULL;
+ }
+ FreeAllGL(mi);
+}
+
+ENTRYPOINT Bool
+glpidstat_handle_event (ModeInfo *mi, XEvent *event)
+{
+ glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)];
+
+ if (event->xany.type == ButtonPress &&
+ event->xbutton.button == Button1)
+ {
+ mp->button_down_p = True;
+ gltrackball_start (mp->trackball,
+ event->xbutton.x, event->xbutton.y,
+ MI_WIDTH (mi), MI_HEIGHT (mi));
+ return True;
+ }
+ else if (event->xany.type == ButtonRelease &&
+ event->xbutton.button == Button1)
+ {
+ mp->button_down_p = False;
+ return True;
+ }
+ else if (event->xany.type == ButtonPress &&
+ (event->xbutton.button == Button4 ||
+ event->xbutton.button == Button5))
+ {
+ gltrackball_mousewheel (mp->trackball, event->xbutton.button, 10,
+ !!event->xbutton.state);
+ return True;
+ }
+ else if (event->xany.type == MotionNotify &&
+ mp->button_down_p)
+ {
+ gltrackball_track (mp->trackball,
+ event->xmotion.x, event->xmotion.y,
+ MI_WIDTH (mi), MI_HEIGHT (mi));
+ return True;
+ }
+
+ return False;
+}
+
+
+/*************************************************************************/
+/* Input/child code to get data from pidstat */
+/*************************************************************************/
+
+/* The header line ends in (x CPU) where x is the number of CPUs - of
+ course that may be multidigit */
+static int
+processHeaderLine(char* buffer)
+{
+ char* openbracket;
+
+ /* Look for the ( before the number */
+ openbracket=strrchr(buffer,'(');
+ if (openbracket!=NULL)
+ {
+ int numCPUs;
+
+ fprintf(stderr,"processHeaderLine with openbracket: %s\n", openbracket);
+
+ numCPUs=atoi(openbracket+1);
+
+ if (numCPUs>0)
+ {
+ glpidstatglob->numCPUs=numCPUs;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+mergeTempProcesses(void)
+{
+ processtmp* curtp;
+ processtmp** lasttp; /* Used for deleting off the temp list */
+ int curstripe;
+
+
+
+ /*fprintf(stderr,"mergeTempProcesses 1st loop (processes/scores):");*/
+ /* Work down the temp list finding if they already have matching processes
+ in the main process list and giving them scores */
+ for(curtp=glpidstatglob->tmplist;curtp;curtp=curtp->next)
+ {
+ curtp->mainprocess=findProcessByNameAndID(curtp->name, curtp->pid);
+ curtp->score=scoreSample(&(curtp->onesample));
+
+ /*fprintf(stderr,"(%p, %p, %f) ",curtp, curtp->mainprocess, curtp->score);*/
+ }
+
+ /*fprintf(stderr,"\n mergeTempProcesses 2nd loop (bottom row update) : ");*/
+ /* For any process that is on the temporary list that is on the bottom row
+ already (i.e. was on the old bottom row) then just update that process
+ and move along.
+ In this pass we also add new processes to the process list and add
+ the sample in the temporary to the main process */
+ lasttp=&(glpidstatglob->tmplist);
+ curtp=glpidstatglob->tmplist;
+ while (curtp)
+ {
+ int found;
+ processinfo* mainproc=curtp->mainprocess;
+
+ /*fprintf(stderr," %p, ", curtp);*/
+ found=0;
+ if (mainproc)
+ {
+ for(curstripe=0;
+ (!found) && (curstripename=strdup(curtp->name);
+ newproc->lastinterest=0; /* Gets tweaked below */
+ newproc->pid=curtp->pid;
+ newproc->count=0;
+ newproc->samples=calloc(samplesPerStripe,sizeof(samplet));
+ if (!newproc->samples)
+ {
+ fprintf(stderr,"Failed to allocate sample data\n");
+ exit(1);
+ }
+
+ /* Add to head */
+ newproc->next=glpidstatglob->processes;
+ glpidstatglob->processes=newproc;
+
+ /* Mod the temporary list entry to point to it */
+ curtp->mainprocess=newproc;
+
+ mainproc=newproc;
+ }
+
+ /* Add the temporary process sample to the process - even if we don't use it now */
+ mainproc->lastinterest=curtp->lastinterest;
+ mainproc->samples[samplesPerStripe-1]=curtp->onesample;
+ /*fprintf(stderr, " %p, %d, ", mainproc, found?curstripe:-1);*/
+
+ if (found)
+ {
+ /* The temp process is on the bottom row already - nothing to be done */
+ processtmp* next=curtp->next;
+
+ /* Delete this temporary list entry */
+ *lasttp=next;
+ free(curtp->name);
+ free(curtp);
+ curtp=next;
+ /* lasttp stays the same since we've deleted this entry */
+ } else {
+ /* Just move along - this entry isn't on the bottom row
+ we'll get around to it in the next pass */
+ lasttp=&(curtp->next);
+ curtp=curtp->next;
+ }
+ };
+
+ /* Fill in scores for the last row */
+ if (!glpidstatglob->lastrowscores)
+ {
+ glpidstatglob->lastrowscores=calloc(numStripes,sizeof(float));
+ if (!glpidstatglob->lastrowscores)
+ {
+ fprintf(stderr,"Failed to allocate last row data\n");
+ exit(1);
+ }
+ }
+
+ /*fprintf(stderr,"\n mergeTempProcesses 3rd loop (last row scores) ");*/
+ for(curstripe=0;curstripelastrowscores[curstripe]=scoreSample(&(proc->samples[samplesPerStripe-1]));
+ if (glpidstatglob->lastrowscores[curstripe]==0.0)
+ {
+ /* So there is no valid sample data at that row - or it's all 0
+ what we need to do is make processes which have hadn't had any interesting
+ data for a long time be move -ve - lastinterest should be less than the
+ global lastsample except where it has been just updated and still
+ has a 0 score */
+ glpidstatglob->lastrowscores[curstripe]=proc->lastinterest-glpidstatglob->lastsample;
+ }
+ } else {
+ /* Lower score than any real process */
+ glpidstatglob->lastrowscores[curstripe]=-1000000.0;
+ }
+
+ /*fprintf(stderr, "%f ", glpidstatglob->lastrowscores[curstripe]);*/
+ }
+
+ /*fprintf(stderr,"\n mergeTempProcesses 4th loop (add new bottom rows) ");*/
+ /* Now go through the temporary list again - this time what's left on it isn't
+ on the bottom row so we have to decide if to add it and where based on scores
+
+ Note even though this tmplist entry isn't in the bottom row, it's process
+ entry was added and it's sample stored in the previous loop */
+ lasttp=&(glpidstatglob->tmplist);
+ curtp=glpidstatglob->tmplist;
+ while (curtp)
+ {
+ processinfo* mainproc=curtp->mainprocess;
+ processtmp* next;
+
+ int bestmatch=-1; /* Stripe with lowest score */
+ float bestscore=MAXFLOAT;
+ unsigned int curstripe=0;
+
+
+ /* I did wonder about sorting this list - but we have to update
+ after each mod anyway */
+ for(curstripe=0;curstripelastrowscores[curstripe]lastrowscores[curstripe];
+ bestmatch=curstripe;
+ }
+ }
+
+ /*fprintf(stderr, " (%f %f %d ) ", curtp->score, bestscore, bestmatch);*/
+ /* If the temp process has a higher score than the lowest
+ scoring entry in the last row then insert the temp.
+
+ Note we might want to put some flipping limit in here, e.g.
+ don't flip if it just got flipped? */
+
+ if (curtp->score > bestscore)
+ {
+ /* Get a pointer to the cell to change */
+ processinfo** toreplaceprocref=getProcessRef(bestmatch, samplesPerStripe-1);
+
+ /* If there is something there then we need to dec it's use */
+ if (*toreplaceprocref)
+ (*toreplaceprocref)->count--;
+
+ *toreplaceprocref=mainproc;
+ mainproc->count++;
+
+ /* Update the scores */
+ glpidstatglob->lastrowscores[bestmatch]=curtp->score;
+ }
+
+ /* Delete this temp process entry */
+ next=curtp->next;
+
+ *lasttp=next;
+ free(curtp->name);
+ free(curtp);
+ curtp=next;
+ /* lasttp stays the same since we've deleted this entry */
+
+ };
+}
+
+#define EXPECTEDFORMAT "# Time PID %usr %system %guest %CPU CPU minflt/s majflt/s VSZ RSS %MEM kB_rd/s kB_wr/s kB_ccwr/s Command"
+static void
+checkFormatLine(char* buffer)
+{
+ if (strcmp(buffer,EXPECTEDFORMAT)!=0)
+ {
+ fprintf(stderr,"pidstat format mismatch - check options and version; we expected: %s\nbut got %s\n", EXPECTEDFORMAT, buffer);
+ exit(1);
+ }
+
+ /* This needs to do the rolling up */
+ {
+ processinfo* curp;
+ unsigned int curs;
+
+ /* Roll all the samples along */
+ for(curp=glpidstatglob->processes;curp;curp=curp->next)
+ {
+ memmove(curp->samples,&(curp->samples[1]),sizeof(curp->samples[0])*samplesPerStripe);
+ clearSample(&(curp->samples[samplesPerStripe-1]));
+ }
+
+ /* Now move the process bindings up */
+ /* Loop accross the stripes removing the process that's about to go off the top*/
+ for(curs=0;curscount--;
+
+ /* Inc the count of the bottom sample's process of the stripe because it's
+ about to become the bottom and 2nd to bottom */
+ curp=getProcess(curs,samplesPerStripe-1);
+ if (curp)
+ curp->count++;
+ }
+
+ /* Now move the whole array up */
+ /* Since we moved the old last line still has the old pointers in - and that's a good
+ start - note we incremented their count before */
+ memmove(glpidstatglob->bindings, glpidstatglob->bindings+numStripes,
+ sizeof(processinfo*)*numStripes*(samplesPerStripe-1));
+
+ mergeTempProcesses();
+
+ /* Any process on the bottom row that hasn't had anything interesting in a while
+ we remove from the bottom row, eventually it will go off the top and
+ eventually get cleaned up by removeOldProcesses */
+ for(curs=0;curslastsample;
+
+ curp=getProcess(curs,samplesPerStripe-1);
+ if ((curp) && (curp->lastinterest<(now-samplesPerStripe)))
+ {
+ /* Remove from bottom row */
+ *(getProcessRef(curs,samplesPerStripe-1))=NULL;
+ curp->count--;
+ }
+ }
+
+ /* Remove any process that haven't seen any action lately */
+ removeOldProcesses();
+ }
+}
+
+static void
+processDataLine(char* buffer)
+{
+ long eventtime;
+ unsigned int pid;
+ float user,system,guest,totalcpu;
+ unsigned int curCPU;
+ float minorfaults, majorfaults;
+ long vsz,rss;
+ float percentmem;
+ float kbread, kbwritten, kbcancelledwrite;
+ float contextswitches, nonvolcs;
+ int scanfRet;
+ processtmp* ourtmpp;
+
+ scanfRet=sscanf(buffer, "%ld %u %f %f %f %f %u %f %f %ld %ld %f %f %f %f %s",
+ &eventtime,&pid,
+ &user,&system,&guest,&totalcpu,&curCPU,
+ &minorfaults,&majorfaults,
+ &vsz, &rss, &percentmem,
+ &kbread, &kbwritten, &kbcancelledwrite,
+ /* &contextswitches, &nonvolcs, */
+ glpidstatglob->tmpcommandname);
+
+ if (scanfRet!=16)
+ {
+ fprintf(stderr,"processDataLine got %d items read from %s\n", scanfRet, buffer);
+ exit(1);
+ };
+
+ /* Hmm insert somewhere, somehow */
+ /* Create an entry on the temporary process list */
+ if (ourtmpp=(processtmp*)malloc(sizeof(processtmp)), ourtmpp==NULL)
+ {
+ fprintf(stderr,"processDataLine failed to allocate temporary process\n");
+ exit(1);
+ }
+
+ /* Glue it into the list */
+ ourtmpp->name=strdup(glpidstatglob->tmpcommandname);
+ ourtmpp->lastinterest=eventtime;
+ ourtmpp->pid=pid;
+
+ /* Sample values are 0...1 */
+ ourtmpp->onesample.user=user/100.0;
+ ourtmpp->onesample.system=system/100.0;
+ ourtmpp->onesample.guest=guest/100.0;
+ ourtmpp->onesample.total=totalcpu/100.0;
+ ourtmpp->onesample.curCPU=curCPU;
+ ourtmpp->onesample.kbread=kbread;
+ ourtmpp->onesample.kbwritten=kbwritten;
+
+ ourtmpp->next=glpidstatglob->tmplist;
+ glpidstatglob->tmplist=ourtmpp;
+
+ if (eventtime>glpidstatglob->lastsample)
+ glpidstatglob->lastsample=eventtime;
+}
+
+/* The types of lines we get are:
+ 1) Header line - which contains the number of CPUs
+ 2) The format line which is the user readable line - we can check
+ that to check that the data line is in the format we expect
+ (if we get really smart we can start parsing it to make it cope
+ with changes and allow the user to extract any field)
+ 3) Data lines - one line per process sample
+ (3a if we get round to it thread lines if we add -t)
+ 4) A blank line we ignore
+
+ We might also get some random crud if the user has started the pidstat
+ via a bad ssh or the like - ideally we could do with ignoring it */
+static void
+processLine (char* buffer)
+{
+ int lineLength=strlen(buffer);
+
+ /* I've seen the line end in a \r - I'm not sure why yet (is this the pty ?)
+ - strip it */
+ if ((lineLength>0) && (buffer[lineLength-1]='\r'))
+ {
+ buffer[lineLength-1]='\0';
+ lineLength--;
+ }
+
+ if (!glpidstatglob->haveGotHeader)
+ {
+ /* The header ends in CPU) */
+ if ((lineLength>4) && (strcmp("CPU)", buffer+(lineLength-4))==0))
+ {
+ glpidstatglob->haveGotHeader=processHeaderLine(buffer);
+ };
+ } else {
+ /* OK we have a header, so we're into real data */
+ if (buffer[0]=='#')
+ {
+ checkFormatLine(buffer);
+ } else if (lineLength==0) {
+ /* Empty line - ignore */
+ } else {
+ /* This had better be a data line */
+ processDataLine(buffer);
+ }
+ }
+}
+
+/*
+ * Called by an XtAppAddInput event
+ * The file descriptor is non blocking, so try and read and do interesting
+ * things when we get a line
+ */
+static void
+incomingDataFunc (XtPointer closure, int *source, XtInputId *id)
+{
+ static char linebuffer[LINEBUFFERSIZE];
+ static unsigned int nextFree=0; /* Next free point in linebuffer */
+ int done=0;
+
+ do
+ {
+ char* lf;
+
+ /* Reinitialises empty or emptied buffer */
+ if (nextFree==0) linebuffer[0]='\0';
+
+ lf=strchr(linebuffer,'\n');
+ if (lf==NULL)
+ {
+ int amountread;
+
+ /* -1 on size to allow for a null - nextFree is never that big due to checks on previous read */
+ amountread=read(glpidstatglob->fdtochild,linebuffer+nextFree,(LINEBUFFERSIZE-nextFree)-1);
+
+ if (amountread<=0)
+ {
+ /* No lf and nothing read, nothing to do */
+ /* TODO: What to do on error */
+ done=1;
+ } else {
+ nextFree+=amountread;
+ linebuffer[nextFree]='\0';
+
+ /* Now check for overlength lines */
+ lf=strchr(linebuffer,'\n');
+ if ((lf==NULL) && (nextFree>=(LINEBUFFERSIZE-1)))
+ {
+ /* We have no lf and we have a full buffer - give up on it */
+ fprintf(stderr,"Overlength none-line\n");
+ nextFree=0;
+ }
+ }
+
+ }
+
+ /* So check lf again - it's none-NULL if it was non-null or if we've just
+ gained a lf */
+ if (lf!=NULL)
+ {
+ *lf='\0';
+ processLine(linebuffer);
+ /* shuffle everything after the line down to the beginning of the buffer */
+ memmove(linebuffer, lf+1, LINEBUFFERSIZE-((lf+1)-linebuffer));
+ nextFree-=((lf+1)-linebuffer);
+ }
+ } while (!done);
+}
+
+/* Called from after the fork, stdin/out/err are already wired */
+static void
+startChild (void)
+{
+ /* Make sure the output we get is in the format we expect
+ without format being changed by locales */
+ putenv("LANG=C");
+ putenv("LC_ALL=C");
+
+ /* Let system take care of any options the user may have specified on the command; thanks */
+ system(pidstatcommandstring);
+
+ /* If this has gone wrong there isn't actually much we can do */
+ exit(1);
+}
+
+/*************************************************************************/
+/* The exported interfaces */
+/*************************************************************************/
+ENTRYPOINT void
+init_glpidstat (ModeInfo * mi)
+{
+ pid_t pid;
+ glpidstatstruct *mp;
+ int pipefd[2];
+
+ /* Per screen struct */
+ if (glpidstat == NULL) {
+ if ((glpidstat = (glpidstatstruct *) calloc(MI_NUM_SCREENS(mi),
+ sizeof (glpidstatstruct))) == NULL)
+ return;
+ }
+
+ /* global struct with all process data */
+ if (glpidstatglob == NULL) {
+ glpidstatglob = (glpidstatglobalstruct *) calloc(1, sizeof(glpidstatglobalstruct));
+ if (glpidstatglob == NULL) return;
+
+ glpidstatglob->bindings = (processinfo**)calloc(numStripes*samplesPerStripe,sizeof(processinfo*));
+ if (glpidstatglob->bindings == NULL) return;
+
+ /* We read this properly from the pidstat output later */
+ glpidstatglob->numCPUs=1;
+ glpidstatglob->lastsample=0;
+ }
+
+ mp = &glpidstat[MI_SCREEN(mi)];
+ {
+ double rot_speed = 0.0;
+ mp->rot = make_rotator (rot_speed, rot_speed, rot_speed, 1, 0, True);
+ mp->trackball = gltrackball_init ();
+ }
+
+ if ((mp->glx_context = init_GL(mi)) != NULL) {
+
+ reshape_glpidstat(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+ glDrawBuffer(GL_BACK);
+ pinit();
+ } else {
+ MI_CLEARWINDOW(mi);
+ }
+ if (!glpidstatglob->texfont)
+ glpidstatglob->texfont=load_texture_font (MI_DISPLAY(mi), "font");
+
+#if 0
+ /* If I use this pipe here the GL stops working - so I used that openpty
+ below - this bugs me */
+ if (pipe(pipefd)==-1)
+ {
+ perror("Pipe create");
+ return;
+ }
+#endif
+ if (openpty(&(pipefd[0]),&(pipefd[1]),NULL,NULL,NULL)==-1)
+ {
+ perror("openpty");
+ return;
+ };
+ glpidstatglob->fdtochild=pipefd[0];
+ switch (pid=fork())
+ {
+ case 0: /* Child */
+ close(0);
+ close(pipefd[0]); /* That's our copy of the side the parent reads from */
+ /* Swizzle it to stdout */
+ if (dup2(pipefd[1],1)==-1)
+ {
+ perror("dup2");
+ exit(1);
+ }
+ close(pipefd[1]); /* It's now on our stdout */
+ startChild();
+ /* We shouldn't get here, and if we do there isn't much we can do about it */
+ exit(0);
+
+ case -1:
+ /* Error */
+ free(glpidstat);
+ free(glpidstatglob);
+ return;
+
+ default: /* Parent */
+ {
+ int oldflags;
+ glpidstatglob->childprocess=pid;
+ /* I think I need the file descriptor to be non-blocking so that I don't hang on a short
+ read when XtAppAddInput calls me back */
+
+ oldflags=fcntl(glpidstatglob->fdtochild, F_GETFL);
+ if (oldflags!=-1)
+ {
+ fcntl(glpidstatglob->fdtochild, F_SETFL, (long)(oldflags | O_NONBLOCK));
+ };
+
+ /* Note this returns a handle that can be used to remove it later */
+ XtAppAddInput(XtDisplayToApplicationContext(mi->dpy), glpidstatglob->fdtochild, (XtPointer) (XtInputReadMask | XtInputExceptMask),
+ incomingDataFunc, NULL /* client data */);
+ }
+ break;
+
+
+ }
+}
+
+ENTRYPOINT void
+draw_glpidstat (ModeInfo * mi)
+{
+ glpidstatstruct *mp;
+
+ Display *display = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+
+ if (glpidstat == NULL)
+ return;
+ mp = &glpidstat[MI_SCREEN(mi)];
+
+ MI_IS_DRAWN(mi) = True;
+
+ if (!mp->glx_context)
+ return;
+
+ glXMakeCurrent(display, window, *(mp->glx_context));
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix();
+
+ glTranslatef(0.0, 0.0, -5.0); /* Compare with the Frustum above */
+
+ /* gltrackball_rotate (mp->trackball); */
+
+/*
+ {
+ double x, y, z;
+ get_rotation (mp->rot, &x, &y, &z, !mp->button_down_p);
+ glRotatef (x * 360, 1.0, 0.0, 0.0);
+ glRotatef (y * 360, 0.0, 1.0, 0.0);
+ glRotatef (z * 360, 0.0, 0.0, 1.0);
+ }
+*/
+ /* glpidstat */
+ if (!draw_glpidstat_strip(mi)) {
+ release_glpidstat(mi);
+ return;
+ }
+
+ glPopMatrix();
+
+ if (MI_IS_FPS(mi)) do_fps (mi);
+ glFlush();
+
+ glXSwapBuffers(display, window);
+
+ /* Check for data ready to import */
+}
+
+#ifndef STANDALONE
+ENTRYPOINT void
+change_glpidstat (ModeInfo * mi)
+{
+ glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)];
+
+ if (!mp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context));
+ pinit();
+}
+#endif /* !STANDALONE */
+
+
+XSCREENSAVER_MODULE ("Glpidstat", glpidstat)
+
+#endif
diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.man xscreensaver-5.07/hacks/glx/glpidstat.man
--- tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.man 1970-01-01 01:00:00.000000000 +0100
+++ xscreensaver-5.07/hacks/glx/glpidstat.man 2008-11-13 17:35:01.000000000 +0000
@@ -0,0 +1,92 @@
+.TH XScreenSaver 1 "" "X Version 11"
+.SH NAME
+glpidstat - display process activity from pidstat graphically
+.SH SYNOPSIS
+.B glpidstat
+[\-display \fIhost:display.screen\fP]
+[\-visual \fIvisual\fP]
+[\-window]
+[\-root]
+[\-fps]
+[\-delay \fInumber\fP]
+[\-pidstatcommandstring \fIcommand\fP]
+[\-numstripes \fInumber\fP]
+[\-samplesperstripe \fInumber\fP]
+[\-maxrangeio \fInumber\fP]
+.SH DESCRIPTION
+The 'pidstat' program, that is part of the systat suite, displays the state of
+processes on a Linux system. This hack displays the output of pidstat
+graphically so you can sit back and watch your machine. This hack
+requires version 8.1.6 of pidstat (or newer).
+Each process is displayed as a stripe; the stripe occupies a wider space and
+moves to the left or right within the space depending on which CPU it is scheduled on.
+CPU 0 is on the left of the space. The stripe itself consists of two parts, the left
+side changes colour based on CPU usage (More Red for more system CPU, Green for total
+and Blue for user CPU usage, but generally some combination) while the right, thinner
+substripe changes colour based on IO (Red for Read, Green for Write).
+.SH OPTIONS
+.TP 8
+.B \-visual \fIvisual\fP
+Specify which visual to use. Legal values are the name of a visual class,
+or the id number (decimal or hex) of a specific visual.
+.TP 8
+.B \-window
+Draw on a newly-created window. This is the default.
+.TP 8
+.B \-root
+Draw on the root window.
+.TP 8
+.B \-delay \fInumber\fP
+Per-frame delay, in microseconds. Default: 20000 (0.02 seconds.).
+.TP 8
+.B \-fps | \-no-fps
+Whether to show a frames-per-second display at the bottom of the screen.
+.TP 8
+.B \-pidstatcommandstring \fIcommand\fP
+The command to use to run pidstat. glpidstat requires the use of the
+h, u,r, and d options on pidstat to get all the output in the format
+it wants. If you have ssh keys setup you can display the state of another
+machine - e.g.
+.TP 8
+.B \-numstripes \fInumber\fP
+The number of processes to display at the same time accross the window.
+.TP 8
+.B \-samplesperstripe \fInumber\fP
+The number of samples shown vertically per stripe, each sample corresponds
+to one set of output from pidstat.
+.TP 8
+.B \-maxrangeio \fInumber\fP
+The amount of IO (in KB/s) which cause the colour of the display to be full brightness.
+.SH ENVIRONMENT
+.PP
+.TP 8
+.B DISPLAY
+to get the default host and display number.
+.TP 8
+.B XENVIRONMENT
+to get the name of a resource file that overrides the global resources
+stored in the RESOURCE_MANAGER property.
+.SH EXAMPLES
+.B glpidstat -pidstatcommandstring '/my/sysstat/source/pidstat -h -u -r -d 1'
+.RS
+Run your own installation of pidstat at an arbitrary location.
+.RE
+.B glpidstat -pidstatcommandstring 'ssh bigbox pidstat -h -u -r -d 1'.
+.RS
+Run pidstat on a remote box (with an ssh key or other system where you don't need to
+enter a password).
+.RE
+.SH SEE ALSO
+.BR X (1),
+.BR xscreensaver (1)
+.BR pidstat (1)
+.SH COPYRIGHT
+Copyright \(co 2008 by Dr. David Alan Gilbert. Permission to use, copy, modify,
+distribute, and sell this software and its documentation for any purpose is
+hereby granted without fee, provided that the above copyright notice appear
+in all copies and that both that copyright notice and this permission notice
+appear in supporting documentation. No representations are made about the
+suitability of this software for any purpose. It is provided "as is" without
+express or implied warranty.
+.SH AUTHOR
+Dr. David Alan Gilbert (dave@treblig.org)
diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/Makefile.in xscreensaver-5.07/hacks/glx/Makefile.in
--- tmp/orig/xscreensaver-5.07/hacks/glx/Makefile.in 2008-08-11 06:12:01.000000000 +0100
+++ xscreensaver-5.07/hacks/glx/Makefile.in 2008-11-13 17:44:05.000000000 +0000
@@ -103,7 +103,7 @@
antinspect.c providence.c pinion.c involute.c boing.c \
texfont.c carousel.c fliptext.c antmaze.c tangram.c \
tangram_shapes.c crackberg.c glhanoi.c cube21.c \
- timetunnel.c juggler3d.c topblock.c glschool.c \
+ timetunnel.c juggler3d.c topblock.c glschool.c glpidstat.c \
glschool_gl.c glschool_alg.c glcells.c voronoi.c \
moebiusgears.c lockward.c cubicgrid.c hypnowheel.c \
skytentacles.c teapot.c
@@ -138,7 +138,7 @@
antinspect.o providence.o pinion.o involute.o boing.o \
texfont.o carousel.o fliptext.o antmaze.o tangram.o \
tangram_shapes.o crackberg.o glhanoi.o cube21.o \
- timetunnel.o juggler3d.o topblock.o glschool.o \
+ timetunnel.o juggler3d.o topblock.o glschool.o glpidstat.o \
glschool_gl.o glschool_alg.o glcells.o voronoi.o \
moebiusgears.o lockward.o cubicgrid.o hypnowheel.o \
skytentacles.o teapot.o
@@ -151,7 +151,7 @@
endgame glblur flurry atunnel flyingtoasters bouncingcow \
glslideshow jigglypuff klein hypertorus glmatrix cubestorm \
glknots blocktube flipflop antspotlight polytopes \
- gleidescope mirrorblob blinkbox noof polyhedra \
+ gleidescope mirrorblob blinkbox noof polyhedra glpidstat \
antinspect providence pinion boing carousel fliptext \
antmaze tangram crackberg glhanoi cube21 timetunnel \
juggler3d topblock glschool glcells voronoi moebiusgears \
@@ -187,7 +187,7 @@
pulsar.man queens.man rubik.man sballs.man sierpinski3d.man \
spheremonics.man sproingies.man stairs.man starwars.man \
stonerview.man superquadrics.man xscreensaver-gl-helper.man \
- endgame.man flurry.man glblur.man atunnel.man \
+ endgame.man flurry.man glblur.man atunnel.man glpidstat.man \
flyingtoasters.man bouncingcow.man glslideshow.man \
jigglypuff.man klein.man hypertorus.man glmatrix.man \
cubestorm.man glknots.man blocktube.man flipflop.man \
@@ -606,6 +606,9 @@
glknots: glknots.o tube.o $(HACK_TRACK_OBJS)
$(CC_HACK) -o $@ $@.o tube.o $(HACK_TRACK_OBJS) $(HACK_LIBS)
+glpidstat: glpidstat.o texfont.o $(HACK_TRACK_OBJS)
+ $(CC_HACK) -o $@ $@.o texfont.o $(HACK_TRACK_OBJS) $(HACK_LIBS) -lutil
+
blocktube: blocktube.o xpm-ximage.o $(HACK_OBJS)
$(CC_HACK) -o $@ $@.o xpm-ximage.o $(HACK_OBJS) $(XPM_LIBS)