/*
 * $Source: /mit/sipb/src/src/xscreensaver/RCS/savescreen.c,v $
 * $Author: ghudson $
 *
 * This file is part of xscreensaver.  It contains the code for the
 * actual locking/blanking of the screen, with the floating icon and
 * the handling of passwords.
 *
 * Author: Jonathan Kamens, MIT Project Athena and
 *                          MIT Student Information Processing Board
 *
 * Coyright (c) 1989 by Jonathan Kamens.  This code may be distributed
 * freely as long as this notice is kept intact in its entirety and
 * every effort is made to send all corrections and improvements to
 * the code back to the author.  Also, don't try to make any money off
 * of it or pretend that you wrote it.
 */

#ifndef lint
     static char rcsid_savescreen_c[] = "$Header: /mit/sipb/src/src/xscreensaver/RCS/savescreen.c,v 1.3 1996/01/28 01:53:35 ghudson Exp svalente $";
#endif

#include "xsaver.h"
#include <signal.h>
#include <ctype.h>
#include <X11/Xaw/Command.h>
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#include "scaling.h"
#include "globals.h"
#include "float.h"
#include "logoutButton.h"

extern char *my_malloc();
extern Cursor blank_cursor();
extern Widget PasswordWindow();
extern Boolean correct_password();
extern void XtMoveWidget();
extern Dimension widget_width();
extern void exitCallback();
#ifdef _IBMR2
extern void enable_hft_hotkey();
extern int disable_hft_hotkey();
#endif

void build_root(), unlock_command();
void ActivateRoot(), RaiseRoot(), RemoveRoot(), GetPassword();
static void LockScreen(), ReallyLockScreen(), hideCursor(),
     SaveScreen(), PasswordTimeout(), ActivateClock(), lock_command();

static int lock_command_pid = 0, did_lock_command = 0;
static String lockedTransString = 
     "<Key>:			GetPassword(Locked)\n\
      <BtnDown>,<BtnUp>:	GetPassword(Locked)\n\
      <Visible>:		RaiseRoot()\n\
      <Map>:			RaiseRoot()\n\
      <Motion>:			PointerMoved()\n";
static XtTranslations lockedTrans = (XtTranslations) NULL;
static Widget root_shell = (Widget) NULL;
static XtIntervalId password_timeout, logout_button_timeout;
static XtIntervalId hide_cursor_timeout;
static Boolean cursor_moved;
static struct {
     int timeout, interval, prefer_blanking, allow_exposures;
} saver, saver2;


#define PASSWORD_TIMEOUT 30	/* time, in seconds, before a password */
				/* prompt will time out */
#define WARNING_TIMEOUT 3	/* time, in seconds, to show the */
				/* caps lock warning */

#ifdef PASSWORD_PORT
extern void close_password_port();
#endif





void build_root()
{
     Arg arglist[10];
     int i = 0;
     char geometry[20];

     (void) sprintf(geometry, "%dx%d+0+0", display_width, display_height);
     if (! root_shell) {
	  XtSetArg(arglist[i], XtNborderWidth, 0); 			i++;
	  XtSetArg(arglist[i], XtNx, 0); 				i++;
	  XtSetArg(arglist[i], XtNy, 0); 				i++;
	  XtSetArg(arglist[i], XtNwidth, display_width);		i++;
	  XtSetArg(arglist[i], XtNheight, display_height);		i++;
	  XtSetArg(arglist[i], XtNgeometry, geometry);			i++;
	  if (defs.use_background) {
	       XtSetArg(arglist[i], XtNbackgroundPixmap,
			ParentRelative); 				i++;
	  }

	  root_shell = XtCreatePopupShell("rootShell",
					  overrideShellWidgetClass,
					  top_widget, arglist, i);
     
	  root_widget = XtCreateManagedWidget("root", widgetClass,
					      root_shell, arglist, i);	i = 0;

	  XtRealizeWidget(root_shell);

	  /* Setting the coordinates to 0, 0 appears not to work in some */
	  /* cases, in particular when people specify Geometry *class* in */
	  /* the resources, so we are going to reposition the window after */
	  /* we create it. */
	  XtMoveWidget(root_shell, 0, 0);
     }
     cursor_moved = False;
     hideCursor();
}



