• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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