diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c3606c..d281ad5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,7 +117,7 @@ else(NOT GEKKO) endif() include_directories(${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) - list(APPEND _BOO_SYS_LIBS X11 Xi xkbcommon xkbcommon-x11 GL ${DBUS_LIBRARY} udev pthread) + list(APPEND _BOO_SYS_LIBS X11 Xi GL ${DBUS_LIBRARY} udev pthread) endif() diff --git a/include/boo/IWindow.hpp b/include/boo/IWindow.hpp index bc5a298..377350f 100644 --- a/include/boo/IWindow.hpp +++ b/include/boo/IWindow.hpp @@ -2,6 +2,7 @@ #define IWINDOW_HPP #include "System.hpp" +#include namespace boo { @@ -174,6 +175,14 @@ enum class EMouseCursor IBeam = 4 }; +enum class EClipboardType +{ + None = 0, + String = 1, + UTF8String = 2, + PNGImage = 3 +}; + class IWindow { public: @@ -211,6 +220,10 @@ public: virtual bool isFullscreen() const=0; virtual void setFullscreen(bool fs)=0; + virtual void claimKeyboardFocus()=0; + virtual bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz)=0; + virtual std::unique_ptr clipboardPaste(EClipboardType type, size_t& sz)=0; + virtual void waitForRetrace()=0; virtual uintptr_t getPlatformHandle() const=0; diff --git a/lib/x11/ApplicationXlib.hpp b/lib/x11/ApplicationXlib.hpp index 87fe056..7b4e112 100644 --- a/lib/x11/ApplicationXlib.hpp +++ b/lib/x11/ApplicationXlib.hpp @@ -35,6 +35,11 @@ static Window GetWindowOfEvent(XEvent* event, bool& windowEvent) { switch (event->type) { + case SelectionRequest: + { + windowEvent = true; + return event->xselectionrequest.owner; + } case ClientMessage: { windowEvent = true; @@ -103,8 +108,44 @@ static Window GetWindowOfEvent(XEvent* event, bool& windowEvent) } IWindow* _WindowXlibNew(const std::string& title, - Display* display, int defaultScreen, + Display* display, int defaultScreen, XIM xIM, XIMStyle bestInputStyle, XFontSet fontset, GLXContext lastCtx); + +static XIMStyle ChooseBetterStyle(XIMStyle style1, XIMStyle style2) +{ + XIMStyle s,t; + XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks | + XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone; + XIMStyle status = XIMStatusArea | XIMStatusCallbacks | + XIMStatusNothing | XIMStatusNone; + if (style1 == 0) return style2; + if (style2 == 0) return style1; + if ((style1 & (preedit | status)) == (style2 & (preedit | status))) + return style1; + s = style1 & preedit; + t = style2 & preedit; + if (s != t) { + if (s | t | XIMPreeditCallbacks) + return (s == XIMPreeditCallbacks)?style1:style2; + else if (s | t | XIMPreeditPosition) + return (s == XIMPreeditPosition)?style1:style2; + else if (s | t | XIMPreeditArea) + return (s == XIMPreeditArea)?style1:style2; + else if (s | t | XIMPreeditNothing) + return (s == XIMPreeditNothing)?style1:style2; + } + else { /* if preedit flags are the same, compare status flags */ + s = style1 & status; + t = style2 & status; + if (s | t | XIMStatusCallbacks) + return (s == XIMStatusCallbacks)?style1:style2; + else if (s | t | XIMStatusArea) + return (s == XIMStatusArea)?style1:style2; + else if (s | t | XIMStatusNothing) + return (s == XIMStatusNothing)?style1:style2; + } + return 0; +} class ApplicationXlib final : public IApplication { @@ -122,6 +163,9 @@ class ApplicationXlib final : public IApplication std::unordered_map m_windows; Display* m_xDisp = nullptr; + XIM m_xIM; + XFontSet m_fontset; + XIMStyle m_bestStyle = 0; int m_xDefaultScreen = 0; int m_xcbFd, m_dbusFd, m_maxFd; @@ -193,6 +237,12 @@ public: return; } + if (setlocale(LC_ALL, "") == nullptr) + { + Log.report(LogVisor::FatalError, "Can't setlocale"); + return; + } + /* Open Xlib Display */ m_xDisp = XOpenDisplay(0); if (!m_xDisp) @@ -201,6 +251,70 @@ public: return; } + /* Configure locale */ + if (!XSupportsLocale()) { + Log.report(LogVisor::FatalError, "X does not support locale %s.", + setlocale(LC_ALL, nullptr)); + return; + } + if (XSetLocaleModifiers("") == nullptr) + Log.report(LogVisor::Warning, "Cannot set locale modifiers."); + + if ((m_xIM = XOpenIM(m_xDisp, nullptr, nullptr, nullptr)) == nullptr) + { + Log.report(LogVisor::FatalError, "Couldn't open input method."); + return; + } + + /* Create the fontset */ + char** missing_charsets; + int num_missing_charsets = 0; + char* default_string; + m_fontset = XCreateFontSet(m_xDisp, + "-adobe-helvetica-*-r-*-*-*-120-*-*-*-*-*-*,\ + -misc-fixed-*-r-*-*-*-130-*-*-*-*-*-*", + &missing_charsets, &num_missing_charsets, + &default_string); + /* + * if there are charsets for which no fonts can + * be found, print a warning message. + */ + if (num_missing_charsets > 0) + { + std::string warn("The following charsets are missing:\n"); + + for(int i=0 ; icount_styles ; ++i) + { + XIMStyle style = im_supported_styles->supported_styles[i]; + if ((style & app_supported_styles) == style) /* if we can handle it */ + m_bestStyle = ChooseBetterStyle(style, m_bestStyle); + } + /* if we couldn't support any of them, print an error and exit */ + if (m_bestStyle == 0) + { + Log.report(LogVisor::FatalError, "interaction style not supported."); + return; + } + XFree(im_supported_styles); + m_xDefaultScreen = DefaultScreen(m_xDisp); X_CURSORS.m_pointer = XCreateFontCursor(m_xDisp, XC_left_ptr); X_CURSORS.m_hArrow = XCreateFontCursor(m_xDisp, XC_sb_h_double_arrow); @@ -289,6 +403,7 @@ public: { XEvent event; XNextEvent(m_xDisp, &event); + if (XFilterEvent(&event, None)) continue; bool windowEvent; Window evWindow = GetWindowOfEvent(&event, windowEvent); if (windowEvent) @@ -355,7 +470,7 @@ public: IWindow* newWindow(const std::string& title) { - IWindow* newWindow = _WindowXlibNew(title, m_xDisp, m_xDefaultScreen, m_lastGlxCtx); + IWindow* newWindow = _WindowXlibNew(title, m_xDisp, m_xDefaultScreen, m_xIM, m_bestStyle, m_fontset, m_lastGlxCtx); m_windows[(Window)newWindow->getPlatformHandle()] = newWindow; return newWindow; } diff --git a/lib/x11/WindowWayland.cpp b/lib/x11/WindowWayland.cpp index 7fb97c4..c6cac19 100644 --- a/lib/x11/WindowWayland.cpp +++ b/lib/x11/WindowWayland.cpp @@ -185,6 +185,20 @@ struct WindowWayland : IWindow } + void claimKeyboardFocus() + { + } + + bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz) + { + return false; + } + + std::unique_ptr clipboardPaste(EClipboardType type, size_t& sz) + { + return std::unique_ptr(); + } + void waitForRetrace() { } diff --git a/lib/x11/WindowXlib.cpp b/lib/x11/WindowXlib.cpp index 8d2507d..78eccd9 100644 --- a/lib/x11/WindowXlib.cpp +++ b/lib/x11/WindowXlib.cpp @@ -18,7 +18,6 @@ #define XK_XKB_KEYS #define XK_LATIN1 #include -#include #include #include #include @@ -71,9 +70,10 @@ void GLXEnableVSync(Display* disp, GLXWindow drawable); extern int XINPUT_OPCODE; -static uint32_t translateKeysym(KeySym sym, ESpecialKey& specialSym, EModifierKey& modifierSym, - Display* d, unsigned state) +static uint32_t translateKeysym(XKeyEvent* ev, ESpecialKey& specialSym, EModifierKey& modifierSym, XIC xIC) { + KeySym sym = XLookupKeysym(ev, 0); + specialSym = ESpecialKey::None; modifierSym = EModifierKey::None; if (sym >= XK_F1 && sym <= XK_F12) @@ -112,6 +112,7 @@ static uint32_t translateKeysym(KeySym sym, ESpecialKey& specialSym, EModifierKe modifierSym = EModifierKey::Alt; else { +#if 0 unsigned n; XkbGetIndicatorState(d, XkbUseCoreKbd, &n); uint32_t utf = xkb_keysym_to_utf32(sym); @@ -119,6 +120,13 @@ static uint32_t translateKeysym(KeySym sym, ESpecialKey& specialSym, EModifierKe return toupper(utf); else return utf; +#endif + uint32_t utf = 0; + KeySym sym; + Status stat; + XmbLookupString(xIC, ev, (char*)&utf, 4, &sym, &stat); + if (stat == XLookupChars || stat == XLookupBoth) + return utf; } return 0; } @@ -154,7 +162,7 @@ static EMouseButton translateButton(unsigned detail) return EMouseButton::None; } -struct XCBAtoms +struct XlibAtoms { Atom m_wmProtocols = 0; Atom m_wmDeleteWindow = 0; @@ -163,7 +171,12 @@ struct XCBAtoms Atom m_netwmStateAdd = 0; Atom m_netwmStateRemove = 0; Atom m_motifWmHints = 0; - XCBAtoms(Display* disp) + 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); @@ -172,9 +185,28 @@ struct XCBAtoms 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 XCBAtoms* S_ATOMS = NULL; +static XlibAtoms* S_ATOMS = NULL; + +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) { @@ -477,6 +509,8 @@ class WindowXlib : public IWindow IWindowCallback* m_callback; Colormap m_colormapId; Window m_windowId; + XIMStyle m_bestStyle; + XIC m_xIC; GraphicsContextGLX m_gfxCtx; uint32_t m_visualId; @@ -519,17 +553,17 @@ class WindowXlib : public IWindow } public: - WindowXlib(const std::string& title, - Display* display, int defaultScreen, - GLXContext lastCtx) + Display* display, int defaultScreen, XIM xIM, XIMStyle bestInputStyle, XFontSet fontset, + GLXContext lastCtx) : m_xDisp(display), m_callback(nullptr), m_gfxCtx(IGraphicsContext::EGraphicsAPI::OpenGL3_3, this, display, defaultScreen, - lastCtx, m_visualId) + lastCtx, m_visualId), + m_bestStyle(bestInputStyle) { if (!S_ATOMS) - S_ATOMS = new XCBAtoms(display); + S_ATOMS = new XlibAtoms(display); /* Default screen */ Screen* screen = ScreenOfDisplay(display, defaultScreen); @@ -565,6 +599,26 @@ public: 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. + */ + XVaNestedList list = XVaCreateNestedList(0, XNFontSet, fontset, nullptr); + m_xIC = XCreateIC(xIM, XNInputStyle, bestInputStyle, + XNClientWindow, m_windowId, + XNPreeditAttributes, list, + XNStatusAttributes, list, + nullptr); + XFree(list); + if (m_xIC == nullptr) + { + Log.report(LogVisor::FatalError, "Couldn't create input context."); + return; + } + 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)}; @@ -832,6 +886,186 @@ public: m_inFs = fs; } + struct ClipData + { + EClipboardType m_type = EClipboardType::None; + std::unique_ptr m_data; + size_t m_sz = 0; + void clear() + { + m_type = EClipboardType::None; + m_data.reset(); + m_sz = 0; + } + } m_clipData; + + void claimKeyboardFocus() + { + XLockDisplay(m_xDisp); + XSetICFocus(m_xIC); + XUnlockDisplay(m_xDisp); + } + + bool clipboardCopy(EClipboardType type, const uint8_t* data, size_t sz) + { + 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 clipboardPaste(EClipboardType type, size_t& sz) + { + 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::FatalError, "Clipboard allocation failed"); + XUnlockDisplay(m_xDisp); + return {}; + } + + if (rem != 0) + { + Log.report(LogVisor::FatalError, "partial clipboard read"); + XUnlockDisplay(m_xDisp); + return {}; + } + + sz = nitems * format / 8; + std::unique_ptr 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) + { +#if 0 + if (se->selection == XA_PRIMARY) + { + std::vector x(sel_formats.GetCount()); + for (int i=0 ; irequestor, se->property, XA_ATOM, + 32, 0, (unsigned char*)x.data(), + sel_formats.GetCount()); + } + else if (se->selection == S_ATOMS->m_clipboard) + { + std::vector x(data.GetCount()); + for (int i=0 ; irequestor, se->property, XA_ATOM, + 32, 0, (unsigned char*)x.data(), + data.GetCount()); + } +#endif + 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 0 + if (se->selection == XA_PRIMARY) + { + String fmt = XAtomName(se->target); + int i = sel_formats.Find(fmt); + if (i >= 0 && sel_ctrl) + { + String d = sel_ctrl->GetSelectionData(fmt); + XChangeProperty(m_xDisp, se->requestor, se->property, se->target, 8, PropModeReplace, + d, d.GetLength()); + } + else + reply.xselection.property = 0; + } + else if (se->selection == S_ATOMS->m_clipboard) + { + int i = data.Find(se->target); + if (i >= 0) + { + String d = data[i].Render(); + XChangeProperty(m_xDisp, se->requestor, se->property, se->target, 8, PropModeReplace, + d, d.GetLength()); + } + else + reply.xselection.property = 0; + } +#endif + 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); + } + void waitForRetrace() { std::unique_lock lk(m_gfxCtx.m_vsyncmt); @@ -943,11 +1177,43 @@ public: }; } + /* 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); + } + void _incomingEvent(void* e) { XEvent* event = (XEvent*)e; switch (event->type) { + case SelectionRequest: + { + handleSelectionRequest(&event->xselectionrequest); + return; + } case ClientMessage: { if (event->xclient.data.l[0] == S_ATOMS->m_wmDeleteWindow && m_callback) @@ -990,6 +1256,27 @@ public: m_ww = event->xconfigure.width; m_wh = event->xconfigure.height; + if (m_bestStyle & XIMPreeditArea) + { + XRectangle preedit_area; + preedit_area.width = event->xconfigure.width*4/5; + preedit_area.height = 0; + GetPreferredGeometry(XNPreeditAttributes, &preedit_area); + preedit_area.x = event->xconfigure.width - preedit_area.width; + preedit_area.y = event->xconfigure.height - preedit_area.height; + SetGeometry(XNPreeditAttributes, &preedit_area); + } + if (m_bestStyle & XIMStatusArea) + { + XRectangle status_area; + status_area.width = event->xconfigure.width/5; + status_area.height = 0; + GetPreferredGeometry(XNStatusAttributes, &status_area); + status_area.x = 0; + status_area.y = event->xconfigure.height - status_area.height; + SetGeometry(XNStatusAttributes, &status_area); + } + if (m_callback) { SWindowRect rect = @@ -1004,9 +1291,10 @@ public: { ESpecialKey specialKey; EModifierKey modifierKey; - uint32_t charCode = translateKeysym(XLookupKeysym(&event->xkey, 0), - specialKey, modifierKey, m_xDisp, event->xkey.state); - EModifierKey modifierMask = translateModifiers(event->xkey.state); + unsigned int state = event->xkey.state; + event->xkey.state &= ~ControlMask; + uint32_t charCode = translateKeysym(&event->xkey, specialKey, modifierKey, m_xIC); + EModifierKey modifierMask = translateModifiers(state); if (charCode) m_callback->charKeyDown(charCode, modifierMask, false); else if (specialKey != ESpecialKey::None) @@ -1022,9 +1310,10 @@ public: { ESpecialKey specialKey; EModifierKey modifierKey; - uint32_t charCode = translateKeysym(XLookupKeysym(&event->xkey, 0), - specialKey, modifierKey, m_xDisp, event->xkey.state); - EModifierKey modifierMask = translateModifiers(event->xkey.state); + unsigned int state = event->xkey.state; + event->xkey.state &= ~ControlMask; + uint32_t charCode = translateKeysym(&event->xkey, specialKey, modifierKey, m_xIC); + EModifierKey modifierMask = translateModifiers(state); if (charCode) m_callback->charKeyUp(charCode, modifierMask); else if (specialKey != ESpecialKey::None) @@ -1302,11 +1591,11 @@ public: }; IWindow* _WindowXlibNew(const std::string& title, - Display* display, int defaultScreen, + Display* display, int defaultScreen, XIM xIM, XIMStyle bestInputStyle, XFontSet fontset, GLXContext lastCtx) { XLockDisplay(display); - IWindow* ret = new WindowXlib(title, display, defaultScreen, lastCtx); + IWindow* ret = new WindowXlib(title, display, defaultScreen, xIM, bestInputStyle, fontset, lastCtx); XUnlockDisplay(display); return ret; }