• 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 * 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