blob: 2f9351ff1eb013673eea791c592c8828421b258b [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tools/sk_app/unix/WindowContextFactory_unix.h"
#include "src/utils/SkUTF.h"
#include "tools/sk_app/WindowContext.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;
#ifdef SK_GL
// 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);
}
#endif
if (!fWindow) {
// 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