/* wmmount - The Window Maker universal mount point
 *
 * 17/06/99  Release 1.0 beta2
 * Copyright (C) 1999  Sam Hawker <shawkie@geocities.com>
 * This software comes with ABSOLUTELY NO WARRANTY
 * This software is free software, and you are welcome to redistribute it
 * under certain conditions
 * See the README file for details.
 */


/* User defines
 */

/* Intervals
 *   INTERVAL  - Minimum interval unit in usecs
 *   INTERVAL1 - Update interval in multiples of def_interval
 *   INTERVAL2 - Button repeat interval in multiples of def_interval
 *   INTERVAL3 - Double-click interval in multiples of def_interval
 */
#define INTERVAL  250000
#define INTERVAL1 10
#define INTERVAL2 1
#define INTERVAL3 1

/* Maximum allowed mountpoints and icons
 */
#define MAXICONS   20
#define MAXMPOINTS 20

/* System-wide config file
 */
#define SYSCONFIG "/usr/X11R6/lib/X11/wmmount/system.wmmount"

/* Config file defaults
 */
#define MOUNTCMD  "/bin/mount %m"
#define UMOUNTCMD "/bin/umount %m"
#define OPENCMD   "/usr/X11R6/bin/nxterm -T '%n - %m' -e mc %m"
#define NAMEFONT  "-*-helvetica-bold-r-*-*-10-*-*-*-*-*-*-*"
#define USAGEFONT "-*-helvetica-medium-r-*-*-10-*-*-*-*-*-*-*"


/* End of user defines
 *
 * -- AVOID MAKING MODIFICATIONS BEYOND THIS POINT --
 */


/* Includes
 */

#include "dockapp.h"

#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#ifdef __linux__
#include <mntent.h>
#include <sys/vfs.h>
#endif
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/mount.h>
#endif

#include "xpm/main.xpm"


/* Variables
 */

Pixmap pm_main;
Pixmap pm_main_mask;

char *mountcmd=NULL;
char *umountcmd=NULL;
char *opencmd=NULL;
char *namefont=NULL;
char *usagefont=NULL;

Font f_name, f_usage;

Pixmap pm_icon[MAXICONS];
int icons=0;

struct mpinfo {
  char *mpoint;
  char *name;
  char *mountcmd;
  char *umountcmd;
  char *opencmd;
  int icon;
  int udisplay;
} mpoint[MAXMPOINTS];
int mpoints=0;

int namex, namey, usagex, usagey;

#define BUTTON1 (int)(1<<0)
#define BUTTON2 (int)(1<<1)
#define BUTTON3 (int)(1<<2)
#define DCLICK  (int)(1<<3)
#define PREV    BUTTON1
#define NEXT    BUTTON2

int state=0;

int select1=0;

int counter1=0;
int counter2=0;
int counter3=0;

bool mounted;
int usage;
char usagestr[8];


/* Functions and procedures
 */

int main(int argc, char **argv);

void init(int argc, char * const *argv);
void done();

void mainloop();

void buttonpress(XButtonEvent *xev);
void buttonrelease(XButtonEvent *xev);

int config_init();
void config_done();

void xpm_init();
void xpm_done();

void font_init();
void font_done();

void checkmount(bool forced);

void drawall();
void drawbtns(int btnmask);
void drawbtn(int x, int y, int w, int h, bool down);

void execcmd(char *cmd, char *path);

/* Implementation
 */

int main(int argc, char **argv) {
  /* Initialize
   */
  init(argc, (char * const *)argv);

  /* Initialize dockapp
   */
  dockapp_init(argc, (char * const *)argv);

  /* Open dockapp
   */
  if(!dockapp_open()) {
    
    /* Read config file
     */
    if(!config_init()) {

      /* Initialize xpms
       */
      xpm_init();
      
      /* Initialize fonts
       */
      font_init();

      /* Check mounts
       */
      checkmount(true);

      /* Draw everything
       */
      drawall();
      
      /* Execute main loop
       */
      mainloop();

      /* Free fonts
       */
      font_done();

      /* Free xpms
       */
      xpm_done();

      /* Free config file data
       */
      config_done();

    }

    /* Close dockapp
     */
    dockapp_close();
    
  }

  /* Free dockapp
   */
  dockapp_done();

  /* Free
   */
  done();

  /* Finish
   */
  return 0;
}

