{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    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 2 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/

 **********************************************************************}


  function GetXErrorString(dpy: PDisplay; code: integer): string;
  //XGetErrorText(display, code, buffer_return, length)
  //      Display *display;
  //      int code;
  //      char *buffer_return;
  //      int length;
  var buf: array[0..10000] of AnsiChar;
  begin
    XGetErrorText(dpy, code, @buf[0], 10000);
    Result:=PCharToStr(buf);
  end;

  procedure cge_XPanic(msg: WideString); //dry up and die in a civilized manner
  begin
     With WindowManager do begin
       EmergencyShutdown:=Yes;
       MotherState.WindowExists:=No;
     end;
     XServerBully:=Yes;
     ExitRequested:=Yes;
     AddLog(MI_ERROR_X_SERVER_FATAL, [msg]);
     LogGoDown(RuEn('X сервер неожиданно убил это приложение','X server did suddenly kill this application'));
     PerformCgeShutdown;
  end;

  //int (*XSetIOErrorHandler(handler))()
  //      int (*handler)(Display *);
  function _X_Fatal_Callback(dpy: PDisplay): integer; cdecl; //never returns!
  begin
    //..if we return from this function, our app will die instantly.
    cge_XPanic('(an unspecified I/O error)');
  end;

  // int (*XSetErrorHandler(handler))()
  //       int (*handler)(Display *, XErrorEvent *)
  function _X_Error_Callback(dpy: PDisplay; Error: PXErrorEvent): integer; cdecl;
  begin
    cge_XPanic('(an unspecified error)');
  end;

  const
    mask: array[0..1] of cardinal = (
      CWBackPixel or CWBorderPixel or CWEventMask
      ,
      CWOverrideRedirect or CWBackPixel or CWSaveUnder or CWBackingStore or CWEventMask
    );

  procedure TWindowManager.CreateWindow;
  var
   attr: TXSetWindowAttributes;
   rep: integer;
  begin
    // would we not do that - our program would silently die at the first error,
    //   being unable to save the session.
    // One of the main reasons I didn't use SDL, glut and the like.
    XSetIOErrorHandler(_X_Fatal_Callback);
    XSetErrorHandler(_X_Error_Callback);


    FillChar(attr, sizeof(attr), 0);
    attr.background_pixel:= 0;
    attr.border_pixel:= 0;

