/*++

Copyright (c) 2004 - 2011, Intel Corporation. All rights reserved.<BR>
Portions copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>
This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

--*/
#include <Common/UnixInclude.h>

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

#include <PiPei.h>
#include <Protocol/SimplePointer.h>
#include <Protocol/SimpleTextIn.h>
#include <Protocol/SimpleTextInEx.h>
#include <Protocol/UgaDraw.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>

#include <Protocol/UnixThunk.h>
#include <Protocol/UnixUgaIo.h>

#include <Ppi/StatusCode.h>

#include <Library/PeCoffLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PrintLib.h>
#include <Library/PcdLib.h>
#include <Library/DebugLib.h>

#include "Gasket.h"
#include "SecMain.h"


extern void msSleep (unsigned long Milliseconds);

/* XQueryPointer  */

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

#define NBR_KEYS 32
typedef struct {
  EFI_UNIX_UGA_IO_PROTOCOL UgaIo;

  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;

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

  EFI_KEY_STATE KeyState;
  
  UGA_REGISTER_KEY_NOTIFY_CALLBACK RegisterdKeyCallback;
  VOID                             *RegisterdKeyCallbackContext;
  
  int                        previous_x;
  int                        previous_y;
  EFI_SIMPLE_POINTER_STATE   pointer_state;
  int                        pointer_state_changed;
} UGA_IO_PRIVATE;

void
HandleEvents(UGA_IO_PRIVATE *drv);

