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