| /* |
| * Copyright 2016 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * f 49 |
| * Prev |
| * Up |
| * |
| * |
| * found in the LICENSE file. |
| */ |
| |
| //#include <tchar.h> |
| |
| #include "tools/sk_app/unix/WindowContextFactory_unix.h" |
| |
| #include "src/utils/SkUTF.h" |
| #include "tools/ModifierKey.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 ModifierKey get_modifiers(const XEvent& event) { |
| static const struct { |
| unsigned fXMask; |
| ModifierKey fSkMask; |
| } gModifiers[] = { |
| { ShiftMask, ModifierKey::kShift }, |
| { ControlMask, ModifierKey::kControl }, |
| { Mod1Mask, ModifierKey::kOption }, |
| }; |
| |
| ModifierKey modifiers = ModifierKey::kNone; |
| 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, |
| InputState::kDown, 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, |
| InputState::kUp, get_modifiers(event)); |
| } |
| break; |
| |
| case MotionNotify: |
| this->onMouse(event.xmotion.x, event.xmotion.y, |
| InputState::kMove, 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, InputState::kDown, 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, InputState::kUp, |
| 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_DAWN |
| case kDawn_BackendType: |
| fWindowContext = |
| window_context_factory::MakeDawnVulkanForXlib(winInfo, fRequestedDisplayParams); |
| break; |
| #endif |
| #ifdef SK_VULKAN |
| case kVulkan_BackendType: |
| fWindowContext = |
| window_context_factory::MakeVulkanForXlib(winInfo, fRequestedDisplayParams); |
| break; |
| #endif |
| case kNativeGL_BackendType: |
| fWindowContext = |
| window_context_factory::MakeGLForXlib(winInfo, fRequestedDisplayParams); |
| break; |
| case kRaster_BackendType: |
| fWindowContext = |
| window_context_factory::MakeRasterForXlib(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 |