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