static void invisibleCursor()
{
     static Cursor cursor = 0;

     if (! cursor) {
	  cursor = blank_cursor();
     }
	  
     XDefineCursor(dpy, XtWindow(root_widget), cursor);
}



static void defaultCursor()
{
     XUndefineCursor(dpy, XtWindow(root_widget));
}



#define CURSOR_TIMEOUT 15 * 1000

/*ARGSUSED*/
void pointerMoved(Widget w, XEvent *event, String *params,
		  Cardinal *num_params)
{
     if (event->xmotion.x == display_width / 2 &&
	 event->xmotion.y == display_height / 2) {
	  /*
	   * When hideCursor warps the mouse to the middle of the
	   * screen, we'll get a motion event.  We don't care about
	   * it.
	   */
	  return;
     }

     cursor_moved = True;
     if (! hide_cursor_timeout) {
	  defaultCursor();
	  hide_cursor_timeout = XtAppAddTimeOut(app_context,
						CURSOR_TIMEOUT,
						hideCursor, 0);
     }
}


static void hideCursor()
{
     hide_cursor_timeout = 0;
     if (! cursor_moved) {
	  XWarpPointer(dpy, None, XtWindow(root_widget), 0, 0, 0, 0,
		       display_width / 2, display_height / 2);
	  invisibleCursor();
     }
     else {
	  hide_cursor_timeout = XtAppAddTimeOut(app_context,
						CURSOR_TIMEOUT,
						hideCursor, 0);
	  cursor_moved = False;
     }
}

/* ARGSUSED */
void ActivateRoot()
{
     int	ret;
     char	buf[80];
     XWMHints	*hints;

     /* I'm using CurrentTime here where I shouldn't -- at some point */
     /* in the future, the code should be fixed to move the map time  */
     /* of the root window.					      */
     
     ActivateClock();

     build_root();

     NewFloat();
     if (defs.disable_x_screensaver) {
	  XGetScreenSaver(dpy, &saver.timeout, &saver.interval,
			  &saver.prefer_blanking, &saver.allow_exposures);
	  XSetScreenSaver(dpy, 0, saver.interval, saver.prefer_blanking,
			  saver.allow_exposures);
     }
     XtMapWidget(root_shell);
     XRaiseWindow(dpy, XtWindow(root_shell));
     /* Window must be mapped for some of the following calls to work, */
     /* so we flush the display to make sure it gets mapped before     */
     /* these calls take place. 				       */
     XSync(dpy, False);
     if (ret = XGrabPointer(dpy, XtWindow(root_widget), True, ButtonPressMask,
			    GrabModeAsync, GrabModeAsync,
			    XtWindow(root_widget), None,
			    CurrentTime) != GrabSuccess) {
	  XGetErrorText(dpy, ret, buf, 80);
	  fprintf(stderr, "%s: %s (%s)!  %s.\n",
		  whoami, "Error grabbing pointer", buf,
		  "Giving up on screensaver activation (try again)");
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to unmap root widget 0x%x (window id 0x%x) in ActivateRoot.\n",
		       root_shell, XtWindow(root_shell));
	  }
	  XtUnmapWidget(root_shell);
	  return;
     }
     if (ret = XGrabKeyboard(dpy, XtWindow(root_widget), False, GrabModeAsync,
			     GrabModeAsync, CurrentTime) != GrabSuccess) {
	  XGetErrorText(dpy, ret, buf, 80);
	  fprintf(stderr, "%s: %s (%s)!  %s.\n",
		  whoami, "Error grabbing keyboard", buf,
		  "Giving up on screensaver activation (try again)");
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to unmap root widget 0x%x (window id 0x%x) in ActivateRoot.\n",
		       root_shell, XtWindow(root_shell));
	  }
	  XtUnmapWidget(root_shell);
	  XUngrabPointer(dpy, CurrentTime);

	  return;
     }
     XtSetKeyboardFocus(root_shell, root_widget);
     XSetInputFocus(dpy, XtWindow(root_widget), RevertToPointerRoot,
		    CurrentTime);
     /* We need to do this for the ICCCM */
     hints = XGetWMHints(dpy, XtWindow(root_widget));
     if (hints) {
	  hints->input = False;
	  XSetWMHints(dpy, XtWindow(root_widget), hints);
	  XtFree((char *) hints);
     }
     
     if (lock_flag) {
	  is_locked = True;
	  LockScreen();
     }
     else
	  SaveScreen();
}



