skia2/tools/sk_app/unix/Window_unix.cpp
Ben Wagner 056bd0dd98 Fix leak of X window title.
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>
2021-01-26 21:44:38 +00:00

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