/*
** xmsgsaver ver 1
**
** saver.c -- main routine
**
** Sep 5,1999 by T.Kudoh(CDR/TK)
**
** Sep 5,1999 -- Ver 1.2
** Sep 7,1999 -- Ver 1.3
** Oct 28,1999 -- Ver 1.4
** Feb 22,2000 -- Ver 1.5
** Feb 23,2000 -- Ver 1.6
** Apr 20,2000 -- Ver 1.8
** Apr 25,2000 -- Ver 1.8.1
*/

#include<stdio.h>
#include<sys/param.h>
#include<X11/Xatom.h>
#include<X11/Intrinsic.h>
#include<X11/StringDefs.h>
#include<X11/extensions/scrnsaver.h>
#include"font.h"

char *Myname;	/* name of process */

typedef struct{
  Pixel foreground;	/* message color */
  Pixel shadowColor;	/* shadow color */
  Pixel background;	/* background color */
  String message;	/* message string */
  String vfontcap;	/* path of VFlib's vfontcap */
  String vectorFont;	/* VFlib font name */
  int fontWidth;	/* width of font */
  int fontHeight;	/* height of font */
  int columnSpace;	/* horizontal space between characters */
  int lineSpace;	/* vertical space between characters */
  int speed;		/* move per tics */
  int tics;		/* tics (1/fps) */
  Boolean dual_size;	/* if 2bytes char has double size */
  int shadowX;		/* horizontal position of shadow */
  int shadowY;		/* vertical position of shadow */
} AppData;

static AppData app_data;

static XtResource Resources[] = {
  {
    XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
    XtOffsetOf(AppData,background),
    XtRImmediate,XtDefaultBackground
  },
  {
    XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
    XtOffsetOf(AppData,foreground),
    XtRImmediate,XtDefaultForeground
  },
  {
    "shadowColor", XtCBorderColor, XtRPixel, sizeof(Pixel),
    XtOffsetOf(AppData,shadowColor),
    XtRImmediate,XtDefaultForeground
  },
  {
    "message","Message",XtRString,sizeof(String),
    XtOffsetOf(AppData,message),
    XtRImmediate,NULL
  },
  {
    "vfontcap","Vfontcap",XtRString,sizeof(String),
    XtOffsetOf(AppData,vfontcap),
    XtRImmediate,NULL
  },
  {
    "vectorFont","VectorFont",XtRString,sizeof(String),
    XtOffsetOf(AppData,vectorFont),
    XtRImmediate,"min"
  },
  {
    "fontWidth","FontWidth",XtRInt,sizeof(int),
    XtOffsetOf(AppData,fontWidth),
    XtRImmediate,(XtPointer)32
  },
  {
    "fontHeight","FontHeight",XtRInt,sizeof(int),
    XtOffsetOf(AppData,fontHeight),
    XtRImmediate,(XtPointer)32
  },
  {
    "columnSpace","ColumnSpace",XtRInt,sizeof(int),
    XtOffsetOf(AppData,columnSpace),
    XtRImmediate,(XtPointer)0
  },
  {
    "lineSpace","LineSpace",XtRInt,sizeof(int),
    XtOffsetOf(AppData,lineSpace),
    XtRImmediate,(XtPointer)2
  },
  {
    "tics","Tics",XtRInt,sizeof(int),
    XtOffsetOf(AppData,tics),
    XtRImmediate,(XtPointer)50
  },
  {
    "speed","Speed",XtRInt,sizeof(int),
    XtOffsetOf(AppData,speed),
    XtRImmediate,(XtPointer)4
  },
  {
    "dualSize","DualSize",XtRBoolean,sizeof(int),
    XtOffsetOf(AppData,dual_size),
    XtRImmediate,(XtPointer)False
  },
  {
    "shadowX","ShadowPosition",XtRInt,sizeof(int),
    XtOffsetOf(AppData,shadowX),
    XtRImmediate,(XtPointer)0
  },
  {
    "shadowY","ShadowPosition",XtRInt,sizeof(int),
    XtOffsetOf(AppData,shadowY),
    XtRImmediate,(XtPointer)0
  },
};

