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
23extern "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
30namespace sk_app {
31
32SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap;
33
34Window* 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
47const long kEventMask = ExposureMask | StructureNotifyMask |
48                        KeyPressMask | KeyReleaseMask |
49                        PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
50
51bool 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
172void 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
194static 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
234static 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
253bool 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
369void 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
378void Window_unix::show() {
379    XMapWindow(fDisplay, fWindow);
380}
381
382bool 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
430void 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
445void 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
462const 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
497void 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