static void LockScreen()
{
     if (! lockedTrans)
	  lockedTrans = XtParseTranslationTable(lockedTransString);
     if (! (defs.ekey || defs.key)) {
	  String str = "Initial";
	  Cardinal num = 1;
	  GetPassword(root_widget, (XEvent *) NULL, &str, &num);
     }
     else
	  ReallyLockScreen();
}



static void ReallyLockScreen()
{
#ifdef _IBMR2
     if (defs.disable_hotkey && disable_hft_hotkey()) {
	  if (defs.require_hotkey) {
	       fprintf(stderr, "%s: error disabling HFT hot key.\n%s: Giving up on screen locking.\n%s: To force screen locking, run %s again\n%s: with the \"-requireHotkey\" option.\n", whoami, whoami, whoami, whoami, whoami);
	       RemoveRoot(0, 0, 0, 0);
	       return;
	  }
	  else {
	       fprintf(stderr, "%s: warning: error disabling HFT hot key.\n%s: Locking screen anyway.\n", whoami, whoami);
	  }
     }
#endif
     lock_command();
     XtOverrideTranslations(root_widget, lockedTrans);
     XtOverrideTranslations(root_shell, lockedTrans);
     if (defs.logout_button_timeout) {
	  /*
	   * It should not be necessary to either remove the time out
	   * or deactivate the logout button, since both of those
	   * should happen when the screen is unlocked.  However,
	   * people are reporting that the logout button is popping up
	   * immediately when they lock the screen, and I don't know
	   * why that's happening, so this seems like a reasonable workaround.
	   */
	  if (logout_button_timeout) {
	       XtRemoveTimeOut(logout_button_timeout);
	  }
	  deactivateLogoutButton();
	  logout_button_timeout =
	       XtAppAddTimeOut(app_context,
			       defs.logout_button_timeout * 60 * 1000,
			       activateLogoutButton,
			       (XtPointer) &logout_button_timeout);
     }
     else {
#ifdef LOGHOST
	  log_string("Button disabled.\n");
#endif
     }

     StartupFloat();
}     


static void SaveScreen()
{
     static String rootTranslations =
	  "<Key>:		RemoveRoot()\n\
           <BtnDown>,<BtnUp>:	RemoveRoot()\n\
           <Visible>:		RaiseRoot()\n\
           <Map>:		RaiseRoot()\n";
     XtTranslations trans = (XtTranslations) NULL;

     if (! trans)
	  trans = XtParseTranslationTable(rootTranslations);
     XtOverrideTranslations(root_widget, trans);
     XtOverrideTranslations(root_shell, trans);
     StartupFloat();
}





static void PasswordTimeout()
{
     char *str = "Timeout";
     Cardinal num = 1;
     GetPassword(root_shell, (XEvent *) NULL, &str, &num);
}




/* The state machine's states are defined in xsaver.h */



