/*
* Modified by Jim Diamond ([email protected]) April 24, 2010
* to reset error_status to 0 at the beginning of open_channel().
*
* Also replaced 0xfff with KeyPressMask and KeyReleaseMask (as
* appropriate) in sendx_channel(); otherwise (at least in Slackware64 13.0)
* the events also get sent to the terminal running pdfopen; this is a Bad
* Thing.
*
* Also added set_focus() and reset_focus() functions.
*
* January 27, 2012 note:
* NOTE: to make reset_focus() work, at least under FVWM2 on
* Slackware64-13.37 when using AR9, I needed to call XOpenDisplay()
* again (since close_channel() has generally been called since
* set_focus() was called),  I needed to sleep a bit (perhaps < 1 second
* is fine, and perhaps other systems might need more, I dunno), and I
* needed to call XFlush().  If someone who understands why the code
* here opens and closes the display for every sendx_<something>() can
* explain why that makes sense, or why I need the sleep() and the
* XFlush(), I'd be happy to be enlightened.
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>

#include "sendx.h"

static char * display_name = NULL;
static Display * display;
static Window root;
static Window window = None;
static XKeyEvent event;

static char error_message[256];
static int error_status = 0;


static void
throw_exception3(const char * msg, const char * s, int i)
{
   sprintf(error_message, msg, s, i);
   error_status = 1;
}


static void
throw_exception3s(const char * msg1, const char * msg2, const char * msg3)
{
   sprintf(error_message, msg1, msg2, msg3);
   error_status = 1;
}


static void
throw_exception(const char * msg)
{
   strncpy(error_message, msg, 256);
   error_status = 1;
}



/*
* Added for window managers like swm and tvtwm that follow solbourne's
* virtual root window concept
*/
static Window
GetRootWindow(Display * disp, int scrn)
{
   Atom __SWM_VROOT = None;
   Window root, rootReturn, parentReturn, * children;
   unsigned int numChildren;
   unsigned i;

   root = RootWindow(disp, scrn);

   /*
    * see if there is a virtual root
    */
   __SWM_VROOT = XInternAtom(disp, "__SWM_VROOT", False);
   XQueryTree(disp, root, &rootReturn, &parentReturn, &children, &numChildren);

   for (i = 0; i < numChildren; i++)
   {
       Atom actual_type;
       int actual_format;
       unsigned long nitems, bytesafter;
       unsigned char * prop_return = NULL;

       if (XGetWindowProperty(disp, children[i], __SWM_VROOT, 0, 1,
                              False, XA_WINDOW, &actual_type, &actual_format,
                              &nitems, &bytesafter, &prop_return)
            == Success && prop_return)
       {
           root = *(Window *)prop_return;
           break;
       }
   }

   if (children)
       XFree((char *)children);

   return (root);
}



/*
* [These functions are from the file "dsimple.c" used with xwininfo.]
*
* Written by Mark Lillibridge.  Last updated 7/1/87
*
* Window_With_Name: routine to locate a window with a given name on a
*      display.  If no window with the given name is found, 0 is returned.
*      If more than one window has the given name, the first one found
*      will be returned.  Only top and its subwindows are looked at.
*      Normally, top should be the Root Window.
*/

static Window
Window_With_Name(Display * dpy, Window top, const char * name)
{
   Window * children, dummy;
   unsigned int nchildren;
   unsigned i;
   Window w = 0;
   char * window_name;

   if (XFetchName(dpy, top, &window_name) && !strcmp(window_name, name))
       return (top);

   if (!XQueryTree(dpy, top, &dummy, &dummy, &children, &nchildren))
       return (0);

   for (i = 0; i < nchildren; i++)
   {
       w = Window_With_Name(dpy, children[i], name);
       if (w)
           break;
   }

   if (children)
       XFree((char *)children);

   return (w);
}



static int
open_channel(const char * wname)
{
   /*
    * display_name = ":0.0";
    */

   error_status = 0;

   if ((display = XOpenDisplay(display_name)) == NULL)
   {
       throw_exception("can't open display");
       return 1;
   }

   if ((root = GetRootWindow(display, DefaultScreen(display))) == 0)
   {
       throw_exception("Cannot get DefaultScreen");
       return 1;
   }

   if ((wname[0] == '\0') && (window != None))
   {
   }                                  /* take selected window */
   else if (wname[0] != '\0')
   {
       if ((window = Window_With_Name(display, root, wname)) == None)
       {
           throw_exception3s("Display %s: can't open window named \"%s\"",
                             XDisplayName(display_name), wname);
           return 1;
       }
   }
   else
   {
       throw_exception3("bad condition in %s at line %d", __FILE__, __LINE__);
       return 1;
   }

   event.type = KeyPress;
   event.serial = 0;
   event.send_event = False;
   event.display = display;
   event.x = event.y = event.x_root = event.y_root = 0;
   event.time = CurrentTime;
   event.same_screen = True;
   event.subwindow = None;
   event.window = window;
   event.root = root;

   return 0;
}



