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