static XrmOptionDescRec Options[] = {
  {"-msg","*message",XrmoptionSepArg,(caddr_t)NULL }, 
  {"-vfcap","*vfontcap",XrmoptionSepArg,(caddr_t)NULL },
  {"-vfont","*vectorFont",XrmoptionSepArg,(caddr_t)NULL },
  {"-fw","*fontWidth",XrmoptionSepArg,(caddr_t)NULL },
  {"-fh","*fontHeight",XrmoptionSepArg,(caddr_t)NULL },
  {"-cs","*columnSpace",XrmoptionSepArg,(caddr_t)NULL },
  {"-ls","*lineSpace",XrmoptionSepArg,(caddr_t)NULL },
  {"-tics","*tics",XrmoptionSepArg,(caddr_t)NULL },
  {"-speed","*speed",XrmoptionSepArg,(caddr_t)NULL },
  {"-dual_size","*dualSize",XrmoptionNoArg,"True" },
  {"+dual_size","*dualSize",XrmoptionNoArg,"False" },
  {"-shadow_x","*shadowX",XrmoptionSepArg,(caddr_t)NULL },
  {"-shadow_y","*shadowY",XrmoptionSepArg,(caddr_t)NULL },
  {"-shadow","*ShadowPosition",XrmoptionSepArg,(caddr_t)NULL },
  {"-shadow_color","*shadowColor",XrmoptionSepArg,(caddr_t)NULL },
};


/* message window create and control */

typedef struct{
	/* user parameters */

	unsigned char *msg;	/* message(must be EUC-japan) */
	char *font;		/* VFlib font name */
	int size_h;		/* character horizontal size */
	int size_v;		/* character vertical size */
	int space_h;		/* horizontal space beetween characters */
	int space_v;		/* vertical space beetween characters */
	int speed;		/* move speed */
	int dual_size;		/* if 2bytes char has double size */
	int shadow_x;		/* horizontal position of shadow */
	int shadow_y;		/* vertical position of shadow */
	int fg;			/* message color */
	int shadow_color;	/* shadow color */
	int bg;			/* background color */

	/* internal parameters */

	int x;			/* position x */
	int y;			/* position y */
	Window window;		/* window */
	Pixmap pixmap;		/* pixmap */
	int width;		/* window width */
	int height;		/* window height */
}msg_t;

static Window Root,Saver;
static msg_t Msg;

static Display *Disp;
static int Scr;				/* default screen */
static Colormap Cmap;
static XtAppContext App;
static int Dep;				/* default depth of screen */
static int Scr_width,Scr_height;	/* size of screen */
static GC Default_GC;

static int SS_event, SS_error;		/* ScreenSaver Event */

static Saver_state;			/* screen saver is active? */

static volatile int Another_client;	/* another screen-saver is running? */


/* error handler */

int err_handler(Display *d,XErrorEvent *e)
{
  /*
  ** if another screen-saver is running,
  ** XScreenSaverSetAttributes() causes an error.
  */

  Another_client = 1;
}


/* create message window */