void init(int argc, char * const *argv) {
  /* Process command-line arguments
   */
  int option_index;
  int option_c;
  struct option long_options[] = {
    {"help", 0, NULL, 'h' },
    {NULL,   0, NULL, '\0'}
  };
  while(true) {
    option_index=0;
    option_c=getopt_long(argc, (char * const *)argv, "-h", long_options, &option_index);
    if(option_c==-1)
      break;
    if(option_c==1) {
      fprintf(stderr, "%s: unwanted argument -- '%s'\n", argv[0], optarg);
    }
    if(option_c>1) {
      switch(option_c) {
      case 'h':
	printf("wmmount - The Window Maker universal mount point\n");
        printf("\n");
	printf("17/06/99  Release 1.0 beta2\n");
        printf("Copyright (C) 1999  Sam Hawker <shawkie@geocities.com>\n");
        printf("This software comes with ABSOLUTELY NO WARRANTY\n");
        printf("This software is free software, and you are welcome to redistribute it\n");
        printf("under certain conditions\n");
	printf("See the README file for details.\n");
	printf("\n");
        printf("usage:\n");
        printf("\n");
	printf("   %s [options] [-- dockapp_options]\n", argv[0]);
        printf("\n");
        printf("options:\n");
        printf("\n");
	printf("   -h, --help           Show this help message\n");
        printf("\n");
        printf("dockapp options:\n");
        printf("\n");
        printf("   -h, --help           Show dockapp help message\n");
	exit(0);
      }
    }
  }
}

void done() {
  /* Free command-line option arguments
   */
}

void mainloop() {
  /* Do the main loop
   */
  XEvent xev;

  fd_set fds;
  struct timeval tv;

  XSelectInput(dockapp_d, dockapp_w_active, ButtonPressMask | ButtonReleaseMask | ExposureMask);
  XMapWindow(dockapp_d, dockapp_w_main);

  while(true) {
    while(XPending(dockapp_d)) {
      XNextEvent(dockapp_d, &xev);
      switch(xev.type) {
      case Expose:
	dockapp_expose();
	break;
      case ButtonPress:
        buttonpress(&xev.xbutton);
	break;
      case ButtonRelease:
	buttonrelease(&xev.xbutton);
	break;
      case ClientMessage:
	if(xev.xclient.data.l[0]==dockapp_a_delwin)
          return;
      }
    }
    FD_ZERO(&fds);
    FD_SET(ConnectionNumber(dockapp_d), &fds);
    tv.tv_sec=(long)INTERVAL/1000000L;
    tv.tv_usec=(long)INTERVAL%1000000L;
    if(!select(ConnectionNumber(dockapp_d)+1, &fds, NULL, NULL, &tv)) {
      counter1++;
      if(counter1>=INTERVAL1) {
	counter1=0;
	/* Update interval
	 */
	checkmount(false);
      }
      counter2++;
      if(counter2>=INTERVAL2) {
	counter2=0;
	/* Button repeat interval
	 */
	if(state & (PREV | NEXT)) {
	  if(state & PREV)
	    select1--;
	  else
	    select1++;
	  if(select1<0)
	    select1=mpoints-1;
	  if(select1>=mpoints)
	    select1=0;
	  checkmount(true);
	}
      }
      counter3++;
      if(counter3>=INTERVAL3) {
	counter3=0;
	/* Double-click interval/timeout
	 */
	state&=~DCLICK;
      }
    }
  }
}

void buttonpress(XButtonEvent *xev) {
  /* Process button press events
   */
  int x=xev->x-(dockapp_size/2-32);
  int y=xev->y-(dockapp_size/2-32);

  char *cmd;

  if(x>=33 && y>=47 && x<=45 && y<=57) {
    select1--;
    if(select1<0)
      select1=mpoints-1;
    counter2=0;
    state|=PREV;
    checkmount(true);
    return;
  }

  if(x>=46 && y>=47 && x<=58 && y<=57) {
    select1++;
    if(select1>mpoints-1)
      select1=0;
    counter2=0;
    state|=NEXT;
    checkmount(true);
    return;
  }

  if(x>=5 && y>=47 && x<=32 && y<=57) {
    if(mounted) {
      cmd=mpoint[select1].umountcmd;
      if(cmd==NULL)
	cmd=umountcmd;
      if(cmd==NULL)
	cmd=UMOUNTCMD;
    }
    else {
      cmd=mpoint[select1].mountcmd;
      if(cmd==NULL)
	cmd=mountcmd;
      if(cmd==NULL)
	cmd=MOUNTCMD;
    }
    execcmd(cmd, NULL);

    return;
  }

  if(x>=4 && y>=4 && x<=59 && y<=42) {
    if(mounted) {
      if(state & DCLICK) {

	cmd=mpoint[select1].opencmd;
	if(cmd==NULL)
	  cmd=opencmd;
        if(cmd==NULL)
	  cmd=OPENCMD;
        execcmd(cmd, NULL);

	state&=~DCLICK;
      }
      else {
	state|=DCLICK;
	counter3=0;
      }
    }
    return;
  }

}

