text: /*
  * Copyright 2016 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
  
  //#include <tchar.h>
  
  #include "tools/sk_app/unix/WindowContextFactory_unix.h"
  
  #include "src/utils/SkUTF.h"
  #include "tools/sk_app/GLWindowContext.h"
  #include "tools/sk_app/unix/Window_unix.h"
  #include "tools/timer/Timer.h"
  
  extern "C" {
      #include "tools/sk_app/unix/keysym2ucs.h"
  }
  #include <X11/Xutil.h>
  #include <X11/XKBlib.h>
  
  namespace sk_app {
  
  SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap;
  
  Window* Window::CreateNativeWindow(void* platformData) {
      Display* display = (Display*)platformData;
      SkASSERT(display);
  
      Window_unix* window = new Window_unix();
      if (!window->initWindow(display)) {
          delete window;
          return nullptr;
      }
  
      return window;
  }
  
  const long kEventMask = ExposureMask | StructureNotifyMask |
                          KeyPressMask | KeyReleaseMask |
                          PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
  
  bool Window_unix::initWindow(Display* display) {
      if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
          this->closeWindow();
      }
      // we already have a window
      if (fDisplay) {
          return true;
      }
      fDisplay = display;
  
      constexpr int initialWidth = 1280;
      constexpr int initialHeight = 960;
  
      // Attempt to create a window that supports GL
  
      // We prefer the more recent glXChooseFBConfig but fall back to glXChooseVisual. They have
      // slight differences in how attributes are specified.
      static int constexpr kChooseFBConfigAtt[] = {
          GLX_RENDER_TYPE, GLX_RGBA_BIT,
          GLX_DOUBLEBUFFER, True,
          GLX_STENCIL_SIZE, 8,
          None
      };
      // For some reason glXChooseVisual takes a non-const pointer to the attributes.
      int chooseVisualAtt[] = {
          GLX_RGBA,
          GLX_DOUBLEBUFFER,
          GLX_STENCIL_SIZE, 8,
          None
      };
      SkASSERT(nullptr == fVisualInfo);
      if (fRequestedDisplayParams.fMSAASampleCount > 1) {
          static const GLint kChooseFBConifgAttCnt = SK_ARRAY_COUNT(kChooseFBConfigAtt);
          GLint msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 4];
          memcpy(msaaChooseFBConfigAtt, kChooseFBConfigAtt, sizeof(kChooseFBConfigAtt));
          SkASSERT(None == msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1]);
          msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
          msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 0] = 1;
          msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 1] = GLX_SAMPLES_ARB;
          msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 2] = fRequestedDisplayParams.fMSAASampleCount;
          msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 3] = None;
          int n;
          fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), msaaChooseFBConfigAtt, &n);
          if (n > 0) {
              fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
          } else {
              static const GLint kChooseVisualAttCnt = SK_ARRAY_COUNT(chooseVisualAtt);
              GLint msaaChooseVisualAtt[kChooseVisualAttCnt + 4];
              memcpy(msaaChooseVisualAtt, chooseVisualAtt, sizeof(chooseVisualAtt));
              SkASSERT(None == msaaChooseVisualAtt[kChooseVisualAttCnt - 1]);
              msaaChooseFBConfigAtt[kChooseVisualAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
              msaaChooseFBConfigAtt[kChooseVisualAttCnt + 0] = 1;
              msaaChooseFBConfigAtt[kChooseVisualAttCnt + 1] = GLX_SAMPLES_ARB;
              msaaChooseFBConfigAtt[kChooseVisualAttCnt + 2] =
                      fRequestedDisplayParams.fMSAASampleCount;
              msaaChooseFBConfigAtt[kChooseVisualAttCnt + 3] = None;
              fVisualInfo = glXChooseVisual(display, DefaultScreen(display), msaaChooseVisualAtt);
              fFBConfig = nullptr;
          }
      }
      if (nullptr == fVisualInfo) {
          int n;
          fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), kChooseFBConfigAtt, &n);
          if (n > 0) {
              fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
          } else {
              fVisualInfo = glXChooseVisual(display, DefaultScreen(display), chooseVisualAtt);
              fFBConfig = nullptr;
          }
      }
  
      if (fVisualInfo) {
          Colormap colorMap = XCreateColormap(display,
                                              RootWindow(display, fVisualInfo->screen),
                                              fVisualInfo->visual,
                                              AllocNone);
          XSetWindowAttributes swa;
          swa.colormap = colorMap;
          swa.event_mask = kEventMask;
          fWindow = XCreateWindow(display,
                                  RootWindow(display, fVisualInfo->screen),
                                  0, 0, // x, y
                                  initialWidth, initialHeight,
                                  0, // border width
                                  fVisualInfo->depth,
                                  InputOutput,
                                  fVisualInfo->visual,
                                  CWEventMask | CWColormap,
                                  &swa);
      } else {
          // Create a simple window instead.  We will not be able to show GL
          fWindow = XCreateSimpleWindow(display,
                                        DefaultRootWindow(display),
                                        0, 0,  // x, y
                                        initialWidth, initialHeight,
                                        0,     // border width
                                        0,     // border value
                                        0);    // background value
          XSelectInput(display, fWindow, kEventMask);
      }
  
      if (!fWindow) {
          return false;
      }
  
      fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
  
      // set up to catch window delete message
      fWmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
      XSetWMProtocols(display, fWindow, &fWmDeleteMessage, 1);
  
      // add to hashtable of windows
      gWindowMap.add(this);
  
      // init event variables
      fPendingPaint = false;
      fPendingResize = false;
  
      return true;
  }
  
  void Window_unix::closeWindow() {
      if (fDisplay) {
          this->detach();
          if (fGC) {
              XFreeGC(fDisplay, fGC);
              fGC = nullptr;
          }
          gWindowMap.remove(fWindow);
          XDestroyWindow(fDisplay, fWindow);
          fWindow = 0;
          if (fFBConfig) {
              XFree(fFBConfig);
              fFBConfig = nullptr;
          }
          if (fVisualInfo) {
              XFree(fVisualInfo);
              fVisualInfo = nullptr;
          }
          fDisplay = nullptr;
      }
  }
  
  static Window::Key get_key(KeySym keysym) {
      static const struct {
          KeySym      fXK;
          Window::Key fKey;
      } gPair[] = {
          { XK_BackSpace, Window::Key::kBack },
          { XK_Clear, Window::Key::kBack },
          { XK_Return, Window::Key::kOK },
          { XK_Up, Window::Key::kUp },
          { XK_Down, Window::Key::kDown },
          { XK_Left, Window::Key::kLeft },
          { XK_Right, Window::Key::kRight },
          { XK_Tab, Window::Key::kTab },
          { XK_Page_Up, Window::Key::kPageUp },
          { XK_Page_Down, Window::Key::kPageDown },
          { XK_Home, Window::Key::kHome },
          { XK_End, Window::Key::kEnd },
          { XK_Delete, Window::Key::kDelete },
          { XK_Escape, Window::Key::kEscape },
          { XK_Shift_L, Window::Key::kShift },
          { XK_Shift_R, Window::Key::kShift },
          { XK_Control_L, Window::Key::kCtrl },
          { XK_Control_R, Window::Key::kCtrl },
          { XK_Alt_L, Window::Key::kOption },
          { XK_Alt_R, Window::Key::kOption },
          { 'A', Window::Key::kA },
          { 'C', Window::Key::kC },
          { 'V', Window::Key::kV },
          { 'X', Window::Key::kX },
          { 'Y', Window::Key::kY },
          { 'Z', Window::Key::kZ },
      };
      for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
          if (gPair[i].fXK == keysym) {
              return gPair[i].fKey;
          }
      }
      return Window::Key::kNONE;
  }
  
  static uint32_t get_modifiers(const XEvent& event) {
      static const struct {
          unsigned    fXMask;
          unsigned    fSkMask;
      } gModifiers[] = {
          { ShiftMask,   Window::kShift_ModifierKey },
          { ControlMask, Window::kControl_ModifierKey },
          { Mod1Mask,    Window::kOption_ModifierKey },
      };
  
      auto modifiers = 0;
      for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
          if (event.xkey.state & gModifiers[i].fXMask) {
              modifiers |= gModifiers[i].fSkMask;
          }
      }
      return modifiers;
  }
  
  bool Window_unix::handleEvent(const XEvent& event) {
      switch (event.type) {
          case MapNotify:
              if (!fGC) {
                  fGC = XCreateGC(fDisplay, fWindow, 0, nullptr);
              }
              break;
  
          case ClientMessage:
              if ((Atom)event.xclient.data.l[0] == fWmDeleteMessage &&
                  gWindowMap.count() == 1) {
                  return true;
              }
              break;
  
          case ButtonPress:
              switch (event.xbutton.button) {
                  case Button1:
                      this->onMouse(event.xbutton.x, event.xbutton.y,
                                    Window::kDown_InputState, get_modifiers(event));
                      break;
                  case Button4:
                      this->onMouseWheel(1.0f, get_modifiers(event));
                      break;
                  case Button5:
                      this->onMouseWheel(-1.0f, get_modifiers(event));
                      break;
              }
              break;
  
          case ButtonRelease:
              if (event.xbutton.button == Button1) {
                  this->onMouse(event.xbutton.x, event.xbutton.y,
                                Window::kUp_InputState, get_modifiers(event));
              }
              break;
  
          case MotionNotify:
              this->onMouse(event.xmotion.x, event.xmotion.y,
                            Window::kMove_InputState, get_modifiers(event));
              break;
  
          case KeyPress: {
              int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
              KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode, 0, shiftLevel);
              Window::Key key = get_key(keysym);
              if (key != Window::Key::kNONE) {
                  if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
                      if (keysym == XK_Escape) {
                          return true;
                      }
                  }
              }
  
              long uni = keysym2ucs(keysym);
              if (uni != -1) {
                  (void) this->onChar((SkUnichar) uni, get_modifiers(event));
              }
          } break;
  
          case KeyRelease: {
              int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
              KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode,
                                                 0, shiftLevel);
              Window::Key key = get_key(keysym);
              (void) this->onKey(key, Window::kUp_InputState,
                                 get_modifiers(event));
          } break;
  
  
          default:
              // these events should be handled in the main event loop
              SkASSERT(event.type != Expose && event.type != ConfigureNotify);
              break;
      }
  
      return false;
  }
  
  void Window_unix::setTitle(const char* title) {
      XTextProperty textproperty;
      XStringListToTextProperty(const_cast<char**>(&title), 1, &textproperty);
      XSetWMName(fDisplay, fWindow, &textproperty);
  }
  
  void Window_unix::show() {
      XMapWindow(fDisplay, fWindow);
  }
  
  bool Window_unix::attach(BackendType attachType) {
      fBackend = attachType;
  
      this->initWindow(fDisplay);
  
      window_context_factory::XlibWindowInfo winInfo;
      winInfo.fDisplay = fDisplay;
      winInfo.fWindow = fWindow;
      winInfo.fFBConfig = fFBConfig;
      winInfo.fVisualInfo = fVisualInfo;
  
      XWindowAttributes attrs;
      if (XGetWindowAttributes(fDisplay, fWindow, &attrs)) {
          winInfo.fWidth = attrs.width;
          winInfo.fHeight = attrs.height;
      } else {
          winInfo.fWidth = winInfo.fHeight = 0;
      }
  
      switch (attachType) {
  #ifdef SK_VULKAN
          case kVulkan_BackendType:
              fWindowContext = window_context_factory::NewVulkanForXlib(winInfo,
                                                                        fRequestedDisplayParams);
              break;
  #endif
          case kNativeGL_BackendType:
              fWindowContext = window_context_factory::NewGLForXlib(winInfo, fRequestedDisplayParams);
              break;
          case kRaster_BackendType:
              fWindowContext = window_context_factory::NewRasterForXlib(winInfo,
                                                                        fRequestedDisplayParams);
              break;
      }
      this->onBackendCreated();
  
      return (SkToBool(fWindowContext));
  }
  
  void Window_unix::onInval() {
      XEvent event;
      event.type = Expose;
      event.xexpose.send_event = True;
      event.xexpose.display = fDisplay;
      event.xexpose.window = fWindow;
      event.xexpose.x = 0;
      event.xexpose.y = 0;
      event.xexpose.width = this->width();
      event.xexpose.height = this->height();
      event.xexpose.count = 0;
  
      XSendEvent(fDisplay, fWindow, False, 0, &event);
  }
  
  void Window_unix::setRequestedDisplayParams(const DisplayParams& params, bool allowReattach) {
  #if defined(SK_VULKAN)
      // Vulkan on unix crashes if we try to reinitialize the vulkan context without remaking the
      // window.
      if (fBackend == kVulkan_BackendType && allowReattach) {
          // Need to change these early, so attach() creates the window context correctly
          fRequestedDisplayParams = params;
  
          this->detach();
          this->attach(fBackend);
          return;
      }
  #endif
  
      INHERITED::setRequestedDisplayParams(params, allowReattach);
  }
  
  }   // namespace sk_app
  