static int create_msg( msg_t *m )
{
  int column,row;	/* columns and rows of message */
  int h,v;		/* size of an character in pixels, including space */
  int hs,vs;		/* size of message in pixels */
  int x,y;		/* position of message */
  int sx,sy;		/* position of shadow */
  int bufwid,bufsiz;	/* width and size of bitmap buffer */
  unsigned char *buf;	/* temporary buffer for bitmap data */
  GC gc;		/* temporary GC; free at the end of this function */
  XGCValues gv;		/* be used to create/change GC */
  Pixmap bitmap;	/* temporary bitmap; free at the end of this function */

  /* check size of message */

  h = m->size_h + m->space_h;
  v = m->size_v + m->space_v;

  FONT_get_size(m->msg,&column,&row,m->dual_size);
  if( column == 0 || row == 0 ){
    fprintf(stderr,"%s: message has 0 size.\n",Myname);
    return -1;
  }
  hs = h*(column-1)+m->size_h;
  vs = v*(row-1)+m->size_v;

  /* get font data */

  bufwid = (hs+7)/8;
  bufsiz = bufwid * vs;
  buf = (unsigned char*)malloc(bufsiz);
  if( buf == NULL ){
    fprintf(stderr,"%s: cannot allocate memory.\n",Myname);
    return -1;
  }
  memset(buf,0,bufsiz);
  if( FONT_get_bitmap(m->msg,m->font,buf,bufwid,
  			m->size_h,m->size_v,h,v,m->dual_size) < 0 ){
    free(buf);
    return -1;
  }

  /* create bitmap */

  FONT_bit_reorder(buf,buf,bufsiz);

  bitmap = XCreateBitmapFromData(Disp,Root,buf,hs,vs);

  /* size of window and pixmap */

  m->width = hs + abs(m->shadow_x) + abs(m->speed);
  m->height = vs + abs(m->shadow_y);

  /* create Pixmap and fill by background color */

  m->pixmap = XCreatePixmap(Disp,Saver,m->width,m->height,Dep);

  gv.foreground = m->bg;

  gc = XCreateGC(Disp,Saver,GCForeground,&gv);
  XFillRectangle(Disp,m->pixmap,gc,0,0,m->width,m->height);

  /* position of message and shadow */

  if( m->shadow_x > 0 ){
    x = 0;
    sx = m->shadow_x;
  }else{
    sx = 0;
    x = -m->shadow_x;
  }

  if( m->speed < 0 ){
    x += -m->speed;
    sx += -m->speed;
  }

  if( m->shadow_y > 0 ){
    y = 0;
    sy = m->shadow_y;
  }else{
    sy = 0;
    y = -m->shadow_y;
  }

  /* draw shadow to pixmap */

  gv.foreground = m->shadow_color;
  gv.clip_x_origin = sx;
  gv.clip_y_origin = sy;
  gv.clip_mask = bitmap;
  XChangeGC(Disp,gc,GCForeground|GCClipXOrigin|GCClipYOrigin|GCClipMask,&gv);
  if( m->shadow_x != 0 || m->shadow_y != 0 )
    XFillRectangle(Disp,m->pixmap,gc,sx,sy,hs,vs);

  /* draw message to pixmap */

  gv.foreground = m->fg;
  gv.clip_x_origin = x;
  gv.clip_y_origin = y;
  XChangeGC(Disp,gc,GCForeground|GCClipXOrigin|GCClipYOrigin,&gv);
  XFillRectangle(Disp,m->pixmap,gc,x,y,hs,vs);

  /* free temporary resources */

  XFreeGC(Disp,gc);
  XFreePixmap(Disp,bitmap);
  free(buf);

  /* create window which across on the screen */

  m->window = XCreateSimpleWindow(Disp,Saver,
					0,0,m->width,m->height,0,m->bg,m->bg);
  XSelectInput(Disp,m->window,ExposureMask);

  /* move window to out of screen */
  XMoveWindow(Disp,m->window,-m->width,0);

  if( m->speed > 0 )
    m->x = -m->width;
  else
    m->x = Scr_width;
  m->y = 0;

  XMapWindow(Disp,m->window);

  return 0;
}


/* move message window */

static void move_msg( msg_t *m )
{
  int range;

  if( m->speed > 0 && m->x < -m->width ){
    m->x = Scr_width;
  }else if( m->speed < 0 && m->x > Scr_width ){
    m->x = -m->width;
  }else{
    m->x -= m->speed;
    XMoveWindow(Disp,m->window,m->x,m->y);
    return;
  }

  range = Scr_height - m->height;
  if( range > 0 )
    m->y = random() % range;
   else
    m->y = -(random() % (-range));
}