{
| PointerMotionMask
| PointerMotionHintMask
| ButtonMotionMask
| Button1MotionMask
| Button2MotionMask
| Button3MotionMask
| ButtonPressMask
| ButtonReleaseMask
| KeyPressMask
| KeyReleaseMask
| EnterNotifyMask
| LeaveNotifyMask
| FocusChangeMask
| StructureMask
| PropertyChangeMask
| VisibilityNotifyMask
| ProximityInMask   //pen touches the tablet
| ProximityOutMask  /pen is removed from the tablet
| SubstructureMask
| ScrollMask
| AllEventsMask
 }

    attr.event_mask:= ExposureMask or KeyPressMask
       or KeyReleaseMask or ButtonPressMask or ButtonReleaseMask
       or PointerMotionMask or SubstructureNotifyMask or FocusChangeMask
       or VisibilityChangeMask or StructureNotifyMask
       or EnterNotify or LeaveNotify;
    if MotherState.InFullScreenMode then begin
      attr.backing_store := NotUseful;
      longint(attr.save_under) := 0; //False; //changed upgrading to FPC 2.4.0 from 2.2.4
      longint(attr.override_redirect):= 1; //True; {Tells the window manager to f*** off. By some miracle, Alt-Tab and Alt-F4 still work ^_^ }
    end;

    WindowHandle:= XCreateWindow(display, DefaultRootWindow(display),
      (InitialWidth - MotherState.DisplayWidth) div 2,
      (InitialHeight - MotherState.DisplayHeight) div 2,

      MotherState.DisplayWidth,MotherState.DisplayHeight,
      0,CopyFromParent,InputOutput,CopyFromParent,
      mask[ord(MotherState.InFullScreenMode)], @attr);
      
    if WindowHandle = 0 then Die('XCreateWindow() returned 0');
    
   (* Выбираем события, обрабатываемые программой *)
    XSelectInput (Display, WindowHandle, ExposureMask or KeyPressMask
       or KeyReleaseMask or ButtonPressMask or ButtonReleaseMask
       or PointerMotionMask or FocusChangeMask
       or VisibilityChangeMask
       or EnterWindowMask or LeaveWindowMask);

   (* Чтобы ловило оповещения о закрытии *)
    pnProtocols[0]:= XInternAtom(display, 'WM_DELETE_WINDOW', True);
    pnProtocols[1]:= XInternAtom(display, 'WM_SAVE_YOURSELF', True);
    nWMProtocols:= XInternAtom(display, 'WM_PROTOCOLS', True);
    XSetWMProtocols(Display, WindowHandle, @pnProtocols[0], 2);
    
   (* Показываем окно *)
    XMapWindow ( display, Windowhandle );
    XFlush(display);

    MotherState.WindowExists:=True;

   (* Without this, the input won't work due to override_redirect *)
    if MotherState.InFullScreenMode then begin
      rep:=0;
      repeat
        inc(rep); if rep > 10 then break;
        Sleep(50);
      until 0 = XGrabKeyboard(display, WindowHandle, true,
          GrabModeAsync, GrabModeAsync, CurrentTime);
      if rep > 10 then Die(RuEn(
        'Не удалось перехватить клавиатуру','Unable to grab keyboard'));

      rep:=0;
      repeat
        inc(rep); if rep > 10 then break;
        Sleep(50);
      until 0 = XGrabPointer(display, WindowHandle, true,
          ButtonPressMask or ButtonReleaseMask or PointerMotionMask,
          GrabModeAsync, GrabModeAsync, WindowHandle, None, CurrentTime);
      if rep > 10 then
        Die(RuEn('Не удалось перехватить мышь','Unable to grab mouse'));
    end;
  end;


  procedure  TWindowManager.CloseWindow;
  begin
    if not MotherState.WindowExists then begin
      VerboseLog('Cannot destroy the window because it doesn''t exist.');
      Exit
    end;

    if CursorCreated then begin
      XUndefineCursor(display, WindowHandle);
      XFlush(display);
      XFreeCursor(display, icon_hwcursor);
      XFlush(display);
    end;

    XDestroyWindow(Display, WindowHandle);

    XFlush(display); {without this line, the fullscreen window would
      freeze, not truly destroyed, hide the error message dialog,
      block any switching via alt-tab...
     resulting in one livid user who *ahem* doesn't like us too much
      after losing all his open documents, having to restart the X.}
    
    VerboseLog('Window destroyed.');
    MotherState.WindowExists:=False;
  end;


  function DivineScanCode(var event: TXEvent): integer;
  var
    sym: TKeySym;
  begin
    sym:= XLookupKeysym(@event.xkey, 0);
    case sym of
      XK_Home               :Exit(ord(KEY_HOME));
      XK_Left               :Exit(ord(KEY_LEFT));
      XK_Up                 :Exit(ord(KEY_UP));
      XK_Right              :Exit(ord(KEY_RIGHT));
      XK_Down               :Exit(ord(KEY_DOWN));
      XK_Page_Up            :Exit(ord(KEY_PGUP));
      XK_Page_Down          :Exit(ord(KEY_PGDN));
      XK_End                :Exit(ord(KEY_END));
      XK_BackSpace          :Exit(ord(KEY_BACKSPACE));
      XK_Tab                :Exit(ord(KEY_TAB));
      XK_Return             :Exit(ord(KEY_ENTER));
      XK_Pause              :Exit(ord(KEY_PAUSE));
      XK_Scroll_Lock        :Exit(ord(KEY_SCROLL_LOCK));
      XK_Sys_Req            :Exit(ord(KEY_SYSRQ));
      XK_Escape             :Exit(ord(KEY_ESCAPE));
      XK_Delete             :Exit(ord(KEY_DELETE));
      XK_Insert             :Exit(ord(KEY_INSERT));
      XK_Menu               :Exit(ord(KEY_APP_MENU));
      XK_Num_Lock           :Exit(ord(KEY_NUM_LOCK));
      XK_KP_Enter           :Exit(ord(KEY_KP_ENTER));
      XK_KP_Home, XK_KP_7   :Exit(ord(KEY_KP_7));
      XK_KP_Left, XK_KP_4   :Exit(ord(KEY_KP_4));
      XK_KP_Up, XK_KP_8     :Exit(ord(KEY_KP_8));
      XK_KP_Right, XK_KP_6  :Exit(ord(KEY_KP_6));
      XK_KP_Down, XK_KP_2   :Exit(ord(KEY_KP_2));
      XK_KP_Page_Up, XK_KP_9  :Exit(ord(KEY_KP_9));
      XK_KP_Page_Down, XK_KP_3:Exit(ord(KEY_KP_3));
      XK_KP_End, XK_KP_1    :Exit(ord(KEY_KP_1));
      XK_KP_Insert, XK_KP_0 :Exit(ord(KEY_KP_0));
      XK_KP_Delete, XK_KP_Decimal :Exit(ord(KEY_KP_DECIMAL));
      XK_KP_Multiply        :Exit(ord(KEY_KP_STAR));
      XK_KP_Add             :Exit(ord(KEY_KP_PLUS));
      XK_KP_Subtract        :Exit(ord(KEY_KP_MINUS));
      XK_KP_Divide          :Exit(ord(KEY_KP_DIVIDE));

      XK_Shift_L            :Exit(ord(KEY_LEFT_SHIFT));
      XK_Shift_R            :Exit(ord(KEY_RIGHT_SHIFT));
      XK_Control_L          :Exit(ord(KEY_LEFT_CTRL));
      XK_Control_R          :Exit(ord(KEY_RIGHT_CTRL));
      XK_Caps_Lock          :Exit(ord(KEY_CAPS_LOCK));

      XK_Alt_L              :Exit(ord(KEY_LEFT_ALT));
      XK_Alt_R              :Exit(ord(KEY_RIGHT_ALT));
      XK_Super_L            :Exit(ord(KEY_LEFT_WINDOWS));
      XK_Super_R            :Exit(ord(KEY_RIGHT_WINDOWS));
    else
      Result:= (event.xKey.keycode - 8) and $FF;
    end;
  end;

  Procedure TWindowManager.OneCycle;
  var
    Event, NextEvent: TXEvent;
    i, s: integer;
    sym: TKeySym;
    scancode: integer;
    ItsAutoRepeat, GotNext: boolean;
    Screen: PScreen;
  begin
    _CheckCursorMode();
    f_HadMessages:= false;

    Screen:= XScreenOfDisplay(display, 0);
    MotherState.ScreenWidth:= Screen^.width;
    MotherState.ScreenHeight:= Screen^.height;

    While XPending(Display) > 0 do begin
      XNextEvent ( Display, @Event );
      if Event._type = KeyRelease then begin
        scancode:= DivineScanCode(event);
        //defeat the auto-repeat feature!
        GotNext:= No;
        ItsAutoRepeat:= No;
        if XPending(Display) > 0 then begin
          GotNext:= Yes;
          XNextEvent ( Display, @NextEvent );
          ItsAutoRepeat:= (NextEvent._type = KeyPress) and (NextEvent.xkey.keycode = Event.xkey.keycode) and (NextEvent.xkey.time = Event.xkey.time);
        end;
        if f_KeyState[scancode] and not ItsAutoRepeat then begin
          f_KeyState[scancode]:= false;
          OnRelease(scancode);
        end;
        if GotNext then Event:= NextEvent;
      end;

      case Event._type of
        KeyPress: begin
          f_HadMessages:= true;
          scancode:= DivineScanCode(event);
          i:= -1;
          if MotherState.TextInput then begin
            {Absolutely empiric! There's no any docs on it,
              it just happens that the Russian keyboard layout is indicated
              as the bit 13 of the "state" field.
             Correspondingly, XLookUpKeysum converts key codes to Russian
              keysyms if the bit 1 of its "index" parameter is set. Go figure.
            }
            s:= (event.xkey.state and $1) or ((event.xkey.state shr (13 - 1)) and 2);
            sym:= XLookupKeysym(@event.xkey, s);
            if sym = 0
              then sym:= XLookupKeysym(@event.xkey, s and 1);
            i:= X11ConvertKeySymToUnicode(sym); //an ugly, ugly function
          end;
          if i > 0 then begin
            OnType(WideChar(i));
            OnPress(0);
          end;
          if (not f_KeyState[scancode]) or (ItsAutoRepeat and MotherState.TextInput
            and (TKey(scancode) in KeysAcceptingAutoRepeatInTextInputMode))
          then begin
            f_KeyState[scancode]:=true;
            if (i <= 0) or (TKey(scancode) in KeysWorkingInTextInputMode)
              then OnPress(scancode);
          end;
        end;

        ClientMessage: begin
          if (Event.xclient.message_type = nWMProtocols)
            and (Event.xclient.data.l[0] = pnProtocols[0])
          then begin
           { cge_XPanic('Received WM_DELETE_WINDOW message');} {immediate shutdown.
              I'm trying to hack around the mysterious lockups here.
              The lockups happen if you close the window,
              it started after I added OpenAL support. Chentrah process gets
              stuck in the Zombie state, and even kill from root doesn't work,
              only the X server restart.
            P.S. Resolved, was a bug in the TModule incorrectly setting CurrentOwner
              state which led to the array out-of-bounds error and trashed memory}

            LogGoDownWM('WM_DELETE_WINDOW');
            ExitRequested:= Yes;
            break;
          end;
          if (Event.xclient.message_type = nWMProtocols)
            and (Event.xclient.data.l[0] = pnProtocols[1])
          then begin
            //cge_XPanic('Received WM_SAVE_YOURSELF message');
            LogGoDownWM('WM_SAVE_YOURSELF');
            ExitRequested:=Yes;
            break;
          end;
        end;
        FocusIn: begin
          if MotherState.InFullScreenMode then begin
            //not implemented
          end;
          HasFocus:=True;
        end;
        FocusOut: begin
          { In the current implementation, switching to other apps from
             the fullscreen mode not supported. Chentrah just shuts down
             if its window loses the focus }
          if MotherState.InFullScreenMode then begin
            LogGoDownWM(RuEn('о потере фокуса','the focus losing'));
            ExitRequested:= Yes;
            break;
          end;
          HasFocus:=false;
        end;
        ButtonPress: begin
          f_HadMessages:= true;
          f_MouseX:=event.xbutton.x;
          f_MouseY:=event.xbutton.y;
          case (Event.xbutton.button) of
            Button1: begin
              f_KeyState[ord(KEY_MOUSE_LEFT)]:=true;
              OnPress(ord(KEY_MOUSE_LEFT));
            end;
            Button3: begin
              f_KeyState[ord(KEY_MOUSE_RIGHT)]:=true;
              OnPress(ord(KEY_MOUSE_RIGHT));
            end;
            Button2: begin
              f_KeyState[ord(KEY_MOUSE_MIDDLE)]:=true;
              OnPress(ord(KEY_MOUSE_MIDDLE));
            end;
            Button4:
              OnPress(ord(KEY_MWHEEL_UP));
            Button5:
              OnPress(ord(KEY_MWHEEL_DOWN));
          end;
        end;
        VisibilityNotify: begin
          MotherState.WindowVisible:=(MotherState.InFullScreenMode or (event.xvisibility.state <> VisibilityFullyObscured));
        end;
        ButtonRelease: begin
          f_HadMessages:= true;
          f_MouseX:=event.xbutton.x;
          f_MouseY:=event.xbutton.y;
          case (Event.xbutton.button) of
            Button1: begin
              f_KeyState[ord(KEY_MOUSE_LEFT)]:=false;
              OnRelease(ord(KEY_MOUSE_LEFT));
            end;
            Button3: begin
              f_KeyState[ord(KEY_MOUSE_RIGHT)]:=false;
              OnRelease(ord(KEY_MOUSE_RIGHT));
            end;
            Button2: begin
              f_KeyState[ord(KEY_MOUSE_MIDDLE)]:=false;
              OnRelease(ord(KEY_MOUSE_MIDDLE));
            end;
          end;
        end;
        MotionNotify: begin
          f_HadMessages:= true;
          f_MouseX:=event.xmotion.x;
          f_MouseY:=event.xmotion.y;
          OnMouseMove;
        end;
        EnterNotify: begin
          f_HadMessages:= true;
          MotherState.MouseIsBeyondOurWindow:= false;
          f_MouseX:=event.xcrossing.x;
          f_MouseY:=event.xcrossing.y;
          OnMouseMove;
        end;
        LeaveNotify: begin
          MotherState.MouseIsBeyondOurWindow:= true;
        end;
        ResizeRequest: begin
         // f_HadMessages:= true;
          MotherState.DisplayWidth:=event.xresizerequest.Width;
          MotherState.DisplayHeight:=event.xresizerequest.Height;
        end;
      else

        if Assigned (TabletManager) and (Event._type = TabletManager.ValuatorEvent)
          then with TXDeviceMotionEvent(Event) do begin
            if (axes_count <> length(TabletManager.AxisInfo))
            or (first_axis <> 0)
              then Die(RuEn('Абнормальный формат сообщения от графического планшета!','Abnornal pen tablet input message!'));
            OnPen(
              x - x_root + ((axis_data[0] - tabletManager.AxisInfo[0].min_value) * MotherState.ScreenWidth) / (tabletManager.AxisInfo[0].max_value - tabletManager.AxisInfo[0].min_value + 1),
              y - y_root + ((axis_data[1] - tabletManager.AxisInfo[1].min_value) * MotherState.ScreenHeight) / (tabletManager.AxisInfo[1].max_value - tabletManager.AxisInfo[1].min_value + 1),
              axis_data[2]  / tabletManager.AxisInfo[2].max_value, //pressure
              axis_data[3]  / tabletManager.AxisInfo[3].max_value, //reserved1
              axis_data[4]  / tabletManager.AxisInfo[4].max_value, //reserved2
              axis_data[5]  / tabletManager.AxisInfo[5].max_value //reserved3
            );
{addlog (' pen event: x=%0, y=%1, pressure=%2', [
              ((axis_data[0] - tabletManager.AxisInfo[0].min_value) * MotherState.DisplayWidth) / (tabletManager.AxisInfo[0].max_value - tabletManager.AxisInfo[0].min_value + 1),
              ((axis_data[1] - tabletManager.AxisInfo[1].min_value) * MotherState.DisplayHeight) / (tabletManager.AxisInfo[1].max_value - tabletManager.AxisInfo[1].min_value + 1),
              axis_data[2]  / tabletManager.AxisInfo[2].max_value
              ]);
}
          end;
      end;
    end;

    if HasFocus or f_HadMessages
      then f_LoseFocusFramesCount:=0
      else inc(f_LoseFocusFramesCount);

    UpdateWindowSize;
  end;

  procedure TWindowManager.UpdateWindowSize;
  var
    attr: TXWindowAttributes;
  begin
    if not GoingDownHard then begin
      XGetWindowAttributes(Display, WindowHandle, @attr);
      if (attr.width <> MotherState.DisplayWidth) or (attr.height <> MotherState.DisplayHeight) then begin
        MotherState.DisplayWidth:=attr.width;
        MotherState.DisplayHeight:=attr.height;
        if not MotherState.InFullScreenMode then begin
          Config['video', 'window_width']:= MotherState.DisplayWidth;
          Config['video', 'window_height']:= MotherState.DisplayHeight;
        end;
      end;
    end;
  end;
  

  procedure TWindowManager.GrabPointer;
  begin
{    if f_PointerGrabbed then Exit;
    if 0 <> XGrabKeyboard(display, WindowHandle, true, GrabModeAsync, GrabModeAsync, CurrentTime)
    then Die(RuEn('Не удалось перехватить клавиатуру','Unable to grab keyboard'));
    if 0 <> XGrabPointer(display, WindowHandle, true, ButtonPressMask or ButtonReleaseMask or PointerMotionMask, GrabModeAsync, GrabModeAsync, WindowHandle, None, CurrentTime)
    then Die(RuEn('Не удалось перехватить мышь','Unable to grab mouse'));
    SetPointerImage(nil, 0, 0);
    f_PointerGrabbed:= True;
}
  end;
  

  procedure TWindowManager.UngrabPointer;
  begin
{    if not f_PointerGrabbed then Exit;
    XUngrabPointer(display, CurrentTime);
    XUngrabKeyboard(display, CurrentTime);
    f_PointerGrabbed:= False;
    SetPointerImage(PointerCache, PointerXHotSpot, PointerYHotSpot);
}
  end;

  

  procedure TWindowManager._UploadPointer;
  var
    PixmapF, PixmapB: TPixmap;
    cursor_fg, cursor_bg : TXColor;
    screen_colormap : TColormap;
    rc : TStatus;
    GC : TGC;
    rValues : TXGCValues;
    x, y: integer;
    OldCursor: TCursor;
  begin
    _UploadGLcursor;
    OldCursor:= icon_hwcursor;
    
    screen_colormap := XDefaultColormap(display, XDefaultScreen(display));//screen);
    (* выделяем черный и белый цвета *)
    if 0 = XAllocNamedColor(display, screen_colormap, 'black', @cursor_fg, @cursor_fg)
      then Die('XAllocNamedColor() failed: unable to alloc "black"');
    if 0 = XAllocNamedColor(display, screen_colormap, 'white', @cursor_bg, @cursor_bg)
      then Die('XAllocNamedColor() failed: unable to alloc "white"');
    PixmapF:= XCreatePixmap(display, WindowHandle, 32, 32, 1);
    PixmapB:= XCreatePixmap(display, WindowHandle, 32, 32, 1);

    //all this stuff only to fill the bitmap with zeroes
    rValues.foreground := 0;
    rValues.background := 0;
    GC:= XCreateGC(Display, PixmapF, GCForeground, @rValues);
    //create a blank transparent cursor at first
    XSetForeground(display, GC, 0);
    for y:=0 to 31 do
      for x:= 0 to 31 do
        XDrawPoint(Display, PixmapF, GC, x, y);
    if MotherState.UseHardware1bitCursor then begin
      //fill it with the mask data
      XSetForeground(display, GC, 1);

      for y:=0 to 31 do
        with PointerBitmap do
          for x:= 0 to 31 do
            if (XorMask[y] and (1 shl x)) <> 0 //white pixel
              then XDrawPoint(Display, PixmapF, GC, x, y);
    end;
    XFreeGC(Display, GC);


    GC:= XCreateGC(Display, PixmapB, GCForeground, @rValues);
    XSetForeground(display, GC, 0);
    for y:=0 to 31 do
      for x:= 0 to 31 do
        XDrawPoint(Display, PixmapB, GC, x, y);
    if MotherState.UseHardware1bitCursor then begin
      XSetForeground(display, GC, 1);
      for y:=0 to 31 do
        with PointerBitmap do
          for x:= 0 to 31 do
            if (AndMask[y] and (1 shl x)) = 0 // black pixel
                then XDrawPoint(Display, PixmapB, GC, x, y);
    end;
    XFreeGC(Display, GC);
    
    icon_hwcursor := XCreatePixmapCursor(
      display, PixmapF, PixmapB, @cursor_fg, @cursor_bg, PointerXHotSpot, PointerYHotSpot);
    XFreePixmap(display, PixmapF);
    XFreePixmap(display, PixmapB);
    XDefineCursor(display, WindowHandle, icon_hwcursor);
    if CursorCreated then begin
      XFreeCursor(display, OldCursor);
      XFlush(display);
    end;
    CursorCreated:= Yes;
  end;