void
fill_shift_mask (struct uga_drv_shift_mask *sm, 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(UGA_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. 
  //
  /* Can this fail ?  */
  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
UgaClose (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo)
{
  UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo;

  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;
}

EFI_STATUS
UgaSize(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, UINT32 Width, UINT32 Height)
{
  UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo;
  XSizeHints size_hints;

  /* Destroy current buffer if created.  */
  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(UGA_IO_PRIVATE *drv, XEvent *ev)
{
  KeySym keysym;
  char str[4];
  EFI_KEY_DATA KeyData;
  int res;

  if (drv->key_count == NBR_KEYS)
    return;

  res = XLookupString(&ev->xkey, str, sizeof(str), &keysym, NULL);
  KeyData.Key.ScanCode = 0;
  KeyData.Key.UnicodeChar = 0;
  KeyData.KeyState.KeyShiftState = 0;

  //
  // KeyRelease is not supported (on Mac) so we can not easily implement Ex functions.
  // If a modifier key is hit by its self we get a keysym. If a modfifier and key is hit
  // we get the state bit set and keysym is the modified key. 
  //
  // We use lack of state bits being set to clear ToggleState and KeyShiftState. We can 
  // also use the stat bits to set ToggleState and KeyShiftState. 
  // 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 {
    drv->KeyState.KeyToggleState |= EFI_CAPS_LOCK_ACTIVE;
  }
  
  if ((ev->xkey.state & ControlMask) == 0) {
    drv->KeyState.KeyShiftState &= ~(EFI_RIGHT_CONTROL_PRESSED | EFI_LEFT_CONTROL_PRESSED);
  } else if ((drv->KeyState.KeyShiftState & EFI_RIGHT_CONTROL_PRESSED) == 0) {
    drv->KeyState.KeyShiftState |= EFI_LEFT_CONTROL_PRESSED;
  }
  
  if ((ev->xkey.state & ShiftMask) == 0) {
    drv->KeyState.KeyShiftState &= ~(EFI_RIGHT_SHIFT_PRESSED | EFI_LEFT_SHIFT_PRESSED);
  } else if ((drv->KeyState.KeyShiftState & EFI_RIGHT_SHIFT_PRESSED) == 0) {
    drv->KeyState.KeyShiftState |= EFI_LEFT_SHIFT_PRESSED;
  }
  
  if ((ev->xkey.state & Mod2Mask) == 0) {
    drv->KeyState.KeyShiftState &= ~(EFI_RIGHT_LOGO_PRESSED | EFI_LEFT_LOGO_PRESSED);
  } else if ((drv->KeyState.KeyShiftState & EFI_RIGHT_LOGO_PRESSED) == 0) {
    drv->KeyState.KeyShiftState |= EFI_LEFT_LOGO_PRESSED;
  }

  if ((ev->xkey.state & 0x2000) == 0) {
    drv->KeyState.KeyShiftState &= ~(EFI_LEFT_ALT_PRESSED);
  } else {
    drv->KeyState.KeyShiftState |= EFI_LEFT_ALT_PRESSED;
  }
  
  
  switch (keysym) {
  case XK_Control_R:
    drv->KeyState.KeyShiftState |= EFI_RIGHT_CONTROL_PRESSED;
    break;
  case XK_Control_L:
    drv->KeyState.KeyShiftState |= EFI_LEFT_CONTROL_PRESSED;
    break;

  case XK_Shift_R:
    drv->KeyState.KeyShiftState |= EFI_RIGHT_SHIFT_PRESSED;
    break;
  case XK_Shift_L:
    drv->KeyState.KeyShiftState |= EFI_LEFT_SHIFT_PRESSED;
    break;
  
  case XK_Mode_switch:
    drv->KeyState.KeyShiftState |= EFI_LEFT_ALT_PRESSED;
    break;

  case XK_Meta_R:
    drv->KeyState.KeyShiftState |= EFI_RIGHT_LOGO_PRESSED;
    break;
  case XK_Meta_L:
    drv->KeyState.KeyShiftState |= EFI_LEFT_LOGO_PRESSED;
    break;

  case XK_Home:       KeyData.Key.ScanCode = SCAN_HOME;       break;
  case XK_End:        KeyData.Key.ScanCode = SCAN_END;        break;
  case XK_Left:       KeyData.Key.ScanCode = SCAN_LEFT;       break;
  case XK_Right:      KeyData.Key.ScanCode = SCAN_RIGHT;      break;
  case XK_Up:         KeyData.Key.ScanCode = SCAN_UP;         break;
  case XK_Down:       KeyData.Key.ScanCode = SCAN_DOWN;       break;
  case XK_Delete:     KeyData.Key.ScanCode = SCAN_DELETE;     break;
  case XK_Insert:     KeyData.Key.ScanCode = SCAN_INSERT;     break;
  case XK_Page_Up:    KeyData.Key.ScanCode = SCAN_PAGE_UP;    break;
  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_F1:   KeyData.Key.ScanCode = SCAN_F1;   break;
  case XK_F2:   KeyData.Key.ScanCode = SCAN_F2;   break;
  case XK_F3:   KeyData.Key.ScanCode = SCAN_F3;   break;
  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;
  case XK_F8:   KeyData.Key.ScanCode = SCAN_F8;   break;
  case XK_F9:   KeyData.Key.ScanCode = SCAN_F9;   break;

  default:
    if (res == 1) {
      KeyData.Key.UnicodeChar = str[0];
    } else {
      return;
    }
  }

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

  CopyMem (&drv->keys[drv->key_wr], &KeyData, sizeof (EFI_KEY_DATA));
  drv->key_wr = (drv->key_wr + 1) % NBR_KEYS;
  drv->key_count++;
  
  
#if defined(__APPLE__) || defined(MDE_CPU_X64)
  ReverseGasketUint64Uint64 (drv->RegisterdKeyCallback ,drv->RegisterdKeyCallbackContext, &KeyData);
#else
  drv->RegisterdKeyCallback (drv->RegisterdKeyCallbackContext, &KeyData);
#endif

  
}


void
handleMouseMoved(UGA_IO_PRIVATE *drv, 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(UGA_IO_PRIVATE *drv, XEvent *ev, 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(UGA_IO_PRIVATE *drv, UINTN X, UINTN Y, UINTN Width, 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(UGA_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);
      break;
    case KeyRelease:
      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(UGA_IO_PRIVATE *drv)
{
  while (XPending(drv->display) != 0)
    {
      XEvent ev;

      XNextEvent (drv->display, &ev);
      HandleEvent(drv, &ev);
    }
}

unsigned long
UgaPixelToColor (UGA_IO_PRIVATE *drv, 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
UgaColorToPixel (UGA_IO_PRIVATE *drv, unsigned long val)
{
  EFI_UGA_PIXEL res;

  memset (&res, 0, sizeof (EFI_UGA_PIXEL));
  /* FIXME: should round instead of truncate.  */
  res.Red = (val >> drv->r.shift) << drv->r.csize;
  res.Green = (val >> drv->g.shift) << drv->g.csize;
  res.Blue = (val >> drv->b.shift) << drv->b.csize;

  return res;
}

STATIC EFI_STATUS
CheckKeyInternal( UGA_IO_PRIVATE *drv, BOOLEAN delay )
{
  HandleEvents(drv);
  if (drv->key_count != 0)
    return EFI_SUCCESS;
  if ( delay )
    /* EFI is polling.  Be CPU-friendly.  */
    msSleep (20);
    return EFI_NOT_READY;
  }

EFI_STATUS
UgaCheckKey(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo)
{
  UGA_IO_PRIVATE  *drv = (UGA_IO_PRIVATE *)UgaIo;
  return( CheckKeyInternal( drv, TRUE ) );
}

EFI_STATUS
EFIAPI
UgaGetKey (
  IN  EFI_UNIX_UGA_IO_PROTOCOL  *UgaIo, 
  IN  EFI_KEY_DATA              *KeyData
  )
{
  UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo;
  EFI_STATUS status;

  status = CheckKeyInternal(drv, FALSE);
  if (status != EFI_SUCCESS)
    return status;

  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
EFIAPI
UgaKeySetState (
  IN EFI_UNIX_UGA_IO_PROTOCOL   *UgaIo, 
  IN EFI_KEY_TOGGLE_STATE       *KeyToggleState
  )
{
  UGA_IO_PRIVATE  *drv = (UGA_IO_PRIVATE *)UgaIo;
//  XKeyEvent       event;
  
  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
EFIAPI
UgaRegisterKeyNotify (
  IN EFI_UNIX_UGA_IO_PROTOCOL           *UgaIo, 
  IN UGA_REGISTER_KEY_NOTIFY_CALLBACK   CallBack,
  IN VOID                               *Context
  )
{
  UGA_IO_PRIVATE  *drv = (UGA_IO_PRIVATE *)UgaIo;

  drv->RegisterdKeyCallback = CallBack;
  drv->RegisterdKeyCallbackContext = Context;

  return EFI_SUCCESS;
}


EFI_STATUS
UgaBlt(
  IN EFI_UNIX_UGA_IO_PROTOCOL                 *UgaIo,
  IN  EFI_UGA_PIXEL                           *BltBuffer OPTIONAL,
  IN  EFI_UGA_BLT_OPERATION                   BltOperation,
  IN  UGA_BLT_ARGS                            *Args
  )
{
  UGA_IO_PRIVATE *Private = (UGA_IO_PRIVATE *)UgaIo;
  UINTN             DstY;
  UINTN             SrcY;
  UINTN             DstX;
  UINTN             SrcX;
  UINTN             Index;
  EFI_UGA_PIXEL     *Blt;
  UINT8             *Dst;
  UINT8             *Src;
  UINTN             Nbr;
  unsigned long     Color;

  //
  //  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++ = UgaColorToPixel(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, UgaPixelToColor(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 = UgaPixelToColor(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) {
      XEvent ev;

      XNextEvent (Private->display, &ev);
      HandleEvent(Private, &ev);
      if (ev.type == NoExpose || ev.type == GraphicsExpose)
        break;
    }
    break;
  case EfiUgaVideoFill:
    Color = UgaPixelToColor(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;
}

STATIC EFI_STATUS
CheckPointerInternal( UGA_IO_PRIVATE *drv, BOOLEAN delay )
{
  HandleEvents(drv);
  if (drv->pointer_state_changed != 0)
    return EFI_SUCCESS;
  if ( delay )
    /* EFI is polling.  Be CPU-friendly.  */
    msSleep (20);
  return EFI_NOT_READY;
}

EFI_STATUS
UgaCheckPointer(EFI_UNIX_UGA_IO_PROTOCOL *UgaIo)
{
  UGA_IO_PRIVATE  *drv = (UGA_IO_PRIVATE *)UgaIo;
  return( CheckPointerInternal( drv, TRUE ) );
}

EFI_STATUS
UgaGetPointerState (EFI_UNIX_UGA_IO_PROTOCOL *UgaIo, EFI_SIMPLE_POINTER_STATE *state)
{
  UGA_IO_PRIVATE *drv = (UGA_IO_PRIVATE *)UgaIo;
  EFI_STATUS status;

  status = CheckPointerInternal( drv, FALSE );
  if (status != EFI_SUCCESS)
    return status;
  
  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
UgaCreate (EFI_UNIX_UGA_IO_PROTOCOL **Uga, CONST CHAR16 *Title)
{
  UGA_IO_PRIVATE *drv;
  unsigned int border_width = 0;
  char *display_name = NULL;
  int title_len;

  drv = (UGA_IO_PRIVATE *)calloc (1, sizeof (UGA_IO_PRIVATE));
  if (drv == NULL)
    return EFI_OUT_OF_RESOURCES;

#if defined(__APPLE__) || defined(MDE_CPU_X64)
//
//
//
  drv->UgaIo.UgaClose    = GasketUgaClose; 
  drv->UgaIo.UgaSize     = GasketUgaSize;
  drv->UgaIo.UgaCheckKey = GasketUgaCheckKey;
  drv->UgaIo.UgaGetKey   = GasketUgaGetKey;
  drv->UgaIo.UgaKeySetState         = GasketUgaKeySetState;
  drv->UgaIo.UgaRegisterKeyNotify   = GasketUgaRegisterKeyNotify;
  drv->UgaIo.UgaBlt      = GasketUgaBlt;
  drv->UgaIo.UgaCheckPointer        = GasketUgaCheckPointer;
  drv->UgaIo.UgaGetPointerState     = GasketUgaGetPointerState;
#else
  drv->UgaIo.UgaClose = UgaClose;
  drv->UgaIo.UgaSize = UgaSize;
  drv->UgaIo.UgaCheckKey = UgaCheckKey;
  drv->UgaIo.UgaGetKey = UgaGetKey;
  drv->UgaIo.UgaKeySetState         = UgaKeySetState;
  drv->UgaIo.UgaRegisterKeyNotify   = UgaRegisterKeyNotify;
  drv->UgaIo.UgaBlt = UgaBlt;
  drv->UgaIo.UgaCheckPointer        = UgaCheckPointer;
  drv->UgaIo.UgaGetPointerState     = UgaGetPointerState;
#endif
  
  

  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->RegisterdKeyCallback = 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)); 

  /* Compute title len and convert to Ascii.  */
  for (title_len = 0; Title[title_len] != 0; title_len++)
    ;
  {
    char title[title_len + 1];
    int i;
    for (i = 0; i < title_len; i++)
      title[i] = Title[i];
    title[i] = 0;
    
    XStoreName (drv->display, drv->win, title);
  }

  XSelectInput (drv->display, drv->win,
                 ExposureMask | KeyPressMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask );
  drv->gc = DefaultGC (drv->display, drv->screen);

  *Uga = (EFI_UNIX_UGA_IO_PROTOCOL *)drv;
  return EFI_SUCCESS;
}
