mirror of
https://github.com/AxioDL/boo.git
synced 2025-05-16 04:11:21 +00:00
Now that we have the fencing and atomic operations in place to ensure access to data on other threads will always occur before the use of delete, we can remove the destructor lock. This will be useful for making ObjToken's move assignment operator noexcept.
1751 lines
60 KiB
C++
1751 lines
60 KiB
C++
#include "boo/IWindow.hpp"
|
|
#include "boo/IGraphicsContext.hpp"
|
|
#include "boo/IApplication.hpp"
|
|
#include "boo/graphicsdev/GL.hpp"
|
|
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
|
#include "boo/graphicsdev/glew.h"
|
|
#include "../Common.hpp"
|
|
|
|
#if BOO_HAS_VULKAN
|
|
#include "boo/graphicsdev/Vulkan.hpp"
|
|
#include <X11/Xlib-xcb.h>
|
|
#endif
|
|
|
|
#include <climits>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <GL/glx.h>
|
|
|
|
#define XK_MISCELLANY
|
|
#define XK_XKB_KEYS
|
|
#define XK_LATIN1
|
|
#include <X11/keysymdef.h>
|
|
#include <X11/XKBlib.h>
|
|
#include <X11/extensions/XInput2.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <logvisor/logvisor.hpp>
|
|
|
|
#include "XlibCommon.hpp"
|
|
|
|
#define REF_DPMM 3.78138
|
|
#define FS_ATOM "_NET_WM_STATE_FULLSCREEN"
|
|
|
|
#define MWM_HINTS_FUNCTIONS (1L << 0)
|
|
#define MWM_HINTS_DECORATIONS (1L << 1)
|
|
|
|
#define MWM_DECOR_BORDER (1L << 1)
|
|
#define MWM_DECOR_RESIZEH (1L << 2)
|
|
#define MWM_DECOR_TITLE (1L << 3)
|
|
#define MWM_DECOR_MENU (1L << 4)
|
|
#define MWM_DECOR_MINIMIZE (1L << 5)
|
|
#define MWM_DECOR_MAXIMIZE (1L << 6)
|
|
|
|
#define MWM_FUNC_RESIZE (1L << 1)
|
|
#define MWM_FUNC_MOVE (1L << 2)
|
|
#define MWM_FUNC_MINIMIZE (1L << 3)
|
|
#define MWM_FUNC_MAXIMIZE (1L << 4)
|
|
#define MWM_FUNC_CLOSE (1L << 5)
|
|
|
|
#undef None
|
|
#undef False
|
|
#undef True
|
|
|
|
using glXCreateContextAttribsARBProc = GLXContext (*)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
|
|
static glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
|
|
static bool s_glxError;
|
|
static int ctxErrorHandler(Display* dpy, XErrorEvent* ev) {
|
|
s_glxError = true;
|
|
return 0;
|
|
}
|
|
|
|
static const int ContextAttribList[7][7] = {
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 5, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 4, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 2, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 0, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
{GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_PROFILE_MASK_ARB,
|
|
GLX_CONTEXT_CORE_PROFILE_BIT_ARB, 0},
|
|
};
|
|
|
|
extern "C" const uint8_t MAINICON_NETWM[];
|
|
extern "C" const size_t MAINICON_NETWM_SZ;
|
|
|
|
namespace boo {
|
|
static logvisor::Module Log("boo::WindowXlib");
|
|
std::unique_ptr<IGraphicsCommandQueue> _NewGLCommandQueue(IGraphicsContext* parent, GLContext* glCtx);
|
|
std::unique_ptr<IGraphicsDataFactory> _NewGLDataFactory(IGraphicsContext* parent, GLContext* glCtx);
|
|
#if BOO_HAS_VULKAN
|
|
std::unique_ptr<IGraphicsCommandQueue> _NewVulkanCommandQueue(VulkanContext* ctx, VulkanContext::Window* windowCtx,
|
|
IGraphicsContext* parent);
|
|
std::unique_ptr<IGraphicsDataFactory> _NewVulkanDataFactory(IGraphicsContext* parent, VulkanContext* ctx);
|
|
#endif
|
|
void _XlibUpdateLastGlxCtx(GLXContext lastGlxCtx);
|
|
void GLXExtensionCheck();
|
|
void GLXEnableVSync(Display* disp, GLXWindow drawable);
|
|
|
|
extern int XINPUT_OPCODE;
|
|
|
|
static std::string translateUTF8(XKeyEvent* ev, XIC xIC) {
|
|
char chs[512];
|
|
KeySym ks;
|
|
Status stat;
|
|
int len = Xutf8LookupString(xIC, ev, chs, 512, &ks, &stat);
|
|
if (len > 1 && (stat == XLookupChars || stat == XLookupBoth))
|
|
return std::string(chs, len);
|
|
return std::string();
|
|
}
|
|
|
|
static char translateKeysym(XKeyEvent* ev, ESpecialKey& specialSym, EModifierKey& modifierSym) {
|
|
KeySym sym = XLookupKeysym(ev, 0);
|
|
|
|
specialSym = ESpecialKey::None;
|
|
modifierSym = EModifierKey::None;
|
|
if (sym >= XK_F1 && sym <= XK_F12)
|
|
specialSym = ESpecialKey(int(ESpecialKey::F1) + sym - XK_F1);
|
|
else if (sym == XK_Escape)
|
|
specialSym = ESpecialKey::Esc;
|
|
else if (sym == XK_Return)
|
|
specialSym = ESpecialKey::Enter;
|
|
else if (sym == XK_BackSpace)
|
|
specialSym = ESpecialKey::Backspace;
|
|
else if (sym == XK_Insert)
|
|
specialSym = ESpecialKey::Insert;
|
|
else if (sym == XK_Delete)
|
|
specialSym = ESpecialKey::Delete;
|
|
else if (sym == XK_Home)
|
|
specialSym = ESpecialKey::Home;
|
|
else if (sym == XK_End)
|
|
specialSym = ESpecialKey::End;
|
|
else if (sym == XK_Page_Up)
|
|
specialSym = ESpecialKey::PgUp;
|
|
else if (sym == XK_Page_Down)
|
|
specialSym = ESpecialKey::PgDown;
|
|
else if (sym == XK_Left)
|
|
specialSym = ESpecialKey::Left;
|
|
else if (sym == XK_Right)
|
|
specialSym = ESpecialKey::Right;
|
|
else if (sym == XK_Up)
|
|
specialSym = ESpecialKey::Up;
|
|
else if (sym == XK_Down)
|
|
specialSym = ESpecialKey::Down;
|
|
else if (sym == XK_Shift_L || sym == XK_Shift_R)
|
|
modifierSym = EModifierKey::Shift;
|
|
else if (sym == XK_Control_L || sym == XK_Control_R)
|
|
modifierSym = EModifierKey::Ctrl;
|
|
else if (sym == XK_Alt_L || sym == XK_Alt_R)
|
|
modifierSym = EModifierKey::Alt;
|
|
else {
|
|
char ch = 0;
|
|
KeySym ks;
|
|
XLookupString(ev, (char*)&ch, 1, &ks, nullptr);
|
|
return ch;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static EModifierKey translateModifiers(unsigned state) {
|
|
EModifierKey retval = EModifierKey::None;
|
|
if (state & ShiftMask)
|
|
retval |= EModifierKey::Shift;
|
|
if (state & ControlMask)
|
|
retval |= EModifierKey::Ctrl;
|
|
if (state & Mod1Mask)
|
|
retval |= EModifierKey::Alt;
|
|
return retval;
|
|
}
|
|
|
|
static EMouseButton translateButton(unsigned detail) {
|
|
switch (detail) {
|
|
case 1:
|
|
return EMouseButton::Primary;
|
|
case 3:
|
|
return EMouseButton::Secondary;
|
|
case 2:
|
|
return EMouseButton::Middle;
|
|
case 8:
|
|
return EMouseButton::Aux1;
|
|
case 9:
|
|
return EMouseButton::Aux2;
|
|
default:
|
|
break;
|
|
}
|
|
return EMouseButton::None;
|
|
}
|
|
|
|
struct XlibAtoms {
|
|
Atom m_wmProtocols = 0;
|
|
Atom m_wmDeleteWindow = 0;
|
|
Atom m_netSupported = 0;
|
|
Atom m_netwmName = 0;
|
|
Atom m_netwmPid = 0;
|
|
Atom m_netwmIcon = 0;
|
|
Atom m_netwmIconName = 0;
|
|
Atom m_netwmState = 0;
|
|
Atom m_netwmStateFullscreen = 0;
|
|
Atom m_netwmStateAdd = 0;
|
|
Atom m_netwmStateRemove = 0;
|
|
Atom m_motifWmHints = 0;
|
|
Atom m_targets = 0;
|
|
Atom m_clipboard = 0;
|
|
Atom m_clipdata = 0;
|
|
Atom m_utf8String = 0;
|
|
Atom m_imagePng = 0;
|
|
XlibAtoms(Display* disp) {
|
|
m_wmProtocols = XInternAtom(disp, "WM_PROTOCOLS", true);
|
|
m_wmDeleteWindow = XInternAtom(disp, "WM_DELETE_WINDOW", true);
|
|
m_netSupported = XInternAtom(disp, "_NET_SUPPORTED", true);
|
|
m_netwmName = XInternAtom(disp, "_NET_WM_NAME", false);
|
|
m_netwmPid = XInternAtom(disp, "_NET_WM_PID", false);
|
|
m_netwmIcon = XInternAtom(disp, "_NET_WM_ICON", false);
|
|
m_netwmIconName = XInternAtom(disp, "_NET_WM_ICON_NAME", false);
|
|
m_netwmState = XInternAtom(disp, "_NET_WM_STATE", false);
|
|
m_netwmStateFullscreen = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", false);
|
|
m_netwmStateAdd = XInternAtom(disp, "_NET_WM_STATE_ADD", false);
|
|
m_netwmStateRemove = XInternAtom(disp, "_NET_WM_STATE_REMOVE", false);
|
|
m_motifWmHints = XInternAtom(disp, "_MOTIF_WM_HINTS", true);
|
|
m_targets = XInternAtom(disp, "TARGETS", false);
|
|
m_clipboard = XInternAtom(disp, "CLIPBOARD", false);
|
|
m_clipdata = XInternAtom(disp, "CLIPDATA", false);
|
|
m_utf8String = XInternAtom(disp, "UTF8_STRING", false);
|
|
m_imagePng = XInternAtom(disp, "image/png", false);
|
|
}
|
|
};
|
|
static XlibAtoms* S_ATOMS = nullptr;
|
|
|
|
static Atom GetClipboardTypeAtom(EClipboardType t) {
|
|
switch (t) {
|
|
case EClipboardType::String:
|
|
return XA_STRING;
|
|
case EClipboardType::UTF8String:
|
|
return S_ATOMS->m_utf8String;
|
|
case EClipboardType::PNGImage:
|
|
return S_ATOMS->m_imagePng;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void genFrameDefault(Screen* screen, int& xOut, int& yOut, int& wOut, int& hOut) {
|
|
float width = screen->width * 2.0 / 3.0;
|
|
float height = screen->height * 2.0 / 3.0;
|
|
xOut = (screen->width - width) / 2.0;
|
|
yOut = (screen->height - height) / 2.0;
|
|
wOut = width;
|
|
hOut = height;
|
|
}
|
|
|
|
static void genFrameDefault(XRRMonitorInfo* screen, int& xOut, int& yOut, int& wOut, int& hOut) {
|
|
float width = screen->width * 2.0 / 3.0;
|
|
float height = screen->height * 2.0 / 3.0;
|
|
xOut = (screen->width - width) / 2.0 + screen->x;
|
|
yOut = (screen->height - height) / 2.0 + screen->y;
|
|
wOut = width;
|
|
hOut = height;
|
|
}
|
|
|
|
struct GraphicsContextXlib : IGraphicsContext {
|
|
EGraphicsAPI m_api;
|
|
EPixelFormat m_pf;
|
|
uint32_t m_drawSamples;
|
|
IWindow* m_parentWindow;
|
|
GLContext* m_glCtx;
|
|
Display* m_xDisp;
|
|
|
|
GraphicsContextXlib(EGraphicsAPI api, EPixelFormat pf, IWindow* parentWindow, Display* disp, GLContext* glCtx)
|
|
: m_api(api), m_pf(pf), m_parentWindow(parentWindow), m_glCtx(glCtx), m_xDisp(disp) {}
|
|
virtual void destroy() = 0;
|
|
virtual void resized(const SWindowRect& rect) = 0;
|
|
};
|
|
|
|
struct GraphicsContextXlibGLX : GraphicsContextXlib {
|
|
GLXContext m_lastCtx = 0;
|
|
|
|
GLXFBConfig m_fbconfig = 0;
|
|
int m_visualid = 0;
|
|
int m_attribIdx = 0;
|
|
GLXWindow m_glxWindow = 0;
|
|
GLXContext m_glxCtx = 0;
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> m_dataFactory;
|
|
std::unique_ptr<IGraphicsCommandQueue> m_commandQueue;
|
|
GLXContext m_mainCtx = 0;
|
|
GLXContext m_loadCtx = 0;
|
|
|
|
public:
|
|
IWindowCallback* m_callback;
|
|
|
|
GraphicsContextXlibGLX(EGraphicsAPI api, IWindow* parentWindow, Display* display, int defaultScreen,
|
|
GLXContext lastCtx, uint32_t& visualIdOut, GLContext* glCtx)
|
|
: GraphicsContextXlib(api, glCtx->m_deepColor ? EPixelFormat::RGBA16 : EPixelFormat::RGBA8, parentWindow, display,
|
|
glCtx)
|
|
, m_lastCtx(lastCtx) {
|
|
m_dataFactory = _NewGLDataFactory(this, m_glCtx);
|
|
|
|
/* Query framebuffer configurations */
|
|
GLXFBConfig* fbConfigs = nullptr;
|
|
int numFBConfigs = 0;
|
|
fbConfigs = glXGetFBConfigs(display, defaultScreen, &numFBConfigs);
|
|
if (!fbConfigs || numFBConfigs == 0) {
|
|
Log.report(logvisor::Fatal, fmt("glXGetFBConfigs failed"));
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < numFBConfigs; ++i) {
|
|
GLXFBConfig config = fbConfigs[i];
|
|
int visualId, depthSize, colorSize, doubleBuffer;
|
|
glXGetFBConfigAttrib(display, config, GLX_VISUAL_ID, &visualId);
|
|
glXGetFBConfigAttrib(display, config, GLX_DEPTH_SIZE, &depthSize);
|
|
glXGetFBConfigAttrib(display, config, GLX_BUFFER_SIZE, &colorSize);
|
|
glXGetFBConfigAttrib(display, config, GLX_DOUBLEBUFFER, &doubleBuffer);
|
|
|
|
/* Double-buffer only */
|
|
if (!doubleBuffer || !visualId)
|
|
continue;
|
|
|
|
if (m_pf == EPixelFormat::RGBA8 && colorSize >= 32) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
break;
|
|
} else if (m_pf == EPixelFormat::RGBA16) {
|
|
if (colorSize >= 64) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
break;
|
|
} else if (!m_visualid && colorSize >= 32) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
}
|
|
} else if (m_pf == EPixelFormat::RGBA8_Z24 && colorSize >= 32 && depthSize >= 24) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
break;
|
|
} else if (m_pf == EPixelFormat::RGBAF32 && colorSize >= 128) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
break;
|
|
} else if (m_pf == EPixelFormat::RGBAF32_Z24 && colorSize >= 128 && depthSize >= 24) {
|
|
m_fbconfig = config;
|
|
m_visualid = visualId;
|
|
break;
|
|
}
|
|
}
|
|
XFree(fbConfigs);
|
|
|
|
if (!m_fbconfig) {
|
|
Log.report(logvisor::Fatal, fmt("unable to find suitable pixel format"));
|
|
return;
|
|
}
|
|
|
|
visualIdOut = m_visualid;
|
|
}
|
|
|
|
void destroy() override {
|
|
if (m_glxCtx) {
|
|
glXDestroyContext(m_xDisp, m_glxCtx);
|
|
m_glxCtx = nullptr;
|
|
}
|
|
if (m_glxWindow) {
|
|
glXDestroyWindow(m_xDisp, m_glxWindow);
|
|
m_glxWindow = 0;
|
|
}
|
|
if (m_loadCtx) {
|
|
glXDestroyContext(m_xDisp, m_loadCtx);
|
|
m_loadCtx = nullptr;
|
|
}
|
|
}
|
|
|
|
~GraphicsContextXlibGLX() override { destroy(); }
|
|
|
|
void resized(const SWindowRect& rect) override {}
|
|
|
|
void _setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
|
|
|
EGraphicsAPI getAPI() const override { return m_api; }
|
|
|
|
EPixelFormat getPixelFormat() const override { return m_pf; }
|
|
|
|
void setPixelFormat(EPixelFormat pf) override {
|
|
if (pf > EPixelFormat::RGBAF32_Z24)
|
|
return;
|
|
m_pf = pf;
|
|
}
|
|
|
|
bool initializeContext(void*) override {
|
|
if (!glXCreateContextAttribsARB) {
|
|
glXCreateContextAttribsARB =
|
|
(glXCreateContextAttribsARBProc)glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB");
|
|
if (!glXCreateContextAttribsARB)
|
|
Log.report(logvisor::Fatal, fmt("unable to resolve glXCreateContextAttribsARB"));
|
|
}
|
|
|
|
s_glxError = false;
|
|
XErrorHandler oldHandler = XSetErrorHandler(ctxErrorHandler);
|
|
for (m_attribIdx = 0; m_attribIdx < int(std::extent_v<decltype(ContextAttribList)>); ++m_attribIdx) {
|
|
m_glxCtx = glXCreateContextAttribsARB(m_xDisp, m_fbconfig, m_lastCtx, true, ContextAttribList[m_attribIdx]);
|
|
if (m_glxCtx)
|
|
break;
|
|
}
|
|
XSetErrorHandler(oldHandler);
|
|
if (!m_glxCtx)
|
|
Log.report(logvisor::Fatal, fmt("unable to make new GLX context"));
|
|
m_glxWindow = glXCreateWindow(m_xDisp, m_fbconfig, m_parentWindow->getPlatformHandle(), nullptr);
|
|
if (!m_glxWindow)
|
|
Log.report(logvisor::Fatal, fmt("unable to make new GLX window"));
|
|
_XlibUpdateLastGlxCtx(m_glxCtx);
|
|
|
|
if (!glXMakeCurrent(m_xDisp, DefaultRootWindow(m_xDisp), m_glxCtx))
|
|
Log.report(logvisor::Fatal, fmt("unable to make GLX context current"));
|
|
if (glewInit() != GLEW_OK)
|
|
Log.report(logvisor::Fatal, fmt("glewInit failed"));
|
|
glXMakeCurrent(m_xDisp, 0, 0);
|
|
|
|
XUnlockDisplay(m_xDisp);
|
|
m_commandQueue = _NewGLCommandQueue(this, m_glCtx);
|
|
m_commandQueue->startRenderer();
|
|
XLockDisplay(m_xDisp);
|
|
|
|
return true;
|
|
}
|
|
|
|
void makeCurrent() override {
|
|
XLockDisplay(m_xDisp);
|
|
if (!glXMakeContextCurrent(m_xDisp, m_glxWindow, m_glxWindow, m_glxCtx))
|
|
Log.report(logvisor::Fatal, fmt("unable to make GLX context current"));
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void postInit() override {
|
|
GLXExtensionCheck();
|
|
XLockDisplay(m_xDisp);
|
|
GLXEnableVSync(m_xDisp, m_glxWindow);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_commandQueue.get(); }
|
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_dataFactory.get(); }
|
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override {
|
|
XLockDisplay(m_xDisp);
|
|
if (!m_mainCtx) {
|
|
s_glxError = false;
|
|
XErrorHandler oldHandler = XSetErrorHandler(ctxErrorHandler);
|
|
m_mainCtx = glXCreateContextAttribsARB(m_xDisp, m_fbconfig, m_glxCtx, true, ContextAttribList[m_attribIdx]);
|
|
XSetErrorHandler(oldHandler);
|
|
if (!m_mainCtx)
|
|
Log.report(logvisor::Fatal, fmt("unable to make main GLX context"));
|
|
}
|
|
if (!glXMakeContextCurrent(m_xDisp, m_glxWindow, m_glxWindow, m_mainCtx))
|
|
Log.report(logvisor::Fatal, fmt("unable to make main GLX context current"));
|
|
XUnlockDisplay(m_xDisp);
|
|
return getDataFactory();
|
|
}
|
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override {
|
|
XLockDisplay(m_xDisp);
|
|
if (!m_loadCtx) {
|
|
s_glxError = false;
|
|
XErrorHandler oldHandler = XSetErrorHandler(ctxErrorHandler);
|
|
m_loadCtx = glXCreateContextAttribsARB(m_xDisp, m_fbconfig, m_glxCtx, true, ContextAttribList[m_attribIdx]);
|
|
XSetErrorHandler(oldHandler);
|
|
if (!m_loadCtx)
|
|
Log.report(logvisor::Fatal, fmt("unable to make load GLX context"));
|
|
}
|
|
if (!glXMakeContextCurrent(m_xDisp, m_glxWindow, m_glxWindow, m_loadCtx))
|
|
Log.report(logvisor::Fatal, fmt("unable to make load GLX context current"));
|
|
XUnlockDisplay(m_xDisp);
|
|
return getDataFactory();
|
|
}
|
|
|
|
void present() override { glXSwapBuffers(m_xDisp, m_glxWindow); }
|
|
};
|
|
|
|
#if BOO_HAS_VULKAN
|
|
struct GraphicsContextXlibVulkan : GraphicsContextXlib {
|
|
xcb_connection_t* m_xcbConn;
|
|
VulkanContext* m_ctx;
|
|
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
|
VkFormat m_format = VK_FORMAT_UNDEFINED;
|
|
VkColorSpaceKHR m_colorspace;
|
|
|
|
GLXFBConfig m_fbconfig = 0;
|
|
int m_visualid = 0;
|
|
|
|
std::unique_ptr<IGraphicsDataFactory> m_dataFactory;
|
|
std::unique_ptr<IGraphicsCommandQueue> m_commandQueue;
|
|
|
|
static void ThrowIfFailed(VkResult res) {
|
|
if (res != VK_SUCCESS)
|
|
Log.report(logvisor::Fatal, fmt("{}\n"), res);
|
|
}
|
|
|
|
public:
|
|
IWindowCallback* m_callback;
|
|
|
|
GraphicsContextXlibVulkan(IWindow* parentWindow, Display* display, xcb_connection_t* xcbConn, int defaultScreen,
|
|
VulkanContext* ctx, uint32_t& visualIdOut, GLContext* glCtx)
|
|
: GraphicsContextXlib(EGraphicsAPI::Vulkan, ctx->m_deepColor ? EPixelFormat::RGBA16 : EPixelFormat::RGBA8,
|
|
parentWindow, display, glCtx)
|
|
, m_xcbConn(xcbConn)
|
|
, m_ctx(ctx) {
|
|
Screen* screen = ScreenOfDisplay(display, defaultScreen);
|
|
m_visualid = screen->root_visual->visualid;
|
|
visualIdOut = screen->root_visual->visualid;
|
|
}
|
|
|
|
void destroy() override {
|
|
VulkanContext::Window& m_windowCtx = *m_ctx->m_windows[m_parentWindow];
|
|
m_windowCtx.m_swapChains[0].destroy(m_ctx->m_dev);
|
|
m_windowCtx.m_swapChains[1].destroy(m_ctx->m_dev);
|
|
if (m_surface) {
|
|
vk::DestroySurfaceKHR(m_ctx->m_instance, m_surface, nullptr);
|
|
m_surface = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
~GraphicsContextXlibVulkan() override { destroy(); }
|
|
|
|
VulkanContext::Window* m_windowCtx = nullptr;
|
|
|
|
void resized(const SWindowRect& rect) override {
|
|
if (m_windowCtx)
|
|
m_ctx->resizeSwapChain(*m_windowCtx, m_surface, m_format, m_colorspace, rect);
|
|
}
|
|
|
|
void _setCallback(IWindowCallback* cb) override { m_callback = cb; }
|
|
|
|
EGraphicsAPI getAPI() const override { return m_api; }
|
|
|
|
EPixelFormat getPixelFormat() const override { return m_pf; }
|
|
|
|
void setPixelFormat(EPixelFormat pf) override {
|
|
if (pf > EPixelFormat::RGBAF32_Z24)
|
|
return;
|
|
m_pf = pf;
|
|
}
|
|
|
|
bool initializeContext(void* getVkProc) override {
|
|
if (m_ctx->m_instance == VK_NULL_HANDLE)
|
|
m_ctx->initVulkan(APP->getUniqueName(), PFN_vkGetInstanceProcAddr(getVkProc));
|
|
|
|
if (!m_ctx->enumerateDevices())
|
|
return false;
|
|
|
|
m_windowCtx = m_ctx->m_windows.emplace(std::make_pair(m_parentWindow, std::make_unique<VulkanContext::Window>()))
|
|
.first->second.get();
|
|
|
|
VkXcbSurfaceCreateInfoKHR surfaceInfo = {};
|
|
surfaceInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
|
|
surfaceInfo.connection = m_xcbConn;
|
|
surfaceInfo.window = m_parentWindow->getPlatformHandle();
|
|
ThrowIfFailed(vk::CreateXcbSurfaceKHR(m_ctx->m_instance, &surfaceInfo, nullptr, &m_surface));
|
|
|
|
/* Iterate over each queue to learn whether it supports presenting */
|
|
VkBool32* supportsPresent = (VkBool32*)malloc(m_ctx->m_queueCount * sizeof(VkBool32));
|
|
for (uint32_t i = 0; i < m_ctx->m_queueCount; ++i)
|
|
vk::GetPhysicalDeviceSurfaceSupportKHR(m_ctx->m_gpus[0], i, m_surface, &supportsPresent[i]);
|
|
|
|
/* Search for a graphics queue and a present queue in the array of queue
|
|
* families, try to find one that supports both */
|
|
if (m_ctx->m_graphicsQueueFamilyIndex == UINT32_MAX) {
|
|
/* First window, init device */
|
|
for (uint32_t i = 0; i < m_ctx->m_queueCount; ++i) {
|
|
if ((m_ctx->m_queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
|
|
if (supportsPresent[i] == VK_TRUE) {
|
|
m_ctx->m_graphicsQueueFamilyIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Generate error if could not find a queue that supports both a graphics
|
|
* and present */
|
|
if (m_ctx->m_graphicsQueueFamilyIndex == UINT32_MAX)
|
|
Log.report(logvisor::Fatal, fmt("Could not find a queue that supports both graphics and present"));
|
|
|
|
m_ctx->initDevice();
|
|
} else {
|
|
/* Subsequent window, verify present */
|
|
if (supportsPresent[m_ctx->m_graphicsQueueFamilyIndex] == VK_FALSE)
|
|
Log.report(logvisor::Fatal, fmt("subsequent surface doesn't support present"));
|
|
}
|
|
free(supportsPresent);
|
|
|
|
if (!vk::GetPhysicalDeviceXcbPresentationSupportKHR(m_ctx->m_gpus[0], m_ctx->m_graphicsQueueFamilyIndex, m_xcbConn,
|
|
m_visualid)) {
|
|
Log.report(logvisor::Fatal, fmt("XCB visual doesn't support vulkan present"));
|
|
return false;
|
|
}
|
|
|
|
/* Get the list of VkFormats that are supported */
|
|
uint32_t formatCount;
|
|
ThrowIfFailed(vk::GetPhysicalDeviceSurfaceFormatsKHR(m_ctx->m_gpus[0], m_surface, &formatCount, nullptr));
|
|
std::vector<VkSurfaceFormatKHR> surfFormats(formatCount);
|
|
ThrowIfFailed(
|
|
vk::GetPhysicalDeviceSurfaceFormatsKHR(m_ctx->m_gpus[0], m_surface, &formatCount, surfFormats.data()));
|
|
|
|
/* If the format list includes just one entry of VK_FORMAT_UNDEFINED,
|
|
* the surface has no preferred format. Otherwise, at least one
|
|
* supported format will be returned. */
|
|
if (formatCount >= 1) {
|
|
if (m_ctx->m_deepColor) {
|
|
for (uint32_t i = 0; i < formatCount; ++i) {
|
|
if (surfFormats[i].format == VK_FORMAT_R16G16B16A16_UNORM) {
|
|
m_format = surfFormats[i].format;
|
|
m_colorspace = surfFormats[i].colorSpace;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (m_format == VK_FORMAT_UNDEFINED) {
|
|
for (uint32_t i = 0; i < formatCount; ++i) {
|
|
if (surfFormats[i].format == VK_FORMAT_B8G8R8A8_UNORM || surfFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
|
|
m_format = surfFormats[i].format;
|
|
m_colorspace = surfFormats[i].colorSpace;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
Log.report(logvisor::Fatal, fmt("no surface formats available for Vulkan swapchain"));
|
|
|
|
if (m_format == VK_FORMAT_UNDEFINED)
|
|
Log.report(logvisor::Fatal, fmt("no UNORM formats available for Vulkan swapchain"));
|
|
|
|
m_ctx->initSwapChain(*m_windowCtx, m_surface, m_format, m_colorspace);
|
|
|
|
m_dataFactory = _NewVulkanDataFactory(this, m_ctx);
|
|
m_commandQueue = _NewVulkanCommandQueue(m_ctx, m_ctx->m_windows[m_parentWindow].get(), this);
|
|
m_commandQueue->startRenderer();
|
|
|
|
return true;
|
|
}
|
|
|
|
void makeCurrent() override {}
|
|
|
|
void postInit() override {}
|
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_commandQueue.get(); }
|
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_dataFactory.get(); }
|
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override { return getDataFactory(); }
|
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override { return getDataFactory(); }
|
|
|
|
void present() override {}
|
|
};
|
|
#endif
|
|
|
|
class WindowXlib final : public IWindow {
|
|
Display* m_xDisp;
|
|
IWindowCallback* m_callback;
|
|
Colormap m_colormapId;
|
|
Window m_windowId;
|
|
XIMStyle m_bestStyle;
|
|
XIC m_xIC = nullptr;
|
|
std::unique_ptr<GraphicsContextXlib> m_gfxCtx;
|
|
uint32_t m_visualId;
|
|
|
|
struct timespec m_waitPeriod = {0, static_cast<long int>(1000000000.0/60.0)};
|
|
struct timespec m_lastWaitTime = {};
|
|
|
|
/* Key state trackers (for auto-repeat detection) */
|
|
std::unordered_set<unsigned long> m_charKeys;
|
|
std::unordered_set<unsigned long> m_specialKeys;
|
|
std::unordered_set<unsigned long> m_modKeys;
|
|
|
|
/* Last known input device id (0xffff if not yet set) */
|
|
int m_lastInputID = 0xffff;
|
|
ETouchType m_touchType = ETouchType::None;
|
|
|
|
/* Scroll valuators */
|
|
int m_hScrollValuator = -1;
|
|
int m_vScrollValuator = -1;
|
|
double m_hScrollLast = 0.0;
|
|
double m_vScrollLast = 0.0;
|
|
|
|
/* Cached window rectangle (to avoid repeated X queries) */
|
|
boo::SWindowRect m_wrect;
|
|
float m_pixelFactor;
|
|
bool m_inFs = false;
|
|
|
|
/* Cached window style */
|
|
EWindowStyle m_styleFlags;
|
|
|
|
/* Current cursor enum */
|
|
EMouseCursor m_cursor = EMouseCursor::None;
|
|
bool m_cursorWait = false;
|
|
static Cursor GetXCursor(EMouseCursor cur) {
|
|
switch (cur) {
|
|
case EMouseCursor::Pointer:
|
|
return X_CURSORS.m_pointer;
|
|
case EMouseCursor::HorizontalArrow:
|
|
return X_CURSORS.m_hArrow;
|
|
case EMouseCursor::VerticalArrow:
|
|
return X_CURSORS.m_vArrow;
|
|
case EMouseCursor::IBeam:
|
|
return X_CURSORS.m_ibeam;
|
|
case EMouseCursor::Crosshairs:
|
|
return X_CURSORS.m_crosshairs;
|
|
default:
|
|
break;
|
|
}
|
|
return X_CURSORS.m_pointer;
|
|
}
|
|
|
|
bool m_openGL = false;
|
|
|
|
public:
|
|
WindowXlib(std::string_view title, Display* display, void* xcbConn, int defaultScreen, XIM xIM,
|
|
XIMStyle bestInputStyle, XFontSet fontset, GLXContext lastCtx, void* vulkanHandle, GLContext* glCtx)
|
|
: m_xDisp(display), m_callback(nullptr), m_bestStyle(bestInputStyle) {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
if (!S_ATOMS)
|
|
S_ATOMS = new XlibAtoms(display);
|
|
|
|
for (int i = 1; i >= 0; --i) {
|
|
#if BOO_HAS_VULKAN
|
|
if (vulkanHandle && i == 1) {
|
|
m_gfxCtx.reset(new GraphicsContextXlibVulkan(this, display, (xcb_connection_t*)xcbConn, defaultScreen,
|
|
&g_VulkanContext, m_visualId, glCtx));
|
|
} else
|
|
#endif
|
|
{
|
|
i = 0;
|
|
m_gfxCtx.reset(new GraphicsContextXlibGLX(IGraphicsContext::EGraphicsAPI::OpenGL3_3, this, display,
|
|
defaultScreen, lastCtx, m_visualId, glCtx));
|
|
m_openGL = true;
|
|
}
|
|
|
|
XVisualInfo visTemplate;
|
|
visTemplate.screen = defaultScreen;
|
|
int numVisuals;
|
|
XVisualInfo* visualList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &numVisuals);
|
|
Visual* selectedVisual = nullptr;
|
|
for (int i = 0; i < numVisuals; ++i) {
|
|
if (visualList[i].visualid == m_visualId) {
|
|
selectedVisual = visualList[i].visual;
|
|
break;
|
|
}
|
|
}
|
|
XFree(visualList);
|
|
|
|
/* Create colormap */
|
|
Screen* screen = ScreenOfDisplay(display, defaultScreen);
|
|
m_colormapId = XCreateColormap(m_xDisp, screen->root, selectedVisual, AllocNone);
|
|
|
|
/* Create window */
|
|
int x, y, w, h;
|
|
int nmonitors = 0;
|
|
XRRMonitorInfo* mInfo = XRRGetMonitors(m_xDisp, screen->root, true, &nmonitors);
|
|
BOO_MSAN_UNPOISON(mInfo, sizeof(XRRMonitorInfo) * nmonitors);
|
|
if (nmonitors) {
|
|
genFrameDefault(mInfo, x, y, w, h);
|
|
m_pixelFactor = mInfo->width / (float)mInfo->mwidth / REF_DPMM;
|
|
} else {
|
|
genFrameDefault(screen, x, y, w, h);
|
|
m_pixelFactor = screen->width / (float)screen->mwidth / REF_DPMM;
|
|
}
|
|
XRRFreeMonitors(mInfo);
|
|
XSetWindowAttributes swa;
|
|
swa.colormap = m_colormapId;
|
|
swa.border_pixmap = 0;
|
|
swa.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
|
|
PointerMotionMask | ExposureMask | StructureNotifyMask | LeaveWindowMask | EnterWindowMask;
|
|
|
|
m_windowId = XCreateWindow(display, screen->root, x, y, w, h, 10, CopyFromParent, CopyFromParent, selectedVisual,
|
|
CWBorderPixel | CWEventMask | CWColormap, &swa);
|
|
|
|
/*
|
|
* Now go create an IC using the style we chose.
|
|
* Also set the window and fontset attributes now.
|
|
*/
|
|
if (xIM) {
|
|
XPoint pt = {0, 0};
|
|
XVaNestedList nlist;
|
|
m_xIC = XCreateIC(xIM, XNInputStyle, bestInputStyle, XNClientWindow, m_windowId, XNFocusWindow, m_windowId,
|
|
XNPreeditAttributes,
|
|
nlist = XVaCreateNestedList(0, XNSpotLocation, &pt, XNFontSet, fontset, nullptr), nullptr);
|
|
XFree(nlist);
|
|
long im_event_mask;
|
|
XGetICValues(m_xIC, XNFilterEvents, &im_event_mask, nullptr);
|
|
XSelectInput(display, m_windowId, swa.event_mask | im_event_mask);
|
|
XSetICFocus(m_xIC);
|
|
}
|
|
|
|
/* The XInput 2.1 extension enables per-pixel smooth scrolling trackpads */
|
|
XIEventMask mask = {XIAllMasterDevices, XIMaskLen(XI_LASTEVENT)};
|
|
mask.mask = (unsigned char*)malloc(mask.mask_len);
|
|
memset(mask.mask, 0, mask.mask_len);
|
|
/* XISetMask(mask.mask, XI_Motion); Can't do this without losing mouse move events :( */
|
|
XISetMask(mask.mask, XI_TouchBegin);
|
|
XISetMask(mask.mask, XI_TouchUpdate);
|
|
XISetMask(mask.mask, XI_TouchEnd);
|
|
XISelectEvents(m_xDisp, m_windowId, &mask, 1);
|
|
free(mask.mask);
|
|
|
|
/* Register netwm extension atom for window closing */
|
|
XSetWMProtocols(m_xDisp, m_windowId, &S_ATOMS->m_wmDeleteWindow, 1);
|
|
|
|
/* Set the title of the window */
|
|
if (S_ATOMS->m_netwmName) {
|
|
XChangeProperty(m_xDisp, m_windowId, S_ATOMS->m_netwmName, S_ATOMS->m_utf8String, 8,
|
|
PropModeReplace, (unsigned char*)title.data(), title.length());
|
|
}
|
|
XStoreName(m_xDisp, m_windowId, title.data());
|
|
|
|
/* Set the title of the window icon */
|
|
XChangeProperty(m_xDisp, m_windowId, XA_WM_ICON_NAME, XA_STRING, 8, PropModeReplace,
|
|
(unsigned char*)title.data(), title.length());
|
|
|
|
/* Add window icon */
|
|
if (MAINICON_NETWM_SZ && S_ATOMS->m_netwmIcon) {
|
|
XChangeProperty(m_xDisp, m_windowId, S_ATOMS->m_netwmIcon, XA_CARDINAL, 32, PropModeReplace, MAINICON_NETWM,
|
|
MAINICON_NETWM_SZ / sizeof(unsigned long));
|
|
}
|
|
|
|
/* Set the pid of the window */
|
|
if (S_ATOMS->m_netwmPid) {
|
|
pid_t pid = getpid();
|
|
XChangeProperty(m_xDisp, m_windowId, S_ATOMS->m_netwmPid, XA_CARDINAL, 32,
|
|
PropModeReplace, (unsigned char*)&pid, 1);
|
|
}
|
|
|
|
/* Initialize context */
|
|
XMapWindow(m_xDisp, m_windowId);
|
|
setStyle(EWindowStyle::Default);
|
|
setCursor(EMouseCursor::Pointer);
|
|
setWindowFrameDefault();
|
|
double hz = getWindowRefreshRate();
|
|
uint64_t nanos = uint64_t(1000000000.0 / hz);
|
|
m_waitPeriod = {nanos / 1000000000, nanos % 1000000000};
|
|
XFlush(m_xDisp);
|
|
|
|
if (!m_gfxCtx->initializeContext(vulkanHandle)) {
|
|
XUnmapWindow(m_xDisp, m_windowId);
|
|
XDestroyWindow(m_xDisp, m_windowId);
|
|
XFreeColormap(m_xDisp, m_colormapId);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
~WindowXlib() override {
|
|
_cleanup();
|
|
if (APP)
|
|
APP->_deletedWindow(this);
|
|
}
|
|
|
|
void setCallback(IWindowCallback* cb) override {
|
|
XLockDisplay(m_xDisp);
|
|
m_callback = cb;
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void closeWindow() override {
|
|
// TODO: Free window resources and prevent further access
|
|
XLockDisplay(m_xDisp);
|
|
XUnmapWindow(m_xDisp, m_windowId);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void showWindow() override {
|
|
XLockDisplay(m_xDisp);
|
|
XMapWindow(m_xDisp, m_windowId);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void hideWindow() override {
|
|
XLockDisplay(m_xDisp);
|
|
XUnmapWindow(m_xDisp, m_windowId);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
std::string getTitle() override {
|
|
unsigned long nitems;
|
|
Atom actualType;
|
|
int actualFormat;
|
|
unsigned long bytes;
|
|
unsigned char* string = nullptr;
|
|
XLockDisplay(m_xDisp);
|
|
int ret = XGetWindowProperty(m_xDisp, m_windowId, XA_WM_NAME, 0, ~0l, false, XA_STRING, &actualType, &actualFormat,
|
|
&nitems, &bytes, &string);
|
|
XUnlockDisplay(m_xDisp);
|
|
if (ret == Success) {
|
|
std::string retval((const char*)string);
|
|
XFree(string);
|
|
return retval;
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
void setTitle(std::string_view title) override {
|
|
XLockDisplay(m_xDisp);
|
|
|
|
/* Set the title of the window */
|
|
if (S_ATOMS->m_netwmName) {
|
|
XChangeProperty(m_xDisp, m_windowId, S_ATOMS->m_netwmName, S_ATOMS->m_utf8String, 8,
|
|
PropModeReplace, (unsigned char*)title.data(), title.length());
|
|
}
|
|
XStoreName(m_xDisp, m_windowId, title.data());
|
|
|
|
/* Set the title of the window icon */
|
|
XChangeProperty(m_xDisp, m_windowId, XA_WM_ICON_NAME, XA_STRING, 8, PropModeReplace,
|
|
(unsigned char*)title.data(), title.length());
|
|
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void setCursor(EMouseCursor cursor) override {
|
|
if (cursor == m_cursor && !m_cursorWait)
|
|
return;
|
|
m_cursor = cursor;
|
|
XLockDisplay(m_xDisp);
|
|
XDefineCursor(m_xDisp, m_windowId, GetXCursor(cursor));
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void setWaitCursor(bool wait) override {
|
|
if (wait && !m_cursorWait) {
|
|
XLockDisplay(m_xDisp);
|
|
XDefineCursor(m_xDisp, m_windowId, X_CURSORS.m_wait);
|
|
XUnlockDisplay(m_xDisp);
|
|
m_cursorWait = true;
|
|
} else if (!wait && m_cursorWait) {
|
|
setCursor(m_cursor);
|
|
m_cursorWait = false;
|
|
}
|
|
}
|
|
|
|
static double calculateRefreshRate(const XRRModeInfo& mi) {
|
|
if (mi.hTotal && mi.vTotal)
|
|
return double(mi.dotClock) / (double(mi.hTotal) * double(mi.vTotal));
|
|
else
|
|
return 60.0;
|
|
}
|
|
|
|
double getWindowRefreshRate() const override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
double ret = 60.0;
|
|
int nmonitors;
|
|
Screen* screen = DefaultScreenOfDisplay(m_xDisp);
|
|
XRRMonitorInfo* mInfo = XRRGetMonitors(m_xDisp, screen->root, true, &nmonitors);
|
|
BOO_MSAN_UNPOISON(mInfo, sizeof(XRRMonitorInfo) * nmonitors);
|
|
if (nmonitors) {
|
|
XRRScreenResources* res = XRRGetScreenResourcesCurrent(m_xDisp, screen->root);
|
|
XRROutputInfo* oinfo = XRRGetOutputInfo(m_xDisp, res, *mInfo->outputs);
|
|
XRRCrtcInfo* ci = XRRGetCrtcInfo(m_xDisp, res, oinfo->crtc);
|
|
for (int i = 0; i < res->nmode; ++i) {
|
|
const XRRModeInfo& mode = res->modes[i];
|
|
BOO_MSAN_UNPOISON(&mode, sizeof(XRRModeInfo));
|
|
if (mode.id == ci->mode) {
|
|
ret = calculateRefreshRate(mode);
|
|
break;
|
|
}
|
|
}
|
|
XRRFreeCrtcInfo(ci);
|
|
XRRFreeOutputInfo(oinfo);
|
|
XRRFreeScreenResources(res);
|
|
}
|
|
XRRFreeMonitors(mInfo);
|
|
return ret;
|
|
}
|
|
|
|
void setWindowFrameDefault() override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
int x, y, w, h, nmonitors;
|
|
Screen* screen = DefaultScreenOfDisplay(m_xDisp);
|
|
XRRMonitorInfo* mInfo = XRRGetMonitors(m_xDisp, screen->root, true, &nmonitors);
|
|
BOO_MSAN_UNPOISON(mInfo, sizeof(XRRMonitorInfo) * nmonitors);
|
|
if (nmonitors)
|
|
genFrameDefault(mInfo, x, y, w, h);
|
|
else
|
|
genFrameDefault(screen, x, y, w, h);
|
|
XRRFreeMonitors(mInfo);
|
|
XWindowChanges values = {(int)x, (int)y, (int)w, (int)h};
|
|
XLockDisplay(m_xDisp);
|
|
XConfigureWindow(m_xDisp, m_windowId, CWX | CWY | CWWidth | CWHeight, &values);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void getWindowFrame(float& xOut, float& yOut, float& wOut, float& hOut) const override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
XWindowAttributes attrs = {};
|
|
XLockDisplay(m_xDisp);
|
|
XGetWindowAttributes(m_xDisp, m_windowId, &attrs);
|
|
XUnlockDisplay(m_xDisp);
|
|
xOut = attrs.x;
|
|
yOut = attrs.y;
|
|
wOut = attrs.width;
|
|
hOut = attrs.height;
|
|
}
|
|
|
|
void getWindowFrame(int& xOut, int& yOut, int& wOut, int& hOut) const override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
XWindowAttributes attrs = {};
|
|
XLockDisplay(m_xDisp);
|
|
XGetWindowAttributes(m_xDisp, m_windowId, &attrs);
|
|
XUnlockDisplay(m_xDisp);
|
|
xOut = attrs.x;
|
|
yOut = attrs.y;
|
|
wOut = attrs.width;
|
|
hOut = attrs.height;
|
|
}
|
|
|
|
void setWindowFrame(float x, float y, float w, float h) override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
XWindowChanges values = {(int)x, (int)y, (int)w, (int)h};
|
|
XLockDisplay(m_xDisp);
|
|
XConfigureWindow(m_xDisp, m_windowId, CWX | CWY | CWWidth | CWHeight, &values);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
void setWindowFrame(int x, int y, int w, int h) override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
XWindowChanges values = {x, y, w, h};
|
|
XLockDisplay(m_xDisp);
|
|
XConfigureWindow(m_xDisp, m_windowId, CWX | CWY | CWWidth | CWHeight, &values);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
float getVirtualPixelFactor() const override { return m_pixelFactor; }
|
|
|
|
bool isFullscreen() const override {
|
|
return m_inFs;
|
|
unsigned long nitems;
|
|
Atom actualType;
|
|
int actualFormat;
|
|
unsigned long bytes;
|
|
Atom* vals = nullptr;
|
|
bool fullscreen = false;
|
|
XLockDisplay(m_xDisp);
|
|
int ret = XGetWindowProperty(m_xDisp, m_windowId, S_ATOMS->m_netwmState, 0, ~0l, false, XA_ATOM, &actualType,
|
|
&actualFormat, &nitems, &bytes, (unsigned char**)&vals);
|
|
XUnlockDisplay(m_xDisp);
|
|
if (ret == Success) {
|
|
for (unsigned long i = 0; i < nitems; ++i) {
|
|
if (vals[i] == S_ATOMS->m_netwmStateFullscreen) {
|
|
fullscreen = true;
|
|
break;
|
|
}
|
|
}
|
|
XFree(vals);
|
|
return fullscreen;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void setStyle(EWindowStyle style) override {
|
|
struct {
|
|
unsigned long flags;
|
|
unsigned long functions;
|
|
unsigned long decorations;
|
|
long inputMode;
|
|
unsigned long status;
|
|
} wmHints = {0};
|
|
|
|
if (S_ATOMS->m_motifWmHints) {
|
|
wmHints.flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS;
|
|
if (True(style & EWindowStyle::Titlebar)) {
|
|
wmHints.decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU;
|
|
wmHints.functions |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE;
|
|
}
|
|
if (True(style & EWindowStyle::Resize)) {
|
|
wmHints.decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH;
|
|
wmHints.functions |= MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE;
|
|
}
|
|
|
|
if (True(style & EWindowStyle::Close))
|
|
wmHints.functions |= MWM_FUNC_CLOSE;
|
|
|
|
XLockDisplay(m_xDisp);
|
|
XChangeProperty(m_xDisp, m_windowId, S_ATOMS->m_motifWmHints, S_ATOMS->m_motifWmHints, 32, PropModeReplace,
|
|
(unsigned char*)&wmHints, 5);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
|
|
m_styleFlags = style;
|
|
}
|
|
|
|
EWindowStyle getStyle() const override { return m_styleFlags; }
|
|
|
|
void setFullscreen(bool fs) override {
|
|
if (fs == m_inFs)
|
|
return;
|
|
|
|
XEvent fsEvent = {0};
|
|
fsEvent.xclient.type = ClientMessage;
|
|
fsEvent.xclient.serial = 0;
|
|
fsEvent.xclient.send_event = true;
|
|
fsEvent.xclient.window = m_windowId;
|
|
fsEvent.xclient.message_type = S_ATOMS->m_netwmState;
|
|
fsEvent.xclient.format = 32;
|
|
fsEvent.xclient.data.l[0] = fs;
|
|
fsEvent.xclient.data.l[1] = S_ATOMS->m_netwmStateFullscreen;
|
|
fsEvent.xclient.data.l[2] = 0;
|
|
XLockDisplay(m_xDisp);
|
|
XSendEvent(m_xDisp, DefaultRootWindow(m_xDisp), false, StructureNotifyMask | SubstructureRedirectMask,
|
|
(XEvent*)&fsEvent);
|
|
XUnlockDisplay(m_xDisp);
|
|
|
|
m_inFs = fs;
|
|
}
|
|
|
|
struct ClipData {
|
|
EClipboardType m_type = EClipboardType::None;
|
|
std::unique_ptr<uint8_t[]> m_data;
|
|
size_t m_sz = 0;
|
|
void clear() {
|
|
m_type = EClipboardType::None;
|
|
m_data.reset();
|
|
m_sz = 0;
|
|
}
|
|
} m_clipData;
|
|
|
|
void claimKeyboardFocus(const int coord[2]) override {
|
|
if (m_xIC) {
|
|
XLockDisplay(m_xDisp);
|
|
if (!coord) {
|
|
XUnsetICFocus(m_xIC);
|
|
XUnlockDisplay(m_xDisp);
|
|
return;
|
|
}
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
XPoint pt = {short(coord[0]), short(m_wrect.size[1] - coord[1])};
|
|
XVaNestedList list = XVaCreateNestedList(0, XNSpotLocation, &pt, nullptr);
|
|
XSetICValues(m_xIC, XNPreeditAttributes, list, nullptr);
|
|
XFree(list);
|
|
XSetICFocus(m_xIC);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
}
|
|
|
|
bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz) override {
|
|
Atom xType = GetClipboardTypeAtom(type);
|
|
if (!xType)
|
|
return false;
|
|
|
|
XLockDisplay(m_xDisp);
|
|
m_clipData.m_type = type;
|
|
m_clipData.m_data.reset(new uint8_t[sz]);
|
|
m_clipData.m_sz = sz;
|
|
memcpy(m_clipData.m_data.get(), data, sz);
|
|
XSetSelectionOwner(m_xDisp, S_ATOMS->m_clipboard, m_windowId, CurrentTime);
|
|
XUnlockDisplay(m_xDisp);
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<uint8_t[]> clipboardPaste(EClipboardType type, size_t& sz) override {
|
|
Atom xType = GetClipboardTypeAtom(type);
|
|
if (!xType)
|
|
return {};
|
|
|
|
XLockDisplay(m_xDisp);
|
|
XConvertSelection(m_xDisp, S_ATOMS->m_clipboard, xType, S_ATOMS->m_clipdata, m_windowId, CurrentTime);
|
|
XFlush(m_xDisp);
|
|
XEvent event;
|
|
for (int i = 0; i < 20; ++i) {
|
|
if (XCheckTypedWindowEvent(m_xDisp, m_windowId, SelectionNotify, &event)) {
|
|
if (event.xselection.property != 0) {
|
|
XSync(m_xDisp, false);
|
|
|
|
unsigned long nitems, rem;
|
|
int format;
|
|
unsigned char* data;
|
|
Atom type;
|
|
|
|
// Atom t1 = S_ATOMS->m_clipboard;
|
|
// Atom t2 = S_ATOMS->m_clipdata;
|
|
|
|
if (XGetWindowProperty(m_xDisp, m_windowId, S_ATOMS->m_clipdata, 0, 32, false, AnyPropertyType, &type,
|
|
&format, &nitems, &rem, &data)) {
|
|
Log.report(logvisor::Fatal, fmt("Clipboard allocation failed"));
|
|
XUnlockDisplay(m_xDisp);
|
|
return {};
|
|
}
|
|
|
|
if (rem != 0) {
|
|
Log.report(logvisor::Fatal, fmt("partial clipboard read"));
|
|
XUnlockDisplay(m_xDisp);
|
|
return {};
|
|
}
|
|
|
|
sz = nitems * format / 8;
|
|
std::unique_ptr<uint8_t[]> ret(new uint8_t[sz]);
|
|
memcpy(ret.get(), data, sz);
|
|
XFree(data);
|
|
XUnlockDisplay(m_xDisp);
|
|
return ret;
|
|
}
|
|
XUnlockDisplay(m_xDisp);
|
|
return {};
|
|
}
|
|
if (XCheckTypedWindowEvent(m_xDisp, m_windowId, SelectionRequest, &event) &&
|
|
event.xselectionrequest.owner == m_windowId)
|
|
handleSelectionRequest(&event.xselectionrequest);
|
|
if (XCheckTypedWindowEvent(m_xDisp, m_windowId, SelectionClear, &event) &&
|
|
event.xselectionclear.window == m_windowId)
|
|
m_clipData.clear();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
XUnlockDisplay(m_xDisp);
|
|
return {};
|
|
}
|
|
|
|
void handleSelectionRequest(XSelectionRequestEvent* se) {
|
|
XEvent reply;
|
|
reply.xselection.type = SelectionNotify;
|
|
reply.xselection.display = m_xDisp;
|
|
reply.xselection.requestor = se->requestor;
|
|
reply.xselection.selection = se->selection;
|
|
reply.xselection.target = se->target;
|
|
reply.xselection.time = se->time;
|
|
reply.xselection.property = se->property;
|
|
if (se->target == S_ATOMS->m_targets) {
|
|
Atom ValidTargets[] = {GetClipboardTypeAtom(m_clipData.m_type)};
|
|
XChangeProperty(m_xDisp, se->requestor, se->property, XA_ATOM, 32, 0, (unsigned char*)ValidTargets,
|
|
m_clipData.m_type != EClipboardType::None);
|
|
} else {
|
|
if (se->target == GetClipboardTypeAtom(m_clipData.m_type)) {
|
|
XChangeProperty(m_xDisp, se->requestor, se->property, se->target, 8, PropModeReplace, m_clipData.m_data.get(),
|
|
m_clipData.m_sz);
|
|
} else
|
|
reply.xselection.property = 0;
|
|
}
|
|
XSendEvent(m_xDisp, se->requestor, false, 0, &reply);
|
|
}
|
|
|
|
#define NSEC_PER_SEC 1000000000
|
|
|
|
static void set_normalized_timespec(struct timespec& ts, time_t sec, int64_t nsec) {
|
|
while (nsec >= NSEC_PER_SEC) {
|
|
nsec -= NSEC_PER_SEC;
|
|
++sec;
|
|
}
|
|
while (nsec < 0) {
|
|
nsec += NSEC_PER_SEC;
|
|
--sec;
|
|
}
|
|
ts.tv_sec = sec;
|
|
ts.tv_nsec = nsec;
|
|
}
|
|
|
|
static struct timespec timespec_add(const struct timespec& lhs, const struct timespec& rhs) {
|
|
struct timespec ts_delta;
|
|
set_normalized_timespec(ts_delta, lhs.tv_sec + rhs.tv_sec,
|
|
lhs.tv_nsec + rhs.tv_nsec);
|
|
return ts_delta;
|
|
}
|
|
|
|
static struct timespec timespec_sub(const struct timespec& lhs, const struct timespec& rhs) {
|
|
struct timespec ts_delta;
|
|
set_normalized_timespec(ts_delta, lhs.tv_sec - rhs.tv_sec,
|
|
lhs.tv_nsec - rhs.tv_nsec);
|
|
return ts_delta;
|
|
}
|
|
|
|
static inline long int timespec_compare(const struct timespec& lhs, const struct timespec& rhs)
|
|
{
|
|
if (lhs.tv_sec < rhs.tv_sec)
|
|
return -1;
|
|
if (lhs.tv_sec > rhs.tv_sec)
|
|
return 1;
|
|
return lhs.tv_nsec - rhs.tv_nsec;
|
|
}
|
|
|
|
int waitForRetrace() override {
|
|
BOO_MSAN_NO_INTERCEPT
|
|
struct timespec tp = {};
|
|
clock_gettime(CLOCK_REALTIME, &tp);
|
|
if (!m_lastWaitTime.tv_sec) {
|
|
/* Initialize reference point */
|
|
sched_param prio = {75};
|
|
sched_setscheduler(0, SCHED_RR, &prio);
|
|
m_lastWaitTime = tp;
|
|
return 0;
|
|
}
|
|
|
|
m_lastWaitTime = timespec_add(m_lastWaitTime, m_waitPeriod);
|
|
long int comp = timespec_compare(m_lastWaitTime, tp);
|
|
if (comp == 0) {
|
|
/* Exactly at the due date */
|
|
return 1;
|
|
} else if (comp > 0) {
|
|
/* Not at due date yet, sleep here */
|
|
struct timespec wait_time = timespec_sub(m_lastWaitTime, tp);
|
|
while (nanosleep(&wait_time, &wait_time)) {}
|
|
return 1;
|
|
} else {
|
|
/* Missed due date, assign next one and return passed cycle count */
|
|
int cycles = 0;
|
|
do {
|
|
m_lastWaitTime = timespec_add(m_lastWaitTime, m_waitPeriod);
|
|
++cycles;
|
|
} while (timespec_compare(m_lastWaitTime, tp) < 0);
|
|
return cycles;
|
|
}
|
|
}
|
|
|
|
uintptr_t getPlatformHandle() const override { return (uintptr_t)m_windowId; }
|
|
|
|
void _pointingDeviceChanged(int deviceId) {
|
|
int nDevices;
|
|
XIDeviceInfo* devices = XIQueryDevice(m_xDisp, deviceId, &nDevices);
|
|
|
|
for (int i = 0; i < nDevices; ++i) {
|
|
XIDeviceInfo* device = &devices[i];
|
|
|
|
/* First iterate classes for scrollables */
|
|
int hScroll = -1;
|
|
int vScroll = -1;
|
|
m_hScrollLast = 0.0;
|
|
m_vScrollLast = 0.0;
|
|
m_hScrollValuator = -1;
|
|
m_vScrollValuator = -1;
|
|
for (int j = 0; j < device->num_classes; ++j) {
|
|
XIAnyClassInfo* dclass = device->classes[j];
|
|
if (dclass->type == XIScrollClass) {
|
|
XIScrollClassInfo* scrollClass = (XIScrollClassInfo*)dclass;
|
|
if (scrollClass->scroll_type == XIScrollTypeVertical)
|
|
vScroll = scrollClass->number;
|
|
else if (scrollClass->scroll_type == XIScrollTypeHorizontal)
|
|
hScroll = scrollClass->number;
|
|
}
|
|
}
|
|
|
|
/* Next iterate for touch and scroll valuators */
|
|
for (int j = 0; j < device->num_classes; ++j) {
|
|
XIAnyClassInfo* dclass = device->classes[j];
|
|
if (dclass->type == XIValuatorClass) {
|
|
XIValuatorClassInfo* valClass = (XIValuatorClassInfo*)dclass;
|
|
if (valClass->number == vScroll) {
|
|
m_vScrollLast = valClass->value;
|
|
m_vScrollValuator = vScroll;
|
|
} else if (valClass->number == hScroll) {
|
|
m_hScrollLast = valClass->value;
|
|
m_hScrollValuator = hScroll;
|
|
}
|
|
} else if (dclass->type == XITouchClass) {
|
|
XITouchClassInfo* touchClass = (XITouchClassInfo*)dclass;
|
|
if (touchClass->mode == XIDirectTouch)
|
|
m_touchType = ETouchType::Display;
|
|
else if (touchClass->mode == XIDependentTouch)
|
|
m_touchType = ETouchType::Trackpad;
|
|
else
|
|
m_touchType = ETouchType::None;
|
|
}
|
|
}
|
|
}
|
|
|
|
XIFreeDeviceInfo(devices);
|
|
m_lastInputID = deviceId;
|
|
}
|
|
|
|
SWindowCoord MakeButtonEventCoord(XEvent* event) const {
|
|
int x = event->xbutton.x;
|
|
int y = m_wrect.size[1] - event->xbutton.y;
|
|
return {{x, y},
|
|
{int(x / m_pixelFactor), int(y / m_pixelFactor)},
|
|
{x / float(m_wrect.size[0]), y / float(m_wrect.size[1])}};
|
|
}
|
|
|
|
SWindowCoord MakeMotionEventCoord(XEvent* event) const {
|
|
int x = event->xmotion.x;
|
|
int y = m_wrect.size[1] - event->xmotion.y;
|
|
return {{x, y},
|
|
{int(x / m_pixelFactor), int(y / m_pixelFactor)},
|
|
{x / float(m_wrect.size[0]), y / float(m_wrect.size[1])}};
|
|
}
|
|
|
|
SWindowCoord MakeCrossingEventCoord(XEvent* event) const {
|
|
int x = event->xcrossing.x;
|
|
int y = m_wrect.size[1] - event->xcrossing.y;
|
|
return {{x, y},
|
|
{int(x / m_pixelFactor), int(y / m_pixelFactor)},
|
|
{x / float(m_wrect.size[0]), y / float(m_wrect.size[1])}};
|
|
}
|
|
|
|
#if 0
|
|
/* This procedure sets the application's size constraints and returns
|
|
* the IM's preferred size for either the Preedit or Status areas,
|
|
* depending on the value of the name argument. The area argument is
|
|
* used to pass the constraints and to return the preferred size.
|
|
*/
|
|
void GetPreferredGeometry(const char* name, XRectangle* area)
|
|
{
|
|
XVaNestedList list;
|
|
list = XVaCreateNestedList(0, XNAreaNeeded, area, nullptr);
|
|
/* set the constraints */
|
|
XSetICValues(m_xIC, name, list, nullptr);
|
|
/* query the preferred size */
|
|
XGetICValues(m_xIC, name, list, nullptr);
|
|
XFree(list);
|
|
}
|
|
|
|
/* This procedure sets the geometry of either the Preedit or Status
|
|
* Areas, depending on the value of the name argument.
|
|
*/
|
|
void SetGeometry(const char* name, XRectangle* area)
|
|
{
|
|
XVaNestedList list;
|
|
list = XVaCreateNestedList(0, XNArea, area, nullptr);
|
|
XSetICValues(m_xIC, name, list, nullptr);
|
|
XFree(list);
|
|
}
|
|
#endif
|
|
|
|
bool _incomingEvent(void* e) override {
|
|
XEvent* event = (XEvent*)e;
|
|
switch (event->type) {
|
|
case SelectionRequest: {
|
|
handleSelectionRequest(&event->xselectionrequest);
|
|
return false;
|
|
}
|
|
case ClientMessage: {
|
|
if (Atom(event->xclient.data.l[0]) == S_ATOMS->m_wmDeleteWindow && m_callback) {
|
|
m_callback->destroyed();
|
|
m_callback = nullptr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case Expose: {
|
|
Window nw = 0;
|
|
XWindowAttributes wxa = {};
|
|
int x = 0, y = 0;
|
|
XTranslateCoordinates(m_xDisp, m_windowId, DefaultRootWindow(m_xDisp), event->xexpose.x, event->xexpose.y, &x, &y,
|
|
&nw);
|
|
XGetWindowAttributes(m_xDisp, m_windowId, &wxa);
|
|
m_wrect.location[0] = x - wxa.x;
|
|
m_wrect.location[1] = y - wxa.y;
|
|
#if 0
|
|
/* This breaks with GNOME, why? */
|
|
m_wrect.size[0] = event->xexpose.width;
|
|
m_wrect.size[1] = event->xexpose.height;
|
|
#else
|
|
m_wrect.size[0] = wxa.width;
|
|
m_wrect.size[1] = wxa.height;
|
|
#endif
|
|
if (m_callback) {
|
|
XUnlockDisplay(m_xDisp);
|
|
m_gfxCtx->resized(m_wrect);
|
|
m_callback->resized(m_wrect, m_openGL);
|
|
XLockDisplay(m_xDisp);
|
|
}
|
|
return false;
|
|
}
|
|
case ConfigureNotify: {
|
|
Window nw = 0;
|
|
XWindowAttributes wxa = {};
|
|
int x = 0, y = 0;
|
|
XTranslateCoordinates(m_xDisp, m_windowId, DefaultRootWindow(m_xDisp), event->xconfigure.x, event->xconfigure.y,
|
|
&x, &y, &nw);
|
|
XGetWindowAttributes(m_xDisp, m_windowId, &wxa);
|
|
m_wrect.location[0] = x - wxa.x;
|
|
m_wrect.location[1] = y - wxa.y;
|
|
m_wrect.size[0] = event->xconfigure.width;
|
|
m_wrect.size[1] = event->xconfigure.height;
|
|
|
|
if (m_callback)
|
|
m_callback->windowMoved(m_wrect);
|
|
return false;
|
|
}
|
|
case KeyPress: {
|
|
if (m_callback) {
|
|
ESpecialKey specialKey;
|
|
EModifierKey modifierKey;
|
|
unsigned int state = event->xkey.state;
|
|
event->xkey.state &= ~ControlMask;
|
|
ITextInputCallback* inputCb = m_callback->getTextInputCallback();
|
|
if (m_xIC) {
|
|
std::string utf8Frag = translateUTF8(&event->xkey, m_xIC);
|
|
if (utf8Frag.size()) {
|
|
if (inputCb)
|
|
inputCb->insertText(utf8Frag);
|
|
return false;
|
|
}
|
|
}
|
|
char charCode = translateKeysym(&event->xkey, specialKey, modifierKey);
|
|
EModifierKey modifierMask = translateModifiers(state);
|
|
if (charCode) {
|
|
if (inputCb && False(modifierMask & (EModifierKey::Ctrl | EModifierKey::Command)))
|
|
inputCb->insertText(std::string(1, charCode));
|
|
|
|
bool isRepeat = m_charKeys.find(charCode) != m_charKeys.cend();
|
|
m_callback->charKeyDown(charCode, modifierMask, isRepeat);
|
|
if (!isRepeat)
|
|
m_charKeys.insert(charCode);
|
|
} else if (specialKey != ESpecialKey::None) {
|
|
bool isRepeat = m_specialKeys.find((unsigned long)specialKey) != m_specialKeys.cend();
|
|
m_callback->specialKeyDown(specialKey, modifierMask, isRepeat);
|
|
if (!isRepeat)
|
|
m_specialKeys.insert((unsigned long)specialKey);
|
|
} else if (True(modifierKey)) {
|
|
bool isRepeat = m_modKeys.find((unsigned long)modifierKey) != m_modKeys.cend();
|
|
m_callback->modKeyDown(modifierKey, isRepeat);
|
|
if (!isRepeat)
|
|
m_modKeys.insert((unsigned long)modifierKey);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case KeyRelease: {
|
|
if (m_callback) {
|
|
ESpecialKey specialKey;
|
|
EModifierKey modifierKey;
|
|
unsigned int state = event->xkey.state;
|
|
event->xkey.state &= ~ControlMask;
|
|
char charCode = translateKeysym(&event->xkey, specialKey, modifierKey);
|
|
EModifierKey modifierMask = translateModifiers(state);
|
|
if (charCode) {
|
|
m_charKeys.erase(charCode);
|
|
m_callback->charKeyUp(charCode, modifierMask);
|
|
} else if (specialKey != ESpecialKey::None) {
|
|
m_specialKeys.erase((unsigned long)specialKey);
|
|
m_callback->specialKeyUp(specialKey, modifierMask);
|
|
} else if (True(modifierKey)) {
|
|
m_modKeys.erase((unsigned long)modifierKey);
|
|
m_callback->modKeyUp(modifierKey);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case ButtonPress: {
|
|
if (m_callback) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
EMouseButton button = translateButton(event->xbutton.button);
|
|
if (button != EMouseButton::None) {
|
|
EModifierKey modifierMask = translateModifiers(event->xbutton.state);
|
|
m_callback->mouseDown(MakeButtonEventCoord(event), (EMouseButton)button, (EModifierKey)modifierMask);
|
|
}
|
|
|
|
/* Also handle legacy scroll events here */
|
|
if (event->xbutton.button >= 4 && event->xbutton.button <= 7 && m_hScrollValuator == -1 &&
|
|
m_vScrollValuator == -1) {
|
|
SScrollDelta scrollDelta = {{0.0, 0.0}, false};
|
|
if (event->xbutton.button == 4)
|
|
scrollDelta.delta[1] = 1.0;
|
|
else if (event->xbutton.button == 5)
|
|
scrollDelta.delta[1] = -1.0;
|
|
else if (event->xbutton.button == 6)
|
|
scrollDelta.delta[0] = 1.0;
|
|
else if (event->xbutton.button == 7)
|
|
scrollDelta.delta[0] = -1.0;
|
|
m_callback->scroll(MakeButtonEventCoord(event), scrollDelta);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case ButtonRelease: {
|
|
if (m_callback) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
EMouseButton button = translateButton(event->xbutton.button);
|
|
if (button != EMouseButton::None) {
|
|
EModifierKey modifierMask = translateModifiers(event->xbutton.state);
|
|
m_callback->mouseUp(MakeButtonEventCoord(event), (EMouseButton)button, (EModifierKey)modifierMask);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case FocusIn: {
|
|
if (m_callback)
|
|
m_callback->focusGained();
|
|
return false;
|
|
}
|
|
case FocusOut: {
|
|
if (m_callback)
|
|
m_callback->focusLost();
|
|
return false;
|
|
}
|
|
case MotionNotify: {
|
|
if (m_callback) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
m_callback->mouseMove(MakeMotionEventCoord(event));
|
|
}
|
|
return false;
|
|
}
|
|
case EnterNotify: {
|
|
if (m_callback) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
m_callback->mouseEnter(MakeCrossingEventCoord(event));
|
|
}
|
|
return false;
|
|
}
|
|
case LeaveNotify: {
|
|
if (m_callback) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
m_callback->mouseLeave(MakeCrossingEventCoord(event));
|
|
}
|
|
return false;
|
|
}
|
|
case GenericEvent: {
|
|
if (event->xgeneric.extension == XINPUT_OPCODE) {
|
|
getWindowFrame(m_wrect.location[0], m_wrect.location[1], m_wrect.size[0], m_wrect.size[1]);
|
|
switch (event->xgeneric.evtype) {
|
|
case XI_Motion: {
|
|
fmt::print(stderr, fmt("motion\n"));
|
|
|
|
XIDeviceEvent* ev = (XIDeviceEvent*)event;
|
|
if (m_lastInputID != ev->deviceid)
|
|
_pointingDeviceChanged(ev->deviceid);
|
|
|
|
int cv = 0;
|
|
double newScroll[2] = {m_hScrollLast, m_vScrollLast};
|
|
bool didScroll = false;
|
|
for (int i = 0; i < ev->valuators.mask_len * 8; ++i) {
|
|
if (XIMaskIsSet(ev->valuators.mask, i)) {
|
|
if (i == m_hScrollValuator) {
|
|
newScroll[0] = ev->valuators.values[cv];
|
|
didScroll = true;
|
|
} else if (i == m_vScrollValuator) {
|
|
newScroll[1] = ev->valuators.values[cv];
|
|
didScroll = true;
|
|
}
|
|
++cv;
|
|
}
|
|
}
|
|
|
|
SScrollDelta scrollDelta = {{newScroll[0] - m_hScrollLast, newScroll[1] - m_vScrollLast}, true};
|
|
|
|
m_hScrollLast = newScroll[0];
|
|
m_vScrollLast = newScroll[1];
|
|
|
|
if (m_callback && didScroll) {
|
|
int event_x = int(ev->event_x) >> 16;
|
|
int event_y = m_wrect.size[1] - (int(ev->event_y) >> 16);
|
|
SWindowCoord coord = {{event_x, event_y},
|
|
{int(event_x / m_pixelFactor), int(event_y / m_pixelFactor)},
|
|
{event_x / float(m_wrect.size[0]), event_y / float(m_wrect.size[1])}};
|
|
m_callback->scroll(coord, scrollDelta);
|
|
}
|
|
return false;
|
|
}
|
|
case XI_TouchBegin: {
|
|
XIDeviceEvent* ev = (XIDeviceEvent*)event;
|
|
if (m_lastInputID != ev->deviceid)
|
|
_pointingDeviceChanged(ev->deviceid);
|
|
|
|
int cv = 0;
|
|
double vals[32] = {};
|
|
for (int i = 0; i < ev->valuators.mask_len * 8 && i < 32; ++i) {
|
|
if (XIMaskIsSet(ev->valuators.mask, i)) {
|
|
vals[i] = ev->valuators.values[cv];
|
|
++cv;
|
|
}
|
|
}
|
|
|
|
STouchCoord coord = {{vals[0], vals[1]}};
|
|
|
|
if (m_callback)
|
|
m_callback->touchDown(coord, ev->detail);
|
|
return false;
|
|
}
|
|
case XI_TouchUpdate: {
|
|
XIDeviceEvent* ev = (XIDeviceEvent*)event;
|
|
if (m_lastInputID != ev->deviceid)
|
|
_pointingDeviceChanged(ev->deviceid);
|
|
|
|
int cv = 0;
|
|
double vals[32] = {};
|
|
for (int i = 0; i < ev->valuators.mask_len * 8 && i < 32; ++i) {
|
|
if (XIMaskIsSet(ev->valuators.mask, i)) {
|
|
vals[i] = ev->valuators.values[cv];
|
|
++cv;
|
|
}
|
|
}
|
|
|
|
STouchCoord coord = {{vals[0], vals[1]}};
|
|
|
|
if (m_callback)
|
|
m_callback->touchMove(coord, ev->detail);
|
|
return false;
|
|
}
|
|
case XI_TouchEnd: {
|
|
XIDeviceEvent* ev = (XIDeviceEvent*)event;
|
|
if (m_lastInputID != ev->deviceid)
|
|
_pointingDeviceChanged(ev->deviceid);
|
|
|
|
int cv = 0;
|
|
double vals[32] = {};
|
|
for (int i = 0; i < ev->valuators.mask_len * 8 && i < 32; ++i) {
|
|
if (XIMaskIsSet(ev->valuators.mask, i)) {
|
|
vals[i] = ev->valuators.values[cv];
|
|
++cv;
|
|
}
|
|
}
|
|
|
|
STouchCoord coord = {{vals[0], vals[1]}};
|
|
|
|
if (m_callback)
|
|
m_callback->touchUp(coord, ev->detail);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void _cleanup() override {
|
|
if (m_gfxCtx) {
|
|
XLockDisplay(m_xDisp);
|
|
m_gfxCtx->destroy();
|
|
m_gfxCtx.reset();
|
|
XUnmapWindow(m_xDisp, m_windowId);
|
|
XDestroyWindow(m_xDisp, m_windowId);
|
|
XFreeColormap(m_xDisp, m_colormapId);
|
|
XUnlockDisplay(m_xDisp);
|
|
}
|
|
}
|
|
|
|
ETouchType getTouchType() const override { return m_touchType; }
|
|
|
|
IGraphicsCommandQueue* getCommandQueue() override { return m_gfxCtx->getCommandQueue(); }
|
|
|
|
IGraphicsDataFactory* getDataFactory() override { return m_gfxCtx->getDataFactory(); }
|
|
|
|
IGraphicsDataFactory* getMainContextDataFactory() override { return m_gfxCtx->getMainContextDataFactory(); }
|
|
|
|
IGraphicsDataFactory* getLoadContextDataFactory() override { return m_gfxCtx->getLoadContextDataFactory(); }
|
|
|
|
bool _isWindowMapped() {
|
|
XWindowAttributes attr;
|
|
XLockDisplay(m_xDisp);
|
|
XGetWindowAttributes(m_xDisp, m_windowId, &attr);
|
|
XUnlockDisplay(m_xDisp);
|
|
return attr.map_state != IsUnmapped;
|
|
}
|
|
};
|
|
|
|
std::shared_ptr<IWindow> _WindowXlibNew(std::string_view title, Display* display, void* xcbConn, int defaultScreen,
|
|
XIM xIM, XIMStyle bestInputStyle, XFontSet fontset, GLXContext lastCtx,
|
|
void* vulkanHandle, GLContext* glCtx) {
|
|
std::shared_ptr<IWindow> ret = std::make_shared<WindowXlib>(title, display, xcbConn, defaultScreen, xIM,
|
|
bestInputStyle, fontset, lastCtx, vulkanHandle, glCtx);
|
|
return ret;
|
|
}
|
|
|
|
} // namespace boo
|