static void
close_channel(void)
{
   /*
    * XFlush(display);
    */
   XCloseDisplay(display);
}



static void
sendx_channel(KeySym ks, int km)
{
   /*
    * km: 0=regular, 1=shift, 2=lock, 4=control
    */
   if (ks < 256)
   {
       event.state = isupper((char)ks);
       switch (ks)
       {
         case 0x08:
           ks = XK_BackSpace;
           break;
         case 0x09:
           ks = XK_Tab;
           break;
         case 0x0A:
           ks = XK_Linefeed;
           break;
         case 0x0B:
           ks = XK_Clear;
           break;
         case 0x0D:
           ks = XK_Return;
           break;
         case 0x13:
           ks = XK_Pause;
           break;
         case 0x14:
           ks = XK_Scroll_Lock;
           break;
         case 0x1B:
           ks = XK_Escape;
           break;
       }
   }
   else
       event.state = 0;

   event.type = KeyPress;
   event.state = km;                  // Mod1Mask
   event.keycode = XKeysymToKeycode(display, ks);

   if (XSendEvent(display, window, True, KeyPressMask, (XEvent *)&event) == 0)
       throw_exception("Error in XSendEvent");

   event.type = KeyRelease;
   if (XSendEvent(display, window, True, KeyReleaseMask, (XEvent *)&event)
       == 0)
       throw_exception("Error in XSendEvent");

   return;
}



int
sendx_string(const char * string, const char * window)
{
   const char * p;

   if (open_channel(window))
       return error_status;

   p = string;
   while (*p)
       sendx_channel(*p++, 0);
   close_channel();

   return error_status;
}



int
sendx_token(const char * string, const char * window)
{
   if (open_channel(window))
       return error_status;

   sendx_channel(XStringToKeysym(string), 0);
   close_channel();

   return error_status;
}



int
sendx_alt_token(const char * string, const char * window)
{
   if (open_channel(window))
       return error_status;

   sendx_channel(XStringToKeysym(string), Mod1Mask);
   close_channel();

   return error_status;
}



int
sendx_controlalt_token(const char * string, const char * window)
{
   if (open_channel(window))
       return error_status;

   sendx_channel(XStringToKeysym(string), Mod1Mask | ControlMask);
   close_channel();

   return error_status;
}



int
sendx_control_token(const char * string, const char * window)
{
   if (open_channel(window))
       return error_status;

   sendx_channel(XStringToKeysym(string), ControlMask);
   close_channel();

   return error_status;
}




static Window previous_window;
static int previous_window_set = 0;
static int revert_to;

/*
* Attempt to give focus to the given window.
* Return 0 on success, non-0 on failure.
* Record the previous window for reset_focus() on success.
*/

int
set_focus(const char * wname)
{
   if ((display = XOpenDisplay(display_name)) == NULL)
   {
       throw_exception("can't open display");
       return 1;
   }

   if ((root = GetRootWindow(display, DefaultScreen(display))) == 0)
   {
       throw_exception("Cannot get DefaultScreen");
       return 1;
   }

   (void)XGetInputFocus(display, &previous_window, &revert_to);
   if (XSetInputFocus(display, Window_With_Name(display, root, wname),
       previous_window, CurrentTime))
   {
       previous_window_set = 1;
       return 0;
   }

   throw_exception3s("Display %s: can't focus window named \"%s\"",
                     XDisplayName(display_name), wname);
   return 1;
}



int
reset_focus(void)
{
   if (previous_window_set)
   {
       sleep(1);
       if ((display = XOpenDisplay(display_name)) == NULL)
       {
           throw_exception("can't open display");
           return 1;
       }

       XSetInputFocus(display, previous_window, revert_to, CurrentTime);
       XFlush(display);
       previous_window_set = 0;
       return 0;
   }

   throw_exception("reset_focus() called when previous window not saved");
   return 1;
}



/*
* void PrintKeySyms () { int i; for (i = 32; i < 127; i++) { printf ("%s[%c]
* ", XKeysymToString (i), i); } for (i = 128 + 32; i < 128 + 127; i++) {
* printf ("%s[%c] ", XKeysymToString (i), i); } printf ("\n"); }
*/