/* timeout handler */

static void timeout( XtPointer p,XtIntervalId *id )
{
  if( Saver_state ){
    move_msg((msg_t*)p);
    XtAppAddTimeOut(App,app_data.tics,timeout,p);
  }
}


/* start saver */

static void start_saver( msg_t *m )
{
  if( Saver_state )
    return;

  if( create_msg(m) < 0 ){
    FONT_deinit();
    exit(1);
  }

  Saver_state = 1;
  if( m->speed < 0 )
    m->x = Scr_width;
  else
    m->x = -m->width;
  move_msg(m);
  XtAppAddTimeOut(App, app_data.tics, timeout,(XtPointer)m);
}


/* stop savcer */

static void stop_saver( msg_t *m )
{
  Saver_state = 0;
  XFreePixmap(Disp,m->pixmap);
}


/* redraw window */

static void redraw( msg_t *m, XEvent *e )
{
  XCopyArea(Disp,m->pixmap,m->window,Default_GC,
  		e->xexpose.x,e->xexpose.y,
  		e->xexpose.width,e->xexpose.height,
  		e->xexpose.x,e->xexpose.y);
}


/* usage */

static void usage( void )
{
  fprintf(stderr,
 	"usage :  %s [options..]\n\n"
	"    options:\n"
	"\t-toolkit options..\n"
	"\t-msg string          message string\n"
	"\t-fg color            message color\n"
	"\t-bg color            background color\n"
	"\t-shadow_color color  shadow color\n"
	"\t-vfcap path          path to vfontcap\n"
	"\t-vfont string        VFlib font name\n"
	"\t-fw number           font width\n"
	"\t-fh number           font height\n"
	"\t-cs number           column space\n"
	"\t-ls number           line space\n"
	"\t-speed number        moving speed\n"
	"\t-tics tics           moving time interval\n"
	"\t-/+dual_size         2bytes character has double width or same width\n"
	"\t-shadow number       x,y position of shadow offset from message\n"
	"\t-shadow_x number     x position of shadow offset from message\n"
	"\t-shadow_y number     y position of shadow offset from message\n"
 	,Myname);
}


