056bd0dd98
Running 'viewer' on Linux with an ASan build produced LSan reports about leaking textproperty.value. The documentation for XStringListToTextProperty states that it is up to the user to free the XTextProperty::value returned with XFree. In addition, check the return value of XStringListToTextProperty for failure. Change-Id: I0db45d3d94f7c8126049c6a343b1aa121f9a7523 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/357597 Reviewed-by: Brian Osman <brianosman@google.com> Reviewed-by: Jim Van Verth <jvanverth@google.com>
506 lines
17 KiB
C++
506 lines
17 KiB
C++
/*
|
|
* Copyright 2016 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* f 49
|
|
* Prev
|
|
* Up
|
|
*
|
|
*
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
//#include <tchar.h>
|
|
|
|
#include "tools/sk_app/unix/WindowContextFactory_unix.h"
|
|
|
|
#include "src/utils/SkUTF.h"
|
|
#include "tools/sk_app/GLWindowContext.h"
|
|
#include "tools/sk_app/unix/Window_unix.h"
|
|
#include "tools/skui/ModifierKey.h"
|
|
#include "tools/timer/Timer.h"
|
|
|
|
extern "C" {
|
|
#include "tools/sk_app/unix/keysym2ucs.h"
|
|
}
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/XKBlib.h>
|
|
|
|
namespace sk_app {
|
|
|
|
SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap;
|
|
|
|
Window* Window::CreateNativeWindow(void* platformData) {
|
|
Display* display = (Display*)platformData;
|
|
SkASSERT(display);
|
|
|
|
Window_unix* window = new Window_unix();
|
|
if (!window->initWindow(display)) {
|
|
delete window;
|
|
return nullptr;
|
|
}
|
|
|
|
return window;
|
|
}
|
|
|
|
const long kEventMask = ExposureMask | StructureNotifyMask |
|
|
KeyPressMask | KeyReleaseMask |
|
|
PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
|
|
|
|
bool Window_unix::initWindow(Display* display) {
|
|
if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
|
|
this->closeWindow();
|
|
}
|
|
// we already have a window
|
|
if (fDisplay) {
|
|
return true;
|
|
}
|
|
fDisplay = display;
|
|
|
|
constexpr int initialWidth = 1280;
|
|
constexpr int initialHeight = 960;
|
|
|
|
// Attempt to create a window that supports GL
|
|
|
|
// We prefer the more recent glXChooseFBConfig but fall back to glXChooseVisual. They have
|
|
// slight differences in how attributes are specified.
|
|
static int constexpr kChooseFBConfigAtt[] = {
|
|
GLX_RENDER_TYPE, GLX_RGBA_BIT,
|
|
GLX_DOUBLEBUFFER, True,
|
|
GLX_STENCIL_SIZE, 8,
|
|
None
|
|
};
|
|
// For some reason glXChooseVisual takes a non-const pointer to the attributes.
|
|
int chooseVisualAtt[] = {
|
|
GLX_RGBA,
|
|
GLX_DOUBLEBUFFER,
|
|
GLX_STENCIL_SIZE, 8,
|
|
None
|
|
};
|
|
SkASSERT(nullptr == fVisualInfo);
|
|
if (fRequestedDisplayParams.fMSAASampleCount > 1) {
|
|
static const GLint kChooseFBConifgAttCnt = SK_ARRAY_COUNT(kChooseFBConfigAtt);
|
|
GLint msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 4];
|
|
memcpy(msaaChooseFBConfigAtt, kChooseFBConfigAtt, sizeof(kChooseFBConfigAtt));
|
|
SkASSERT(None == msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1]);
|
|
msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
|
|
msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 0] = 1;
|
|
msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 1] = GLX_SAMPLES_ARB;
|
|
msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 2] = fRequestedDisplayParams.fMSAASampleCount;
|
|
msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 3] = None;
|
|
int n;
|
|
fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), msaaChooseFBConfigAtt, &n);
|
|
if (n > 0) {
|
|
fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
|
|
} else {
|
|
static const GLint kChooseVisualAttCnt = SK_ARRAY_COUNT(chooseVisualAtt);
|
|
GLint msaaChooseVisualAtt[kChooseVisualAttCnt + 4];
|
|
memcpy(msaaChooseVisualAtt, chooseVisualAtt, sizeof(chooseVisualAtt));
|
|
SkASSERT(None == msaaChooseVisualAtt[kChooseVisualAttCnt - 1]);
|
|
msaaChooseFBConfigAtt[kChooseVisualAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
|
|
msaaChooseFBConfigAtt[kChooseVisualAttCnt + 0] = 1;
|
|
msaaChooseFBConfigAtt[kChooseVisualAttCnt + 1] = GLX_SAMPLES_ARB;
|
|
msaaChooseFBConfigAtt[kChooseVisualAttCnt + 2] =
|
|
fRequestedDisplayParams.fMSAASampleCount;
|
|
msaaChooseFBConfigAtt[kChooseVisualAttCnt + 3] = None;
|
|
fVisualInfo = glXChooseVisual(display, DefaultScreen(display), msaaChooseVisualAtt);
|
|
fFBConfig = nullptr;
|
|
}
|
|
}
|
|
if (nullptr == fVisualInfo) {
|
|
int n;
|
|
fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), kChooseFBConfigAtt, &n);
|
|
if (n > 0) {
|
|
fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
|
|
} else {
|
|
fVisualInfo = glXChooseVisual(display, DefaultScreen(display), chooseVisualAtt);
|
|
fFBConfig = nullptr;
|
|
}
|
|
}
|
|
|
|
if (fVisualInfo) {
|
|
Colormap colorMap = XCreateColormap(display,
|
|
RootWindow(display, fVisualInfo->screen),
|
|
fVisualInfo->visual,
|
|
AllocNone);
|
|
XSetWindowAttributes swa;
|
|
swa.colormap = colorMap;
|
|
swa.event_mask = kEventMask;
|
|
fWindow = XCreateWindow(display,
|
|
RootWindow(display, fVisualInfo->screen),
|
|
0, 0, // x, y
|
|
initialWidth, initialHeight,
|
|
0, // border width
|
|
fVisualInfo->depth,
|
|
InputOutput,
|
|
fVisualInfo->visual,
|
|
CWEventMask | CWColormap,
|
|
&swa);
|
|
} else {
|
|
// Create a simple window instead. We will not be able to show GL
|
|
fWindow = XCreateSimpleWindow(display,
|
|
DefaultRootWindow(display),
|
|
0, 0, // x, y
|
|
initialWidth, initialHeight,
|
|
0, // border width
|
|
0, // border value
|
|
0); // background value
|
|
XSelectInput(display, fWindow, kEventMask);
|
|
}
|
|
|
|
if (!fWindow) {
|
|
return false;
|
|
}
|
|
|
|
fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
|
|
|
|
// set up to catch window delete message
|
|
fWmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
|
XSetWMProtocols(display, fWindow, &fWmDeleteMessage, 1);
|
|
|
|
// add to hashtable of windows
|
|
gWindowMap.add(this);
|
|
|
|
// init event variables
|
|
fPendingPaint = false;
|
|
fPendingResize = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Window_unix::closeWindow() {
|
|
if (fDisplay) {
|
|
this->detach();
|
|
if (fGC) {
|
|
XFreeGC(fDisplay, fGC);
|
|
fGC = nullptr;
|
|
}
|
|
gWindowMap.remove(fWindow);
|
|
XDestroyWindow(fDisplay, fWindow);
|
|
fWindow = 0;
|
|
if (fFBConfig) {
|
|
XFree(fFBConfig);
|
|
fFBConfig = nullptr;
|
|
}
|
|
if (fVisualInfo) {
|
|
XFree(fVisualInfo);
|
|
fVisualInfo = nullptr;
|
|
}
|
|
fDisplay = nullptr;
|
|
}
|
|
}
|
|
|
|
static skui::Key get_key(KeySym keysym) {
|
|
static const struct {
|
|
KeySym fXK;
|
|
skui::Key fKey;
|
|
} gPair[] = {
|
|
{ XK_BackSpace, skui::Key::kBack },
|
|
{ XK_Clear, skui::Key::kBack },
|
|
{ XK_Return, skui::Key::kOK },
|
|
{ XK_Up, skui::Key::kUp },
|
|
{ XK_Down, skui::Key::kDown },
|
|
{ XK_Left, skui::Key::kLeft },
|
|
{ XK_Right, skui::Key::kRight },
|
|
{ XK_Tab, skui::Key::kTab },
|
|
{ XK_Page_Up, skui::Key::kPageUp },
|
|
{ XK_Page_Down, skui::Key::kPageDown },
|
|
{ XK_Home, skui::Key::kHome },
|
|
{ XK_End, skui::Key::kEnd },
|
|
{ XK_Delete, skui::Key::kDelete },
|
|
{ XK_Escape, skui::Key::kEscape },
|
|
{ XK_Shift_L, skui::Key::kShift },
|
|
{ XK_Shift_R, skui::Key::kShift },
|
|
{ XK_Control_L, skui::Key::kCtrl },
|
|
{ XK_Control_R, skui::Key::kCtrl },
|
|
{ XK_Alt_L, skui::Key::kOption },
|
|
{ XK_Alt_R, skui::Key::kOption },
|
|
{ 'a', skui::Key::kA },
|
|
{ 'c', skui::Key::kC },
|
|
{ 'v', skui::Key::kV },
|
|
{ 'x', skui::Key::kX },
|
|
{ 'y', skui::Key::kY },
|
|
{ 'z', skui::Key::kZ },
|
|
};
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
|
|
if (gPair[i].fXK == keysym) {
|
|
return gPair[i].fKey;
|
|
}
|
|
}
|
|
return skui::Key::kNONE;
|
|
}
|
|
|
|
static skui::ModifierKey get_modifiers(const XEvent& event) {
|
|
static const struct {
|
|
unsigned fXMask;
|
|
skui::ModifierKey fSkMask;
|
|
} gModifiers[] = {
|
|
{ ShiftMask, skui::ModifierKey::kShift },
|
|
{ ControlMask, skui::ModifierKey::kControl },
|
|
{ Mod1Mask, skui::ModifierKey::kOption },
|
|
};
|
|
|
|
skui::ModifierKey modifiers = skui::ModifierKey::kNone;
|
|
for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
|
|
if (event.xkey.state & gModifiers[i].fXMask) {
|
|
modifiers |= gModifiers[i].fSkMask;
|
|
}
|
|
}
|
|
return modifiers;
|
|
}
|
|
|
|
bool Window_unix::handleEvent(const XEvent& event) {
|
|
switch (event.type) {
|
|
case MapNotify:
|
|
if (!fGC) {
|
|
fGC = XCreateGC(fDisplay, fWindow, 0, nullptr);
|
|
}
|
|
break;
|
|
|
|
case ClientMessage:
|
|
if ((Atom)event.xclient.data.l[0] == fWmDeleteMessage &&
|
|
gWindowMap.count() == 1) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case ButtonPress:
|
|
switch (event.xbutton.button) {
|
|
case Button1:
|
|
this->onMouse(event.xbutton.x, event.xbutton.y,
|
|
skui::InputState::kDown, get_modifiers(event));
|
|
break;
|
|
case Button4:
|
|
this->onMouseWheel(1.0f, get_modifiers(event));
|
|
break;
|
|
case Button5:
|
|
this->onMouseWheel(-1.0f, get_modifiers(event));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
if (event.xbutton.button == Button1) {
|
|
this->onMouse(event.xbutton.x, event.xbutton.y,
|
|
skui::InputState::kUp, get_modifiers(event));
|
|
}
|
|
break;
|
|
|
|
case MotionNotify:
|
|
this->onMouse(event.xmotion.x, event.xmotion.y,
|
|
skui::InputState::kMove, get_modifiers(event));
|
|
break;
|
|
|
|
case KeyPress: {
|
|
int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
|
|
KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode, 0, shiftLevel);
|
|
skui::Key key = get_key(keysym);
|
|
if (key != skui::Key::kNONE) {
|
|
if (!this->onKey(key, skui::InputState::kDown, get_modifiers(event))) {
|
|
if (keysym == XK_Escape) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
long uni = keysym2ucs(keysym);
|
|
if (uni != -1) {
|
|
(void) this->onChar((SkUnichar) uni, get_modifiers(event));
|
|
}
|
|
} break;
|
|
|
|
case KeyRelease: {
|
|
int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
|
|
KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode,
|
|
0, shiftLevel);
|
|
skui::Key key = get_key(keysym);
|
|
(void) this->onKey(key, skui::InputState::kUp,
|
|
get_modifiers(event));
|
|
} break;
|
|
|
|
case SelectionClear: {
|
|
// Lost selection ownership
|
|
fClipboardText.clear();
|
|
} break;
|
|
|
|
case SelectionRequest: {
|
|
Atom UTF8 = XInternAtom(fDisplay, "UTF8_STRING", 0),
|
|
CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0);
|
|
|
|
const XSelectionRequestEvent* xsr = &event.xselectionrequest;
|
|
|
|
XSelectionEvent xsel = {};
|
|
xsel.type = SelectionNotify;
|
|
xsel.requestor = xsr->requestor;
|
|
xsel.selection = xsr->selection;
|
|
xsel.target = xsr->target;
|
|
xsel.property = xsr->property;
|
|
xsel.time = xsr->time;
|
|
|
|
if (xsr->selection != CLIPBOARD) {
|
|
// A request for a different kind of selection. This shouldn't happen.
|
|
break;
|
|
}
|
|
|
|
if (fClipboardText.empty() || xsr->target != UTF8 || xsr->property == None) {
|
|
// We can't fulfill this request. Deny it.
|
|
xsel.property = None;
|
|
XSendEvent(fDisplay, xsr->requestor, True, NoEventMask, (XEvent*)&xsel);
|
|
} else {
|
|
// We can fulfill this request! Update the contents of the CLIPBOARD property,
|
|
// and let the requestor know.
|
|
XChangeProperty(fDisplay, xsr->requestor, xsr->property, UTF8, /*format=*/8,
|
|
PropModeReplace, (unsigned char*)fClipboardText.data(),
|
|
fClipboardText.length());
|
|
XSendEvent(fDisplay, xsr->requestor, True, NoEventMask, (XEvent*)&xsel);
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
// these events should be handled in the main event loop
|
|
SkASSERT(event.type != Expose && event.type != ConfigureNotify);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Window_unix::setTitle(const char* title) {
|
|
XTextProperty textproperty;
|
|
if (!XStringListToTextProperty(const_cast<char**>(&title), 1, &textproperty)) {
|
|
return;
|
|
}
|
|
XSetWMName(fDisplay, fWindow, &textproperty);
|
|
XFree(textproperty.value);
|
|
}
|
|
|
|
void Window_unix::show() {
|
|
XMapWindow(fDisplay, fWindow);
|
|
}
|
|
|
|
bool Window_unix::attach(BackendType attachType) {
|
|
fBackend = attachType;
|
|
|
|
this->initWindow(fDisplay);
|
|
|
|
window_context_factory::XlibWindowInfo winInfo;
|
|
winInfo.fDisplay = fDisplay;
|
|
winInfo.fWindow = fWindow;
|
|
winInfo.fFBConfig = fFBConfig;
|
|
winInfo.fVisualInfo = fVisualInfo;
|
|
|
|
XWindowAttributes attrs;
|
|
if (XGetWindowAttributes(fDisplay, fWindow, &attrs)) {
|
|
winInfo.fWidth = attrs.width;
|
|
winInfo.fHeight = attrs.height;
|
|
} else {
|
|
winInfo.fWidth = winInfo.fHeight = 0;
|
|
}
|
|
|
|
switch (attachType) {
|
|
#ifdef SK_DAWN
|
|
case kDawn_BackendType:
|
|
fWindowContext =
|
|
window_context_factory::MakeDawnVulkanForXlib(winInfo, fRequestedDisplayParams);
|
|
break;
|
|
#endif
|
|
#ifdef SK_VULKAN
|
|
case kVulkan_BackendType:
|
|
fWindowContext =
|
|
window_context_factory::MakeVulkanForXlib(winInfo, fRequestedDisplayParams);
|
|
break;
|
|
#endif
|
|
#ifdef SK_GL
|
|
case kNativeGL_BackendType:
|
|
fWindowContext =
|
|
window_context_factory::MakeGLForXlib(winInfo, fRequestedDisplayParams);
|
|
break;
|
|
#endif
|
|
case kRaster_BackendType:
|
|
fWindowContext =
|
|
window_context_factory::MakeRasterForXlib(winInfo, fRequestedDisplayParams);
|
|
break;
|
|
}
|
|
this->onBackendCreated();
|
|
|
|
return (SkToBool(fWindowContext));
|
|
}
|
|
|
|
void Window_unix::onInval() {
|
|
XEvent event;
|
|
event.type = Expose;
|
|
event.xexpose.send_event = True;
|
|
event.xexpose.display = fDisplay;
|
|
event.xexpose.window = fWindow;
|
|
event.xexpose.x = 0;
|
|
event.xexpose.y = 0;
|
|
event.xexpose.width = this->width();
|
|
event.xexpose.height = this->height();
|
|
event.xexpose.count = 0;
|
|
|
|
XSendEvent(fDisplay, fWindow, False, 0, &event);
|
|
}
|
|
|
|
void Window_unix::setRequestedDisplayParams(const DisplayParams& params, bool allowReattach) {
|
|
#if defined(SK_VULKAN)
|
|
// Vulkan on unix crashes if we try to reinitialize the vulkan context without remaking the
|
|
// window.
|
|
if (fBackend == kVulkan_BackendType && allowReattach) {
|
|
// Need to change these early, so attach() creates the window context correctly
|
|
fRequestedDisplayParams = params;
|
|
|
|
this->detach();
|
|
this->attach(fBackend);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
INHERITED::setRequestedDisplayParams(params, allowReattach);
|
|
}
|
|
|
|
const char* Window_unix::getClipboardText() {
|
|
Atom UTF8 = XInternAtom(fDisplay, "UTF8_STRING", 0),
|
|
CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0),
|
|
XSEL_DATA = XInternAtom(fDisplay, "XSEL_DATA", 0);
|
|
|
|
// Ask for a UTF8 copy of the CLIPBOARD...
|
|
XEvent event;
|
|
XConvertSelection(fDisplay, CLIPBOARD, UTF8, XSEL_DATA, fWindow, CurrentTime);
|
|
XSync(fDisplay, 0);
|
|
XNextEvent(fDisplay, &event);
|
|
if (event.type == SelectionNotify &&
|
|
event.xselection.selection == CLIPBOARD &&
|
|
event.xselection.property != None) {
|
|
|
|
// We got a response
|
|
Atom type;
|
|
int format;
|
|
unsigned long nitems, bytes_after;
|
|
char* data;
|
|
|
|
// Fetch the CLIPBOARD property
|
|
XSelectionEvent xsel = event.xselection;
|
|
XGetWindowProperty(xsel.display, xsel.requestor, xsel.property, /*offset=*/0,
|
|
/*length=*/~0L, /*delete=*/False, AnyPropertyType, &type, &format,
|
|
&nitems, &bytes_after, (unsigned char**)&data);
|
|
SkASSERT(bytes_after == 0);
|
|
if (type == UTF8) {
|
|
fClipboardText.assign(data, nitems);
|
|
}
|
|
XFree(data);
|
|
XDeleteProperty(xsel.display, xsel.requestor, xsel.property);
|
|
}
|
|
return fClipboardText.c_str();
|
|
}
|
|
|
|
void Window_unix::setClipboardText(const char* text) {
|
|
fClipboardText.assign(text);
|
|
|
|
// Take ownership of the CLIPBOARD
|
|
Atom CLIPBOARD = XInternAtom(fDisplay, "CLIPBOARD", 0);
|
|
XSetSelectionOwner(fDisplay, CLIPBOARD, fWindow, CurrentTime);
|
|
}
|
|
|
|
} // namespace sk_app
|