/*
 * Copyright (C) 1991 by the Massachusetts Institute of Technology
 */

/*
 * Needs to link against: Xlib.
 */

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <sys/types.h>
#include <sys/time.h>

#define MAX_SCREENS 25

Display *display[MAX_SCREENS]; /* I want more... more... more ! */
Cursor  cursor[MAX_SCREENS];   /* cursor for each display */
Window  root[MAX_SCREENS];     /* each one has a root */
int	flags[MAX_SCREENS];    /* Flag Options for each screen */
int     mscreen;               /* number of displays in use */
char    *prog;                 /* the name of this program */

/*
 * This option means that the screen is a broken VS3100 screen, so we
 * need to use a left margin of 8 pixels
 */
#define OPTION_VS3100  	1
#define OPTION_TICKLE_SSAVE	2

int Special_X_Error_Handler();
int (*Default_X_Error_Handler)();

int bad_window_found;		/* This is set if a bad window error */
				/* is ignored */

int debug;

/*
 * the invisible cursor 
 *
 * But what if I don't have a 16x16 cursor?
 * Then you lose, buddy.
 */

static const char foo_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

int main (int argc, char *argv[]) {
  XEvent event;
  Pixmap p;
  XColor a, b;
  KeySym keysym;
  Window retroot, retchild;
  int retrootx, retrooty;
  int retchildx, retchildy;
  unsigned int retmask;
  int current_screen = 0;
  int local = 0;
  int x, y;
  int lx, ly; 
  int cur;
  int i;
  int done = 0;
  int delta = 0;

  bzero(display, sizeof(display));
  for (i=0; i < MAX_SCREENS; i++)
	  flags[i] = 0;
  prog = *argv;
  
  if(argc == 1) {
      printf("usage: %s local <display2> ...\n", prog);
      return 1;
    }

  /*
   * each argument is a display name except the magic "local" which 
   * will get transformed into the local display. 
   */

  while(*++argv != (char *) NULL) {
      if (!strcmp (*argv, "-debug")) {
	  debug++;
	  continue;
      }
      if (!strcmp (*argv, "-B")) {
	      flags[current_screen] |= OPTION_VS3100;
	      continue;
      }
      if (!strcmp (*argv, "-S")) {
	      flags[current_screen] |= OPTION_TICKLE_SSAVE;
	      continue;
      }
      if(strcmp(*argv, "local") == 0) { 
	  *argv = "";
	  local = current_screen;
	}
      display[current_screen] = XOpenDisplay (*argv);
      if (display[current_screen] == (Display *) NULL) {
	  fprintf(stderr, "%s: unable to open display: %s\n", prog, *argv);
	  return 1;
	}
      root[current_screen] = DefaultRootWindow(display[current_screen]);
      p = XCreateBitmapFromData(display[current_screen], root[current_screen],
				foo_bits, 16, 16); 
      cursor[current_screen] = XCreatePixmapCursor(display[current_screen], 
						   p, p, &a, &b, 0, 0);

      ++current_screen;
      if(current_screen >= MAX_SCREENS)
	{
	  fprintf(stderr, "be real\n");
	  return 1;
	}	
    }
  mscreen = current_screen - 1;
  
  /*
   * start with the local screen. Grab and erase the cursors on all the
   * others
   */

  current_screen = 0;
  while(display[current_screen] != (Display *) NULL)
    if(current_screen != local)
      grab_screen(current_screen++);
    else
      ++current_screen;

  XSelectInput(display[local], root[local],
	       PointerMotionMask | KeyPressMask | KeyReleaseMask);

  current_screen = local;

  /*
   * Redirect the error routine so bad windows don't kill us.
   */
  Default_X_Error_Handler = XSetErrorHandler(Special_X_Error_Handler);
  
  while (1) {
      int no_state;
      XNextEvent(display[local], &event); /* getting nulls to start? */

      switch(event.type) {
	case MotionNotify:	

	  /*
	   * we should be using the event data, but then I'd have to 
	   * keep track of which display it came from and I'm tired.
	   */

#ifndef notdef
	      XQueryPointer(display[local], root[local], 
			&retroot, &retchild,
			&retrootx, &retrooty, 
			&retchildx, &retchildy,
			&retmask);
#else
	      retrootx = event.xmotion.x_root;
	      retrooty = event.xmotion.y_root;
#endif

	  /*
	   * cut corners.. electric bills rising
	   */

	  x -= delta;
	  lx = x;
	  ly = y;
	  x = retrootx;
	  y = retrooty;
	  if((x == lx) && (y == ly))
	    continue;

	  /*
	   * scroll off the right
	   */

	  if(x + delta >= (DisplayWidth(display[local],
				DefaultScreen(display[local])) - 1) &&
	     x + delta < (DisplayWidth(display[current_screen],
				DefaultScreen(display[current_screen])) - 1))
	    {
	      delta = x;
	      x = 1;
	    }
	  else
	    if(x + delta >= (DisplayWidth(display[current_screen],
				  DefaultScreen(display[current_screen])) - 1))
	      {
		cur = current_screen;
		if(display[current_screen + 1] != (Display *) NULL)
		  ++current_screen;
		else
		  current_screen = 0;
		switch_screen(cur, current_screen);	      
		x  = 1;
		delta = 0;
		XWarpPointer(display[cur], None, root[cur],
			     0, 0, 0, 0, x+delta, y);
		XSync(display[cur], False);
		XWarpPointer(display[local], None, root[local], 
			     0, 0, 0, 0, x+delta, y);
		XSync(display[local], False);
	      }
	    else
	      
	    /*
	     * scroll off the left 
	     */

	      if ((flags[current_screen] & OPTION_VS3100) ?
		      (x <= 7) : (x <= 0))
		{
		  if(delta)
		    {
		      x+=delta;
		      delta = 0;
		      continue;
		    }
		  cur = current_screen;
		  if(current_screen > 0)
		    --current_screen;
		  else
		    current_screen = mscreen;
		  
		  switch_screen(cur, current_screen);
		  x   = DisplayWidth(display[current_screen],
				     DefaultScreen(display[current_screen])) - 2;
		  if(x >= DisplayWidth(display[local],
				     DefaultScreen(display[local])))
		    {
		      x = DisplayWidth(display[local],
					   DefaultScreen(display[local]));
		    }
		     
		  XWarpPointer(display[cur], None, root[cur],
			       0, 0, 0, 0, x, y);
		  /*		XSync(display[cur], False); /* probably not needed */
		  XWarpPointer(display[local], None, root[local], 
			       0, 0, 0, 0, x, y); /* is this needed? */
		  XSync(display[local], False);
		}
	      else
		
		/*
		 * don't play with yourself
		 */
		
		if(current_screen == local)
		  continue;	  
	  

	  XWarpPointer(display[current_screen], None, 
		       root[current_screen], 
		       0, 0, 0, 0, x, y);
	  goto send_it;

	case KeyPress:
	case KeyRelease:
	  keysym = XKeycodeToKeysym(display[local], event.xkey.keycode,
				    event.xkey.state);
	  if (!keysym) {
	      no_state = 1;
	      keysym = XKeycodeToKeysym(display[local], event.xkey.keycode, 0);
	  }
	  else
	      no_state = 0;
	  if (keysym == XK_F6)	    
	      return 0;
	  if(keysym == XK_F2)
	    {
	      cur = current_screen;
	      if(display[current_screen + 1] != (Display *) NULL)
		++current_screen;
	      else
		current_screen = 0;
	      switch_screen(cur, current_screen);	      
	      XSync(display[local], False);
	    }
	  if(keysym == XK_F1)
	    {
	      cur = current_screen;
	      if(current_screen > 0)
		--current_screen;
	      else
		current_screen = mscreen;
	      
	      switch_screen(cur, current_screen);
	      XSync(display[local], False);
	    }
	  if (debug)
	      printf ("keycode %d, state %d => keysym %d => keycode",
		      event.xkey.keycode, event.xkey.state, keysym);
	  if (keysym)
	      event.xkey.keycode = XKeysymToKeycode(display[current_screen], 
						    keysym);
	  if (!no_state) {
	      KeySym new_sym;
	      int i, max_k;
	      new_sym = XKeycodeToKeysym (display[current_screen],
					  event.xkey.keycode,
					  event.xkey.state);
	      if (new_sym != keysym) {
		  max_k = display[current_screen]->keysyms_per_keycode;
		  for (i = 0; i < max_k; i++) {
		      new_sym = XKeycodeToKeysym (display[current_screen], event.xkey.keycode, i);
		      if (new_sym == keysym)
			  break;
		  }
		  if (i != max_k)
		      event.xkey.state = i;
	      }
	  }
	  if (debug) {
	      printf (" %d/%d\n", event.xkey.keycode, event.xkey.state);
	      fflush (stdout);
	  }

	case ButtonPress:
	case ButtonRelease:
      send_it:
	  XQueryPointer(display[current_screen], root[current_screen],
			&retroot, &retchild,
			&retrootx, &retrooty, &retchildx, &retchildy,
			&retmask);
	  event.xbutton.display   = display[current_screen];
	  event.xbutton.window    = root[current_screen];
	  event.xbutton.root      = root[current_screen];
	  event.xbutton.subwindow = retchild;
	
	  if (retchild != None) {
	      bad_window_found = 0;
	      while (retchild != None) {
		  event.xbutton.window = retchild;
		  XQueryPointer(display[current_screen], retchild, 
				&retroot, &retchild,
				&retrootx, &retrooty, 
				&retchildx, &retchildy,
				&retmask);
		  if (bad_window_found)
			  break;
		}
	      event.xbutton.x = retchildx;
	      event.xbutton.y = retchildy;
	      event.xbutton.subwindow = event.xbutton.window;
#ifdef DEBUG
	      fprintf(stdout, "w: %x; x: %d; y:%d\n",
		      event.xbutton.window,
		      event.xbutton.x,
		      event.xbutton.y);
#endif
	    }

	  if(current_screen == local)
	    continue;	  
	  XSendEvent(display[current_screen], PointerWindow, True,
		     (ButtonPressMask | ButtonReleaseMask | 
		      KeyPressMask | KeyReleaseMask), &event);
	  if (flags[current_screen] & OPTION_TICKLE_SSAVE)
		  XForceScreenSaver(display[current_screen],
				    ScreenSaverReset);
	  XSync(display[current_screen], False);
	}
    }
}




switch_screen(old, new)
     int old;
     int new;
{
  grab_screen(old);
  ungrab_screen(new);
}



grab_screen(s)
     int s;
{
  XGrabPointer(display[s], root[s], False,
	       PointerMotionMask | ButtonPressMask | 
	       ButtonReleaseMask,
	       GrabModeAsync, GrabModeAsync, root[s],
	       cursor[s], CurrentTime);
  
  XGrabKeyboard(display[s], root[s], True,
		GrabModeAsync, GrabModeAsync, CurrentTime);
}



ungrab_screen(s)
     int s;
{
  XUngrabPointer(display[s],  CurrentTime);
  XUngrabKeyboard(display[s], CurrentTime);
}

/*
 * They have Olympics for Error handlers like this one.....
 */
Special_X_Error_Handler(dpy, event)
	Display	*dpy;
	XErrorEvent	*event;
{
	if (event->error_code == BadWindow) {
		bad_window_found++;
		return 0;
	}
	else
	  return(Default_X_Error_Handler(dpy, event));
}