/* ARGSUSED */
void GetPassword(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
     static char password[MAXPASSWORD];
     static char *ptr;
     static String gettingPasswordTranslations =
	  "<Key>Return:		GetPassword(Done)\n\
	   <Key>:		GetPassword(Reading)\n\
           <Visible>:		RaiseRoot()\n\
           <Map>:		RaiseRoot()\n\
           <Motion>:		PointerMoved()\n";
     static XtTranslations gettingPasswordTrans = (XtTranslations) NULL;
     static Widget prompt;
     static int state = DOING_NOTHING;
     Arg arglist[2];
     int i = 0;
     
     if (! gettingPasswordTrans)
	  gettingPasswordTrans =
	       XtParseTranslationTable(gettingPasswordTranslations);
     if (**params == 'L') {
	  DeactivateFloat();
	  ptr = password;
	  state = GETTING_LOCKED_PASSWORD;
	  prompt = PasswordWindow(state);
	  XtSetArg(arglist[i], XtNtranslations, gettingPasswordTrans);	i++;
	  XtSetValues(root_shell, arglist, i);
	  XtSetValues(root_widget, arglist, i);				i = 0;
	  XtMapWidget(prompt);
	  password_timeout = XtAppAddTimeOut(app_context,
					     PASSWORD_TIMEOUT * 1000, 
					     PasswordTimeout, NULL);
     }
     else if (**params == 'I') {
	  ptr = password;
	  state = GETTING_FIRST_INITIAL_PASSWORD;
	  prompt = PasswordWindow(state);
	  XtOverrideTranslations(root_widget, gettingPasswordTrans);
	  XtOverrideTranslations(root_shell, gettingPasswordTrans);
	  XtMapWidget(prompt);
	  password_timeout = XtAppAddTimeOut(app_context,
					     PASSWORD_TIMEOUT * 1000, 
					     PasswordTimeout, NULL);
     }
     else if (**params == 'R' && state != SHOWING_CAPSLOCK_WARNING) {
	  XtRemoveTimeOut(password_timeout);
	  password_timeout = XtAppAddTimeOut(app_context,
					     PASSWORD_TIMEOUT * 1000, 
					     PasswordTimeout, NULL);
	  if (ptr - password < MAXPASSWORD - 1) {
	       char key_buf[5];
	       int num_read;
	       
	       num_read = XLookupString(&event->xkey, key_buf, 5,
					(KeySym *) NULL,
					(XComposeStatus *) NULL);
	       if (num_read == 1) {
		    switch (*key_buf) {
		    case '\025':
			 /* Check for ctrl-u first -- ctrl-u clears the */
			 /* entered password */
			 ptr = password;
			 *ptr = '\0';
			 break;
		    case '\010':
		    case '\177':
			 /* ctrl-h and ctrl-? delete a character */
			 if (ptr - password > 0) {
			      ptr--;
			      *ptr = '\0';
			 }
			 break;
		    default:
			 *ptr = *key_buf;
			 ptr++;
		    }
	       }
	  }
     }
     else if (**params == 'D') {
	  XtRemoveTimeOut(password_timeout);
#if 0
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to unmap prompt widget 0x%x (window id 0x%x) in GetPassword.\n",
		       prompt, XtWindow(prompt));
	  }
	  XtUnmapWidget(prompt);
