diff --git a/README.md b/README.md index 4f5c74b..f8b185a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Browsing patches? There is a [map of patches](https://coggle.it/diagram/YjT2DD6j ### Changelog: +2024-07-17 - Added the input method patch + 2024-07-16 - Added the mouse motion support patch 2023-06-15 - Added the caret width patch @@ -138,6 +140,9 @@ Browsing patches? There is a [map of patches](https://coggle.it/diagram/YjT2DD6j - [initialtext](https://tools.suckless.org/dmenu/patches/initialtext/) - adds an option to provide preselected text + - input-method + - adds support for input methods (fctix, ibus, etc.) + - [instant](https://tools.suckless.org/dmenu/patches/instant/) - adds a flag that will cause dmenu to select an item immediately if there is only one matching option left diff --git a/dmenu.c b/dmenu.c index 4871e3c..65bf5ab 100644 --- a/dmenu.c +++ b/dmenu.c @@ -269,6 +269,9 @@ cleanup(void) size_t i; XUngrabKey(dpy, AnyKey, AnyModifier, root); + #if INPUTMETHOD_PATCH + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + #endif // INPUTMETHOD_PATCH for (i = 0; i < SchemeLast; i++) free(scheme[i]); for (i = 0; items && items[i].text; ++i) @@ -1505,7 +1508,6 @@ run(void) #if PRESELECT_PATCH int i; #endif // PRESELECT_PATCH - while (!XNextEvent(dpy, &ev)) { #if PRESELECT_PATCH if (preselected) { @@ -1519,8 +1521,15 @@ run(void) preselected = 0; } #endif // PRESELECT_PATCH + #if INPUTMETHOD_PATCH + if (XFilterEvent(&ev, None)) + continue; + if (composing) + continue; + #else if (XFilterEvent(&ev, win)) continue; + #endif // INPUTMETHOD_PATCH switch(ev.type) { #if MOUSE_SUPPORT_PATCH case ButtonPress: @@ -1787,13 +1796,16 @@ setup(void) (unsigned char *) &dock, 1); #endif // WMTYPE_PATCH - /* input methods */ if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) die("XOpenIM failed: could not open input device"); + #if INPUTMETHOD_PATCH + init_input_method(xim); + #else xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, win, XNFocusWindow, win, NULL); + #endif // INPUTMETHOD_PATCH #if MANAGED_PATCH if (managed) { @@ -1815,8 +1827,13 @@ setup(void) XSelectInput(dpy, dws[i], FocusChangeMask); XFree(dws); } + #if !INPUTMETHOD_PATCH grabfocus(); + #endif // INPUTMETHOD_PATCH } + #if INPUTMETHOD_PATCH + grabfocus(); + #endif // INPUTMETHOD_PATCH drw_resize(drw, mw, mh); drawmenu(); } @@ -1932,6 +1949,10 @@ main(int argc, char *argv[]) #if XRESOURCES_PATCH if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); + #if INPUTMETHOD_PATCH + if (!XSetLocaleModifiers("")) + fputs("warning: could not set locale modifiers", stderr); + #endif // INPUTMETHOD_PATCH if (!(dpy = XOpenDisplay(NULL))) die("cannot open display"); @@ -2164,6 +2185,10 @@ main(int argc, char *argv[]) #else // !XRESOURCES_PATCH if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fputs("warning: no locale support\n", stderr); + #if INPUTMETHOD_PATCH + if (!XSetLocaleModifiers("")) + fputs("warning: could not set locale modifiers", stderr); + #endif // INPUTMETHOD_PATCH if (!(dpy = XOpenDisplay(NULL))) die("cannot open display"); screen = DefaultScreen(dpy); diff --git a/patch/include.c b/patch/include.c index c8a6612..27144f0 100644 --- a/patch/include.c +++ b/patch/include.c @@ -13,6 +13,9 @@ #if HIGHPRIORITY_PATCH #include "highpriority.c" #endif +#if INPUTMETHOD_PATCH +#include "inputmethod.c" +#endif #if DYNAMIC_OPTIONS_PATCH #include "dynamicoptions.c" #endif diff --git a/patch/include.h b/patch/include.h index c3cb75b..8cbecb8 100644 --- a/patch/include.h +++ b/patch/include.h @@ -4,6 +4,9 @@ #if FZFEXPECT_PATCH #include "fzfexpect.h" #endif +#if INPUTMETHOD_PATCH +#include "inputmethod.h" +#endif #if MULTI_SELECTION_PATCH #include "multiselect.h" #endif diff --git a/patch/inputmethod.c b/patch/inputmethod.c new file mode 100644 index 0000000..42a41c5 --- /dev/null +++ b/patch/inputmethod.c @@ -0,0 +1,196 @@ +static size_t nextrunetext(const char *text, size_t position, int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = position + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +/* return bytes from beginning of text to nth utf8 rune to the right */ +static size_t runebytes(const char *text, size_t n) +{ + size_t ret; + + ret = 0; + while (n-- > 0) + ret += nextrunetext(text + ret, 0, 1); + return ret; +} + +/* return number of characters from beginning of text to nth byte to the right + */ +static size_t runechars(const char *text, size_t n) +{ + size_t ret, i; + + ret = i = 0; + while (i < n) { + i += nextrunetext(text + i, 0, 1); + ret++; + } + return ret; +} + +/* move caret on pre-edit text */ +static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata) +{ + XIMPreeditCaretCallbackStruct *pcaret; + + (void)xic; + pcaret = (XIMPreeditCaretCallbackStruct *)calldata; + if (!pcaret) + return; + switch (pcaret->direction) { + case XIMForwardChar: + cursor = nextrune(+1); + break; + case XIMBackwardChar: + cursor = nextrune(-1); + break; + case XIMForwardWord: + movewordedge(+1); + break; + case XIMBackwardWord: + movewordedge(-1); + break; + case XIMLineStart: + cursor = 0; + break; + case XIMLineEnd: + if (preview[cursor] != '\0') + cursor = strlen(preview); + break; + case XIMAbsolutePosition: + cursor = runebytes(text, pcaret->position); + break; + case XIMDontChange: + /* do nothing */ + break; + case XIMCaretUp: + case XIMCaretDown: + case XIMNextLine: + case XIMPreviousLine: + /* not implemented */ + break; + } + pcaret->position = runechars(preview, cursor); +} + +/* start input method pre-editing */ +static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata) +{ + (void)xic; + (void)calldata; + (void)clientdata; + composing = 1; + printf("PREEDIT\n"); + return BUFSIZ; +} + +/* end input method pre-editing */ +static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata) +{ + (void)xic; + (void)clientdata; + (void)calldata; + printf("DONE\n"); + + composing = 0; +} + +/* draw input method pre-edit text */ +static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata) +{ + XIMPreeditDrawCallbackStruct *pdraw; + size_t beg, dellen, inslen, endlen; + + printf("DRAW\n"); + + (void)xic; + pdraw = (XIMPreeditDrawCallbackStruct *)calldata; + if (!pdraw) + return; + + /* we do not support wide characters */ + if (pdraw->text && pdraw->text->encoding_is_wchar == True) { + fputs("warning: wchar is not supportecd; use utf8", stderr); + return; + } + + beg = runebytes(text, pdraw->chg_first); + dellen = runebytes(preview + beg, pdraw->chg_length); + inslen = pdraw->text ? runebytes(pdraw->text->string.multi_byte, pdraw->text->length) : 0; + endlen = 0; + if (beg + dellen < strlen(preview)) + endlen = strlen(preview + beg + dellen); + + /* we cannot change text past the end of our pre-edit string */ + + if (beg + dellen >= BUFSIZ || beg + inslen >= BUFSIZ) + return; + + /* get space for text to be copied, and copy it */ + memmove(preview + beg + inslen, preview + beg + dellen, endlen + 1); + if (pdraw->text && pdraw->text->length) + memcpy(preview + beg, pdraw->text->string.multi_byte, inslen); + (preview + beg + inslen + endlen)[0] = '\0'; + + /* get caret position */ + cursor = runebytes(text, pdraw->caret); +} + +static void init_input_method(XIM xim) +{ + XVaNestedList preedit = NULL; + XICCallback start, done, draw, caret; + XIMStyle preeditstyle; + XIMStyle statusstyle; + XIMStyles *imstyles; + int i; + + /* get styles supported by input method */ + if (XGetIMValues(xim, XNQueryInputStyle, &imstyles, NULL) != NULL) + fputs("XGetIMValues: could not obtain input method values", stderr); + + /* check whether input method support on-the-spot pre-editing */ + preeditstyle = XIMPreeditNothing; + statusstyle = XIMStatusNothing; + for (i = 0; i < imstyles->count_styles; i++) { + if (imstyles->supported_styles[i] & XIMPreeditCallbacks) { + preeditstyle = XIMPreeditCallbacks; + break; + } + } + + /* create callbacks for the input context */ + start.client_data = NULL; + done.client_data = NULL; + draw.client_data = (XPointer)text; + caret.client_data = (XPointer)text; + start.callback = (XICProc)preeditstart; + done.callback = (XICProc)preeditdone; + draw.callback = (XICProc)preeditdraw; + caret.callback = (XICProc)preeditcaret; + + /* create list of values for input context */ + preedit = XVaCreateNestedList(0, XNPreeditStartCallback, &start, XNPreeditDoneCallback, + &done, XNPreeditDrawCallback, &draw, XNPreeditCaretCallback, + &caret, NULL); + if (preedit == NULL) + fputs("XVaCreateNestedList: could not create nested list", stderr); + + xic = XCreateIC(xim, XNInputStyle, preeditstyle | statusstyle, XNPreeditAttributes, preedit, + XNClientWindow, win, XNFocusWindow, win, NULL); + XFree(preedit); + + long eventmask; + /* get events the input method is interested in */ + if (XGetICValues(xic, XNFilterEvents, &eventmask, NULL)) + fputs("XGetICValues: could not obtain input context values", stderr); + + XSelectInput(dpy, win, + ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask | + PointerMotionMask | eventmask); +} diff --git a/patch/inputmethod.h b/patch/inputmethod.h new file mode 100644 index 0000000..21dd4c0 --- /dev/null +++ b/patch/inputmethod.h @@ -0,0 +1,11 @@ +static int composing; +static char preview[512] = ""; + +static size_t nextrunetext(const char *text, size_t position, int inc); +static size_t runebytes(const char *text, size_t n); +static size_t runechars(const char *text, size_t n); +static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata); +static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata); +static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata); +static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata); +static void init_input_method(XIM xim); diff --git a/patches.def.h b/patches.def.h index 25cbf82..d3076bf 100644 --- a/patches.def.h +++ b/patches.def.h @@ -108,6 +108,12 @@ */ #define INITIALTEXT_PATCH 0 +/* Adds support for input methods (fctix, ibus, etc.) allowing the user to change the + * keyboard layout while dmenu is open. + * https://github.com/bakkeby/dmenu-flexipatch/pull/22 + */ +#define INPUTMETHOD_PATCH 0 + /* This patch adds a flag which will cause dmenu to select an item immediately if there * is only one matching option left. * https://tools.suckless.org/dmenu/patches/instant/