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
23 extern "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
30 namespace sk_app {
31
32 SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap;
33
CreateNativeWindow(void * platformData)34 Window* 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
47 const long kEventMask = ExposureMask | StructureNotifyMask |
48 KeyPressMask | KeyReleaseMask |
49 PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
50
initWindow(Display * display)51 bool 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
closeWindow()172 void 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
get_key(KeySym keysym)194 static 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
get_modifiers(const XEvent & event)234 static 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
handleEvent(const XEvent & event)253 bool 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
setTitle(const char * title)369 void 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
show()378 void Window_unix::show() {
379 XMapWindow(fDisplay, fWindow);
380 }
381
attach(BackendType attachType)382 bool 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
onInval()430 void 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
setRequestedDisplayParams(const DisplayParams & params,bool allowReattach)445 void 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
getClipboardText()462 const 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
setClipboardText(const char * text)497 void 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