int main( int argc, char **argv )
{
  Widget top;
  XEvent event;
  XScreenSaverNotifyEvent *sevent;
  XScreenSaverInfo *info;
  Pixmap pix_blank;
  GC gc;
  XSetWindowAttributes attr;
  unsigned long mask;
  XColor dummy;
  unsigned char buf[MAXHOSTNAMELEN+1];
  int (*old_handler)(Display*,XErrorEvent*);
  XID kill_id;
  Atom kill_type;

  Myname = argv[0] ? argv[0] : "xmsgsaver";

  srandom(time(NULL));

  top = XtVaAppInitialize(&App,"XMsgSaver",Options,XtNumber(Options),
				&argc,argv,NULL,NULL);
  XtGetApplicationResources(top,&app_data,
  				Resources,XtNumber(Resources),NULL,0);

  if( argc > 1 ){
    usage();
    return 1;
  }

  Disp = XtDisplay(top);
  Root = DefaultRootWindow(Disp);
  Scr = DefaultScreen(Disp);
  Dep = DefaultDepth(Disp,Scr);
  Scr_width = DisplayWidth(Disp,Scr);
  Scr_height = DisplayHeight(Disp,Scr);
  Cmap = DefaultColormap(Disp,Scr);
  Default_GC = DefaultGC(Disp,Scr);

  if( !XScreenSaverQueryExtension(Disp,&SS_event,&SS_error) ){
    fprintf(stderr,"%s: MIT-SCREEN-SAVER is not supported.\n",Myname);
    return 1;
  }
  XScreenSaverSelectInput(Disp,Root,ScreenSaverNotifyMask);

  pix_blank = XCreatePixmap(Disp,Root,1,1,1);
  gc = XCreateGC(Disp,pix_blank,0,NULL);
  XSetForeground(Disp,gc,0);
  XFillRectangle(Disp,pix_blank,gc,0,0,1,1);

  XFreeGC(Disp,gc);

  info = XScreenSaverAllocInfo();
  XScreenSaverQueryInfo(Disp,Root,info);

  if( app_data.message == NULL ){
    gethostname(buf,sizeof(buf));
    Msg.msg = buf;
  }else{
    Msg.msg = app_data.message;
  }

  Msg.size_h = abs(app_data.fontWidth);
  Msg.size_v = abs(app_data.fontHeight);

  if( Msg.size_h + app_data.columnSpace >= 0 )
    Msg.space_h = app_data.columnSpace;
  else
    Msg.space_h = -Msg.size_h;

  if( Msg.size_v + app_data.lineSpace >= 0 )
    Msg.space_v = app_data.lineSpace;
  else
    Msg.space_v = -Msg.size_v;

  Msg.fg = app_data.foreground;
  Msg.bg = app_data.background;
  Msg.speed = app_data.speed == 0 ? 1 : app_data.speed;
  Msg.font = app_data.vectorFont;
  Msg.dual_size = app_data.dual_size;
  Msg.shadow_x = app_data.shadowX;
  Msg.shadow_y = app_data.shadowY;
  Msg.shadow_color = app_data.shadowColor;

  mask = 0;
  attr.colormap = Cmap;
  mask |= CWColormap;
  attr.background_pixel = app_data.background;
  mask |= CWBackPixel;
  attr.cursor = XCreatePixmapCursor(Disp,pix_blank,pix_blank,
					&dummy,&dummy,0,0);
  mask |= CWCursor;


  old_handler = XSetErrorHandler(err_handler);
  Another_client = 0;
  XScreenSaverSetAttributes(Disp,Root,0,0,
      				Scr_width,Scr_height,0,
        			CopyFromParent,CopyFromParent,CopyFromParent,
				mask, &attr);
  XSync(Disp,False);
  XSetErrorHandler(old_handler);

  if( Another_client ){
    if(XScreenSaverGetRegistered(Disp,Scr,&kill_id, &kill_type)){
      XKillClient(Disp,kill_id);
      XSync(Disp,False);
    }
    XScreenSaverSetAttributes(Disp,Root,0,0,
      				Scr_width,Scr_height,0,
        			CopyFromParent,CopyFromParent,CopyFromParent,
				mask, &attr);
  }

  XScreenSaverRegister (Disp,Scr,(XID)pix_blank,XA_PIXMAP);

  if( FONT_init(app_data.vfontcap) < 0 ){
    return -1;
  }

  XSetErrorHandler(err_handler);	/* ignore error */

  Saver = info->window;
  if(info->state == ScreenSaverOn){
    if(info->kind != ScreenSaverExternal){
      XResetScreenSaver(Disp);
      XActivateScreenSaver(Disp);
    }
    start_saver(&Msg);
  }

  for(;;){
    XtAppNextEvent(App,&event);
    if(event.type == SS_event){
      sevent = (XScreenSaverNotifyEvent*)&event;
      if(sevent->state == ScreenSaverOn){
        if(sevent->kind != ScreenSaverExternal){
          XResetScreenSaver(Disp);
          XActivateScreenSaver(Disp);
        }else{
          start_saver(&Msg);
        }
      }else if(sevent->state == ScreenSaverOff) {
        stop_saver(&Msg);
      }
    }else if( event.type == Expose && event.xexpose.window == Msg.window ){
      redraw(&Msg,&event);
    }else{
      XtDispatchEvent(&event);
    }
  }	
  /* NOTREACHED */
  return 1;
}
