From f1ab0e2d8a46d921e7e45ba7e246455e7b3c4b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Tue, 23 Aug 2022 04:04:45 +0200 Subject: X11: use input methods, abandon xkbcommon And fix a redundant XCreatePixmap() call leaking resources. --- CMakeLists.txt | 2 +- README.adoc | 4 +-- nncmpp.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bb37ff..7a685c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ if (WITH_PULSE) list (APPEND extra_libraries ${libpulse_LIBRARIES}) endif () -pkg_check_modules (x11 x11 xkbcommon xrender xft fontconfig) +pkg_check_modules (x11 x11 xrender xft fontconfig) option (WITH_X11 "Use FFTW to enable spectrum visualisation" ${x11_FOUND}) if (WITH_X11) if (NOT x11_FOUND) diff --git a/README.adoc b/README.adoc index e772b1b..fd297d6 100644 --- a/README.adoc +++ b/README.adoc @@ -40,8 +40,8 @@ Building Build dependencies: CMake, pkg-config, asciidoctor, liberty (included), termo (included) + Runtime dependencies: ncursesw, libunistring, cURL, - fftw3 (optional), libpulse (optional) -Optional X11 dependencies: x11, xkbcommon, xft + fftw3 (optional), libpulse (optional) + +Optional X11 dependencies: x11, xft $ git clone --recursive https://git.janouch.name/p/nncmpp.git $ mkdir nncmpp/build diff --git a/nncmpp.c b/nncmpp.c index 19f1903..2ceaa12 100644 --- a/nncmpp.c +++ b/nncmpp.c @@ -113,7 +113,6 @@ enum #include #include #include -#include #endif // WITH_X11 #define APP_TITLE PROGRAM_NAME ///< Left top corner @@ -1345,6 +1344,8 @@ static struct app_context bool pulse_control_requested; ///< PulseAudio control desired by user #ifdef WITH_X11 + XIM x11_im; ///< Input method + XIC x11_ic; ///< Input method context Display *dpy; ///< X display handle struct poller_fd x11_event; ///< X11 events on wire struct poller_idle xpending_event; ///< X11 events possibly in I/O queues @@ -5739,6 +5740,8 @@ x11_flip (void) static void x11_destroy (void) { + XDestroyIC (g.x11_ic); + XCloseIM (g.x11_im); XDestroyWindow (g.dpy, g.x11_window); XRenderFreePicture (g.dpy, g.x11_pixmap_picture); XFreePixmap (g.dpy, g.x11_pixmap); @@ -5843,12 +5846,15 @@ x11_convert_keysym (KeySym keysym) static void on_x11_keypress (XEvent *e) { + // A kibibyte long buffer will have to suffice for anyone. XKeyEvent *ev = &e->xkey; - unsigned unconsumed_mods = 0; + char buf[1 << 10] = {}, *p = buf; KeySym keysym = None; - if (!XkbLookupKeySym (g.dpy, - (KeyCode) ev->keycode, ev->state, &unconsumed_mods, &keysym)) - return; + Status status = 0; + int len = Xutf8LookupString + (g.x11_ic, ev, buf, sizeof buf, &keysym, &status); + if (status == XBufferOverflow) + print_warning ("input method overflow"); termo_key_t key = {}; if (ev->state & ShiftMask) @@ -5862,19 +5868,37 @@ on_x11_keypress (XEvent *e) { key.type = TERMO_TYPE_FUNCTION; key.code.number = 1 + keysym - XK_F1; + app_process_termo_event (&key); } else if ((key.code.sym = x11_convert_keysym (keysym)) != TERMO_SYM_UNKNOWN) + { key.type = TERMO_TYPE_KEYSYM; - else if ((key.code.codepoint = xkb_keysym_to_utf32 (keysym))) + app_process_termo_event (&key); + } + else if (len) { - // Not filling in UTF-8, but xkb_keysym_to_utf8() exists. key.type = TERMO_TYPE_KEY; key.modifiers &= ~TERMO_KEYMOD_SHIFT; - } - else - return; - app_process_termo_event (&key); + int32_t cp = 0; + struct utf8_iter iter = { .s = buf, .len = len }; + size_t cp_len = 0; + while ((cp = utf8_iter_next (&iter, &cp_len)) >= 0) + { + termo_key_t k = key; + memcpy (k.multibyte, p, MIN (cp_len, sizeof k.multibyte - 1)); + p += cp_len; + + // This is unfortunate, but probably in the right place. + if (cp >= 32) + k.code.codepoint = cp; + else if (ev->state & ShiftMask) + k.code.codepoint = cp + 64; + else + k.code.codepoint = cp + 96; + app_process_termo_event (&k); + } + } } static void @@ -5975,6 +5999,9 @@ on_x11_pending (void *user_data) { if (XNextEvent (g.dpy, &ev.core)) exit_fatal ("XNextEvent returned non-zero"); + if (XFilterEvent (&ev.core, None)) + continue; + on_x11_event (&ev.core); } @@ -6093,11 +6120,18 @@ x11_init_fonts (void) static void x11_init (void) { + // https://tedyin.com/posts/a-brief-intro-to-linux-input-method-framework/ + if (!XSupportsLocale ()) + print_warning ("locale not supported by Xlib"); + XSetLocaleModifiers (""); + if (!(g.dpy = XkbOpenDisplay (NULL, &g.xkb_base_event_code, NULL, NULL, NULL, NULL))) exit_fatal ("cannot open display"); if (!XftDefaultHasRender (g.dpy)) exit_fatal ("XRender is not supported"); + if (!(g.x11_im = XOpenIM (g.dpy, NULL, NULL, NULL))) + exit_fatal ("failed to open an input method"); x11_default_error_handler = XSetErrorHandler (on_x11_error); @@ -6143,16 +6177,14 @@ x11_init (void) g.ui_height = 24 * g.ui_vunit; g.ui_width = g.ui_height * 4 / 3; + long im_event_mask = 0; + if (!XGetIMValues (g.x11_im, XNFilterEvents, &im_event_mask, NULL)) + attrs.event_mask |= im_event_mask; + Visual *visual = DefaultVisual (g.dpy, screen); g.x11_window = XCreateWindow (g.dpy, RootWindow (g.dpy, screen), 100, 100, g.ui_width, g.ui_height, 0, CopyFromParent, InputOutput, visual, CWEventMask | CWBackPixel | CWBitGravity, &attrs); - g.x11_pixmap = XCreatePixmap (g.dpy, g.x11_window, g.ui_width, g.ui_height, - DefaultDepth (g.dpy, screen)); - - x11_init_pixmap (); - g.xft_draw = XftDrawCreate (g.dpy, g.x11_pixmap, visual, cmap); - g.ui = &x11_ui; XTextProperty prop = {}; char *name = PROGRAM_NAME; @@ -6160,6 +6192,31 @@ x11_init (void) XSetWMName (g.dpy, g.x11_window, &prop); XFree (prop.value); + // TODO: It is possible to do, e.g., on-the-spot. + XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing; + XIMStyles *im_styles = NULL; + bool im_style_found = false; + if (!XGetIMValues (g.x11_im, XNQueryInputStyle, &im_styles, NULL) + && im_styles) + { + for (unsigned i = 0; i < im_styles->count_styles; i++) + im_style_found |= im_styles->supported_styles[i] == im_style; + XFree (im_styles); + } + if (!im_style_found) + print_warning ("failed to find the desired input method style"); + if (!(g.x11_ic = XCreateIC (g.x11_im, + XNInputStyle, im_style, + XNClientWindow, g.x11_window, + NULL))) + exit_fatal ("failed to open an input context"); + + XSetICFocus (g.x11_ic); + + x11_init_pixmap (); + g.xft_draw = XftDrawCreate (g.dpy, g.x11_pixmap, visual, cmap); + g.ui = &x11_ui; + XMapWindow (g.dpy, g.x11_window); } -- cgit v1.2.3