1/* 2* Copyright 2016 Google Inc. 3* 4* Use of this source code is governed by a BSD-style license that can be 5* f 49 6* Prev 7* Up 8* 9* 10* found in the LICENSE file. 11*/ 12 13//#include <tchar.h> 14 15#include "tools/sk_app/unix/WindowContextFactory_unix.h" 16 17#include "src/utils/SkUTF.h" 18#include "tools/sk_app/GLWindowContext.h" 19#include "tools/sk_app/unix/Window_unix.h" 20#include "tools/skui/ModifierKey.h" 21#include "tools/timer/Timer.h" 22 23extern "C" { 24 #include "tools/sk_app/unix/keysym2ucs.h" 25} 26#include <X11/Xatom.h> 27#include <X11/Xutil.h> 28#include <X11/XKBlib.h> 29 30namespace sk_app { 31 32SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap; 33 34Window* Window::CreateNativeWindow(void* platformData) { 35 Display* display = (Display*)platformData; 36 SkASSERT(display); 37 38 Window_unix* window = new Window_unix(); 39 if (!window->initWindow(display)) { 40 delete window; 41 return nullptr; 42 } 43 44 return window; 45} 46 47const long kEventMask = ExposureMask | StructureNotifyMask | 48 KeyPressMask | KeyReleaseMask | 49 PointerMotionMask | ButtonPressMask | ButtonReleaseMask; 50 51bool Window_unix::initWindow(Display* display) { 52 if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) { 53 this->closeWindow(); 54 } 55 // we already have a window 56 if (fDisplay) { 57 return true; 58 } 59 fDisplay = display; 60 61 constexpr int initialWidth = 1280; 62 constexpr int initialHeight = 960; 63 64 // Attempt to create a window that supports GL 65 66 // We prefer the more recent glXChooseFBConfig but fall back to glXChooseVisual. They have 67 // slight differences in how attributes are specified. 68 static int constexpr kChooseFBConfigAtt[] = { 69 GLX_RENDER_TYPE, GLX_RGBA_BIT, 70 GLX_DOUBLEBUFFER, True, 71 GLX_STENCIL_SIZE, 8, 72 None 73 }; 74 // For some reason glXChooseVisual takes a non-const pointer to the attributes. 75 int chooseVisualAtt[] = { 76 GLX_RGBA, 77 GLX_DOUBLEBUFFER, 78 GLX_STENCIL_SIZE, 8, 79 None 80 }; 81 SkASSERT(nullptr == fVisualInfo); 82 if (fRequestedDisplayParams.fMSAASampleCount > 1) { 83 static const GLint kChooseFBConifgAttCnt = SK_ARRAY_COUNT(kChooseFBConfigAtt); 84 GLint msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 4]; 85 memcpy(msaaChooseFBConfigAtt, kChooseFBConfigAtt, sizeof(kChooseFBConfigAtt)); 86 SkASSERT(None == msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1]); 87 msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB; 88 msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 0] = 1; 89 msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 1] = GLX_SAMPLES_ARB; 90 msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 2] = fRequestedDisplayParams.fMSAASampleCount; 91 msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 3] = None; 92 int n; 93 fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), msaaChooseFBConfigAtt, &n); 94 if (n > 0) { 95 fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig); 96 } else { 97 static const GLint kChooseVisualAttCnt = SK_ARRAY_COUNT(chooseVisualAtt); 98 GLint msaaChooseVisualAtt[kChooseVisualAttCnt + 4]; 99 memcpy(msaaChooseVisualAtt, chooseVisualAtt, sizeof(chooseVisualAtt)); 100 SkASSERT(None == msaaChooseVisualAtt[kChooseVisualAttCnt - 1]); 101 msaaChooseFBConfigAtt[kChooseVisualAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB; 102 msaaChooseFBConfigAtt[kChooseVisualAttCnt + 0] = 1; 103 msaaChooseFBConfigAtt[kChooseVisualAttCnt + 1] = GLX_SAMPLES_ARB; 104 msaaChooseFBConfigAtt[kChooseVisualAttCnt + 2] = 105 fRequestedDisplayParams.fMSAASampleCount; 106 msaaChooseFBConfigAtt[kChooseVisualAttCnt + 3] = None; 107 fVisualInfo = glXChooseVisual(display, DefaultScreen(display), msaaChooseVisualAtt); 108 fFBConfig = nullptr; 109 } 110 } 111 if (nullptr == fVisualInfo) { 112 int n; 113 fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), kChooseFBConfigAtt, &n); 114 if (n > 0) { 115 fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig); 116 } else { 117 fVisualInfo = glXChooseVisual(display, DefaultScreen(display), chooseVisualAtt); 118 fFBConfig = nullptr; 119 } 120 } 121 122 if (fVisualInfo) { 123 Colormap colorMap = XCreateColormap(display, 124 RootWindow(display, fVisualInfo->screen), 125 fVisualInfo->visual, 126 AllocNone); 127 XSetWindowAttributes swa; 128 swa.colormap = colorMap; 129 swa.event_mask = kEventMask; 130 fWindow = XCreateWindow(display, 131 RootWindow(display, fVisualInfo->screen), 132 0, 0, // x, y 133 initialWidth, initialHeight, 134 0, // border width 135 fVisualInfo->depth, 136 InputOutput, 137 fVisualInfo->visual, 138 CWEventMask | CWColormap, 139 &swa); 140 } else { 141 // Create a simple window instead. We will not be able to show GL 142 fWindow = XCreateSimpleWindow(display, 143 DefaultRootWindow(display), 144 0, 0, // x, y 145 initialWidth, initialHeight, 146 0, // border width 147 0, // border value 148 0); // background value 149 XSelectInput(display, fWindow, kEventMask); 150 } 151 152 if (!fWindow) { 153 return false; 154 } 155 156 fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount; 157 158 // set up to catch window delete message 159 fWmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False); 160 XSetWMProtocols(display, fWindow, &fWmDeleteMessage, 1); 161 162 // add to hashtable of windows 163 gWindowMap.add(this); 164 165 // init event variables 166 fPendingPaint = false; 167 fPendingResize = false; 168 169 return true; 170} 171 172void Window_unix::closeWindow() { 173 if (fDisplay) { 174 this->detach(); 175 if (fGC) { 176 XFreeGC(fDisplay, fGC); 177 fGC = nullptr; 178 } 179 gWindowMap.remove(fWindow); 180 XDestroyWindow(fDisplay, fWindow); 181 fWindow = 0; 182 if (fFBConfig) { 183 XFree(fFBConfig); 184 fFBConfig = nullptr; 185 } 186 if (fVisualInfo) { 187 XFree(fVisualInfo); 188 fVisualInfo = nullptr; 189 } 190 fDisplay = nullptr; 191 } 192} 193 194static skui::Key get_key(KeySym keysym) { 195 static const struct { 196 KeySym fXK; 197 skui::Key fKey; 198 } gPair[] = { 199 { XK_BackSpace, skui::Key::kBack }, 200 { XK_Clear, skui::Key::kBack }, 201 { XK_Return, skui::Key::kOK }, 202 { XK_Up, skui::Key::kUp }, 203 { XK_Down, skui::Key::kDown }, 204 { XK_Left, skui::Key::kLeft }, 205 { XK_Right, skui::Key::kRight }, 206 { XK_Tab, skui::Key::kTab }, 207 { XK_Page_Up, skui::Key::kPageUp }, 208 { XK_Page_Down, skui::Key::kPageDown }, 209 { XK_Home, skui::Key::kHome }, 210 { XK_End, skui::Key::kEnd }, 211 { XK_Delete, skui::Key::kDelete }, 212 { XK_Escape, skui::Key::kEscape }, 213 { XK_Shift_L, skui::Key::kShift }, 214 { XK_Shift_R, skui::Key::kShift }, 215 { XK_Control_L, skui::Key::kCtrl }, 216 { XK_Control_R, skui::Key::kCtrl }, 217 { XK_Alt_L, skui::Key::kOption }, 218 { XK_Alt_R, skui::Key::kOption }, 219 { 'a', skui::Key::kA }, 220 { 'c', skui::Key::kC }, 221 { 'v', skui::Key::kV }, 222 { 'x', skui::Key::kX }, 223 { 'y', skui::Key::kY }, 224 { 'z', skui::Key::kZ }, 225 }; 226 for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { 227 if (gPair[i].fXK == keysym) { 228 return gPair[i].fKey; 229 } 230 } 231 return skui::Key::kNONE; 232} 233 234static skui::ModifierKey get_modifiers(const XEvent& event) { 235 static const struct { 236 unsigned fXMask; 237 skui::ModifierKey fSkMask; 238 } gModifiers[] = { 239 { ShiftMask, skui::ModifierKey::kShift }, 240 { ControlMask, skui::ModifierKey::kControl }, 241 { Mod1Mask, skui::ModifierKey::kOption }, 242 }; 243 244 skui::ModifierKey modifiers = skui::ModifierKey::kNone; 245 for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) { 246 if (event.xkey.state & gModifiers[i].fXMask) { 247 modifiers |= gModifiers[i].fSkMask; 248 } 249 } 250 return modifiers; 251} 252 253bool Window_unix::handleEvent(const XEvent& event) { 254 switch (event.type) { 255 case MapNotify: 256 if (!fGC) { 257 fGC = XCreateGC(fDisplay, fWindow, 0, nullptr); 258 } 259 break; 260 261 case ClientMessage: 262 if ((Atom)event.xclient.data.l[0] == fWmDeleteMessage && 263 gWindowMap.count() == 1) { 264 return true; 265 } 266 break; 267 268 case ButtonPress: 269 switch (event.xbutton.button) { 270 case Button1: 271 this->onMouse(event.xbutton.x, event.xbutton.y, 272 skui::InputState::kDown, get_modifiers(event)); 273 break; 274 case Button4: 275 this->onMouseWheel(1.0f, get_modifiers(event)); 276 break; 277 case Button5: 278 this->onMouseWheel(-1.0f, get_modifiers(event)); 279 break; 280 } 281 break; 282 283 case ButtonRelease: 284 if (event.xbutton.button == Button1) { 285 this->onMouse(event.xbutton.x, event.xbutton.y, 286 skui::InputState::kUp, get_modifiers(event)); 287 } 288 break; 289 290 case MotionNotify: 291 this->onMouse(event.xmotion.x, event.xmotion.y, 292 skui::InputState::kMove, get_modifiers(event)); 293 break; 294 295 case KeyPress: { 296 int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0; 297 KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode, 0, shiftLevel); 298 skui::Key key = get_key(keysym); 299 if (key != skui::Key::kNONE) { 300 if (!this->onKey(key, skui::InputState::kDown, get_modifiers(event))) { 301 if (keysym == XK_Escape) { 302 return true; 303 } 304 } 305 } 306 307 long uni = keysym2ucs(keysym); 308 if (uni != -1) { 309 (void) this->onChar((SkUnichar) uni, get_modifiers(event)); 310 } 311 } break; 312 313 case KeyRelease: { 314 int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0; 315 KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode, 316 0, shiftLevel); 317 skui::Key key = get_key(keysym); 318 (void) this->onKey(key, skui::InputState::kUp, 319 get_modifiers(event)); 320 } break; 321 322 case SelectionClear: { 323 // Lost selection ownership 324 fClipboardText.clear(); 325 } break; 326 327 case SelectionRequest: { 328 Atom UTF8 = XInternAtom(fDisplay, "UTF8_STRING", 0), 329 CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0); 330 331 const XSelectionRequestEvent* xsr = &event.xselectionrequest; 332 333 XSelectionEvent xsel = {}; 334 xsel.type = SelectionNotify; 335 xsel.requestor = xsr->requestor; 336 xsel.selection = xsr->selection; 337 xsel.target = xsr->target; 338 xsel.property = xsr->property; 339 xsel.time = xsr->time; 340 341 if (xsr->selection != CLIPBOARD) { 342 // A request for a different kind of selection. This shouldn't happen. 343 break; 344 } 345 346 if (fClipboardText.empty() || xsr->target != UTF8 || xsr->property == None) { 347 // We can't fulfill this request. Deny it. 348 xsel.property = None; 349 XSendEvent(fDisplay, xsr->requestor, True, NoEventMask, (XEvent*)&xsel); 350 } else { 351 // We can fulfill this request! Update the contents of the CLIPBOARD property, 352 // and let the requestor know. 353 XChangeProperty(fDisplay, xsr->requestor, xsr->property, UTF8, /*format=*/8, 354 PropModeReplace, (unsigned char*)fClipboardText.data(), 355 fClipboardText.length()); 356 XSendEvent(fDisplay, xsr->requestor, True, NoEventMask, (XEvent*)&xsel); 357 } 358 } break; 359 360 default: 361 // these events should be handled in the main event loop 362 SkASSERT(event.type != Expose && event.type != ConfigureNotify); 363 break; 364 } 365 366 return false; 367} 368 369void Window_unix::setTitle(const char* title) { 370 XTextProperty textproperty; 371 if (!XStringListToTextProperty(const_cast<char**>(&title), 1, &textproperty)) { 372 return; 373 } 374 XSetWMName(fDisplay, fWindow, &textproperty); 375 XFree(textproperty.value); 376} 377 378void Window_unix::show() { 379 XMapWindow(fDisplay, fWindow); 380} 381 382bool Window_unix::attach(BackendType attachType) { 383 fBackend = attachType; 384 385 this->initWindow(fDisplay); 386 387 window_context_factory::XlibWindowInfo winInfo; 388 winInfo.fDisplay = fDisplay; 389 winInfo.fWindow = fWindow; 390 winInfo.fFBConfig = fFBConfig; 391 winInfo.fVisualInfo = fVisualInfo; 392 393 XWindowAttributes attrs; 394 if (XGetWindowAttributes(fDisplay, fWindow, &attrs)) { 395 winInfo.fWidth = attrs.width; 396 winInfo.fHeight = attrs.height; 397 } else { 398 winInfo.fWidth = winInfo.fHeight = 0; 399 } 400 401 switch (attachType) { 402#ifdef SK_DAWN 403 case kDawn_BackendType: 404 fWindowContext = 405 window_context_factory::MakeDawnVulkanForXlib(winInfo, fRequestedDisplayParams); 406 break; 407#endif 408#ifdef SK_VULKAN 409 case kVulkan_BackendType: 410 fWindowContext = 411 window_context_factory::MakeVulkanForXlib(winInfo, fRequestedDisplayParams); 412 break; 413#endif 414#ifdef SK_GL 415 case kNativeGL_BackendType: 416 fWindowContext = 417 window_context_factory::MakeGLForXlib(winInfo, fRequestedDisplayParams); 418 break; 419#endif 420 case kRaster_BackendType: 421 fWindowContext = 422 window_context_factory::MakeRasterForXlib(winInfo, fRequestedDisplayParams); 423 break; 424 } 425 this->onBackendCreated(); 426 427 return (SkToBool(fWindowContext)); 428} 429 430void Window_unix::onInval() { 431 XEvent event; 432 event.type = Expose; 433 event.xexpose.send_event = True; 434 event.xexpose.display = fDisplay; 435 event.xexpose.window = fWindow; 436 event.xexpose.x = 0; 437 event.xexpose.y = 0; 438 event.xexpose.width = this->width(); 439 event.xexpose.height = this->height(); 440 event.xexpose.count = 0; 441 442 XSendEvent(fDisplay, fWindow, False, 0, &event); 443} 444 445void Window_unix::setRequestedDisplayParams(const DisplayParams& params, bool allowReattach) { 446#if defined(SK_VULKAN) 447 // Vulkan on unix crashes if we try to reinitialize the vulkan context without remaking the 448 // window. 449 if (fBackend == kVulkan_BackendType && allowReattach) { 450 // Need to change these early, so attach() creates the window context correctly 451 fRequestedDisplayParams = params; 452 453 this->detach(); 454 this->attach(fBackend); 455 return; 456 } 457#endif 458 459 INHERITED::setRequestedDisplayParams(params, allowReattach); 460} 461 462const char* Window_unix::getClipboardText() { 463 Atom UTF8 = XInternAtom(fDisplay, "UTF8_STRING", 0), 464 CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0), 465 XSEL_DATA = XInternAtom(fDisplay, "XSEL_DATA", 0); 466 467 // Ask for a UTF8 copy of the CLIPBOARD... 468 XEvent event; 469 XConvertSelection(fDisplay, CLIPBOARD, UTF8, XSEL_DATA, fWindow, CurrentTime); 470 XSync(fDisplay, 0); 471 XNextEvent(fDisplay, &event); 472 if (event.type == SelectionNotify && 473 event.xselection.selection == CLIPBOARD && 474 event.xselection.property != None) { 475 476 // We got a response 477 Atom type; 478 int format; 479 unsigned long nitems, bytes_after; 480 char* data; 481 482 // Fetch the CLIPBOARD property 483 XSelectionEvent xsel = event.xselection; 484 XGetWindowProperty(xsel.display, xsel.requestor, xsel.property, /*offset=*/0, 485 /*length=*/~0L, /*delete=*/False, AnyPropertyType, &type, &format, 486 &nitems, &bytes_after, (unsigned char**)&data); 487 SkASSERT(bytes_after == 0); 488 if (type == UTF8) { 489 fClipboardText.assign(data, nitems); 490 } 491 XFree(data); 492 XDeleteProperty(xsel.display, xsel.requestor, xsel.property); 493 } 494 return fClipboardText.c_str(); 495} 496 497void Window_unix::setClipboardText(const char* text) { 498 fClipboardText.assign(text); 499 500 // Take ownership of the CLIPBOARD 501 Atom CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0); 502 XSetSelectionOwner(fDisplay, CLIPBOARD, fWindow, CurrentTime); 503} 504 505} // namespace sk_app 506