void buttonrelease(XButtonEvent *xev) {
  /* Process button release events
   */
  state&=~(PREV | NEXT);
  drawbtns(PREV | NEXT);
  dockapp_expose();
}

int config_init() {
  /* Process config file
   */

  char *home;
  char *filename;

  FILE *f;

  char buf[256];

  int keynum1;
  char *keyarg1;
  static char *keys1[] = {
    "mountcmd=",
    "umountcmd=",
    "opencmd=",
    "namefont=",
    "usagefont=",
    "icon ",
    "mountpoint ",
    ""
  };
  
  int keynum2;
  char *keyarg2;
  static char *keys2[] = {
    "mountpoint ",
    "name=",
    "iconnumber=",
    "usagedisplay=",
    "mountcmd=",
    "umountcmd=",
    "opencmd=",
    ""
  };

  home=getenv("HOME");
  filename=(char *)malloc(strlen(home)+strlen("/.wmmount")+1);
  sprintf(filename, "%s/.wmmount", home);
  f=fopen(filename, "r");
  free(filename);
  if(f==NULL) {
    fprintf(stderr, "%s: User config file not found\n", dockapp_argv[0]);
    filename=SYSCONFIG;
    f=fopen(filename, "r");
    if(f==NULL) {
      fprintf(stderr, "%s: System config file not found\n", dockapp_argv[0]);
      return -1;
    }
  }

  while(1) {
    fgets(buf, 256, f);
    if(feof(f)!=0)
      break;
    buf[strlen(buf)-1]='\0';

    keynum1=0;
    while(1) {
      if(strncmp(buf, keys1[keynum1], strlen(keys1[keynum1]))==0)
	break;
      keynum1++;
    }

    keyarg1=buf+strlen(keys1[keynum1]);

    if(keynum1==0)
      mountcmd=strdup(keyarg1);
    if(keynum1==1)
      umountcmd=strdup(keyarg1);
    if(keynum1==2)
      opencmd=strdup(keyarg1);
    if(keynum1==3)
      namefont=strdup(keyarg1);
    if(keynum1==4)
      usagefont=strdup(keyarg1);
    if(keynum1==5) {
      if(icons<MAXICONS) {
	dockapp_createpixmap(NULL, NULL, keyarg1, &pm_icon[icons], NULL, NULL, NULL);
	icons++;
      }
    }
    if(keynum1==6) {
      while(mpoints<MAXMPOINTS && feof(f)==0) {
	mpoint[mpoints].mountcmd=NULL;
	mpoint[mpoints].umountcmd=NULL;
	mpoint[mpoints].opencmd=NULL;
	mpoint[mpoints].mpoint=strdup(keyarg1);

	while(1) {
	  fgets(buf, 256, f);
	  if(feof(f)!=0)
	    break;
	  buf[strlen(buf)-1]='\0';

	  keynum2=0;
	  while(1) {
	    if(strncmp(buf, keys2[keynum2], strlen(keys2[keynum2]))==0)
	      break;
	    keynum2++;
	  }

	  keyarg2=buf+strlen(keys2[keynum2]);

	  if(keynum2==0)
	    break;
	  if(keynum2==1)
	    mpoint[mpoints].name=strdup(keyarg2);
	  if(keynum2==2)
	    sscanf(keyarg2, "%i", &mpoint[mpoints].icon);
	  if(keynum2==3)
	    sscanf(keyarg2, "%i", &mpoint[mpoints].udisplay);
	  if(keynum2==4)
	    mpoint[mpoints].mountcmd=strdup(keyarg2);
	  if(keynum2==5)
	    mpoint[mpoints].umountcmd=strdup(keyarg2);
	  if(keynum2==6)
	    mpoint[mpoints].opencmd=strdup(keyarg2);
	}
	mpoints++;
      }
    }

  }

  fclose(f);

  if(mpoints==0) {
    fprintf(stderr, "%s: No mountpoints specified\n", dockapp_argv[0]);
    return -1;
  }

  return 0;
}