#endif
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to destroy prompt widget 0x%x (window id 0x%x) in GetPassword.\n",
		       prompt, XtWindow(prompt));
	  }
	  XtDestroyWidget(prompt);
	  if (state == SHOWING_CAPSLOCK_WARNING) {
	       ReallyLockScreen();
	  }
	  else {
	       *ptr = '\0';
	       if (state == GETTING_LOCKED_PASSWORD) {
		    state = DOING_NOTHING;
		    if (correct_password(password)) {
			 RemoveRoot(root_shell, (XEvent *) NULL, (String *) NULL,
				    (Cardinal *) NULL);
			 bzero(password, strlen(password));
		    }
		    else {
			 int any_uppers = 0, any_lowers = 0;
			 char *c;
			 
			 /* There must be at least one upper */
			 /* and no lowers! */
			 c = password;
			 while (*c) {
			      any_uppers |= (isupper(*c));
			      any_lowers |= (islower(*c));
			      c++;
			 }
			 bzero(password, strlen(password));
			 if (any_uppers && (!any_lowers)) {
			      state = SHOWING_CAPSLOCK_WARNING;
			      prompt = PasswordWindow(state);
			      XtMapWidget(prompt);
			      password_timeout = XtAppAddTimeOut(app_context,
								 WARNING_TIMEOUT * 1000, 
								 PasswordTimeout, NULL);
			 } 
			 else {
			      XtOverrideTranslations(root_widget, lockedTrans);
			      XtOverrideTranslations(root_shell, lockedTrans);
			      ActivateFloat();
			 }
		    }
	       }
	       else if (state == GETTING_FIRST_INITIAL_PASSWORD) {
		    ptr = password;
		    if (*ptr) {
			 state = GETTING_SECOND_INITIAL_PASSWORD;
			 XtFree(defs.key);
			 defs.key = my_malloc(strlen(password) + 1,
					      "password");
			 strcpy(defs.key, password);
			 bzero(password, strlen(password));
			 prompt = PasswordWindow(state);
			 XtMapWidget(prompt);
		    }
		    else { /* User just hit return.  That's a no-no, so ask */
			 /* for a password again */
			 char *param = "Initial";
			 Cardinal num = 1;
			 
			 XBell(dpy, 100);
			 state = DOING_NOTHING;
			 GetPassword(root_shell, (XEvent *) NULL, &param, &num);
		    }
	       }
	       else if (state == GETTING_SECOND_INITIAL_PASSWORD) {
		    if (! strcmp(password, defs.key)) {
			 install_password();
			 bzero(password, strlen(password));
		    ReallyLockScreen();
		    }
		    else {
			 char *param = "Initial";
			 Cardinal num = 1;
			 
			 bzero(password, strlen(password));
			 bzero(defs.key, strlen(defs.key));
			 state = DOING_NOTHING;
			 GetPassword(root_shell, (XEvent *) NULL, &param, &num);
		    }
	       }
	  }
     }
     else if (**params == 'T') {
#if 0
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to unmap prompt widget 0x%x (window id 0x%x) in GetPassword.\n",
		       prompt, XtWindow(prompt));
	  }
	  XtUnmapWidget(prompt);
#endif
	  if (debug_file) {
	       fprintf(debug_file,
		       "About to destroy prompt widget 0x%x (window id 0x%x) in GetPassword.\n",
		       prompt, XtWindow(prompt));
	  }
	  XtDestroyWidget(prompt);
	  bzero(password, ptr - password);
	  if (state == GETTING_LOCKED_PASSWORD || state == SHOWING_CAPSLOCK_WARNING)
	       ReallyLockScreen();
	  else if (state == GETTING_FIRST_INITIAL_PASSWORD)
	       RemoveRoot((Widget) NULL, (XEvent *) NULL, (String *) NULL,
			  (Cardinal *) NULL);
	  else if (state == GETTING_SECOND_INITIAL_PASSWORD)
	       RemoveRoot((Widget) NULL, (XEvent *) NULL, (String *) NULL,
			  (Cardinal *) NULL);
     }
}
     
            


static void ActivateClock()
{
     struct timeval tim;

     gettimeofday(&tim, (struct timezone *) NULL);
     xtimes.start = tim.tv_sec;
     xtimes.current = xtimes.start + 1; /* don't ask; if you can't figure */
				      /* out why I'm adding 1 to this,  */
				      /* you don't want to know :-)     */
}     





void change_timeout(new_val)
char *new_val;
{
     if (! new_val)
	  return;
     
     if (*new_val)
	  defs.timeout = atoi(new_val);
     else
	  defs.timeout = 0;
#ifdef MAXTIMEOUT
     if ((defs.timeout > MAXTIMEOUT) || (defs.timeout == 0)) {
	  fprintf(stderr, "%s: Timeout cannot be greater than %d minutes.\n",
		  whoami, MAXTIMEOUT);
	  defs.timeout = MAXTIMEOUT;
     }
#endif
}




