From 62d5e610c205fc81782687edd1c51b022ef2a508 Mon Sep 17 00:00:00 2001 From: Bert Gijsbers Date: Thu, 13 Jun 2024 21:41:02 +0200 Subject: [PATCH] Support composing characters in the address bar with X input method. --- man/icewm.pod | 1 + src/yinputline.cc | 90 +++++++++++++++++++++++++++++++++++++---------- src/yinputline.h | 3 ++ src/ylocale.cc | 3 -- src/ywindow.cc | 7 +++- src/ywindow.h | 5 +++ src/yxapp.cc | 14 ++++++++ src/yxapp.h | 3 ++ 8 files changed, 104 insertions(+), 22 deletions(-) diff --git a/man/icewm.pod b/man/icewm.pod index b8265e748..f62317e0a 100644 --- a/man/icewm.pod +++ b/man/icewm.pod @@ -528,6 +528,7 @@ otherwise it is activated by B=C. In it a shell command can be typed. On I it is executed by the B=C. On I this command is executed in a new terminal as given by B. +I cancels editing the address bar command. Commands are executed relative to the working directory of icewm. This is shown by C. Change it with C. Without argument C diff --git a/src/yinputline.cc b/src/yinputline.cc index 9b43a3a45..f4c947dc9 100644 --- a/src/yinputline.cc +++ b/src/yinputline.cc @@ -41,6 +41,7 @@ YInputLine::YInputLine(YWindow *parent, YInputListener *listener): fBlinkTime(333), fKeyPressed(0), fListener(listener), + inputContext(nullptr), inputFont(inputFontName), inputBg(&clrInput), inputFg(&clrInputText), @@ -53,6 +54,8 @@ YInputLine::YInputLine(YWindow *parent, YInputListener *listener): } YInputLine::~YInputLine() { + if (inputContext) + XDestroyIC(inputContext); } void YInputLine::setText(mstring text, bool asMarked) { @@ -322,13 +325,16 @@ bool YInputLine::handleKey(const XKeyEvent &key) { fListener->inputReturn(this, control); return true; } - else - { - char s[16]; + else { + const int n = 16; + wchar_t* s = new wchar_t[n]; - if (getCharFromEvent(key, s, sizeof(s))) { - replaceSelection(s, strlen(s)); + int len = getWCharFromEvent(key, s, n); + if (len) { + replaceSelection(s, len); return true; + } else { + delete[] s; } } } @@ -344,6 +350,18 @@ bool YInputLine::handleKey(const XKeyEvent &key) { return YWindow::handleKey(key); } +int YInputLine::getWCharFromEvent(const XKeyEvent& key, wchar_t* s, int maxLen) { + KeySym keysym = None; + Status status = None; + int len = XwcLookupString(inputContext, const_cast(&key), + s, maxLen, &keysym, &status); + + if (inrange(len, 0, maxLen - 1)) { + s[len] = None; + } + return len; +} + void YInputLine::handleButton(const XButtonEvent &button) { if (button.type == ButtonPress) { if (button.button == 1) { @@ -429,6 +447,9 @@ void YInputLine::handleClick(const XButtonEvent &up, int /*count*/) { inputMenu->setPopDownListener(this); } else if (up.button == 2 && xapp->isButton(up.state, Button2Mask)) { requestSelection(true); + } else if (up.button == 1 && xapp->isButton(up.state, Button1Mask)) { + if (fHasFocus == false) + gotFocus(); } } @@ -466,18 +487,18 @@ unsigned YInputLine::offsetToPos(int offset) { } void YInputLine::handleFocus(const XFocusChangeEvent &focus) { + if (focus.mode == NotifyGrab || focus.mode == NotifyUngrab) + return; + if (focus.type == FocusIn && focus.detail != NotifyPointer && focus.detail != NotifyPointerRoot) { - fHasFocus = true; selectAll(); - cursorBlinkTimer->setTimer(fBlinkTime, this, true); + gotFocus(); } else if (focus.type == FocusOut/* && fHasFocus == true*/) { - fHasFocus = false; - repaint(); - cursorBlinkTimer = null; + lostFocus(); if (inputMenu && inputMenu == xapp->popup()) { } else if (fListener) { @@ -566,6 +587,16 @@ void YInputLine::replaceSelection(const char* insert, int amount) { repaint(); } +void YInputLine::replaceSelection(wchar_t* insert, int amount) { + unsigned from = min(curPos, markPos); + unsigned to = max(curPos, markPos); + YWideString wide(amount, insert); + fText.replace(from, to - from, wide); + curPos = markPos = from + wide.length(); + limit(); + repaint(); +} + bool YInputLine::deleteSelection() { if (hasSelection()) { replaceSelection("", 0); @@ -849,20 +880,43 @@ bool YInputLine::isFocusTraversable() { } void YInputLine::gotFocus() { - if (fHasFocus == false) { - fHasFocus = true; - fCursorVisible = true; - cursorBlinkTimer->setTimer(fBlinkTime, this, true); + fHasFocus = true; + fCursorVisible = true; + cursorBlinkTimer->setTimer(fBlinkTime, this, true); + + if (focused() || (YWindow::gotFocus(), focused() == false)) repaint(); + + if (inputContext == nullptr) { + inputContext = + XCreateIC(xapp->xim(), + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, handle(), + XNFocusWindow, handle(), + nullptr); + unsigned long mask = None; + XGetICValues(inputContext, XNFilterEvents, &mask, nullptr); + if (mask) { + addEventMask(mask); + } + eventFiltering(true); } + + XSetICFocus(inputContext); + XwcResetIC(inputContext); } void YInputLine::lostFocus() { - if (cursorBlinkTimer) { - cursorBlinkTimer = null; - fHasFocus = false; + cursorBlinkTimer = null; + fCursorVisible = false; + fHasFocus = false; + if (focused()) + YWindow::lostFocus(); + else repaint(); - } + + if (inputContext) + XUnsetICFocus(inputContext); } // vim: set sw=4 ts=4 et: diff --git a/src/yinputline.h b/src/yinputline.h index 3f673fc95..415a1c3a9 100644 --- a/src/yinputline.h +++ b/src/yinputline.h @@ -55,6 +55,7 @@ class YInputLine: bool move(unsigned pos, bool extend); bool hasSelection() const { return curPos != markPos; } void replaceSelection(const char* str, int len); + void replaceSelection(wchar_t* str, int len); bool deleteSelection(); bool deleteNextChar(); bool deletePreviousChar(); @@ -80,6 +81,7 @@ class YInputLine: unsigned offsetToPos(int offset); static mstring completeVariable(mstring var); static mstring completeUsername(mstring user); + int getWCharFromEvent(const XKeyEvent& key, wchar_t* s, int maxLen); YWideString fText; unsigned markPos; @@ -93,6 +95,7 @@ class YInputLine: unsigned fKeyPressed; YInputListener* fListener; + XIC inputContext; YFont inputFont; YColorName inputBg; YColorName inputFg; diff --git a/src/ylocale.cc b/src/ylocale.cc index ce0f241de..158ac19ff 100644 --- a/src/ylocale.cc +++ b/src/ylocale.cc @@ -37,7 +37,6 @@ class YConverter { iconv_t localer() const { return toLocale; } const char* localeName() const { return fLocaleName; } const char* codesetName() const { return fCodeset; } - const char* modifiers() const { return fModifiers; } private: void getConverters(); @@ -47,7 +46,6 @@ class YConverter { iconv_t toUnicode; iconv_t toLocale; const char* fLocaleName; - const char* fModifiers; const char* fCodeset; }; @@ -61,7 +59,6 @@ YConverter::YConverter(const char* localeName) : "Falling back to 'C' locale'.")); fLocaleName = setlocale(LC_ALL, "C"); } - fModifiers = XSetLocaleModifiers(""); fCodeset = getCodeset(); MSG(("locale: %s, MB_CUR_MAX: %zd, codeset: %s, endian: %c", diff --git a/src/ywindow.cc b/src/ywindow.cc index 5f1b13a5e..fb32f1518 100644 --- a/src/ywindow.cc +++ b/src/ywindow.cc @@ -565,6 +565,10 @@ void YWindow::raiseTo(YWindow* inferior) { } void YWindow::handleEvent(const XEvent &event) { + if (hasbit(flags, wfFiltering) && + XFilterEvent(const_cast(&event), fHandle)) + return; + switch (event.type) { case KeyPress: case KeyRelease: @@ -1863,9 +1867,10 @@ bool YWindow::getCharFromEvent(const XKeyEvent &key, char *s, int maxLen) { char keyBuf[16]; KeySym ksym; XKeyEvent kev = key; + static XComposeStatus compose = { NULL, 0 }; // FIXME: - int klen = XLookupString(&kev, keyBuf, sizeof(keyBuf), &ksym, nullptr); + int klen = XLookupString(&kev, keyBuf, sizeof(keyBuf), &ksym, &compose); #ifndef USE_XmbLookupString if ((klen == 0) && (ksym < 0x1000)) { klen = 1; diff --git a/src/ywindow.h b/src/ywindow.h index d1f8dbc52..79ffba727 100644 --- a/src/ywindow.h +++ b/src/ywindow.h @@ -291,6 +291,10 @@ class YWindow : protected YWindowList, private YWindowNode { void unmanageWindow() { removeWindow(); } +protected: + void eventFiltering(bool f) { if (f) flags |= wfFiltering; + else flags &= ~wfFiltering; } + private: enum WindowFlags { wfVisible = 1 << 0, @@ -300,6 +304,7 @@ class YWindow : protected YWindowList, private YWindowNode { wfToplevel = 1 << 4, wfNullSize = 1 << 5, wfFocused = 1 << 6, + wfFiltering = 1 << 7, }; Window create(); diff --git a/src/yxapp.cc b/src/yxapp.cc index 40d59962c..1e6fd5fb5 100644 --- a/src/yxapp.cc +++ b/src/yxapp.cc @@ -1079,6 +1079,7 @@ YXApplication::YXApplication(int *argc, char ***argv, const char *displayName): lastEventTime(CurrentTime), fPopup(nullptr), xfd(this), + fXIM(initInput(fDisplay)), fXGrabWindow(nullptr), fGrabWindow(nullptr), fKeycodeMap(nullptr), @@ -1100,6 +1101,17 @@ YXApplication::YXApplication(int *argc, char ***argv, const char *displayName): initModifiers(); } +XIM YXApplication::initInput(Display* dpy) { + XSetLocaleModifiers(""); + + XIM xim = XOpenIM(dpy, None, nullptr, nullptr); + if (xim == nullptr) { + XSetLocaleModifiers("@im=none"); + xim = XOpenIM(dpy, None, nullptr, nullptr); + } + return xim; +} + void YExtension::init(Display* dis, QueryFunc ext, QueryFunc ver) { supported = (*ext)(dis, &eventBase, &errorBase) && (*ver)(dis, &versionMajor, &versionMinor); @@ -1141,6 +1153,8 @@ YXApplication::~YXApplication() { XFreeColormap(display(), fColormap32); if (fKeycodeMap) XFree(fKeycodeMap); + if (fXIM) + XCloseIM(fXIM); xfd.unregisterPoll(); XCloseDisplay(display()); diff --git a/src/yxapp.h b/src/yxapp.h index ad0d5c79c..101eb6c36 100644 --- a/src/yxapp.h +++ b/src/yxapp.h @@ -194,6 +194,7 @@ class YXApplication: public YApplication { void unshift(KeySym* key, unsigned* mod); static const char* getHelpText(); + XIM xim() const { return fXIM; } protected: virtual int handleError(XErrorEvent* xev); @@ -228,6 +229,7 @@ class YXApplication: public YApplication { YPopupWindow *fPopup; friend class YXPoll; YXPoll xfd; + XIM fXIM; lazy fClip; YWindow *fXGrabWindow; @@ -245,6 +247,7 @@ class YXApplication: public YApplication { virtual void flushXEvents(); void initModifiers(); + static XIM initInput(Display* dpy); static void initAtoms(); static const char* parseArgs(int argc, char **argv, const char *displayName);