void config_done() {
  /* Free config file stuff
   */
  free(mountcmd);
  free(umountcmd);
  free(opencmd);
  free(namefont);
  free(usagefont);

  while(mpoints>0) {
    mpoints--;
    free(mpoint[mpoints].mpoint);
    free(mpoint[mpoints].name);
    free(mpoint[mpoints].mountcmd);
    free(mpoint[mpoints].umountcmd);
    free(mpoint[mpoints].opencmd);
  }
}

void xpm_init() {
  /* Create pixmaps from xpm data
   */
  dockapp_createpixmap(main_xpm, NULL, NULL, &pm_main, &pm_main_mask, NULL, NULL);

  XCopyArea(dockapp_d, pm_main_mask, dockapp_pm_mask, dockapp_gc_mask, 0, 0, 64, 64, 0, 0);
  dockapp_installmask();
}

void xpm_done() {
  /* Free xpms
   */

  /* Done automatically by X, so I won't bother
   */
}

void font_init() {
  /* Initialize fonts
   */
  XFontStruct *xfs;
  char *font;

  font=namefont;
  if(font==NULL)
    font=NAMEFONT;
  xfs=XLoadQueryFont(dockapp_d, font);

  f_name=xfs->fid;
  namex=6-xfs->min_bounds.lbearing;
  namey=5+xfs->ascent;
  usagey=namey;
  usagey+=xfs->descent;
  XFreeFontInfo(NULL, xfs, 1);

  font=usagefont;
  if(font==NULL)
    font=USAGEFONT;
  xfs=XLoadQueryFont(dockapp_d, USAGEFONT);
  
  f_usage=xfs->fid;
  usagex=6-xfs->min_bounds.lbearing;
  usagey+=xfs->ascent;
  XFreeFontInfo(NULL, xfs, 1);
}

void font_done() {
  /* Free fonts
   */

  /* Done automatically by X
   */
}

void checkmount(bool forced) {
  /* Check mount and usage
   */

  bool getmounted;
  int getusage, getavail;

  dev_t dev1, dev2;
  struct stat st;
  struct statfs sfs;
  char *buf;

  float value;
  char unit;

  getmounted=false;
  if(strcmp(mpoint[select1].mpoint, "/")==0)
    /* I think I can assume / is mounted.
     */
    getmounted=true;
  else {
    /* Check that the directory above the mount point is on a different device
     */
    buf=strdup(mpoint[select1].mpoint);

    stat(buf, &st);
    dev1=st.st_dev;

    if(buf[strlen(buf)-1]=='/')
      buf[strlen(buf)-1]='\0';
    *(strrchr(buf, '/')+1)='\0';

    stat(buf, &st);
    dev2=st.st_dev;

    if(memcmp(&dev1, &dev2, sizeof(dev_t))!=0)
      getmounted=true;

    free(buf);
  }

  if(getmounted && mpoint[select1].udisplay!=0) {
    statfs(mpoint[select1].mpoint, &sfs);
    getusage=(sfs.f_blocks-sfs.f_bfree);
    getavail=(sfs.f_bavail);
  }

  if(getmounted && (!mounted || (getusage!=usage) || forced)) {

    value=getusage;
    switch(mpoint[select1].udisplay) {
    case 1:
      value=getavail;
    case 2:
      value*=(float)sfs.f_bsize;

      value/=1024.0;
      unit='k';
      if(value>=999.5) {
	value/=1024.0;
	unit='M';
      }
      if(value>=999.5) {
	value/=1024.0;
	unit='G';
      }

      if(value<9.995) {
	sprintf(usagestr, "%1.02f%cB", value, unit);
	break;
      }
      if(value<99.95) {
	sprintf(usagestr, "%1.01f%cB", value, unit);
	break;
      }
      if(value<999.5) {
	sprintf(usagestr, "%1.0f%cB", value, unit);
	break;
      }
      strcpy(usagestr, "E big");
      break;
    case 3:
      if(getusage+getavail==0)
	strcpy(usagestr, "E div0");
      else
	sprintf(usagestr, "%.01f%%", getusage*100.0/(getusage+getavail));
      break;
    default:
      strcpy(usagestr, "");
    }
  }

  if((getmounted!=mounted) || (getmounted && getusage!=usage && mpoint[select1].udisplay!=0) || forced) {
    mounted=getmounted;
    usage=getusage;
    drawall();
    dockapp_expose();
  }

}