void change_lock(new_val)
char *new_val;
{
     if (! new_val)
	  return;

     XtFree(defs.lock_command);
     if (*new_val) {
	  defs.lock_command = my_malloc(strlen(new_val) + 1, "lock command");
	  strcpy(defs.lock_command, new_val);
     }
     else {
	  defs.lock_command = 0;
     }
}




void change_unlock(new_val)
char *new_val;
{
     if (! new_val)
	  return;
     
     XtFree(defs.unlock_command);
     if (*new_val) {
	  defs.unlock_command =
	       my_malloc(strlen(new_val) + 1, "unlock command");
	  strcpy(defs.unlock_command, new_val);
     }
     else {
	  defs.unlock_command = 0;
     }
}



/* ARGSUSED */
void RaiseRoot(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
     XRaiseWindow(dpy, XtWindow(root_shell));
}



/* ARGSUSED */
void RemoveRoot(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
     is_locked = False;
#ifdef _IBMR2
     if (defs.disable_hotkey)
	  enable_hft_hotkey();
#endif
     unlock_command();
     DeactivateFloat();
     if (logout_button_timeout) {
	  XtRemoveTimeOut(logout_button_timeout);
	  logout_button_timeout = 0;
     }
     if (hide_cursor_timeout) {
	  XtRemoveTimeOut(hide_cursor_timeout);
	  hide_cursor_timeout = 0;
     }
     deactivateLogoutButton();
     if (debug_file) {
	  fprintf(debug_file,
		  "About to unmap root widget 0x%x (window id 0x%x) in RemoveRoot.\n",
		       root_shell, XtWindow(root_shell));
     }
     XtUnmapWidget(root_shell);
     XUngrabPointer(dpy, CurrentTime);
     XUngrabKeyboard(dpy, CurrentTime);
     if (defs.disable_x_screensaver) {
	  XGetScreenSaver(dpy, &saver2.timeout, &saver2.interval,
			  &saver2.prefer_blanking, &saver2.allow_exposures);
	  if ((saver2.timeout == 0) && (saver.interval == saver2.interval) &&
	      (saver.prefer_blanking == saver2.prefer_blanking) &&
	      (saver.allow_exposures == saver2.allow_exposures))
	       /* The saver hasn't been changed underneath us */
	       XSetScreenSaver(dpy, saver.timeout, saver.interval,
			       saver.prefer_blanking, saver.allow_exposures);
     }
     if (! defs.display_icon)
	  exitCallback(NULL, NULL, NULL);
}




static void lock_command()
{
     did_lock_command = 1;
     if ((! lock_command_pid) && defs.lock_command) {
	  if ((lock_command_pid = fork()) == -1) {
	       fprintf(stderr, "%s: ", whoami);
	       perror("executing lock command");
	       lock_command_pid = 0;
	  }
	  else if (! lock_command_pid) {
#if (defined(sun) && defined(__svr4__)) || defined(sgi) || defined(__hpux) || defined(linux)
	       setsid();
#else
	       setpgrp(getpid(), getpid());
#endif
	       XtCloseDisplay(dpy);
#ifdef PASSWORD_PORT
	       close_password_port();
#endif
	       system(defs.lock_command);
	       exit(0);
	  }
     }
}




void unlock_command()
{
     if (! did_lock_command)
	  return;
     did_lock_command = 0;
     if (lock_command_pid) {
#if (defined(sun) && defined(__svr4__)) || defined(NO_KILLPG) || defined(__hpux)
	  kill(-lock_command_pid, SIGTERM);
#else
	  killpg(lock_command_pid, SIGTERM);
#endif
	  lock_command_pid = 0;
     }
     if (lock_flag && defs.unlock_command) {
	  int pid;

	  if ((pid = fork()) == -1) {
	       fprintf(stderr, "%s: ", whoami);
	       perror("executing unlock command");
	  }
	  else if (! pid) {
	       XtCloseDisplay(dpy);
#ifdef PASSWORD_PORT
	       close_password_port();
#endif
	       system(defs.unlock_command);
	       exit(0);
	  }
     }
}



