/* xdragon interface to host X11 server

Copyright (C) 2015 Ken D. Olum

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "xdragon.h"
#include <windows.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>

int key_delay = 0;

/* We use two connections to the X server.  One is for input, i.e.,
   the hotkey and focus changes.  This is also the control connection
   for the record extension.  There is a thread waiting on this
   connection, so it can't be used for exposing windows and simulating
   keystrokes, because such requests require waiting for replies and
   there would be two threads waiting on the same connection.  So
   those things are sent to the output connection */

Display *outputDisplay;		/* X display for simulating keys */
Display *inputDisplay;		/* X display for receiving events */
Display *recordDisplay;	    /* Data connection for record extension */

Window vmWindow = None; /* X window that is running the VM running Dragon */
bool vmFocus = False;	 /* Does the VM X window have focus? */

KeySym hotkeysym = XK_KP_Add;
KeyCode hotkeycode;

/* Open connections to X server on given display, normally host:0, where
   hosts the name of the host machine on which we are running in a VM */
xOpen(char *displayName) {
  int event_base, error_base, major_version, minor_version;

  outputDisplay = XOpenDisplay(displayName);
  if (outputDisplay == NULL) {
    fprintf(stderr, "Can't open display");
    exit(2);}

  /* Check that XTest is supported */
  if (!XTestQueryExtension(outputDisplay, &event_base, &error_base,
			   &major_version, &minor_version)){
        XCloseDisplay(outputDisplay);
	fprintf(stderr, "X server does not support the Xtest extension\n");
	exit(2);}

  /* Synchronize the output display, so simulated keys take effect right
     away.  If we didn't do this, we would need XFlush to prevent keys from
     being stuck down */
  XSynchronize(outputDisplay,True);

  inputDisplay = XOpenDisplay(displayName);
  if (inputDisplay == NULL) {
    fprintf(stderr, "Can't open display for event handling");
    exit(2);}

  /* To use the record extension, the control display needs to be
     synchronized.  */
  XSynchronize(inputDisplay,True);
  
  recordDisplay = XOpenDisplay(displayName);
  if (recordDisplay == NULL) {
    fprintf(stderr, "Can't open display for record extension data");
    exit(2);}

  setup_translation();
  xStart();
}

/* Tell user about the VM window */
printStartWindow() {
  XTextProperty vmWindowName;
  char **windowName_list;
  int windowName_count;

  if (vmWindow == None) {
    fprintf(stderr, "No current window\n"); }
  else if (!XGetWMName(outputDisplay, vmWindow, &vmWindowName)) {
    fprintf(stderr, "Can't get window name\n"); }
  else if (!XTextPropertyToStringList(&vmWindowName,
				      &windowName_list, &windowName_count)) {
    fprintf(stderr, "Can't decode window name\n"); }
  else {
    fprintf(stderr,"xdragon started in window %lx: '%s'\n",
	    vmWindow, *windowName_list);
    XFreeStringList(windowName_list);
    }}

/* Called at startup.  We assume that the current X window is the VM */
xStart() {
  int revertTo;

  XGetInputFocus(inputDisplay, &vmWindow, &revertTo);
  if (vmWindow == PointerRoot) {
    fprintf(stderr, "PointerRoot not handled\n");
    vmWindow = None;}
  printStartWindow();
  vmFocus = True;
  /* Let us know whenever focus changes involving this window */
  XSelectInput(inputDisplay, vmWindow, FocusChangeMask);
  }

extern bool recycle_wait; /* Set if we're waiting to get an event we simulated */
extern KeyCode recycle_kc; /* KeyCode that we're waiting for */
extern bool recycle_ud;    /* True if key press, False if release */
int recycle_timeout = 20; /* Milliseconds to wait for fake event to be seen by record*/

/* Wait for record to intercept the fake event that we have just sent */
/* This is called in the thread that has just sent the X key */
wait_for_recycle() {
  /* This always waits for the timeout, instead of continuing as soon as the
     other thread has seen the recycled event.  On the other hand, perhaps
     this delay between simulated keystrokes is useful.  */
  Sleep(recycle_timeout);
  if (recycle_wait) {	  /* Timeout without getting recycled event */
    fprintf(stderr, "Failed to see recycled key event, kc %d, ud %d\n", recycle_kc, recycle_ud);
    recycle_wait = 0;
  }}

/* Press or release given Windows virtual key */
xSendKey(char vk, char is_press) {
  KeySym ks;
  KeyCode kc;

  ks = translate_key(vk);	/* Translate from VK to KeySym */
  /* Don't push hotkey to avoid infinite loops. */
  if ((ks != NoSymbol) && (ks != hotkeysym)) {
    if (kc = XKeysymToKeycode(outputDisplay, ks)){
      /* Avoid infinite loop */
      if (vmFocus) {
	debug("Not sending key to VM window\n"); }
      else {
	// debug("code %d, is_press = %d\n", kc, is_press);
	/* Get ready to see recycled key in other thread */
	recycle_kc = kc; recycle_ud = is_press; recycle_wait = 1;
	XTestFakeKeyEvent(outputDisplay, kc, is_press, key_delay);
	/* We would need XFlush if we were not in synchronous mode */
	wait_for_recycle(); /* Wait for record to see it */
      }}
    else debug("No keycode for KeySym %d\n", (int)ks);
    }
}

/* Expose VM X window*/
xShow() {
  if (vmWindow == None) debug("unknown VM window\n")
    else XMapRaised(outputDisplay, vmWindow);
}

/* Bury  VM X window*/
xBury() {
  if (vmWindow == None) debug("unknown VM window\n")
    else XLowerWindow(outputDisplay, vmWindow);
}

/* Setup to intercept hotkey, making its events delivered only to this client */
setupHotkey() {
  hotkeycode = XKeysymToKeycode(inputDisplay, hotkeysym);
  if (!hotkeycode) {
    fprintf(stderr, "No keycode for hot KeySym %d\n", (int)hotkeysym);exit(3);}
  /* Should we do this multiple times if there are multiple screens? */
  XGrabKey(inputDisplay, hotkeycode, 0, XDefaultRootWindow(inputDisplay), 
	   False, GrabModeAsync, GrabModeAsync);
}

/* Hotkey thread top level.  Wait for events on display and process hotkeys */
DWORD WINAPI eventTopLevel() {
  XEvent ev;

  debug("Event thread starting\n");

  while (1) {
    XNextEvent(inputDisplay, &ev);
    switch (ev.type) {
    case KeyPress:
    case KeyRelease:
      if (ev.xkey.keycode == hotkeycode) {
	if (ev.type == KeyPress) doSendInput(XDRAGON_HOTKEY_VK);}
      else fprintf(stderr, "Unexpected X key event\n");
      break;
    case FocusIn:
      if (ev.xfocus.window == vmWindow) vmFocus = True;
      break;
    case FocusOut:
      if (ev.xfocus.window == vmWindow) vmFocus = False;
      break;
    /* Grabbing changes the keyboard mapping and yields these events */
    case MappingNotify:
      break;
    default: fprintf(stderr,"Unexpected X event of type %d\n", ev.type);
    }}}


/* The top level function for the record thread.  It is defined here
   so that we can use definitions from Windows.h, which is not used in
   record.c. */
DWORD WINAPI recordTopLevel() {
  doRecord (); }