void drawall() {
  /* Draw everything
   */
  XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_clip, 0, 0, 64, 64, 0, 0);

  XCopyArea(dockapp_d, pm_icon[mpoint[select1].icon], dockapp_pm_main, dockapp_gc_main, 0, 0, 32, 10, 25, 30);

  XSetFont(dockapp_d, dockapp_gc_main, f_name);
  XSetForeground(dockapp_d, dockapp_gc_main, dockapp_color0);
  XDrawString(dockapp_d, dockapp_pm_main, dockapp_gc_main, namex, namey, (char *)mpoint[select1].name, strlen(mpoint[select1].name));

  if(mounted) {
    XSetFont(dockapp_d, dockapp_gc_main, f_usage);
    XSetForeground(dockapp_d, dockapp_gc_main, dockapp_color0);
    XDrawString(dockapp_d, dockapp_pm_main, dockapp_gc_main, usagex, usagey, (char *)usagestr, strlen(usagestr));
  }

  drawbtns(BUTTON1 | BUTTON2 | BUTTON3);
}

void drawbtns(int btnmask) {
  /* Draw buttons
   */
  if(btnmask & BUTTON1)
    drawbtn(33, 47, 13, 11, (state & BUTTON1));
  if(btnmask & BUTTON2)
    drawbtn(46, 47, 13, 11, (state & BUTTON2));
  if(btnmask & BUTTON3)
    drawbtn(5, 47, 28, 11, mounted);
}

void drawbtn(int x, int y, int w, int h, bool down) {
  /* Draw single button
   */
  if(down) {
    XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_main, x, y, 1, h-1, x+w-1, y+1);
    XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_main, x+w-1, y+1, 1, h-1, x, y);
    XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_main, x, y, w-1, 1, x+1, y+h-1);
    XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_main, x+1, y+h-1, w-1, 1, x, y);
  }
  else
    XCopyArea(dockapp_d, pm_main, dockapp_pm_main, dockapp_gc_main, x, y, w, h, x, y);
}

void execcmd(char *cmd, char *path) {
  /* Execute external command (without using /bin/sh)
   */

  /* This has got quite complicated now
   *
   * It splits the command-line into its arguments, performs substitutions and
   * then executes the command
   *
   * It doesn't wait for a return and children are reaped using SIGCHLD
   */

  int argc=1;
  char **argv=NULL;

  int i, j;
  char c;

  int buflen=strlen(cmd)+1;
  char *buf=(char *)malloc(buflen);

  bool quoted=false;
  char quotec;

  char *subst;

  int pid;

  signal(SIGCHLD, SIG_IGN);

  j=0;
  for(i=0;i<strlen(cmd);i++) {
    c=cmd[i];
    if(!quoted && (c=='\'' || c=='\"')) {
      quoted=true;
      quotec=c;
      continue;
    }
    if(quoted && c==quotec) {
      quoted=false;
      continue;
    }
    if(c=='\\') {
      i++;
      if(i<strlen(cmd))
	c=cmd[i];
      else
	break;
    }
    if(c=='%') {
      i++;
      if(i<strlen(cmd))
	c=cmd[i];
      else
	break;
      subst=NULL;
      if(c=='m')
	subst=mpoint[select1].mpoint;
      if(c=='n')
	subst=mpoint[select1].name;
      if(subst!=NULL) {
	buflen+=strlen(subst)-2;
	buf=(char *)realloc(buf, buflen);
	strcpy(buf+j, subst);
	j+=strlen(subst);
	continue;
      }
    }
    if(!quoted && c==' ') {
      c='\0';
      argc++;
    }
    buf[j]=c;
    j++;
  }
  buf[j]='\0';

  argv=(char **)malloc((argc+1)*sizeof(char *));

  j=0;
  for(i=0;i<argc;i++) {
    argv[i]=buf+j;
    j+=strlen(buf+j)+1;
  }
  argv[argc]=NULL;

  pid=fork();
  if(pid==0) {
    if(path!=NULL)
      chdir(path);
    execv(argv[0], argv);
    free(argv);
    free(buf);
    exit(127);
  }
}


syntax highlighted by Code2HTML, v. 0.9.1