Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Window_unix.cpp
Go to the documentation of this file.
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
9
10#include "src/base/SkUTF.h"
13#include "tools/timer/Timer.h"
15
16extern "C" {
18}
19#include <X11/Xatom.h>
20#include <X11/Xutil.h>
21#include <X11/XKBlib.h>
22
23namespace sk_app {
24
26
27Window* 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
40const long kEventMask = ExposureMask | StructureNotifyMask |
41 KeyPressMask | KeyReleaseMask |
42 PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
43
44bool 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);
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] =
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
168void 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
190static 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
230static 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
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
249bool 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,
269 break;
270 case Button4:
271 this->onMouseWheel(1.0f, 0, 0, get_modifiers(event));
272 break;
273 case Button5:
274 this->onMouseWheel(-1.0f, 0, 0, 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,
283 }
284 break;
285
286 case MotionNotify:
287 this->onMouse(event.xmotion.x, event.xmotion.y,
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) {
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,
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
365void 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
375 XMapWindow(fDisplay, fWindow);
376}
377
379 fBackend = attachType;
380
381 this->initWindow(fDisplay);
382
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#if defined(SK_DAWN) && defined(SK_GRAPHITE)
399 case kGraphiteDawn_BackendType:
400 fWindowContext = skwindow::MakeGraphiteDawnVulkanForXlib(winInfo,
402 break;
403#endif
404#ifdef SK_VULKAN
405 case kVulkan_BackendType:
407 break;
408#endif
409#if defined(SK_VULKAN) && defined(SK_GRAPHITE)
410 case kGraphiteVulkan_BackendType:
412 break;
413#endif
414#ifdef SK_GL
415 case kNativeGL_BackendType:
416 fWindowContext = skwindow::MakeGLForXlib(winInfo, fRequestedDisplayParams);
417 break;
418#endif
421 break;
422 }
423 this->onBackendCreated();
424
425 return (SkToBool(fWindowContext));
426}
427
429 XEvent event;
430 event.type = Expose;
431 event.xexpose.send_event = True;
432 event.xexpose.display = fDisplay;
433 event.xexpose.window = fWindow;
434 event.xexpose.x = 0;
435 event.xexpose.y = 0;
436 event.xexpose.width = this->width();
437 event.xexpose.height = this->height();
438 event.xexpose.count = 0;
439
440 XSendEvent(fDisplay, fWindow, False, 0, &event);
441}
442
444#if defined(SK_VULKAN)
445 // Vulkan on unix crashes if we try to reinitialize the vulkan context without remaking the
446 // window.
447 if (fBackend == kVulkan_BackendType && allowReattach) {
448 // Need to change these early, so attach() creates the window context correctly
450
451 this->detach();
452 this->attach(fBackend);
453 return;
454 }
455#endif
456
458}
459
461 Atom UTF8 = XInternAtom(fDisplay, "UTF8_STRING", 0),
462 CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0),
463 XSEL_DATA = XInternAtom(fDisplay, "XSEL_DATA", 0);
464
465 // Ask for a UTF8 copy of the CLIPBOARD...
466 XEvent event;
467 XConvertSelection(fDisplay, CLIPBOARD, UTF8, XSEL_DATA, fWindow, CurrentTime);
468 XSync(fDisplay, 0);
469 XNextEvent(fDisplay, &event);
470 if (event.type == SelectionNotify &&
471 event.xselection.selection == CLIPBOARD &&
472 event.xselection.property != None) {
473
474 // We got a response
475 Atom type;
476 int format;
477 unsigned long nitems, bytes_after;
478 char* data;
479
480 // Fetch the CLIPBOARD property
481 XSelectionEvent xsel = event.xselection;
482 XGetWindowProperty(xsel.display, xsel.requestor, xsel.property, /*offset=*/0,
483 /*length=*/~0L, /*delete=*/False, AnyPropertyType, &type, &format,
484 &nitems, &bytes_after, (unsigned char**)&data);
485 SkASSERT(bytes_after == 0);
486 if (type == UTF8) {
487 fClipboardText.assign(data, nitems);
488 }
489 XFree(data);
490 XDeleteProperty(xsel.display, xsel.requestor, xsel.property);
491 }
492 return fClipboardText.c_str();
493}
494
496 fClipboardText.assign(text);
497
498 // Take ownership of the CLIPBOARD
499 Atom CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0);
500 XSetSelectionOwner(fDisplay, CLIPBOARD, fWindow, CurrentTime);
501}
502
503} // namespace sk_app
#define SkASSERT(cond)
Definition SkAssert.h:116
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
int32_t SkUnichar
Definition SkTypes.h:175
void show() override
bool handleEvent(const XEvent &event)
void setTitle(const char *) override
const char * getClipboardText() override
bool attach(BackendType) override
static SkTDynamicHash< Window_unix, XWindow > gWindowMap
Definition Window_unix.h:58
void onInval() override
bool initWindow(Display *display)
void setClipboardText(const char *) override
void setRequestedDisplayParams(const DisplayParams &, bool allowReattach) override
bool onKey(skui::Key key, skui::InputState state, skui::ModifierKey modifiers)
Definition Window.cpp:52
bool onChar(SkUnichar c, skui::ModifierKey modifiers)
Definition Window.cpp:48
static Window * CreateNativeWindow(void *platformData)
void detach()
Definition Window.cpp:25
@ kRaster_BackendType
Definition Window.h:90
virtual void setRequestedDisplayParams(const DisplayParams &, bool allowReattach=true)
Definition Window.cpp:137
int height() const
Definition Window.cpp:130
bool onMouseWheel(float delta, int x, int y, skui::ModifierKey modifiers)
Definition Window.cpp:60
DisplayParams fRequestedDisplayParams
Definition Window.h:171
std::unique_ptr< skwindow::WindowContext > fWindowContext
Definition Window.h:174
bool onMouse(int x, int y, skui::InputState state, skui::ModifierKey modifiers)
Definition Window.cpp:56
int width() const
Definition Window.cpp:123
void onBackendCreated()
Definition Window.cpp:44
const EmbeddedViewParams * params
GLFWwindow * window
Definition main.cc:45
FlKeyEvent * event
uint32_t uint32_t * format
std::u16string text
long keysym2ucs(KeySym keysym)
Definition keysym2ucs.c:818
static skui::ModifierKey get_modifiers(const XEvent &event)
static skui::Key get_key(KeySym keysym)
const long kEventMask
ModifierKey
Definition ModifierKey.h:9
Key
Definition Key.h:6
std::unique_ptr< WindowContext > MakeGraphiteVulkanForXlib(const XlibWindowInfo &info, const DisplayParams &displayParams)
std::unique_ptr< WindowContext > MakeRasterForXlib(const XlibWindowInfo &info, const DisplayParams &params)
std::unique_ptr< WindowContext > MakeVulkanForXlib(const XlibWindowInfo &info, const DisplayParams &displayParams)
GdkModifierType state
guint16 keycode