diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7db6931 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CC=gcc + +CFLAGS=-Wall -Werror -Wextra -pedantic + +LDFLAGS=-lm -lXaw -lXmu -lXt -lSM -lICE -lXpm -lXext -lX11 +# gcc -o xdu -O2 -fno-strength-reduce -fno-strict-aliasing -L/usr/lib64 xdu.o xwin.o -lXaw -lXmu -lXt -lSM -lICE -lXpm -lXext -lX11 + +SRCS = xdu.c xwin.c +OBJS = xdu.o xwin.o + +all: xdu + +xdu: $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +xwin.o: xwin.c + +clean: + rm -f xdu xdu.o xwin.o + +# diff --git a/README b/README new file mode 100644 index 0000000..84b306e --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +================================================================ +XDU - Display the output of "du" in an X window +================================================================ + +XDU is a program for displaying a graphical tree of disk space +utilization as reported by the UNIX utility "du". You can +navigate up and down in the tree, sort things, and print out +information. See the manual page for details. + +This program can be found by anonymous ftp at + + ftp.arl.mil:pub/xdu-3.0.tar.Z + +and should appear in the X11R6 contrib files. + +================================================================ +Compilation +================================================================ + +Use "xmkmf" to build a Makefile from the Imakefile. +Then "make", "make install", "make install.man". +But if for some reason you can't do that, try: + + cc -o xdu xdu.c xwin.c -lXaw -lXt -lXext -lX11 + +See the XDu.ad file if e.g. you have problems with + the selected font. + +This release was tested against X11R6 patch level 1 on an +SGI running Irix 5.2. It has been tested against X11R5 on +SunOS 4.1.3, SunOS 5.2 (Solaris), SGI Irix 4.0.5, Gould +UTX 2.1. + +================================================================ +Revision History +================================================================ + +Version 3.0 5 Jun 94 + X11R6 contrib release + Popup help window + Now uses Athena widgets, but no menus or buttons yet + +Version 2.1 22 Jul 93 + Fixed a bug in the sorting code where traversal back up a + sorted tree could land you in the wrong parent directory. + +Version 2.0 21 Jul 93 + Added sorting. + Command line options. + More resources. + Better redraw behavior. + Bug fixes (to handle trailing slashes and directories + with no or zero size information). + +Version 1.1 5 Oct 91 + Added resource control + Display of size information + More accurate label positioning + +Version 1.0 4 Sep 91 + First public release + +================================================================ +Remaining Bug? +================================================================ + + On startup on a Sun (but not on an SGI), keyboard input + may not be received by the application until you move the + mouse out of and back into the window. Button presses are + fine. Does anyone know what's going on there? + Mark Evans pointed out a fix - now in the BUG section of + the manual page, but I would still like to hear how/why + this happens. + +================================================================ +Acknowledgements +================================================================ +Thanks for bug reports and code fixes from: + +Casey Leedom +Stephen Gildea +Nelson Minar +Don Tiessen +Gerry.Tomlinson@newcastle.ac.uk +Mark Evans +Juha Takala + +And the many others who told me what they thought about it. + +Send any bugs/comments to: + +Phil Dykstra + +http://info.arl.mil/~phil/ diff --git a/version.h b/version.h new file mode 100644 index 0000000..a650768 --- /dev/null +++ b/version.h @@ -0,0 +1 @@ +#define XDU_VERSION "3.0" diff --git a/xdu.c b/xdu.c new file mode 100644 index 0000000..6b9f5f9 --- /dev/null +++ b/xdu.c @@ -0,0 +1,994 @@ + +/* + * X D U . C + * + * Display the output of "du" in an X window. + * + * Phillip C. Dykstra + * + * 4 Sep 1991. + * + * Copyright (c) Phillip C. Dykstra 1991, 1993, 1994 + * The X Consortium, and any party obtaining a copy of these files from + * the X Consortium, directly or indirectly, is granted, free of charge, a + * full and unrestricted irrevocable, world-wide, paid up, royalty-free, + * nonexclusive right and license to deal in this software and + * documentation files (the "Software"), including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons who receive + * copies from any such party to do so. This license includes without + * limitation a license to do the foregoing actions under any patents of + * the party supplying this software to the X Consortium. + * + * Slightly modifications Wed Apr 9 09:16:16 MET DST 1997 by + * Peter Marquardt, marquardt_p@mpimg-berlin-dahlem.mpg.de + * + * added ability to print postscriptfiles Thr Oct 1 12:17 MET DST 1998 by + * Marius Tolzmann, tolzmann@mpimg-berlin-dahlem.mpg.de + * (new option -f and new key p !!!) + */ + +#include +#include +#include +#include "version.h" + +/*extern char *malloc(), *calloc();*/ + +#define MAXDEPTH 80 /* max elements in a path */ +#define MAXNAME 1024 /* max pathname element length */ +#define MAXPATH 4096 /* max total pathname length */ +#define NCOLS 5 /* default number of columns in display */ + +/* What we IMPORT from xwin.c */ +extern int xsetup(), xmainloop(), xdrawrect(), xrepaint(); + +/* What we EXPORT to xwin.c */ +/* extern int press(), reset(), repaint(), setorder(), reorder(); */ +/* extern nodeinfo(), helpinfo(); */ +int ncols = NCOLS; + +/* internal routines */ +#ifdef NEED_STRDUP +char *strdup(); +#endif +void addtree(); +void parse_file(); +void parse_entry(); +void dumptree(); +void clearrects(); +void sorttree(); + +/* order to sort paths by */ +#define ORD_FIRST 1 +#define ORD_LAST 2 +#define ORD_ALPHA 3 +#define ORD_SIZE 4 +#define ORD_RALPHA 5 +#define ORD_RSIZE 6 +#define ORD_DEFAULT ORD_FIRST +int order = ORD_DEFAULT; + +/* + * Rectangle Structure + * Stores window coordinates of a displayed rectangle + * so that we can "find" it again on key presses. + */ +struct rect { + int left; + int top; + int width; + int height; +}; + +/* + * Node Structure + * Each node in the path tree is linked in with one of these. + */ +struct node { + char *name; + long long size; /* from here down in the tree */ + long num; /* entry number - for resorting */ + struct rect rect; /* last drawn screen rectangle */ + struct node *peer; /* siblings */ + struct node *child; /* list of children if !NULL */ + struct node *parent; /* backpointer to parent */ +} top; +struct node *topp = ⊤ +#define NODE_NULL ((struct node *)0) +long nnodes = 0; + +/* + * create a new node with the given name and size info + */ +struct node * +makenode(name,size) +char *name; +long long size; +{ + struct node *np; + + np = (struct node *)calloc(1,sizeof(struct node)); + np->name = strdup(name); + np->size = size; + np->num = nnodes; + nnodes++; + + return np; +} + +/* + * Return the node (if any) which has a draw rectangle containing + * the given x,y point. + */ +struct node * +findnode(treep, x, y) +struct node *treep; +int x, y; +{ + struct node *np; + struct node *np2; + + if (treep == NODE_NULL) + return NODE_NULL; + + if (x >= treep->rect.left && x < treep->rect.left+treep->rect.width + && y >= treep->rect.top && y < treep->rect.top+treep->rect.height) { + /*printf("found %s\n", treep->name);*/ + return treep; /* found */ + } + + /* for each child */ + for (np = treep->child; np != NULL; np = np->peer) { + if ((np2 = findnode(np,x,y)) != NODE_NULL) + return np2; + } + return NODE_NULL; +} + +/* + * return a count of the number of children of a given node + */ +int +numchildren(nodep) +struct node *nodep; +{ + int n; + + if (nodep == NODE_NULL) + return 0; + + n = 0; + for (nodep = nodep->child; nodep != NODE_NULL; nodep=nodep->peer) + n++; + + return n; +} + +/* + * fix_tree - This function repairs the tree when certain nodes haven't + * had their sizes initialized. [DPT911113] + * * * * This function is recursive * * * + */ +long +fix_tree(top) +struct node *top; +{ + struct node *nd; + + if (top == NODE_NULL) /* recursion end conditions */ + return 0; + if (top->size >= 0) /* also halt recursion on valid size */ + return top->size; /* (remember: sizes init. to -1) */ + + top->size = 0; + for (nd = top->child; nd != NODE_NULL; nd = nd->peer) + top->size += fix_tree(nd); + + return top->size; +} + +static char usage[] = "\ +Usage: xdu [-options ...] filename\n\ + or xdu [-options ...] < du.out\n\ +\n\ +Graphically displays the output of du in an X window\n\ + options include:\n\ + -s Don't display size information\n\ + +s Display size information (default)\n\ + -n Sort in numerical order (largest first)\n\ + -rn Sort in reverse numerical order\n\ + -a Sort in alphabetical order\n\ + -ra Sort in reverse alphabetical order\n\ + -c num Set number of columns to num\n\ + -f Set filename for postscript print !\n\ + Toolkit options: -fg, -bg, -rv, -display, -geometry, etc.\n\ +"; + +main(argc,argv) +int argc; +char **argv; +{ + top.name = strdup("[root]"); + top.size = -1; + + xsetup(&argc,argv); + if (argc == 1) { + if (isatty(fileno(stdin))) { + fprintf(stderr, usage); + exit(1); + } else { + parse_file("-"); + } + } else if (argc == 2 && strcmp(argv[1],"-help") != 0) { + parse_file(argv[1]); + } else { + fprintf(stderr, usage); + exit(1); + } + top.size = fix_tree(&top); + + /*dumptree(&top,0);*/ + if (order != ORD_DEFAULT) + sorttree(&top, order); + + topp = ⊤ + /* don't display root if only one child */ + if (numchildren(topp) == 1) + topp = topp->child; + + xmainloop(); + exit(0); +} + +void +parse_file(filename) +char *filename; +{ + char buf[4096]; + char name[4096]; + long long size; + FILE *fp; + char *buff; + + if (strcmp(filename, "-") == 0) { + fp = stdin; + } else { + if ((fp = fopen(filename, "r")) == 0) { + fprintf(stderr, "xdu: can't open \"%s\"\n", filename); + exit(1); + } + } + while (fgets(buf,sizeof(buf),fp) != NULL) { +/* sscanf(buf, "%lld %s\n", &size, name); */ + sscanf(buf, "%lld", &size); + buff=buf; + while ( (*buff >= '0' && *buff <= '9') || (*buff == ' ' || *buff == '\t' ) ) { + buff++; + } + strncpy(name,buff,strlen(buff)-1); + name[strlen(buff)-1] = '\0'; +/* printf("%d %s\n", size, name); */ + parse_entry(name,size); + } + fclose(fp); +} + +/* bust up a path string and link it into the tree */ +void parse_entry(char *name, long long size) +{ + char *path[MAXDEPTH]; /* break up path into this list */ + char buf[MAXNAME]; /* temp space for path element name */ + int arg, indx; + int length; /* nelson@reed.edu - trailing / fix */ + + if (*name == '/') + name++; /* skip leading / */ + + length = strlen(name); + if ((length > 0) && (name[length-1] == '/')) { + /* strip off trailing / (e.g. GNU du) */ + name[length-1] = 0; + } + + arg = 0; indx = 0; + bzero((char *)path,sizeof(path)); + bzero(buf,sizeof(buf)); + while (*name) { + if (*name == '/') { + buf[indx] = 0; + path[arg++] = strdup(buf); + indx = 0; + if (arg >= MAXDEPTH) + break; + } else { + buf[indx++] = *name; + if (indx >= MAXNAME) + break; + } + name++; + } + buf[indx] = 0; + path[arg++] = strdup(buf); + path[arg] = NULL; + + addtree(&top,path,size); +} + +/* + * Determine where n1 should go compared to n2 + * based on the current sorting order. + * Return -1 if is should be before. + * 0 if it is a toss up. + * 1 if it should go after. + */ +int +compare(n1,n2,order) +struct node *n1, *n2; +int order; +{ + int ret; + + switch (order) { + case ORD_SIZE: + if (n2->size>n1->size) { + return 1; + } else if (n2->sizesize) { + return -1; + } else { + return strcmp(n1->name,n2->name); + } + /* not reached */ + case ORD_RSIZE: + if (n1->size>n2->size) { + return 1; + } else if (n1->sizesize) { + return -1; + } else { + return strcmp(n1->name,n2->name); + } + /* not reached */ + case ORD_ALPHA: + return strcmp(n1->name,n2->name); + break; + case ORD_RALPHA: + return strcmp(n2->name,n1->name); + break; + case ORD_FIRST: + /*return -1;*/ + return (n1->num - n2->num); + break; + case ORD_LAST: + /*return 1;*/ + return (n2->num - n1->num); + break; + } + + /* shouldn't get here */ + fprintf(stderr,"xdu: bad insertion order\n"); + return 0; +} + +void +insertchild(nodep,childp,order) +struct node *nodep; /* parent */ +struct node *childp; /* child to be added */ +int order; /* FIRST, LAST, ALPHA, SIZE */ +{ + struct node *np, *np1; + + if (nodep == NODE_NULL || childp == NODE_NULL) + return; + if (childp->peer != NODE_NULL) { + fprintf(stderr, "xdu: can't insert child with peers\n"); + return; + } + + childp->parent = nodep; + if (nodep->child == NODE_NULL) { + /* no children, order doesn't matter */ + nodep->child = childp; + return; + } + /* nodep has at least one child already */ + if (compare(childp,nodep->child,order) < 0) { + /* new first child */ + childp->peer = nodep->child; + nodep->child = childp; + return; + } + np1 = nodep->child; + for (np = np1->peer; np != NODE_NULL; np = np->peer) { + if (compare(childp,np,order) < 0) { + /* insert between np1 and np */ + childp->peer = np; + np1->peer = childp; + return; + } + np1 = np; + } + /* at end, link new child on */ + np1->peer = childp; +} + +/* add path as a child of top - recursively */ +void +addtree(top, path, size) +struct node *top; +char *path[]; +long long size; +{ + struct node *np; + + /*printf("addtree(\"%s\",\"%s\",%d)\n", top->name, path[0], size);*/ + + /* check all children for a match */ + for (np = top->child; np != NULL; np = np->peer) { + if (strcmp(path[0],np->name) == 0) { + /* name matches */ + if (path[1] == NULL) { + /* end of the chain, save size */ + np->size = size; + return; + } + /* recurse */ + addtree(np,&path[1],size); + return; + } + } + /* no child matched, add a new child */ + np = makenode(path[0],-1); + insertchild(top,np,order); + + if (path[1] == NULL) { + /* end of the chain, save size */ + np->size = size; + return; + } + /* recurse */ + addtree(np,&path[1],size); + return; +} + +/* debug tree print */ +void +dumptree(np,level) +struct node *np; +int level; +{ + int i; + struct node *subnp; + + for (i = 0; i < level; i++) + printf(" "); + + printf("%s %d\n", np->name, np->size); + for (subnp = np->child; subnp != NULL; subnp = subnp->peer) { + dumptree(subnp,level+1); + } +} + +void +sorttree(np, order) +struct node *np; +int order; +{ + struct node *subnp; + struct node *np0, *np1, *np2, *np3; + + /* sort the trees of each of this nodes children */ + for (subnp = np->child; subnp != NODE_NULL; subnp = subnp->peer) { + sorttree(subnp, order); + } + /* then put the given nodes children in order */ + np0 = np; /* np0 points to node before np1 */ + for (np1 = np->child; np1 != NODE_NULL; np1 = np1->peer) { + np2 = np1; /* np2 points to node before np3 */ + for (np3 = np1->peer; np3 != NODE_NULL; np3 = np3->peer) { + if (compare(np3,np1,order) < 0) { + /* swap links */ + if (np0 == np) + np0->child = np3; + else + np0->peer = np3; + np2->peer = np3->peer; + np3->peer = np1; + + /* adjust pointers */ + np1 = np3; + np3 = np2; + } + np2 = np3; + } + np0 = np1; + } +} + +/* + * Draws a node in the given rectangle, and all of its children + * to the "right" of the given rectangle. + */ +drawnode(nodep, rect) +struct node *nodep; /* node whose children we should draw */ +struct rect rect; /* rectangle to draw all children in */ +{ + struct rect subrect; + + /*printf("Drawing \"%s\" %d\n", nodep->name, nodep->size);*/ + + xdrawrect(nodep->name, nodep->size, + rect.left,rect.top,rect.width,rect.height); + + /* save current screen rectangle for lookups */ + nodep->rect.left = rect.left; + nodep->rect.top = rect.top; + nodep->rect.width = rect.width; + nodep->rect.height = rect.height; + + /* draw children in subrectangle */ + subrect.left = rect.left+rect.width; + subrect.top = rect.top; + subrect.width = rect.width; + subrect.height = rect.height; + drawchildren(nodep, subrect); +} + +/* + * Draws all children of a node within the given rectangle. + * Recurses on children. + */ +drawchildren(nodep, rect) +struct node *nodep; /* node whose children we should draw */ +struct rect rect; /* rectangle to draw all children in */ +{ + long long totalsize; + int totalheight; + struct node *np; + double fractsize; + int height; + int top; + long long size; + + /*printf("Drawing children of \"%s\", %d\n", nodep->name, nodep->size);*/ + /*printf("In [%d,%d,%d,%d]\n", rect.left,rect.top,rect.width,rect.height);*/ + + top = rect.top; + size = 0; + + + totalheight = rect.height; + totalsize = nodep->size; + if (totalsize == 0) { + /* total the sizes of the children */ + totalsize = 0; + for (np = nodep->child; np != NULL; np = np->peer) + totalsize += np->size; + nodep->size = totalsize; + } + + /* for each child */ + for (np = nodep->child; np != NULL; np = np->peer) { + fractsize = totalsize ? np->size / (double)totalsize : 0; + height = fractsize * totalheight + 0.5; + + { + struct rect subrect; + /* printf("%s, drawrect[%d,%d,%d,%d]\n", np->name, + rect.left,top,rect.width,height); */ + xdrawrect(np->name, np->size, + rect.left,top,rect.width,height); + + /* save current screen rectangle for lookups */ + np->rect.left = rect.left; + np->rect.top = top; + np->rect.width = rect.width; + np->rect.height = height; + + + /* draw children in subrectangle */ + subrect.left = rect.left+rect.width; + subrect.top = top; + subrect.width = rect.width; + subrect.height = height; + drawchildren(np, subrect); + + size+=np->size; + top=rect.top+(totalsize ? (double)size/totalsize*rect.height+0.5 : 0); + } + } +} + +/* + * clear the rectangle information of a given node + * and all of its decendents + */ +void +clearrects(nodep) +struct node *nodep; +{ + struct node *np; + + if (nodep == NODE_NULL) + return; + + nodep->rect.left = 0; + nodep->rect.top = 0; + nodep->rect.width = 0; + nodep->rect.height = 0; + + /* for each child */ + for (np = nodep->child; np != NULL; np = np->peer) { + clearrects(np); + } +} + +pwd() +{ + struct node *np; + struct node *stack[MAXDEPTH]; + int num = 0; + struct node *rootp; + char path[MAXPATH]; + + rootp = ⊤ + if (numchildren(rootp) == 1) + rootp = rootp->child; + + np = topp; + while (np != NODE_NULL) { + stack[num++] = np; + if (np == rootp) + break; + np = np->parent; + } + + path[0] = '\0'; + while (--num >= 0) { + strcat(path,stack[num]->name); + if (num != 0) + strcat(path,"/"); + } + printf("%s %lld (%.2f%%)\n", path, topp->size, + 100.0*topp->size/rootp->size); +} + +#ifdef NEED_STRDUP +char * +strdup(s) +char *s; +{ + int n; + char *cp; + + n = strlen(s); + cp = malloc(n+1); + strcpy(cp,s); + + return cp; +} +#endif + +/**************** External Entry Points ****************/ + +void press(int x, int y) +{ + struct node *np; + + /*printf("press(%d,%d)...\n",x,y);*/ + np = findnode(&top,x,y); + /*printf("Found \"%s\"\n", np?np->name:"(null)");*/ + if (np == topp) { + /* already top, go up if possible */ + if (np->parent != &top || numchildren(&top) != 1) + np = np->parent; + /*printf("Already top, parent = \"%s\"\n", np?np->name:"(null)");*/ + } + if (np != NODE_NULL) { + topp = np; + xrepaint(); + } +} + +void reset(void) +{ + topp = ⊤ + if (numchildren(topp) == 1) + topp = topp->child; + xrepaint(); +} + +void repaint(int width,int height) +{ + struct rect rect; + + /* define a rectangle to draw into */ + rect.top = 0; + rect.left = 0; + rect.width = width/ncols; + rect.height = height; + + clearrects(&top); /* clear current rectangle info */ + drawnode(topp,rect); /* draw tree into given rectangle */ +#if 0 + pwd(); /* display current path */ +#endif +} + + +void setorder(char *op) +{ + if (strcmp(op, "size") == 0) { + order = ORD_SIZE; + } else if (strcmp(op, "rsize") == 0) { + order = ORD_RSIZE; + } else if (strcmp(op, "alpha") == 0) { + order = ORD_ALPHA; + } else if (strcmp(op, "ralpha") == 0) { + order = ORD_RALPHA; + } else if (strcmp(op, "first") == 0) { + order = ORD_FIRST; + } else if (strcmp(op, "last") == 0) { + order = ORD_LAST; + } else if (strcmp(op, "reverse") == 0) { + switch (order) { + case ORD_ALPHA: + order = ORD_RALPHA; + break; + case ORD_RALPHA: + order = ORD_ALPHA; + break; + case ORD_SIZE: + order = ORD_RSIZE; + break; + case ORD_RSIZE: + order = ORD_SIZE; + break; + case ORD_FIRST: + order = ORD_LAST; + break; + case ORD_LAST: + order = ORD_FIRST; + break; + } + } else { + fprintf(stderr, "xdu: bad order \"%s\"\n", op); + } +} + +void reorder(char *op) /* order name */ +{ + setorder(op); + sorttree(topp, order); + xrepaint(); +} + +void nodeinfo(void) +{ + struct node *np; + + /* display current root path */ + pwd(); + + /* display each child of this node */ + for (np = topp->child; np != NULL; np = np->peer) { + printf("%-12lld %s\n", np->size, np->name); + } +} + +void helpinfo(void) +{ + fprintf(stdout, "\n\ +XDU Version %s - Keyboard Commands\n\ + a sort alphabetically\n\ + n sort numerically (largest first)\n\ + f sort first-in-first-out\n\ + l sort last-in-first-out\n\ + r reverse sort\n\ + / goto the root\n\ + q quit (also Escape)\n\ + p print to file (as postscript)\n\ + i info to standard out\n\ +0-9 set number of columns (0=10)\n\ +", XDU_VERSION); +} + +void fprintpsstart(FILE *fp) +{ + fprintf(fp, + "%%!\n" + "%%%%BoundingBox: 26 693 85 808\n" + "%%%%Title: xdu output\n" + "%%%%Orientation: Portrait\n" + "%%%%Pages: 1\n" + "%%%%DocumentFonts: (atend)\n" + "%%%%EndComments\n" + "%%%%BeginProlog\n\n" + "/xdudict 2 dict def\n" + "xdudict begin\n\n" + "end\n\n" + "%%%%EndProlog\n" + "%%%%Page: 1 1\n\n" + "xdudict begin\n" + "/xdusavedpage save def\n\n" + "1 setmiterlimit\n" + "1 setlinewidth\n" + "0 setgray\n" + "72 0 mul 72 11.60 mul translate\n" + "72 128 div 100.000 mul 100 div dup neg scale\n" + "gsave\n" + "/xduorigctm matrix currentmatrix def\n\n" + ); +} + +void fprintpsend(FILE *fp) +{ + fprintf(fp, + "grestore\n" + "xdusavedpage restore\n" + "end\n" + "showpage\n" + "\n" + "%%%%Trailer\n" + "%%%%DocumentFonts: fixed\n" + "%%%%EOF\n" + ); +} + +void fprintpsbox(FILE *fp, int x1, int y1, int x2, int y2) +{ + fprintf(fp, + "%%BOX\n" + "0 setgray\n" + "gsave\n" + " 10 setmiterlimit\n" + " gsave\n" + " newpath\n" + " %i %i moveto %i %i lineto %i %i lineto %i %i lineto\n" + " closepath\n" + " stroke\n" + " grestore\n" + "grestore\n" + "\n", + x1,y1,x2+x1,y1,x2+x1,y2+y1,x1,y2+y1 + ); +} + +void fprintpstext(FILE *fp, int x, int y, char *s) +{ + fprintf(fp, + "%%TEXT\n" + "0 setgray\n" + "/fixed findfont [10 0 0 -10 0 0] makefont setfont\n" + " gsave\n" + " %i %i moveto (%s) show\n" + " grestore\n" + "\n", + x,y,s + ); +} + +void savepschildren(FILE *fp, struct node *nodep, struct rect rect, int showsize) +{ + long long totalsize; + int totalheight, height, top,size; + struct node *np; + char *name, label[1024]; + struct rect subrect; + + top = rect.top; + size = 0; + totalheight = rect.height; + totalsize = nodep->size; + + if(totalsize == 0) { + for(np=nodep->child;np!=NULL;np=np->peer) totalsize += np->size; + nodep->size = totalsize; + } + + for(np=nodep->child;np!=NULL;np=np->peer) { + height = (totalsize ? (np->size/(double)totalsize) : 0) * totalheight + 0.5; + + switch (showsize) { + case 1: sprintf(label,"%s \\(%lldk\\)", np->name, np->size);name = label;break; + case 2: sprintf(label,"%s \\(%.2fM\\)", np->name, (double)np->size / (double)1024);name = label;break; + case 3: sprintf(label,"%s \\(%.2fG\\)", np->name, (double)np->size / (double)(1024*1024));name = label;break; + case 4: sprintf(label,"%s \\(%.2fT\\)", np->name, (double)np->size / (double)(1024*1024*1024));name = label;break; + default: printf("arghhhhh!"); + break; + } + + fprintpsbox(fp, rect.left, top, rect.width, height); + + if(height>10) + fprintpstext(fp, rect.left+4, top+(height)/2.0+3.0, name); + + subrect.left = rect.left+rect.width; + subrect.top = top; + subrect.width = rect.width; + subrect.height = height; + + savepschildren(fp, np, subrect, showsize); + + size += np->size; + top = rect.top+(totalsize?(double)size/(double)totalsize*rect.height+0.5:0); + } +} + +void savepsnode(FILE *fp, struct node *nodep, struct rect rect, int showsize) +{ + struct rect subrect; + char label[1024], *name; + + switch (showsize) { + case 1: sprintf(label,"%s \\(%lldk\\)", nodep->name, nodep->size);name = label;break; + case 2: sprintf(label,"%s \\(%.2fM\\)", nodep->name, (double)nodep->size / (double)1024);name = label;break; + case 3: sprintf(label,"%s \\(%.2fG\\)", nodep->name, (double)nodep->size / (double)(1024*1024));name = label;break; + case 4: sprintf(label,"%s \\(%.2fT\\)", nodep->name, (double)nodep->size / (double)(1024*1024*1024));name = label;break; + default: + break; + } + + fprintpsbox(fp, rect.left, rect.top, rect.width, rect.height); + fprintpstext(fp, rect.left+4, rect.top+(rect.height-rect.top)/2, name); + + subrect.left = rect.left+rect.width; + subrect.top = rect.top; + subrect.width = rect.width; + subrect.height = rect.height; + savepschildren(fp, nodep,subrect, showsize); + +} + + +void savetops(char *fname, int showsize) +{ + FILE *fp; + struct rect rect; + + if(fp=fopen(fname, "w")) { + fprintpsstart(fp); + + rect.top = 40; + rect.left = 40; + rect.width = 960/ncols; + rect.height = 1400; + + savepsnode(fp, topp, rect, showsize); + + fprintpsend(fp); + fclose(fp); + } + +} + +void uponechild(void) +{ + struct node *np, *parent=NULL; + + np = topp; + if(np->parent != &top || numchildren(&top) != 1) { + parent=np->parent; + } + if(parent!=NODE_NULL) { + np=parent->child; + if(topp!=np) { + for(;np->peer;np=np->peer) { + if(np->peer==topp) { + topp=np; + xrepaint(); + return; + } + } + } + } + +} + +void downonechild(void) +{ + if(topp->peer!=NODE_NULL) { + topp=topp->peer; + xrepaint(); + } + +} diff --git a/xdu.man b/xdu.man new file mode 100644 index 0000000..b4e4023 --- /dev/null +++ b/xdu.man @@ -0,0 +1,204 @@ +.TH XDU 1 X11 +.SH NAME +xdu \- display the output of "du -k" in an X window +.SH SYNOPSIS +.B du -k \|| xdu [options] +.SH DESCRIPTION +.I Xdu +is a program for displaying a graphical tree of disk space +utilization as reported by the UNIX utility "du". The +user can navigate through the tree structure and change +the order of the displayed information. The window +is divided up into several columns, each of which is one level +deeper in the directory hierarchy (from left to right). Boxes +are drawn for each directory. The amount of vertical space +occupied by each box is directly proportional to the amount of +disk space consumed by it and all of its children. The name of +each directory and the amount of data are displayed provided +that there is enough space within its box. Any space at the +"bottom" of a box not covered by its children to the right +represents the space consumed by files \fIin\fR that directory +(as opposed to space from its children). +.PP +There are several command line options available. Equivalent +resources (if any) are shown with each option. +.TP +.B \+s +(.showsize: true) +display sizes (the default). +.TP +.B \-s +(.showsize: false) +don't display sizes. +.TP +.BI \-c " num" +display \fInum\fR columns. +.TP +.B \-a +(.order: alpha) +sorts display alphabetically. +.TP +.B \-ra +(.order: ralpha) +sorts display reverse alphabetically. +.TP +.B \-n +(.order: size) +sorts display numerically (largest first). +.TP +.B \-rn +(.order: rsize) +sorts display reverse numerically (smallest first). +.TP +.BI \-fg " color" +(.foregound) +determines the color of the text and lines. +.TP +.BI \-bg " color" +(.background) +determines the color of the background. +.TP +.B \-rv +reverse video (for monochrome displays) +.PP +In addition to these the usual toolkit options such as +\-rv, \-font, \-display, \-geometry, are supported. +.SH MOUSE ACTIONS +The user can move up or down the tree by clicking the left mouse on +a directory box. If the left most box is selected, the display will +move up one level (assuming you are not already at the root). If any +other box is selected, it will be placed against the left edge of the +window and the display will be rescaled appropriately. +At any time the middle mouse will bring you back to the root. +Clicking the right mouse will exit the program. +.SH KEYSTROKES +Several keystroke commands are supported. Note that all sorting happens +from the current root node down, so it is possible to sort one subtree +differently than another by clicking into it, doing a sort, and going +back up to the parent. +.TP +.B 1-9,0 +sets the number of columns in the display (0 = 10). +.TP +.B a +alphabetical sort. +.TP +.B n +numerical sort (largest first). +.TP +.B f +first-in-first-out sort (this is the order the +data was read into the program). +.TP +.B l +last-in-first-out sort. +.TP +.B r +reverse sense of sort. +.TP +.B s +toggle size display. +.TP +.B h +display a popup help window. +.TP +.B i +display information about the current root node to standard out. +The first line shows the path within the tree, the total size +from this node on down, and the precentage that total represents +of all of the data given to \fIxdu\fR. Subsequent lines show the +size and name information for all children of this node in the +order they are currently sorted in. This allows tiny directories +to be seen that otherwise could not be labled on the display, +and also allows for cutting and pasting of the information. +.TP +.B / +goto the root. +.TP +.B q +(or Escape) +exit the program. +.SH ACTIONS +All mouse and keyboard events trigger actions so they can all +be rebound by translations to suit the individual. The action +functions are: +.TP +.B reset() +goes to the root node. +.TP +.B goto() +moves down into the directory currently under the mouse +(and is thus probably only useful bound to a mouse button). +.TP +.B quit() +exits the program. +.TP +.B reorder(type) +sorts the display from the current root node down according to +one of: alpha, ralpha (reverse alphabetical), size (largest to +smallest), rsize (smallest to largest), first (as originally read +in), last (opposite of original data), reverse (reverse whatever +sort mode is currently selected). +.TP +.B size() +toggles size display on/off +.TP +.B ncol(num) +sets the number of columns to num. +.TP +.B info() +displays directory information as described in the KEYBOARD section. +.TP +.B help() +displays a popup help window. +.PP +As an example of rebinding these, you could put the following +in your resources file: +.sp +.nf +XDu*translations: #override\\n\\ + X: reorder(reverse)\\n\\ + : info() +.fi +.sp +to make the 'x' key reverse the current sort order and the right +mouse button print the directory information message. +.SH RESOURCES +The following resources are supported with some sample values. +.PP +.nf +XDu*foreground: yellow +XDu*background: blue4 +XDu*window.width: 600 +XDu*window.height: 480 +XDu*help*foreground: green +XDu*help*background: red4 +XDu.ncol: 5 +XDu.font: -*-helvetica-bold-r-normal--14-* +XDu.showsize: False +XDu.order: size +.fi +.SH EXAMPLE +.I +cd /usr/src +.br +.I +du > /tmp/du.out +.br +.I +xdu \-n /tmp/du.out +.SH BUGS +On some machines keyboard input may not be accepted by xdu until +the mouse is moved out of and back into the window. I have been +told that putting the following in your resources file may fix +this. +.sp +.nf +.FocusLenience: True +.fi +.SH "SEE ALSO" +du(1) +.SH AUTHOR +Phillip C. Dykstra +.br + diff --git a/xwin.c b/xwin.c new file mode 100644 index 0000000..38e6589 --- /dev/null +++ b/xwin.c @@ -0,0 +1,535 @@ +/* + * XDU - X Window System Interface. + * + * We hide all of the X hieroglyphics inside of this module. + * + * Phillip C. Dykstra + * + * 4 Sep 1991. + * + * Copyright (c) Phillip C. Dykstra 1991, 1993, 1994 + * The X Consortium, and any party obtaining a copy of these files from + * the X Consortium, directly or indirectly, is granted, free of charge, a + * full and unrestricted irrevocable, world-wide, paid up, royalty-free, + * nonexclusive right and license to deal in this software and + * documentation files (the "Software"), including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons who receive + * copies from any such party to do so. This license includes without + * limitation a license to do the foregoing actions under any patents of + * the party supplying this software to the X Consortium. + */ +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifndef X_NOT_STDC_ENV +#include /* for exit() */ +#endif + +/* IMPORTS: routines that this module vectors out to */ +extern int press(); +extern int reset(); +extern int uponechild(); +extern int downonechild(); +extern int repaint(); +extern int reorder(); +extern int setorder(); +extern int nodeinfo(); +extern int helpinfo(); +extern int ncols; + +/* EXPORTS: routines that this module exports outside */ +extern int xsetup(); +extern int xmainloop(); +extern int xclear(); +extern int xrepaint(); +extern int xrepaint_noclear(); +extern int xdrawrect(); + +/* internal routines */ +static void help_popup(); +static void help_popdown(); + +static String fallback_resources[] = { +"*window.width: 600", +"*window.height: 480", +"*help.width: 500", +"*help.height: 330", +"*order: first", +"*psfile: xdu_out.ps", +NULL +}; + +/* Application Resources */ +typedef struct { + Pixel foreground; + Pixel background; + XFontStruct *font; + int ncol; + int showsize; + char *order; + char *psfilename; +} res_data, *res_data_ptr; +static res_data res; + +static XtResource application_resources[] = { + { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), + XtOffset(res_data_ptr,foreground), XtRString, XtDefaultForeground}, + { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel), + XtOffset(res_data_ptr,background), XtRString, XtDefaultBackground}, + { XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *), + XtOffset(res_data_ptr,font), XtRString, XtDefaultFont }, + { "ncol", "Ncol", XtRInt, sizeof(int), + XtOffset(res_data_ptr,ncol), XtRString, "5"}, + { "showsize", "ShowSize", XtRInt, sizeof(int), + XtOffset(res_data_ptr,showsize), XtRString, "1"}, + { "order", "Order", XtRString, sizeof(String), + XtOffset(res_data_ptr,order), XtRString, "first"}, + { "psfile", "PSFile", XtRString, sizeof(String), + XtOffset(res_data_ptr,psfilename), XtRString, "xdu_out.ps"} +}; + +/* Command Line Options */ +static XrmOptionDescRec options[] = { + {"-c", "*ncol", XrmoptionSepArg, NULL}, + {"-f", "*psfile", XrmoptionSepArg, NULL}, + {"+s", "*showsize", XrmoptionNoArg, "1"}, + {"-s", "*showsize", XrmoptionNoArg, "0"}, + {"-n", "*order", XrmoptionNoArg, "size"}, + {"-rn", "*order", XrmoptionNoArg, "rsize"}, + {"-a", "*order", XrmoptionNoArg, "alpha"}, + {"-ra", "*order", XrmoptionNoArg, "ralpha"} +}; + +/* action routines */ +static void a_goto(); +static void a_reset(); +static void a_quit(); +static void a_reorder(); +static void a_size(); +static void a_saveps(); +static void a_up(); +static void a_down(); +static void a_ncol(); +static void a_info(); +static void a_help(); +static void a_removehelp(); + +static XtActionsRec actionsTable[] = { + { "reset", a_reset }, + { "goto", a_goto }, + { "quit", a_quit }, + { "reorder", a_reorder }, + { "size", a_size }, + { "ncol", a_ncol }, + { "saveps", a_saveps }, + { "uponechild", a_up }, + { "downonechild", a_down }, + { "info", a_info }, + { "help", a_help }, + { "RemoveHelp", a_removehelp } +}; + +static char defaultTranslations[] = "\ +Q: quit()\n\ +Escape: quit()\n\ +:/: reset()\n\ +S: size()\n\ +P: saveps()\n\ +I: info()\n\ +H: help()\n\ +Help: help()\n\ +:?: help()\n\ +A: reorder(alpha)\n\ +N: reorder(size)\n\ +F: reorder(first)\n\ +L: reorder(last)\n\ +R: reorder(reverse)\n\ +U: uponechild()\n\ +D: downonechild()\n\ +1: ncol(1)\n\ +2: ncol(2)\n\ +3: ncol(3)\n\ +4: ncol(4)\n\ +5: ncol(5)\n\ +6: ncol(6)\n\ +7: ncol(7)\n\ +8: ncol(8)\n\ +9: ncol(9)\n\ +0: ncol(10)\n\ +: goto()\n\ +: reset()\n\ +"; + +/* action routines */ + +static void a_quit(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + XtDestroyApplicationContext(XtWidgetToApplicationContext(w)); + exit(0); +} + +static void a_goto(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + press(event->xbutton.x, event->xbutton.y); +} + +static void a_reset(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + reset(); +} + +static void a_reorder(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + if (*num_params != 1) { + fprintf(stderr, "xdu: bad number of params to reorder action\n"); + } else { + reorder(*params); + } +} + +static void a_size(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + res.showsize++; + if (res.showsize>=5) res.showsize = 0; + xrepaint(); +} + +static void a_saveps(Widget w, XEvent *e, String *params, Cardinal *num_params) +{ + fprintf(stderr,"saving as postscript to file: %s ..",res.psfilename); + savetops(res.psfilename, res.showsize); + fprintf(stderr,"saved !\n"); +} + +static void a_up(Widget w, XEvent *e, String *params, Cardinal *num_params) +{ + uponechild(); +} + +static void a_down(Widget w, XEvent *e, String *params, Cardinal *num_params) +{ + downonechild(); +} + +static void a_ncol(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + int n; + + if (*num_params != 1) { + fprintf(stderr, "xdu: bad number of params to ncol action\n"); + return; + } + n = atoi(*params); + if (n < 1 || n > 1000) { + fprintf(stderr, "xdu: bad value to ncol action\n"); + return; + } + ncols = res.ncol = n; + xrepaint(); +} + +static void a_info(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + nodeinfo(); +} + +static void a_help(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + /*helpinfo();*/ + help_popup(); +} + +static void a_removehelp(w, event, params, num_params) +Widget w; +XEvent *event; +String *params; +Cardinal *num_params; +{ + help_popdown(); +} + +/* callback routines */ + +static void c_resize(w, data, event, continue_to_dispatch) +Widget w; +XtPointer data; +XEvent *event; +Boolean *continue_to_dispatch; +{ + /*printf("Resize\n");*/ + xrepaint(); +} + +static void c_repaint(w, data, event, continue_to_dispatch) +Widget w; +XtPointer data; +XEvent *event; +Boolean *continue_to_dispatch; +{ + /*printf("Expose\n");*/ + xrepaint_noclear(); +} + +/* X Window related variables */ +static Cursor WorkingCursor; +static Display *dpy; +static int screen; +static Visual *vis; +static Window win; +static GC gc; +static GC cleargc; +static XtAppContext app_con; + +Widget toplevel; + +/* External Functions */ + +int +xsetup(argcp, argv) +int *argcp; +char **argv; +{ + XtTranslations trans_table; + Widget w; + XGCValues gcv; + int n; + Arg args[5]; + + /* Create the top level Widget */ + n = 0; + XtSetArg(args[n], XtNtitle, "XDU Disk Usage Display ('h' for help)\n"); n++; + toplevel = XtAppInitialize(&app_con, "XDu", + options, XtNumber(options), + argcp, argv, + fallback_resources, args, n); + + XtGetApplicationResources(toplevel, (XtPointer)&res, + application_resources, XtNumber(application_resources), + NULL, 0 ); + + XtAppAddActions(app_con, actionsTable, XtNumber(actionsTable)); + trans_table = XtParseTranslationTable(defaultTranslations); + + /* Create a simple Label class widget to draw in */ + n = 0; + XtSetArg(args[n], XtNlabel, ""); n++; + w = XtCreateManagedWidget("window", labelWidgetClass, toplevel, + args, n); + + /* events */ + XtAddEventHandler(w, ExposureMask, False, c_repaint, NULL); + XtAddEventHandler(w, StructureNotifyMask, False, c_resize, NULL); + XtAugmentTranslations(w, trans_table); + + XtRealizeWidget(toplevel); + + /* We need these for the raw Xlib calls */ + win = XtWindow(w); + dpy = XtDisplay(w); + screen = DefaultScreen(dpy); + vis = DefaultVisual(dpy,screen); + + gcv.foreground = res.foreground; + gcv.background = res.background; + gcv.font = res.font->fid; + gc = XCreateGC(dpy, win, (GCFont|GCForeground|GCBackground), &gcv); + + setorder(res.order); + ncols = res.ncol; + return(1); +} + +xmainloop() +{ + XtAppMainLoop(app_con); + return(0); +} + +xclear() +{ + XClearWindow(dpy, win); +} + +xrepaint() +{ + XWindowAttributes xwa; + + XClearWindow(dpy, win); + XGetWindowAttributes(dpy, win, &xwa); + repaint(xwa.width, xwa.height); +} + +xrepaint_noclear() +{ + XWindowAttributes xwa; + + XGetWindowAttributes(dpy, win, &xwa); + repaint(xwa.width, xwa.height); +} + +xdrawrect(name, size, x, y, width, height) +char *name; +long long size; +int x, y, width, height; +{ + int textx, texty; + char label[1024]; + XCharStruct overall; + int ascent, descent, direction; + int cheight; + + /*printf("draw(%d,%d,%d,%d)\n", x, y, width, height );*/ + XDrawRectangle(dpy, win, gc, x, y, width, height); + + switch (res.showsize) { + case 1: sprintf(label,"%s (%lldk)", name, size);name = label;break; + case 2: sprintf(label,"%s (%.2fM)", name, (double)size / (double)1024);name = label;break; + case 3: sprintf(label,"%s (%.2fG)", name, (double)size / (double)(1024*1024));name = label;break; + case 4: sprintf(label,"%s (%.2fT)", name, (double)size / (double)(1024*1024*1024));name = label;break; + default: + break; + } + + XTextExtents(res.font, name, strlen(name), &direction, + &ascent, &descent, &overall); + cheight = overall.ascent + overall.descent; + if (height < (cheight + 2)) + return; + + /* print label */ + textx = x + 4; + texty = y + height/2.0 + (overall.ascent - overall.descent)/2.0 + 1.5; + XDrawString(dpy, win, gc, textx, texty, name, strlen(name)); +} + +static Widget popup; + +static void +help_popup() +{ + Widget form, text, src; + Arg args[15]; + int n; + Atom wm_delete_window; + XtTranslations trans_table; + + if (popup != NULL) { + XtPopup(popup, XtGrabNone); + return; + } + + /* popup shell */ + n = 0; + XtSetArg(args[n], XtNtitle, "XDU Help"); n++; + popup = XtCreatePopupShell("helpPopup", transientShellWidgetClass, + toplevel, args, n); + + /* form container */ + n = 0; + XtSetArg(args[n], XtNborderWidth, 0); n++; + XtSetArg(args[n], XtNdefaultDistance, 0); n++; + form = XtCreateManagedWidget("form", formWidgetClass, + popup, args, n); + + /* text widget in form */ + n = 0; + XtSetArg(args[n], XtNborderWidth, 0); n++; + XtSetArg(args[n], XtNresize, XawtextResizeBoth); n++; + /* fallback resources weren't working here on the Sun */ + XtSetArg(args[n], XtNwidth, 500); n++; + XtSetArg(args[n], XtNheight, 330); n++; + text = XtCreateManagedWidget("help", asciiTextWidgetClass, + form, args, n); + + /* create text source */ + n = 0; + XtSetArg(args[n], XtNtype, XawAsciiString); n++; + XtSetArg(args[n], XtNeditType, XawtextRead); n++; + XtSetArg(args[n], XtNstring, "\ +XDU Version 3.0 - Phil Dykstra \n\ +\n\ +Keyboard Commands\n\ + a sort alphabetically\n\ + n sort numerically (largest first)\n\ + f sort first-in-first-out\n\ + l sort last-in-first-out\n\ + r reverse sort\n\ + s toggle size display\n\ + / goto the root\n\ + i node info to standard out\n\ + h this help message\n\ + q quit (also Escape)\n\ +0-9 set number of columns (0=10)\n\ +\n\ + p print to file\n\ + u go up one child\n\ + p go down one child\n\ +\n\ +Mouse Commands\n\ + Left Goto node (goto parent if leftmost box)\n\ + Middle Back to root\n\ + Right Quit\n\ +"); n++; + src = XtCreateWidget("textSource", asciiSrcObjectClass, + text, args, n); + /* set text source */ + XawTextSetSource(text, src, 0); + + XtRealizeWidget(popup); + XtPopup(popup, XtGrabNone); + + trans_table = XtParseTranslationTable("Q: RemoveHelp()"); + XtAugmentTranslations(form, trans_table); + + /* Set up ICCCM delete window */ + wm_delete_window = XInternAtom(XtDisplay(popup), "WM_DELETE_WINDOW", False); + XtOverrideTranslations(popup, XtParseTranslationTable("WM_PROTOCOLS: RemoveHelp()")); + XSetWMProtocols(XtDisplay(popup), XtWindow(popup), &wm_delete_window, 1); +} + +static void +help_popdown() +{ + XtPopdown(popup); +}