/*++ @file

Copyright (c) 2004 - 2019, Intel Corporation. All rights reserved.<BR>
Portions copyright (c) 2008 - 2011, Apple Inc. All rights reserved.<BR>

SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Host.h"

#include <sys/ipc.h>
#include <sys/shm.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/extensions/XShm.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>

#define KEYSYM_LOWER  0
#define KEYSYM_UPPER  1

struct uga_drv_shift_mask {
  unsigned char    shift;
  unsigned char    size;
  unsigned char    csize;
};

#define NBR_KEYS  32
typedef struct {
  EMU_GRAPHICS_WINDOW_PROTOCOL                        GraphicsIo;

  Display                                             *display;
  int                                                 screen; // values for window_size in main
  Window                                              win;
  GC                                                  gc;
  Visual                                              *visual;

  int                                                 depth;
  unsigned int                                        width;
  unsigned int                                        height;
  unsigned int                                        line_bytes;
  unsigned int                                        pixel_shift;
  unsigned char                                       *image_data;

  struct uga_drv_shift_mask                           r, g, b;

  int                                                 use_shm;
  XShmSegmentInfo                                     xshm_info;
  XImage                                              *image;
  char                                                *Title;

  unsigned int                                        key_rd;
  unsigned int                                        key_wr;
  unsigned int                                        key_count;
  EFI_KEY_DATA                                        keys[NBR_KEYS];

  EFI_KEY_STATE                                       KeyState;

  EMU_GRAPHICS_WINDOW_REGISTER_KEY_NOTIFY_CALLBACK    MakeRegisterdKeyCallback;
  EMU_GRAPHICS_WINDOW_REGISTER_KEY_NOTIFY_CALLBACK    BreakRegisterdKeyCallback;
  VOID                                                *RegisterdKeyCallbackContext;

  int                                                 previous_x;
  int                                                 previous_y;
  EFI_SIMPLE_POINTER_STATE                            pointer_state;
  int                                                 pointer_state_changed;
} GRAPHICS_IO_PRIVATE;

void
HandleEvents (
  IN GRAPHICS_IO_PRIVATE  *Drv
  );

void
fill_shift_mask (
  IN  struct uga_drv_shift_mask  *sm,
  IN  unsigned long              mask
  )
{
  sm->shift = 0;
  sm->size  = 0;
  while ((mask & 1) == 0) {
    mask >>= 1;
    sm->shift++;
  }

  while (mask & 1) {
    sm->size++;
    mask >>= 1;
  }

  sm->csize = 8 - sm->size;
}

int
TryCreateShmImage (
  IN  GRAPHICS_IO_PRIVATE  *Drv
  )
{
  Drv->image = XShmCreateImage (
                 Drv->display,
                 Drv->visual,
                 Drv->depth,
                 ZPixmap,
                 NULL,
                 &Drv->xshm_info,
                 Drv->width,
                 Drv->height
                 );
  if (Drv->image == NULL) {
    return 0;
  }

  switch (Drv->image->bitmap_unit) {
    case 32:
      Drv->pixel_shift = 2;
      break;
    case 16:
      Drv->pixel_shift = 1;
      break;
    case 8:
      Drv->pixel_shift = 0;
      break;
  }

  Drv->xshm_info.shmid = shmget (
                           IPC_PRIVATE,
                           Drv->image->bytes_per_line * Drv->image->height,
                           IPC_CREAT | 0777
                           );
  if (Drv->xshm_info.shmid < 0) {
    XDestroyImage (Drv->image);
    return 0;
  }

  Drv->image_data = shmat (Drv->xshm_info.shmid, NULL, 0);
  if (!Drv->image_data) {
    shmctl (Drv->xshm_info.shmid, IPC_RMID, NULL);
    XDestroyImage (Drv->image);
    return 0;
  }

 #ifndef __APPLE__
  //
  // This closes shared memory in real time on OS X. Only closes after folks quit using
  // it on Linux.
  //
  shmctl (Drv->xshm_info.shmid, IPC_RMID, NULL);
 #endif

  Drv->xshm_info.shmaddr = (char *)Drv->image_data;
  Drv->image->data       = (char *)Drv->image_data;

  if (!XShmAttach (Drv->display, &Drv->xshm_info)) {
    shmdt (Drv->image_data);
    XDestroyImage (Drv->image);
    return 0;
  }

  return 1;
}

EFI_STATUS
X11Size (
  IN  EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo,
  IN  UINT32                        Width,
  IN  UINT32                        Height
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;
  XSizeHints           size_hints;

  // Destroy current buffer if created.
  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;
  if (Drv->image != NULL) {
    // Before destroy buffer, need to make sure the buffer available for access.
    XDestroyImage (Drv->image);

    if (Drv->use_shm) {
      shmdt (Drv->image_data);
    }

    Drv->image_data = NULL;
    Drv->image      = NULL;
  }

  Drv->width  = Width;
  Drv->height = Height;
  XResizeWindow (Drv->display, Drv->win, Width, Height);

  // Allocate image.
  if (XShmQueryExtension (Drv->display) && TryCreateShmImage (Drv)) {
    Drv->use_shm = 1;
  } else {
    Drv->use_shm = 0;
    if (Drv->depth > 16) {
      Drv->pixel_shift = 2;
    } else if (Drv->depth > 8) {
      Drv->pixel_shift = 1;
    } else {
      Drv->pixel_shift = 0;
    }

    Drv->image_data = malloc ((Drv->width * Drv->height) << Drv->pixel_shift);
    Drv->image      = XCreateImage (
                        Drv->display,
                        Drv->visual,
                        Drv->depth,
                        ZPixmap,
                        0,
                        (char *)Drv->image_data,
                        Drv->width,
                        Drv->height,
                        8 << Drv->pixel_shift,
                        0
                        );
  }

  Drv->line_bytes = Drv->image->bytes_per_line;

  fill_shift_mask (&Drv->r, Drv->image->red_mask);
  fill_shift_mask (&Drv->g, Drv->image->green_mask);
  fill_shift_mask (&Drv->b, Drv->image->blue_mask);

  // Set WM hints.
  size_hints.flags      = PSize | PMinSize | PMaxSize;
  size_hints.min_width  = size_hints.max_width = size_hints.base_width = Width;
  size_hints.min_height = size_hints.max_height = size_hints.base_height = Height;
  XSetWMNormalHints (Drv->display, Drv->win, &size_hints);

  XMapWindow (Drv->display, Drv->win);
  HandleEvents (Drv);
  return EFI_SUCCESS;
}

void
handleKeyEvent (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  XEvent               *ev,
  IN  BOOLEAN              Make
  )
{
  KeySym        *KeySym;
  EFI_KEY_DATA  KeyData;
  int           KeySymArraySize;

  if (Make) {
    if (Drv->key_count == NBR_KEYS) {
      return;
    }
  }

  // keycode is a physical key on the keyboard
  // KeySym is a mapping of a physical key
  // KeyboardMapping is the array of KeySym for a given keycode. key, shifted key, option key, command key, ...
  //
  // Returns an array of KeySymArraySize of KeySym for the keycode. [0] is lower case, [1] is upper case,
  // [2] and [3] are based on option and command modifiers. The problem we have is command V
  // could be mapped to a crazy Unicode character so the old scheme of returning a string.
  //
  KeySym = XGetKeyboardMapping (Drv->display, ev->xkey.keycode, 1, &KeySymArraySize);

  KeyData.Key.ScanCode           = 0;
  KeyData.Key.UnicodeChar        = 0;
  KeyData.KeyState.KeyShiftState = 0;

  //
  // Skipping EFI_SCROLL_LOCK_ACTIVE & EFI_NUM_LOCK_ACTIVE since they are not on Macs
  //
  if ((ev->xkey.state & LockMask) == 0) {
    Drv->KeyState.KeyToggleState &= ~EFI_CAPS_LOCK_ACTIVE;
  } else {
    if (Make) {
      Drv->KeyState.KeyToggleState |= EFI_CAPS_LOCK_ACTIVE;
    }
  }

  // Skipping EFI_MENU_KEY_PRESSED and EFI_SYS_REQ_PRESSED

  switch (*KeySym) {
    case XK_Control_R:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_RIGHT_CONTROL_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_RIGHT_CONTROL_PRESSED;
      }

      break;
    case XK_Control_L:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_LEFT_CONTROL_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_LEFT_CONTROL_PRESSED;
      }

      break;

    case XK_Shift_R:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_RIGHT_SHIFT_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_RIGHT_SHIFT_PRESSED;
      }

      break;
    case XK_Shift_L:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_LEFT_SHIFT_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_LEFT_SHIFT_PRESSED;
      }

      break;

    case XK_Mode_switch:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_LEFT_ALT_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_LEFT_ALT_PRESSED;
      }

      break;

    case XK_Meta_R:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_RIGHT_LOGO_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_RIGHT_LOGO_PRESSED;
      }

      break;
    case XK_Meta_L:
      if (Make) {
        Drv->KeyState.KeyShiftState |=  EFI_LEFT_LOGO_PRESSED;
      } else {
        Drv->KeyState.KeyShiftState &= ~EFI_LEFT_LOGO_PRESSED;
      }

      break;

    case XK_KP_Home:
    case XK_Home:       KeyData.Key.ScanCode = SCAN_HOME;
      break;

    case XK_KP_End:
    case XK_End:        KeyData.Key.ScanCode = SCAN_END;
      break;

    case XK_KP_Left:
    case XK_Left:       KeyData.Key.ScanCode = SCAN_LEFT;
      break;

    case XK_KP_Right:
    case XK_Right:      KeyData.Key.ScanCode = SCAN_RIGHT;
      break;

    case XK_KP_Up:
    case XK_Up:         KeyData.Key.ScanCode = SCAN_UP;
      break;

    case XK_KP_Down:
    case XK_Down:       KeyData.Key.ScanCode = SCAN_DOWN;
      break;

    case XK_KP_Delete:
    case XK_Delete:       KeyData.Key.ScanCode = SCAN_DELETE;
      break;

    case XK_KP_Insert:
    case XK_Insert:     KeyData.Key.ScanCode = SCAN_INSERT;
      break;

    case XK_KP_Page_Up:
    case XK_Page_Up:    KeyData.Key.ScanCode = SCAN_PAGE_UP;
      break;

    case XK_KP_Page_Down:
    case XK_Page_Down:  KeyData.Key.ScanCode = SCAN_PAGE_DOWN;
      break;

    case XK_Escape:     KeyData.Key.ScanCode = SCAN_ESC;
      break;

    case XK_Pause:      KeyData.Key.ScanCode = SCAN_PAUSE;
      break;

    case XK_KP_F1:
    case XK_F1:   KeyData.Key.ScanCode = SCAN_F1;
      break;

    case XK_KP_F2:
    case XK_F2:   KeyData.Key.ScanCode = SCAN_F2;
      break;

    case XK_KP_F3:
    case XK_F3:   KeyData.Key.ScanCode = SCAN_F3;
      break;

    case XK_KP_F4:
    case XK_F4:   KeyData.Key.ScanCode = SCAN_F4;
      break;

    case XK_F5:   KeyData.Key.ScanCode = SCAN_F5;
      break;
    case XK_F6:   KeyData.Key.ScanCode = SCAN_F6;
      break;
    case XK_F7:   KeyData.Key.ScanCode = SCAN_F7;
      break;

    // Don't map into X11 by default on a Mac
    // System Preferences->Keyboard->Keyboard Shortcuts can be configured
    // to not use higher function keys as shortcuts and the will show up
    // in X11.
    case XK_F8:   KeyData.Key.ScanCode = SCAN_F8;
      break;
    case XK_F9:   KeyData.Key.ScanCode = SCAN_F9;
      break;
    case XK_F10:  KeyData.Key.ScanCode = SCAN_F10;
      break;

    case XK_F11:  KeyData.Key.ScanCode = SCAN_F11;
      break;
    case XK_F12:  KeyData.Key.ScanCode = SCAN_F12;
      break;

    case XK_F13:  KeyData.Key.ScanCode = SCAN_F13;
      break;
    case XK_F14:  KeyData.Key.ScanCode = SCAN_F14;
      break;
    case XK_F15:  KeyData.Key.ScanCode = SCAN_F15;
      break;
    case XK_F16:  KeyData.Key.ScanCode = SCAN_F16;
      break;
    case XK_F17:  KeyData.Key.ScanCode = SCAN_F17;
      break;
    case XK_F18:  KeyData.Key.ScanCode = SCAN_F18;
      break;
    case XK_F19:  KeyData.Key.ScanCode = SCAN_F19;
      break;
    case XK_F20:  KeyData.Key.ScanCode = SCAN_F20;
      break;
    case XK_F21:  KeyData.Key.ScanCode = SCAN_F21;
      break;
    case XK_F22:  KeyData.Key.ScanCode = SCAN_F22;
      break;
    case XK_F23:  KeyData.Key.ScanCode = SCAN_F23;
      break;
    case XK_F24:  KeyData.Key.ScanCode = SCAN_F24;
      break;

    // No mapping in X11
    // case XK_:   KeyData.Key.ScanCode = SCAN_MUTE;            break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_VOLUME_UP;       break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_VOLUME_DOWN;     break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_BRIGHTNESS_UP;   break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_BRIGHTNESS_DOWN; break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_SUSPEND;         break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_HIBERNATE;       break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_TOGGLE_DISPLAY;  break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_RECOVERY;        break;
    // case XK_:   KeyData.Key.ScanCode = SCAN_EJECT;           break;

    case XK_BackSpace:  KeyData.Key.UnicodeChar = 0x0008;
      break;

    case XK_KP_Tab:
    case XK_Tab:        KeyData.Key.UnicodeChar = 0x0009;
      break;

    case XK_Linefeed:   KeyData.Key.UnicodeChar = 0x000a;
      break;

    case XK_KP_Enter:
    case XK_Return:     KeyData.Key.UnicodeChar = 0x000d;
      break;

    case XK_KP_Equal: KeyData.Key.UnicodeChar = L'=';
      break;
    case XK_KP_Multiply: KeyData.Key.UnicodeChar = L'*';
      break;
    case XK_KP_Add: KeyData.Key.UnicodeChar = L'+';
      break;
    case XK_KP_Separator: KeyData.Key.UnicodeChar = L'~';
      break;
    case XK_KP_Subtract: KeyData.Key.UnicodeChar = L'-';
      break;
    case XK_KP_Decimal: KeyData.Key.UnicodeChar = L'.';
      break;
    case XK_KP_Divide: KeyData.Key.UnicodeChar = L'/';
      break;

    case XK_KP_0: KeyData.Key.UnicodeChar = L'0';
      break;
    case XK_KP_1: KeyData.Key.UnicodeChar = L'1';
      break;
    case XK_KP_2: KeyData.Key.UnicodeChar = L'2';
      break;
    case XK_KP_3: KeyData.Key.UnicodeChar = L'3';
      break;
    case XK_KP_4: KeyData.Key.UnicodeChar = L'4';
      break;
    case XK_KP_5: KeyData.Key.UnicodeChar = L'5';
      break;
    case XK_KP_6: KeyData.Key.UnicodeChar = L'6';
      break;
    case XK_KP_7: KeyData.Key.UnicodeChar = L'7';
      break;
    case XK_KP_8: KeyData.Key.UnicodeChar = L'8';
      break;
    case XK_KP_9: KeyData.Key.UnicodeChar = L'9';
      break;

    default:
      ;
  }

  // The global state is our state
  KeyData.KeyState.KeyShiftState  = Drv->KeyState.KeyShiftState;
  KeyData.KeyState.KeyToggleState = Drv->KeyState.KeyToggleState;

  if (*KeySym < XK_BackSpace) {
    if (((Drv->KeyState.KeyShiftState & (EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED)) != 0) ||
        ((Drv->KeyState.KeyToggleState & EFI_CAPS_LOCK_ACTIVE) != 0))
    {
      KeyData.Key.UnicodeChar = (CHAR16)KeySym[KEYSYM_UPPER];

      // Per UEFI spec since we converted the Unicode clear the shift bits we pass up
      KeyData.KeyState.KeyShiftState &= ~(EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED);
    } else {
      KeyData.Key.UnicodeChar = (CHAR16)KeySym[KEYSYM_LOWER];
    }
  } else {
    // XK_BackSpace is the start of XK_MISCELLANY. These are the XK_? keys we process in this file
  }

  if (Make) {
    memcpy (&Drv->keys[Drv->key_wr], &KeyData, sizeof (EFI_KEY_DATA));
    Drv->key_wr = (Drv->key_wr + 1) % NBR_KEYS;
    Drv->key_count++;
    if (Drv->MakeRegisterdKeyCallback != NULL) {
      ReverseGasketUint64Uint64 (Drv->MakeRegisterdKeyCallback, Drv->RegisterdKeyCallbackContext, &KeyData);
    }
  } else {
    if (Drv->BreakRegisterdKeyCallback != NULL) {
      ReverseGasketUint64Uint64 (Drv->BreakRegisterdKeyCallback, Drv->RegisterdKeyCallbackContext, &KeyData);
    }
  }
}

void
handleMouseMoved (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  XEvent               *ev
  )
{
  if (ev->xmotion.x != Drv->previous_x) {
    Drv->pointer_state.RelativeMovementX += (ev->xmotion.x - Drv->previous_x);
    Drv->previous_x                       = ev->xmotion.x;
    Drv->pointer_state_changed            = 1;
  }

  if (ev->xmotion.y != Drv->previous_y) {
    Drv->pointer_state.RelativeMovementY += (ev->xmotion.y - Drv->previous_y);
    Drv->previous_y                       = ev->xmotion.y;
    Drv->pointer_state_changed            = 1;
  }

  Drv->pointer_state.RelativeMovementZ = 0;
}

void
handleMouseDown (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  XEvent               *ev,
  IN  BOOLEAN              Pressed
  )
{
  if (ev->xbutton.button == Button1) {
    Drv->pointer_state_changed    = (Drv->pointer_state.LeftButton != Pressed);
    Drv->pointer_state.LeftButton = Pressed;
  }

  if ( ev->xbutton.button == Button2 ) {
    Drv->pointer_state_changed     = (Drv->pointer_state.RightButton != Pressed);
    Drv->pointer_state.RightButton = Pressed;
  }
}

void
Redraw (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  UINTN                X,
  IN  UINTN                Y,
  IN  UINTN                Width,
  IN  UINTN                Height
  )
{
  if (Drv->use_shm) {
    XShmPutImage (
      Drv->display,
      Drv->win,
      Drv->gc,
      Drv->image,
      X,
      Y,
      X,
      Y,
      Width,
      Height,
      False
      );
  } else {
    XPutImage (
      Drv->display,
      Drv->win,
      Drv->gc,
      Drv->image,
      X,
      Y,
      X,
      Y,
      Width,
      Height
      );
  }

  XFlush (Drv->display);
}

void
HandleEvent (
  GRAPHICS_IO_PRIVATE  *Drv,
  XEvent               *ev
  )
{
  switch (ev->type) {
    case Expose:
      Redraw (
        Drv,
        ev->xexpose.x,
        ev->xexpose.y,
        ev->xexpose.width,
        ev->xexpose.height
        );
      break;
    case GraphicsExpose:
      Redraw (
        Drv,
        ev->xgraphicsexpose.x,
        ev->xgraphicsexpose.y,
        ev->xgraphicsexpose.width,
        ev->xgraphicsexpose.height
        );
      break;
    case KeyPress:
      handleKeyEvent (Drv, ev, TRUE);
      break;
    case KeyRelease:
      handleKeyEvent (Drv, ev, FALSE);
      break;
    case MappingNotify:
      XRefreshKeyboardMapping (&ev->xmapping);
      break;
    case MotionNotify:
      handleMouseMoved (Drv, ev);
      break;
    case ButtonPress:
      handleMouseDown (Drv, ev, TRUE);
      break;
    case ButtonRelease:
      handleMouseDown (Drv, ev, FALSE);
      break;
 #if 0
    case DestroyNotify:
      XCloseDisplay (Drv->display);
      exit (1);
      break;
 #endif
    case NoExpose:
    default:
      break;
  }
}

void
HandleEvents (
  IN  GRAPHICS_IO_PRIVATE  *Drv
  )
{
  XEvent  ev;

  while (XPending (Drv->display) != 0) {
    XNextEvent (Drv->display, &ev);
    HandleEvent (Drv, &ev);
  }
}

unsigned long
X11PixelToColor (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  EFI_UGA_PIXEL        pixel
  )
{
  return ((pixel.Red   >> Drv->r.csize) << Drv->r.shift)
         | ((pixel.Green >> Drv->g.csize) << Drv->g.shift)
         | ((pixel.Blue  >> Drv->b.csize) << Drv->b.shift);
}

EFI_UGA_PIXEL
X11ColorToPixel (
  IN  GRAPHICS_IO_PRIVATE  *Drv,
  IN  unsigned long        val
  )
{
  EFI_UGA_PIXEL  Pixel;

  memset (&Pixel, 0, sizeof (EFI_UGA_PIXEL));

  // Truncation not an issue since X11 and EFI are both using 8 bits per color
  Pixel.Red   =   (val >> Drv->r.shift) << Drv->r.csize;
  Pixel.Green = (val >> Drv->g.shift) << Drv->g.csize;
  Pixel.Blue  =  (val >> Drv->b.shift) << Drv->b.csize;

  return Pixel;
}

EFI_STATUS
X11CheckKey (
  IN  EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  HandleEvents (Drv);

  if (Drv->key_count != 0) {
    return EFI_SUCCESS;
  }

  return EFI_NOT_READY;
}

EFI_STATUS
X11GetKey (
  IN  EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo,
  IN  EFI_KEY_DATA                  *KeyData
  )
{
  EFI_STATUS           EfiStatus;
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  EfiStatus = X11CheckKey (GraphicsIo);
  if (EFI_ERROR (EfiStatus)) {
    return EfiStatus;
  }

  CopyMem (KeyData, &Drv->keys[Drv->key_rd], sizeof (EFI_KEY_DATA));
  Drv->key_rd = (Drv->key_rd + 1) % NBR_KEYS;
  Drv->key_count--;

  return EFI_SUCCESS;
}

EFI_STATUS
X11KeySetState (
  IN EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo,
  IN EFI_KEY_TOGGLE_STATE          *KeyToggleState
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  if (*KeyToggleState & EFI_CAPS_LOCK_ACTIVE) {
    if ((Drv->KeyState.KeyToggleState & EFI_CAPS_LOCK_ACTIVE) == 0) {
      //
      // We could create an XKeyEvent and send a XK_Caps_Lock to
      // the UGA/GOP Window
      //
    }
  }

  Drv->KeyState.KeyToggleState = *KeyToggleState;
  return EFI_SUCCESS;
}

EFI_STATUS
X11RegisterKeyNotify (
  IN EMU_GRAPHICS_WINDOW_PROTOCOL                      *GraphicsIo,
  IN EMU_GRAPHICS_WINDOW_REGISTER_KEY_NOTIFY_CALLBACK  MakeCallBack,
  IN EMU_GRAPHICS_WINDOW_REGISTER_KEY_NOTIFY_CALLBACK  BreakCallBack,
  IN VOID                                              *Context
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  Drv->MakeRegisterdKeyCallback    = MakeCallBack;
  Drv->BreakRegisterdKeyCallback   = BreakCallBack;
  Drv->RegisterdKeyCallbackContext = Context;

  return EFI_SUCCESS;
}

EFI_STATUS
X11Blt (
  IN EMU_GRAPHICS_WINDOW_PROTOCOL     *GraphicsIo,
  IN  EFI_UGA_PIXEL                   *BltBuffer OPTIONAL,
  IN  EFI_UGA_BLT_OPERATION           BltOperation,
  IN  EMU_GRAPHICS_WINDOWS__BLT_ARGS  *Args
  )
{
  GRAPHICS_IO_PRIVATE  *Private;
  UINTN                DstY;
  UINTN                SrcY;
  UINTN                DstX;
  UINTN                SrcX;
  UINTN                Index;
  EFI_UGA_PIXEL        *Blt;
  UINT8                *Dst;
  UINT8                *Src;
  UINTN                Nbr;
  unsigned long        Color;
  XEvent               ev;

  Private = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  //
  //  Check bounds
  //
  if (  (BltOperation == EfiUgaVideoToBltBuffer)
     || (BltOperation == EfiUgaVideoToVideo))
  {
    //
    // Source is Video.
    //
    if (Args->SourceY + Args->Height > Private->height) {
      return EFI_INVALID_PARAMETER;
    }

    if (Args->SourceX + Args->Width > Private->width) {
      return EFI_INVALID_PARAMETER;
    }
  }

  if (  (BltOperation == EfiUgaBltBufferToVideo)
     || (BltOperation == EfiUgaVideoToVideo)
     || (BltOperation == EfiUgaVideoFill))
  {
    //
    // Destination is Video
    //
    if (Args->DestinationY + Args->Height > Private->height) {
      return EFI_INVALID_PARAMETER;
    }

    if (Args->DestinationX + Args->Width > Private->width) {
      return EFI_INVALID_PARAMETER;
    }
  }

  switch (BltOperation) {
    case EfiUgaVideoToBltBuffer:
      Blt          = (EFI_UGA_PIXEL *)((UINT8 *)BltBuffer + (Args->DestinationY * Args->Delta) + Args->DestinationX * sizeof (EFI_UGA_PIXEL));
      Args->Delta -= Args->Width * sizeof (EFI_UGA_PIXEL);
      for (SrcY = Args->SourceY; SrcY < (Args->Height + Args->SourceY); SrcY++) {
        for (SrcX = Args->SourceX; SrcX < (Args->Width + Args->SourceX); SrcX++) {
          *Blt++ = X11ColorToPixel (Private, XGetPixel (Private->image, SrcX, SrcY));
        }

        Blt = (EFI_UGA_PIXEL *)((UINT8 *)Blt + Args->Delta);
      }

      break;
    case EfiUgaBltBufferToVideo:
      Blt          = (EFI_UGA_PIXEL *)((UINT8 *)BltBuffer + (Args->SourceY * Args->Delta) + Args->SourceX * sizeof (EFI_UGA_PIXEL));
      Args->Delta -= Args->Width * sizeof (EFI_UGA_PIXEL);
      for (DstY = Args->DestinationY; DstY < (Args->Height + Args->DestinationY); DstY++) {
        for (DstX = Args->DestinationX; DstX < (Args->Width + Args->DestinationX); DstX++) {
          XPutPixel (Private->image, DstX, DstY, X11PixelToColor (Private, *Blt));
          Blt++;
        }

        Blt = (EFI_UGA_PIXEL *)((UINT8 *)Blt + Args->Delta);
      }

      break;
    case EfiUgaVideoToVideo:
      Dst = Private->image_data + (Args->DestinationX << Private->pixel_shift)
            + Args->DestinationY * Private->line_bytes;
      Src = Private->image_data + (Args->SourceX << Private->pixel_shift)
            + Args->SourceY * Private->line_bytes;
      Nbr = Args->Width << Private->pixel_shift;
      if (Args->DestinationY < Args->SourceY) {
        for (Index = 0; Index < Args->Height; Index++) {
          memcpy (Dst, Src, Nbr);
          Dst += Private->line_bytes;
          Src += Private->line_bytes;
        }
      } else {
        Dst += (Args->Height - 1) * Private->line_bytes;
        Src += (Args->Height - 1) * Private->line_bytes;
        for (Index = 0; Index < Args->Height; Index++) {
          //
          // Source and Destination Y may be equal, therefore Dst and Src may
          // overlap.
          //
          memmove (Dst, Src, Nbr);
          Dst -= Private->line_bytes;
          Src -= Private->line_bytes;
        }
      }

      break;
    case EfiUgaVideoFill:
      Color = X11PixelToColor (Private, *BltBuffer);
      for (DstY = Args->DestinationY; DstY < (Args->Height + Args->DestinationY); DstY++) {
        for (DstX = Args->DestinationX; DstX < (Args->Width + Args->DestinationX); DstX++) {
          XPutPixel (Private->image, DstX, DstY, Color);
        }
      }

      break;
    default:
      return EFI_INVALID_PARAMETER;
  }

  //
  //  Refresh screen.
  //
  switch (BltOperation) {
    case EfiUgaVideoToVideo:
      XCopyArea (
        Private->display,
        Private->win,
        Private->win,
        Private->gc,
        Args->SourceX,
        Args->SourceY,
        Args->Width,
        Args->Height,
        Args->DestinationX,
        Args->DestinationY
        );

      while (1) {
        XNextEvent (Private->display, &ev);
        HandleEvent (Private, &ev);
        if ((ev.type == NoExpose) || (ev.type == GraphicsExpose)) {
          break;
        }
      }

      break;
    case EfiUgaVideoFill:
      Color = X11PixelToColor (Private, *BltBuffer);
      XSetForeground (Private->display, Private->gc, Color);
      XFillRectangle (
        Private->display,
        Private->win,
        Private->gc,
        Args->DestinationX,
        Args->DestinationY,
        Args->Width,
        Args->Height
        );
      XFlush (Private->display);
      break;
    case EfiUgaBltBufferToVideo:
      Redraw (Private, Args->DestinationX, Args->DestinationY, Args->Width, Args->Height);
      break;
    default:
      break;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
X11CheckPointer (
  IN  EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  HandleEvents (Drv);
  if (Drv->pointer_state_changed != 0) {
    return EFI_SUCCESS;
  }

  return EFI_NOT_READY;
}

EFI_STATUS
X11GetPointerState (
  IN  EMU_GRAPHICS_WINDOW_PROTOCOL  *GraphicsIo,
  IN  EFI_SIMPLE_POINTER_STATE      *State
  )
{
  EFI_STATUS           EfiStatus;
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)GraphicsIo;

  EfiStatus = X11CheckPointer (GraphicsIo);
  if (EfiStatus != EFI_SUCCESS) {
    return EfiStatus;
  }

  memcpy (State, &Drv->pointer_state, sizeof (EFI_SIMPLE_POINTER_STATE));

  Drv->pointer_state.RelativeMovementX = 0;
  Drv->pointer_state.RelativeMovementY = 0;
  Drv->pointer_state.RelativeMovementZ = 0;
  Drv->pointer_state_changed           = 0;
  return EFI_SUCCESS;
}

EFI_STATUS
X11GraphicsWindowOpen (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;
  unsigned int         border_width  = 0;
  char                 *display_name = NULL;

  Drv = (GRAPHICS_IO_PRIVATE *)calloc (1, sizeof (GRAPHICS_IO_PRIVATE));
  if (Drv == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Drv->GraphicsIo.Size              = GasketX11Size;
  Drv->GraphicsIo.CheckKey          = GasketX11CheckKey;
  Drv->GraphicsIo.GetKey            = GasketX11GetKey;
  Drv->GraphicsIo.KeySetState       = GasketX11KeySetState;
  Drv->GraphicsIo.RegisterKeyNotify = GasketX11RegisterKeyNotify;
  Drv->GraphicsIo.Blt               = GasketX11Blt;
  Drv->GraphicsIo.CheckPointer      = GasketX11CheckPointer;
  Drv->GraphicsIo.GetPointerState   = GasketX11GetPointerState;

  Drv->key_count                   = 0;
  Drv->key_rd                      = 0;
  Drv->key_wr                      = 0;
  Drv->KeyState.KeyShiftState      = EFI_SHIFT_STATE_VALID;
  Drv->KeyState.KeyToggleState     = EFI_TOGGLE_STATE_VALID;
  Drv->MakeRegisterdKeyCallback    = NULL;
  Drv->BreakRegisterdKeyCallback   = NULL;
  Drv->RegisterdKeyCallbackContext = NULL;

  Drv->display = XOpenDisplay (display_name);
  if (Drv->display == NULL) {
    fprintf (stderr, "uga: cannot connect to X server %s\n", XDisplayName (display_name));
    free (Drv);
    return EFI_DEVICE_ERROR;
  }

  Drv->screen = DefaultScreen (Drv->display);
  Drv->visual = DefaultVisual (Drv->display, Drv->screen);
  Drv->win    = XCreateSimpleWindow (
                  Drv->display,
                  RootWindow (Drv->display, Drv->screen),
                  0,
                  0,
                  4,
                  4,
                  border_width,
                  WhitePixel (Drv->display, Drv->screen),
                  BlackPixel (Drv->display, Drv->screen)
                  );

  Drv->depth = DefaultDepth (Drv->display, Drv->screen);
  XDefineCursor (Drv->display, Drv->win, XCreateFontCursor (Drv->display, XC_pirate));

  Drv->Title = malloc (StrSize (This->ConfigString));
  UnicodeStrToAsciiStrS (This->ConfigString, Drv->Title, StrSize (This->ConfigString));
  XStoreName (Drv->display, Drv->win, Drv->Title);

  //  XAutoRepeatOff (Drv->display);
  XSelectInput (
    Drv->display,
    Drv->win,
    ExposureMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask
    );
  Drv->gc = DefaultGC (Drv->display, Drv->screen);

  This->Private   = (VOID *)Drv;
  This->Interface = (VOID *)Drv;
  return EFI_SUCCESS;
}

EFI_STATUS
X11GraphicsWindowClose (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  GRAPHICS_IO_PRIVATE  *Drv;

  Drv = (GRAPHICS_IO_PRIVATE *)This->Private;

  if (Drv == NULL) {
    return EFI_SUCCESS;
  }

  if (Drv->image != NULL) {
    XDestroyImage (Drv->image);

    if (Drv->use_shm) {
      shmdt (Drv->image_data);
    }

    Drv->image_data = NULL;
    Drv->image      = NULL;
  }

  XDestroyWindow (Drv->display, Drv->win);
  XCloseDisplay (Drv->display);

 #ifdef __APPLE__
  // Free up the shared memory
  shmctl (Drv->xshm_info.shmid, IPC_RMID, NULL);
 #endif

  free (Drv);
  return EFI_SUCCESS;
}

EMU_IO_THUNK_PROTOCOL  gX11ThunkIo = {
  &gEmuGraphicsWindowProtocolGuid,
  NULL,
  NULL,
  0,
  GasketX11GraphicsWindowOpen,
  GasketX11GraphicsWindowClose,
  NULL
};
