/** * xrdp: A Remote Desktop Protocol server. * * Copyright (C) Jay Sorg 2009-2013 * Copyright (C) Laxmikant Rashinkar 2009-2012 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include "input.h" #include "thread_calls.h" static IBusBus *bus; static IBusEngine *g_engine; /* This is the engine name enabled before unicode engine enabled */ static gchar *last_input_name; static int id = 0; static int xrdp_input_enable(void) { IBusEngineDesc *desc; const gchar *name; if (!bus) { LOG(LOG_LEVEL_ERROR, "xrdp_ibus_init: input method switched failed, ibus not connected"); return 1; } /* Re-check the current global engine on every call rather than * trusting a one-time flag: ibus (particularly with * use_global_engine disabled, which is common) can silently swap * the active engine back to the user's own IME between calls, and * we need to notice that and reassert XrdpIme rather than keep * committing text to an engine that's no longer active. */ desc = ibus_bus_get_global_engine(bus); name = desc ? ibus_engine_desc_get_name(desc) : NULL; if (name && !g_ascii_strcasecmp(name, "XrdpIme") && g_engine) { /* XrdpIme is the global engine AND our cached engine instance is * still the enabled one - genuinely nothing to do. If g_engine * is NULL here, ibus disabled our instance (see * xrdp_input_ibus_engine_disable) without changing the global * engine name, so fall through and reassert below to get a * fresh, enabled instance. */ g_object_unref(desc); return 0; } if (!last_input_name && name) { /* remember user's original input method (first time only), will * switch back when disconnected. Copy the name out since it's * owned by desc, which we're about to unref. */ last_input_name = g_strdup(name); } if (desc) { g_object_unref(desc); } if (!ibus_bus_set_global_engine(bus, "XrdpIme")) { LOG(LOG_LEVEL_ERROR, "xrdp_input_enable: input method enable failed"); return 1; } LOG(LOG_LEVEL_INFO, "xrdp_ibus_init: input method switched sucessfully, old input name: %s", last_input_name); return 0; } static gboolean xrdp_input_commit_text_cb(gpointer data) { gunichar chr = GPOINTER_TO_UINT(data); /* Runs on the same thread/main context as xrdp_input_main_loop() * (see the g_main_context_invoke() call below), so this re-check of * g_engine is race-free against xrdp_input_ibus_engine_enable() / * xrdp_input_ibus_engine_disable(), which are dispatched on that * same thread. * * NOTE: engine->has_focus was tried here as a gate (skip/retry the * commit until the engine reports focus) on the theory that ibus * drops commits sent while unfocused. That made things strictly * worse: has_focus read FALSE on every single attempt, even ones * that would otherwise have succeeded, because IBusEngine does not * appear to auto-maintain that field from the FocusIn/FocusOut * D-Bus calls in this setup - nothing in this file ever writes to * it, so it's not a trustworthy signal without our own focus-in/ * focus-out handlers actually tracking it. Do not reintroduce a * has_focus check without first confirming it actually flips to * TRUE (e.g. by logging it from a real focus-in signal handler). */ if (g_engine) { ibus_engine_commit_text(g_engine, ibus_text_new_from_unichar(chr)); } else { LOG(LOG_LEVEL_ERROR, "xrdp_input_send_unicode: no active ibus engine to commit to"); } return G_SOURCE_REMOVE; } int xrdp_input_send_unicode(char32_t unicode) { LOG(LOG_LEVEL_DEBUG, "xrdp_input_send_unicode: received unicode input %i", unicode); if (xrdp_input_enable()) { return 1; } /* ibus_engine_commit_text() must run on the same thread that's * pumping the glib main loop the engine's D-Bus connection belongs * to (the xrdp_input_main_loop thread) - that's also the thread * that processes FocusIn/FocusOut for the engine. Calling it * directly from here (chansrv's own thread) lets the commit race * against that focus churn with no ordering guarantee, so ibus can * silently drop it. g_main_context_invoke() marshals the actual * commit onto the correct thread instead. */ g_main_context_invoke(NULL, xrdp_input_commit_text_cb, GUINT_TO_POINTER((guint)unicode)); return 0; } static void xrdp_input_ibus_engine_enable(IBusEngine *engine) { LOG(LOG_LEVEL_INFO, "xrdp_ibus_engine_enable: IM enabled"); g_engine = engine; } static void xrdp_input_ibus_engine_disable(IBusEngine *engine) { LOG(LOG_LEVEL_INFO, "xrdp_ibus_engine_disable: IM disabled"); /* ibus can disable our engine instance (e.g. it loses focus) while * still reporting "XrdpIme" as the global engine name, since that's * a separate piece of bookkeeping. Clear g_engine so xrdp_input_enable() * notices the mismatch instead of committing text to a disabled * engine on the next keypress. */ if (engine == g_engine) { g_engine = NULL; } } static void xrdp_input_ibus_disconnect(IBusEngine *engine) { LOG(LOG_LEVEL_INFO, "xrdp_ibus_engine_disable: IM disabled"); if (g_engine) { g_object_unref(g_engine); g_engine = NULL; } if (bus) { g_object_unref(bus); bus = NULL; } g_free(last_input_name); last_input_name = NULL; /* ibus_main()/ibus_quit() operate on a single loop shared by the * whole process, not one per IBusBus. Without this, the * xrdp_input_main_loop thread for this (now dead) connection stays * blocked in ibus_main() forever, and a fresh thread + loop gets * started on the next reconnect - leaking a thread per reconnect. */ ibus_quit(); } static gboolean engine_process_key_event_cb(IBusEngine *engine, guint keyval, guint keycode, guint state) { /* Pass the keyboard event to system */ return FALSE; } static IBusEngine * xrdp_input_ibus_create_engine(IBusFactory *factory, gchar *engine_name, gpointer user_data) { IBusEngine *engine; gchar *path = g_strdup_printf("/org/freedesktop/IBus/Engine/%i", ++id); engine = ibus_engine_new(engine_name, path, ibus_bus_get_connection(bus)); LOG(LOG_LEVEL_DEBUG, "xrdp_input_ibus_create_engine: Creating IM Engine with name:%s and id:%d\n", engine_name, id); g_free(path); g_signal_connect(engine, "process-key-event", G_CALLBACK(engine_process_key_event_cb), NULL); g_signal_connect(engine, "enable", G_CALLBACK(xrdp_input_ibus_engine_enable), NULL); g_signal_connect(engine, "disable", G_CALLBACK(xrdp_input_ibus_engine_disable), NULL); return engine; } /*****************************************************************************/ static THREAD_RV THREAD_CC xrdp_input_main_loop(void *in_val) { IBusFactory *factory; IBusComponent *component; IBusEngineDesc *desc; THREAD_RV rv = 0; LOG(LOG_LEVEL_DEBUG, "xrdp_input_main_loop: Entering ibus loop"); g_signal_connect(bus, "disconnected", G_CALLBACK(xrdp_input_ibus_disconnect), NULL); factory = ibus_factory_new(ibus_bus_get_connection(bus)); g_object_ref_sink(factory); g_signal_connect(factory, "create-engine", G_CALLBACK(xrdp_input_ibus_create_engine), NULL); ibus_factory_add_engine(factory, "XrdpIme", IBUS_TYPE_ENGINE); component = ibus_component_new("org.freedesktop.IBus.XrdpIme", /* name */ "Xrdp input method", /* description */ "1.1", /* version */ "MIT", /* license */ "seflerZ", /* author */ "default", /* homepage */ "default", /* cmd */ "xrdpime"); /* text domain */ desc = ibus_engine_desc_new("XrdpIme", "unicode input method for xrdp", "unicode input method for xrdp", "unicode", "MIT", "seflerZ", "default", /* icon */ "default"); /* layout */ ibus_component_add_engine(component, desc); ibus_bus_register_component(bus, component); ibus_main(); g_object_unref(desc); g_object_unref(component); g_object_unref(factory); return rv; } int xrdp_input_unicode_destroy(void) { LOG(LOG_LEVEL_DEBUG, "xrdp_input_unicode_destory: ibus input is under destory"); if (last_input_name && bus) { LOG(LOG_LEVEL_INFO, "xrdp_input_unicode_destory: ibus engine rolling back to origin: %s", last_input_name); ibus_bus_set_global_engine(bus, last_input_name); } if (g_engine) { g_object_unref(g_engine); } if (bus) { g_object_unref(bus); } g_free(last_input_name); last_input_name = NULL; bus = NULL; g_engine = NULL; return 0; } int xrdp_input_unicode_init(void) { if (bus) { if (ibus_bus_is_connected(bus)) { /* Already initialized, just re-enable it */ xrdp_input_enable(); return 0; } /* The bus is stale (e.g. the ibus daemon restarted and left a * dead connection behind). Tear it down so we reconnect below * instead of operating on a dead connection. */ LOG(LOG_LEVEL_WARNING, "xrdp_ibus_init: existing iBus connection is stale, reconnecting"); if (g_engine) { g_object_unref(g_engine); g_engine = NULL; } g_object_unref(bus); bus = NULL; g_free(last_input_name); last_input_name = NULL; /* Belt-and-suspenders: normally the "disconnected" signal handler * (xrdp_input_ibus_disconnect) already called this when the bus * dropped. But a stale connection can be detected here without * that signal ever having fired, and ibus_main()/ibus_quit() are * process-global rather than per-bus, so make sure the old * xrdp_input_main_loop thread isn't left blocked in ibus_main() * before we start a new one below. */ ibus_quit(); } /* Wait because the ibus daemon may not be ready on first login */ const char *addr = ibus_get_address(); unsigned int cnt = 0; while (!addr && cnt < 10) { usleep(500 * 1000); // half a second addr = ibus_get_address(); ++cnt; } if (!addr) { LOG(LOG_LEVEL_ERROR, "xrdp_ibus_init: Timed out waiting for iBus daemon"); return 1; } LOG(LOG_LEVEL_INFO, "xrdp_ibus_init: Initializing the iBus engine"); ibus_init(); bus = ibus_bus_new(); g_object_ref_sink(bus); if (!ibus_bus_is_connected(bus)) { LOG(LOG_LEVEL_ERROR, "xrdp_ibus_init: Connect to iBus failed"); g_object_unref(bus); bus = NULL; return 1; } LOG(LOG_LEVEL_INFO, "xrdp_ibus_init: iBus connected"); tc_thread_create(xrdp_input_main_loop, NULL); /* No global engine may be set yet at this point (e.g. ibus hasn't * picked a default on this fresh session, or the user simply never * had one configured) - that's a normal state, not a failure. There * is nothing to remember here; xrdp_input_enable() already handles * a NULL/absent current engine correctly when it's actually needed. */ return 0; }