1
0
Fork 0
mirror of https://github.com/davatorium/rofi.git synced 2024-11-18 13:54:36 -05:00

input method (#1735)

* input method draft

* restoring relese event

* using unused macro, removing debug code, handling disconnection

* review fixes, new update_im_window_pos method

* initializing variables correctly

* initializing im pos queue correctly

* ime window positioning

* add widget_get_y_pos() position

* [Build] Update makefile with imdkit

* [CI] Add imdkit as dependency.

* [XCB] rofi_view_paste don't throw warning, print debug.

* [XCB] rofi_view_paste lower 'failed to convert selection'

* [Build] Add minimum version check to imdkit

* new macro XCB_IMDKIT_1_0_3_LOWER

* [Build] Try to support old version of imdkit in meson/makefile.

* [Build] Fix typo in meson.build

* [XIM] Don't set use compound/set use utf8 when on old version.

* [Build] Allow building without imdkit.

* [Doc] Add imdkit to dependency list.

Co-authored-by: Dave Davenport <qball@gmpclient.org>
This commit is contained in:
duarm 2022-11-08 15:18:45 -03:00 committed by GitHub
parent 579902deff
commit 6d02648d3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 308 additions and 21 deletions

View file

@ -29,6 +29,7 @@ runs:
libxcb-xkb-dev \
libxcb-xrm-dev \
libxcb-cursor-dev \
libxcb-imdkit-dev \
libxkbcommon-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \

View file

@ -39,6 +39,7 @@ You can also use [Meson](https://mesonbuild.com/) as an alternative.
* xcb-util
* xcb-util-wm (sometimes split as libxcb-ewmh and libxcb-icccm)
* xcb-util-cursor
* xcb-imdkit (optional, 1.0.3 or up preferred)
On debian based systems, the developer packages are in the form of: `<package>-dev` on rpm based
`<package>-devel`.

View file

@ -145,6 +145,7 @@ rofi_CFLAGS=\
$(libsn_CFLAGS)\
$(cairo_CFLAGS)\
$(gdkpixbuf_CFLAGS)\
$(imdclient_CFLAGS)\
-DMANPAGE_PATH="\"$(mandir)/\""\
-I$(top_srcdir)/include/\
-I$(top_builddir)/lexer/\
@ -165,6 +166,7 @@ rofi_LDADD=\
$(pango_LIBS)\
$(cairo_LIBS)\
$(gdkpixbuf_LIBS)\
$(imdclient_LIBS)\
$(LIBS)
##

View file

@ -146,6 +146,10 @@ PKG_CHECK_MODULES([glib], [glib-2.0 >= ${glib_min_version} gio-unix-2.0 gmod
AC_DEFINE_UNQUOTED([GLIB_VERSION_MIN_REQUIRED], [(G_ENCODE_VERSION(${glib_min_major},${glib_min_minor}))], [The lower GLib version supported])
AC_DEFINE_UNQUOTED([GLIB_VERSION_MAX_ALLOWED], [(G_ENCODE_VERSION(${glib_min_major},${glib_min_minor}))], [The highest GLib version supported])
GW_CHECK_XCB([xcb-aux xcb-xkb xkbcommon xkbcommon-x11 xcb-ewmh xcb-icccm xcb-cursor xcb-randr xcb-xinerama ])
PKG_CHECK_MODULES([imdclient], [xcb-imdkit <= 1.0.2 ],
[AC_DEFINE([XCB_IMDKIT_1_0_3_LOWER], [1], [Indicate lower version of imdclient])
AC_DEFINE([XCB_IMDKIT],[1], [IMD Kit missing])],
[PKG_CHECK_MODULES([imdclient], [xcb-imdkit >= 1.0.3],[AC_DEFINE([XCB_IMDKIT],[1], [IMD Kit missing])],[HAVE_IMDKIT=0])])
PKG_CHECK_MODULES([pango], [pango pangocairo])
PKG_CHECK_MODULES([cairo], [cairo cairo-xcb])
PKG_CHECK_MODULES([libsn], [libstartup-notification-1.0 ])

View file

@ -341,5 +341,15 @@ void rofi_view_set_window_title(const char *title);
* set ellipsize mode to start.
*/
void rofi_view_ellipsize_start(RofiViewState *state);
/**
* @param new_x New XIM window x pos
* @param new_y New XIM window y pos
*
* Updates the XIM window position to new_x and new_y, relative to the
* main_window
*/
gboolean rofi_set_im_window_pos(int new_x, int new_y);
/** @} */
#endif

View file

@ -32,6 +32,7 @@
#include <glib.h>
#include <libsn/sn.h>
#include "xcb.h"
#include <libgwater-xcb.h>
#include <xcb/xcb.h>
#include <xcb/xcb_ewmh.h>
@ -45,6 +46,10 @@ struct _xcb_stuff {
GMainLoop *main_loop;
GWaterXcbSource *source;
xcb_connection_t *connection;
#ifdef XCB_IMDKIT
xcb_xic_t ic;
xcb_xim_t *im;
#endif
xcb_ewmh_connection_t ewmh;
xcb_screen_t *screen;
int screen_nbr;

View file

@ -29,6 +29,10 @@
#define ROFI_XCB_H
#include <cairo.h>
#include <config.h>
#ifdef XCB_IMDKIT
#include <xcb-imdkit/imclient.h>
#endif
#include <xcb/xcb.h>
/**
@ -220,6 +224,7 @@ extern WindowManagerQuirk current_window_manager;
* @returns NULL if window was not found, or unmapped, otherwise returns a
* cairo_surface.
*/
cairo_surface_t *x11_helper_get_screenshot_surface_window(xcb_window_t window,
int size);
@ -233,4 +238,11 @@ cairo_surface_t *x11_helper_get_screenshot_surface_window(xcb_window_t window,
void cairo_image_surface_blur(cairo_surface_t *surface, double radius,
double deviation);
#ifdef XCB_IMDKIT
/**
* IME Forwarding
*/
void x11_event_handler_fowarding(xcb_xim_t *im, xcb_xic_t ic,
xcb_key_press_event_t *event, void *user_data);
#endif
#endif

View file

@ -74,9 +74,27 @@ deps += [
dependency('libstartup-notification-1.0'),
]
check = dependency('check', version: '>= 0.11.0', required: get_option('check'))
imdkit_new = dependency('xcb-imdkit', version: '>= 1.0.3', required: false)
imdkit_old = dependency('xcb-imdkit', version: '<= 1.0.2', required: false)
check = dependency('check', version: '>= 0.11.0', required: get_option('check'))
header_conf = configuration_data()
if imdkit_new.found()
deps += imdkit_new
header_conf.set('XCB_IMDKIT_1_0_3_LOWER', false)
header_conf.set('XCB_IMDKIT', true)
elif imdkit_old.found()
deps+= imdkit_old
header_conf.set('XCB_IMDKIT_1_0_3_LOWER', true)
header_conf.set('XCB_IMDKIT', true)
else
header_conf.set('XCB_IMDKIT_1_0_3_LOWER', false)
header_conf.set('XCB_IMDKIT', false)
endif
header_conf.set_quoted('PACKAGE_NAME', meson.project_name())
header_conf.set_quoted('PACKAGE_VERSION', meson.project_version())
header_conf.set_quoted('VERSION', meson.project_version())

View file

@ -379,6 +379,13 @@ static void help(G_GNUC_UNUSED int argc, char **argv) {
#else
printf("\t• asan %sdisabled%s\n", is_term ? color_red : "",
is_term ? color_reset : "");
#endif
#ifdef XCB_IMDKIT
printf("\t• imdkit %senabled%s\n", is_term ? color_green : "",
is_term ? color_reset : "");
#else
printf("\t• imdkit %sdisabled%s\n", is_term ? color_red : "",
is_term ? color_reset : "");
#endif
printf("\n");
printf("For more information see: %sman rofi%s\n", is_term ? color_bold : "",

View file

@ -38,6 +38,9 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef XCB_IMDKIT
#include <xcb-imdkit/encoding.h>
#endif
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xkb.h>
@ -79,6 +82,20 @@ void rofi_view_update(RofiViewState *state, gboolean qr);
static int rofi_view_calculate_height(RofiViewState *state);
#ifdef XCB_IMDKIT
static void xim_commit_string(xcb_xim_t *im, G_GNUC_UNUSED xcb_xic_t ic,
G_GNUC_UNUSED uint32_t flag, char *str,
uint32_t length, G_GNUC_UNUSED uint32_t *keysym,
G_GNUC_UNUSED size_t nKeySym,
G_GNUC_UNUSED void *user_data);
static void xim_disconnected(G_GNUC_UNUSED xcb_xim_t *im,
G_GNUC_UNUSED void *user_data);
xcb_xim_im_callback xim_callback = {.forward_event =
x11_event_handler_fowarding,
.commit_string = xim_commit_string,
.disconnected = xim_disconnected};
#endif
/** Thread pool used for filtering */
GThreadPool *tpool = NULL;
@ -784,6 +801,86 @@ rofi_view_setup_fake_transparency(widget *win,
TICK_N("Fake transparency");
}
}
#ifdef XCB_IMDKIT
static void xim_commit_string(xcb_xim_t *im, G_GNUC_UNUSED xcb_xic_t ic,
G_GNUC_UNUSED uint32_t flag, char *str,
uint32_t length, G_GNUC_UNUSED uint32_t *keysym,
G_GNUC_UNUSED size_t nKeySym,
G_GNUC_UNUSED void *user_data) {
RofiViewState *state = rofi_view_get_active();
if (state == NULL) {
return;
}
#ifndef XCB_IMDKIT_1_0_3_LOWER
if (xcb_xim_get_encoding(im) == XCB_XIM_UTF8_STRING) {
rofi_view_handle_text(state, str);
} else if (xcb_xim_get_encoding(im) == XCB_XIM_COMPOUND_TEXT) {
size_t newLength = 0;
char *utf8 = xcb_compound_text_to_utf8(str, length, &newLength);
if (utf8) {
rofi_view_handle_text(state, utf8);
}
}
#else
size_t newLength = 0;
char *utf8 = xcb_compound_text_to_utf8(str, length, &newLength);
if (utf8) {
rofi_view_handle_text(state, utf8);
}
#endif
}
static void xim_disconnected(G_GNUC_UNUSED xcb_xim_t *im,
G_GNUC_UNUSED void *user_data) {
xcb->ic = 0;
}
static void create_ic_callback(xcb_xim_t *im, xcb_xic_t new_ic,
G_GNUC_UNUSED void *user_data) {
xcb->ic = new_ic;
if (xcb->ic) {
xcb_xim_set_ic_focus(im, xcb->ic);
}
}
gboolean rofi_set_im_window_pos(int new_x, int new_y) {
if (!xcb->ic)
return false;
static xcb_point_t spot = {.x = 0, .y = 0};
if (spot.x != new_x || spot.y != new_y) {
spot.x = new_x;
spot.y = new_y;
xcb_xim_nested_list nested = xcb_xim_create_nested_list(
xcb->im, XCB_XIM_XNSpotLocation, &spot, NULL);
xcb_xim_set_ic_values(xcb->im, xcb->ic, NULL, NULL, XCB_XIM_XNClientWindow,
&CacheState.main_window, XCB_XIM_XNFocusWindow,
&CacheState.main_window, XCB_XIM_XNPreeditAttributes,
&nested, NULL);
free(nested.data);
}
return true;
}
static void open_xim_callback(xcb_xim_t *im, G_GNUC_UNUSED void *user_data) {
RofiViewState *state = rofi_view_get_active();
uint32_t input_style = XCB_IM_PreeditPosition | XCB_IM_StatusArea;
xcb_point_t spot;
spot.x += widget_get_x_pos(&state->text->widget) +
textbox_get_cursor_x_pos(state->text);
spot.y += widget_get_y_pos(&state->text->widget) +
widget_get_height(&state->text->widget);
xcb_xim_nested_list nested =
xcb_xim_create_nested_list(im, XCB_XIM_XNSpotLocation, &spot, NULL);
xcb_xim_create_ic(
im, create_ic_callback, NULL, XCB_XIM_XNInputStyle, &input_style,
XCB_XIM_XNClientWindow, &CacheState.main_window, XCB_XIM_XNFocusWindow,
&CacheState.main_window, XCB_XIM_XNPreeditAttributes, &nested, NULL);
free(nested.data);
}
#endif
void __create_window(MenuFlags menu_flags) {
uint32_t selmask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
XCB_CW_BIT_GRAVITY | XCB_CW_BACKING_STORE |
@ -799,6 +896,15 @@ void __create_window(MenuFlags menu_flags) {
XCB_GRAVITY_STATIC, XCB_BACKING_STORE_NOT_USEFUL,
xcb_event_masks, map};
#ifdef XCB_IMDKIT
xcb_xim_set_im_callback(xcb->im, &xim_callback, NULL);
#endif
// Open connection to XIM server.
#ifdef XCB_IMDKIT
xcb_xim_open(xcb->im, open_xim_callback, true, NULL);
#endif
xcb_window_t box_window = xcb_generate_id(xcb->connection);
xcb_void_cookie_t cc = xcb_create_window_checked(
xcb->connection, depth->depth, box_window, xcb_stuff_get_root_window(), 0,
@ -810,6 +916,7 @@ void __create_window(MenuFlags menu_flags) {
g_error("xcb_create_window() failed error=0x%x\n", error->error_code);
exit(EXIT_FAILURE);
}
TICK_N("xcb create window");
CacheState.gc = xcb_generate_id(xcb->connection);
xcb_create_gc(xcb->connection, CacheState.gc, box_window, 0, 0);
@ -1107,6 +1214,7 @@ static void update_callback(textbox *t, icon *ico, unsigned int index,
textbox_set_pango_attributes(t, list);
pango_attr_list_unref(list);
}
g_list_free(add_list);
g_free(text);
} else {
@ -1149,6 +1257,14 @@ void rofi_view_update(RofiViewState *state, gboolean qr) {
cairo_set_operator(d, CAIRO_OPERATOR_OVER);
widget_draw(WIDGET(state->main_window), d);
#ifdef XCB_IMDKIT
int x = widget_get_x_pos(&state->text->widget) +
textbox_get_cursor_x_pos(state->text);
int y = widget_get_y_pos(&state->text->widget) +
widget_get_height(&state->text->widget);
rofi_set_im_window_pos(x, y);
#endif
TICK_N("widgets");
cairo_surface_flush(CacheState.edit_surf);
if (qr) {

View file

@ -30,6 +30,9 @@
#define G_LOG_DOMAIN "X11Helper"
#include "config.h"
#ifdef XCB_IMDKIT
#include <xcb-imdkit/encoding.h>
#endif
#include <cairo-xcb.h>
#include <cairo.h>
#include <glib.h>
@ -62,6 +65,7 @@
#include "xcb-internal.h"
#include "xcb.h"
#include <libsn/sn.h>
#include <stdbool.h>
#include "mode.h"
#include "modes/window.h"
@ -84,6 +88,9 @@ WindowManagerQuirk current_window_manager = WM_EWHM;
*/
struct _xcb_stuff xcb_int = {.connection = NULL,
.screen = NULL,
#ifdef XCB_IMDKIT
.im = NULL,
#endif
.screen_nbr = -1,
.sndisplay = NULL,
.sncontext = NULL,
@ -1061,6 +1068,35 @@ int monitor_active(workarea *mon) {
return FALSE;
}
static bool get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out) {
xcb_get_atom_name_cookie_t cookie;
xcb_get_atom_name_reply_t *reply;
int length;
char *name;
if (atom == 0) {
*out = NULL;
return true;
}
cookie = xcb_get_atom_name(conn, atom);
reply = xcb_get_atom_name_reply(conn, cookie, NULL);
if (!reply)
return false;
length = xcb_get_atom_name_name_length(reply);
name = xcb_get_atom_name_name(reply);
(*out) = g_strndup(name, length);
if (!(*out)) {
free(reply);
return false;
}
free(reply);
return true;
}
/**
* @param state Internal state of the menu.
* @param xse X selection event.
@ -1070,7 +1106,7 @@ int monitor_active(workarea *mon) {
static void rofi_view_paste(RofiViewState *state,
xcb_selection_notify_event_t *xse) {
if (xse->property == XCB_ATOM_NONE) {
g_warning("Failed to convert selection");
g_debug("Failed to convert selection");
} else if (xse->property == xcb->ewmh.UTF8_STRING) {
gchar *text = window_get_text_prop(xse->requestor, xcb->ewmh.UTF8_STRING);
if (text != NULL && text[0] != '\0') {
@ -1085,7 +1121,13 @@ static void rofi_view_paste(RofiViewState *state,
}
g_free(text);
} else {
g_warning("Failed");
char *out = NULL;
if (get_atom_name(xcb->connection, xse->property, &out)) {
g_debug("rofi_view_paste: Got unknown atom: %s", out);
g_free(out);
} else {
g_debug("rofi_view_paste: Got unknown, unnamed: %s", out);
}
}
}
@ -1142,6 +1184,32 @@ static gboolean x11_button_to_nk_bindings_scroll(guint32 x11_button,
return TRUE;
}
static void rofi_key_press_event_handler(xcb_key_press_event_t *xkpe,
RofiViewState *state) {
gchar *text;
xcb->last_timestamp = xkpe->time;
if (config.xserver_i300_workaround) {
text = nk_bindings_seat_handle_key_with_modmask(
xcb->bindings_seat, NULL, xkpe->state, xkpe->detail,
NK_BINDINGS_KEY_STATE_PRESS);
} else {
text = nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkpe->detail,
NK_BINDINGS_KEY_STATE_PRESS);
}
if (text != NULL) {
rofi_view_handle_text(state, text);
g_free(text);
}
}
static void rofi_key_release_event_handler(xcb_key_release_event_t *xkre,
G_GNUC_UNUSED RofiViewState *state) {
xcb->last_timestamp = xkre->time;
nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkre->detail,
NK_BINDINGS_KEY_STATE_RELEASE);
}
/**
* Process X11 events in the main-loop (gui-thread) of the application.
*/
@ -1301,28 +1369,26 @@ static void main_loop_x11_event_handler_view(xcb_generic_event_t *event) {
}
case XCB_KEY_PRESS: {
xcb_key_press_event_t *xkpe = (xcb_key_press_event_t *)event;
gchar *text;
xcb->last_timestamp = xkpe->time;
if (config.xserver_i300_workaround) {
text = nk_bindings_seat_handle_key_with_modmask(
xcb->bindings_seat, NULL, xkpe->state, xkpe->detail,
NK_BINDINGS_KEY_STATE_PRESS);
} else {
text = nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkpe->detail,
NK_BINDINGS_KEY_STATE_PRESS);
}
if (text != NULL) {
rofi_view_handle_text(state, text);
g_free(text);
#ifdef XCB_IMDKIT
if (xcb->ic) {
xcb_xim_forward_event(xcb->im, xcb->ic, xkpe);
} else
#endif
{
rofi_key_press_event_handler(xkpe, state);
}
break;
}
case XCB_KEY_RELEASE: {
xcb_key_release_event_t *xkre = (xcb_key_release_event_t *)event;
xcb->last_timestamp = xkre->time;
nk_bindings_seat_handle_key(xcb->bindings_seat, NULL, xkre->detail,
NK_BINDINGS_KEY_STATE_RELEASE);
#ifdef XCB_IMDKIT
if (xcb->ic) {
xcb_xim_forward_event(xcb->im, xcb->ic, xkre);
} else
#endif
{
rofi_key_release_event_handler(xkre, state);
}
break;
}
default:
@ -1331,6 +1397,25 @@ static void main_loop_x11_event_handler_view(xcb_generic_event_t *event) {
rofi_view_maybe_update(state);
}
#ifdef XCB_IMDKIT
void x11_event_handler_fowarding(G_GNUC_UNUSED xcb_xim_t *im,
G_GNUC_UNUSED xcb_xic_t ic,
xcb_key_press_event_t *event,
G_GNUC_UNUSED void *user_data) {
RofiViewState *state = rofi_view_get_active();
if (state == NULL) {
return;
}
uint8_t type = event->response_type & ~0x80;
if (type == XCB_KEY_PRESS) {
rofi_key_press_event_handler(event, state);
} else if (type == XCB_KEY_RELEASE) {
xcb_key_release_event_t *xkre = (xcb_key_release_event_t *)event;
rofi_key_release_event_handler(xkre, state);
}
}
#endif
static gboolean main_loop_x11_event_handler(xcb_generic_event_t *ev,
G_GNUC_UNUSED gpointer user_data) {
if (ev == NULL) {
@ -1346,6 +1431,13 @@ static gboolean main_loop_x11_event_handler(xcb_generic_event_t *ev,
// status);
return G_SOURCE_CONTINUE;
}
#ifdef XCB_IMDKIT
if (xcb->im && xcb_xim_filter_event(xcb->im, ev)) {
return G_SOURCE_CONTINUE;
}
#endif
uint8_t type = ev->response_type & ~0x80;
if (type == xcb->xkb.first_event) {
switch (ev->pad0) {
@ -1375,6 +1467,7 @@ static gboolean main_loop_x11_event_handler(xcb_generic_event_t *ev,
if (xcb->sndisplay != NULL) {
sn_xcb_display_process_event(xcb->sndisplay, ev);
}
main_loop_x11_event_handler_view(ev);
return G_SOURCE_CONTINUE;
}
@ -1542,6 +1635,9 @@ gboolean display_setup(GMainLoop *main_loop, NkBindings *bindings) {
find_arg_str("-display", &display_str);
xcb->main_loop = main_loop;
#ifdef XCB_IMDKIT
xcb_compound_text_init();
#endif
xcb->source = g_water_xcb_source_new(g_main_loop_get_context(xcb->main_loop),
display_str, &xcb->screen_nbr,
main_loop_x11_event_handler, NULL, NULL);
@ -1550,6 +1646,16 @@ gboolean display_setup(GMainLoop *main_loop, NkBindings *bindings) {
return FALSE;
}
xcb->connection = g_water_xcb_source_get_connection(xcb->source);
#ifdef XCB_IMDKIT
xcb->im = xcb_xim_create(xcb->connection, xcb->screen_nbr, NULL);
#endif
#ifdef XCB_IMDKIT
#ifndef XCB_IMDKIT_1_0_3_LOWER
xcb_xim_set_use_compound_text(xcb->im, true);
xcb_xim_set_use_utf8_string(xcb->im, true);
#endif
#endif
TICK_N("Open Display");
@ -1819,6 +1925,11 @@ void display_cleanup(void) {
xcb_ewmh_connection_wipe(&(xcb->ewmh));
xcb_flush(xcb->connection);
xcb_aux_sync(xcb->connection);
#ifdef XCB_IMDKIT
xcb_xim_close(xcb->im);
xcb_xim_destroy(xcb->im);
xcb->im = NULL;
#endif
g_water_xcb_source_free(xcb->source);
xcb->source = NULL;
xcb->connection = NULL;