/** * 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. */ /* * IBus Unicode input support for chansrv. * * All IBus objects (bus, engine, factory, component, and the private * GMainContext/GMainLoop that services them) are owned and only ever * touched by the IBus thread (xrdp_input_main_loop). chansrv's own * thread never calls into libibus directly - it hands off unicode * codepoints via a thread-safe queue and wakes the IBus thread's loop: * * chansrv thread IBus thread (private main loop) * | | * |-- queue('你') | * |-- queue('好') --> XrdpIme not yet enabled? * | | (create-engine is async) * | v * | wait for "enable" signal * | | * | v * | drain queue, commit each char * | | * | commit('你'), commit('好') * * This means a character queued before XrdpIme has finished being * created/enabled isn't lost - it's committed as soon as the engine * becomes ready, instead of racing a one-shot commit against * asynchronous engine setup. */ #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include "input.h" #include "thread_calls.h" typedef struct { gunichar unicode; } XrdpUnicodeEvent; /* These are owned and accessed by the IBus thread, except where noted. */ static IBusBus *bus = NULL; static IBusEngine *g_engine = NULL; static gchar *last_input_name = NULL; static GMainContext *ibus_context = NULL; static GMainLoop *ibus_loop = NULL; /* Shared between chansrv and the IBus thread. */ static GAsyncQueue *unicode_queue = NULL; static GMutex state_mutex; static GCond state_cond; static gboolean ibus_ready = FALSE; static gboolean ibus_thread_exited = TRUE; static gboolean ibus_shutting_down = FALSE; /* TRUE from xrdp_input_unicode_destroy() until the IBus thread has run xrdp_input_reset_cb() */ static gboolean ibus_reset_pending = FALSE; /* When xrdp_input_enable() last asked ibus to switch to XrdpIme (0 = never). * Guarded by state_mutex. While the engine instance it asked for is still * being created, asking again would make ibus destroy that instance and * build another, and the characters queued for the first one are lost. */ static gint64 last_engine_request_time = 0; #define XRDP_INPUT_ENGINE_CREATE_WAIT_US (2 * G_TIME_SPAN_SECOND) static int engine_id = 0; /* Debounce state for committing queued unicode text - IBus-thread only, * same as g_engine et al. above. RDP clients that compose IME text * locally (e.g. Microsoft Remote Desktop for Mac) have been observed to * *also* forward the raw pre-composition keystrokes over the normal * keyboard channel, then send a compensating burst of Backspace key * events sized to exactly undo them once composition finishes - see the * commit that introduced this debounce for the measurements (backspace * count matched leaked-character count exactly, every time: 23-for-23, * 29-for-29). Those backspaces and this engine's queued commit arrive * over two independent channels with no ordering guarantee, so without * this delay the commit can land in the middle of the client's own * cleanup and get partially deleted. Waiting for raw key traffic on this * engine to go quiet before committing sidesteps that race entirely, * since it's the same fix as "wait for the client's known-correct * cleanup to finish" without needing to trust the client to order things * itself. This is an empirical mitigation, not a protocol guarantee - * a client that pauses mid-burst for longer than the quiet window could * still race it, hence the absolute cap below. */ static gint64 last_raw_key_time = 0; static gint64 queue_wait_since = 0; #define XRDP_INPUT_COMMIT_QUIET_US ((gint64)100 * 1000) #define XRDP_INPUT_COMMIT_MAX_WAIT_US ((gint64)500 * 1000) /*****************************************************************************/ static void xrdp_input_ibus_engine_enable(IBusEngine *engine) { IBusEngine *old_engine = NULL; LOG(LOG_LEVEL_INFO, "xrdp_ibus_engine_enable: IM enabled, engine=%p", engine); /* g_engine is read cross-thread (unlocked) by xrdp_input_enable() on * chansrv's own thread - see the comment there. Publish it under * state_mutex, same pattern as `bus`. */ g_mutex_lock(&state_mutex); if (g_engine != NULL && g_engine != engine) { old_engine = g_engine; g_engine = NULL; } if (g_engine == NULL) { g_engine = g_object_ref(engine); } g_mutex_unlock(&state_mutex); if (old_engine != NULL) { g_object_unref(old_engine); } if (ibus_context != NULL) { g_main_context_wakeup(ibus_context); } } /*****************************************************************************/ static void xrdp_input_ibus_engine_disable(IBusEngine *engine) { IBusEngine *old_engine = NULL; LOG(LOG_LEVEL_INFO, "xrdp_ibus_engine_disable: IM disabled, engine=%p", engine); g_mutex_lock(&state_mutex); if (g_engine == engine) { old_engine = g_engine; g_engine = NULL; } g_mutex_unlock(&state_mutex); if (old_engine != NULL) { g_object_unref(old_engine); } } /*****************************************************************************/ static gboolean engine_process_key_event_cb(IBusEngine *engine, guint keyval, guint keycode, guint state) { last_raw_key_time = g_get_monotonic_time(); LOG(LOG_LEVEL_DEBUG, "IBUS-KEY: engine=%p keyval=0x%08X keycode=%u state=0x%08X", engine, keyval, keycode, state); /* XrdpIme is a Unicode commit bridge, not a keyboard-event IME - it * never consumes keys itself. last_raw_key_time above is what keeps * this from racing the client's own compensating keystrokes; see the * comment on it. */ return FALSE; } /*****************************************************************************/ static IBusEngine * xrdp_input_ibus_create_engine(IBusFactory *factory, gchar *engine_name, gpointer user_data) { IBusEngine *engine; gchar *path; path = g_strdup_printf("/org/freedesktop/IBus/Engine/%d", ++engine_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 name=%s id=%d", engine_name, engine_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; } /*****************************************************************************/ /* Must run on chansrv's own thread, NOT the IBus thread - confirmed by * hitting the deadlock this avoids. ibus_bus_set_global_engine() blocks * synchronously waiting for ibus-daemon's reply, but ibus-daemon can't * reply until it finishes instantiating the engine, which means calling * back into *our own* IBusFactory's "create-engine" over the same * connection. If this function runs on the IBus thread, that thread is * stuck blocked inside this call and can't service that nested * callback - ibus-daemon times out waiting and set_global_engine fails * with "Set global engine failed: Timeout was reached". Calling this * from chansrv's thread instead leaves the IBus thread free to answer * the nested call while this blocks. g_dbus_connection sync calls are * documented thread-safe to issue from any thread, so that part is * fine despite `bus` otherwise being owned by the IBus thread - but * the `bus` pointer itself is not: the IBus thread can null it out * (disconnect, teardown) at any time. Take our own reference under * state_mutex before touching it, so a concurrent teardown drops the * shared pointer without pulling the object out from under us. */ static gboolean xrdp_input_enable(void) { IBusEngineDesc *desc; const gchar *name; IBusBus *local_bus; gboolean result; gboolean engine_ready; gchar *last_input_name_snapshot; g_mutex_lock(&state_mutex); local_bus = (bus != NULL) ? g_object_ref(bus) : NULL; g_mutex_unlock(&state_mutex); if (local_bus == NULL || !ibus_bus_is_connected(local_bus)) { LOG(LOG_LEVEL_ERROR, "xrdp_input_enable: IBus is not connected"); g_clear_object(&local_bus); return FALSE; } /* g_engine and last_input_name are otherwise IBus-thread-only (see * the comment at their declarations) - snapshot both under * state_mutex here rather than reading them directly, since this * function runs on chansrv's own thread and the IBus thread can * write either of them at any time (engine enable/disable, an * unexpected daemon disconnect tearing both down). Working from a * private copy of the name avoids racing a concurrent free of it. */ g_mutex_lock(&state_mutex); engine_ready = (g_engine != NULL); last_input_name_snapshot = (last_input_name != NULL) ? g_strdup(last_input_name) : NULL; g_mutex_unlock(&state_mutex); desc = ibus_bus_get_global_engine(local_bus); name = desc != NULL ? ibus_engine_desc_get_name(desc) : NULL; if (name != NULL && g_ascii_strcasecmp(name, "XrdpIme") == 0) { if (engine_ready) { g_free(last_input_name_snapshot); g_object_unref(desc); g_object_unref(local_bus); return TRUE; } /* XrdpIme is already the global engine, but ibus_bus_set_global_ * engine() only sets the *name* synchronously - the actual * engine instance (g_engine) is created asynchronously after. * This is that brief in-flight window, NOT a case of some * other engine having taken over, so it must not hit the * "leave a deliberately different engine alone" guard below * (name == "XrdpIme" is *always* != last_input_name by * construction, so that guard would otherwise reject every * commit that lands in this window - which, since composing * even one phrase sends several characters in quick * succession, was most of them). * * If we asked for this engine moments ago, it is simply still * being created: return and let the queue/prepare-check * mechanism wait for g_engine. Do NOT ask again - that makes * ibus destroy the instance under construction and create * another (seen as "IM enabled / IM disabled / IM enabled" in * the log), losing what was queued for the first. Only when no * request is recent is this a stale name (ibus disabled the * instance but left the name), which needs the re-set below. */ gboolean in_flight; g_mutex_lock(&state_mutex); in_flight = (last_engine_request_time != 0 && g_get_monotonic_time() - last_engine_request_time < XRDP_INPUT_ENGINE_CREATE_WAIT_US); g_mutex_unlock(&state_mutex); if (in_flight) { g_free(last_input_name_snapshot); g_object_unref(desc); g_object_unref(local_bus); return TRUE; } } else if (last_input_name_snapshot != NULL && name != NULL && g_ascii_strcasecmp(name, last_input_name_snapshot) != 0) { /* Only reclaim XrdpIme if the current engine is either unset, * or is the original one we remembered before ever switching * away from it (last_input_name) - that's ibus silently * reverting on its own, which is exactly the scenario this * reassertion exists to fix. Anything else named is a * deliberate user action (e.g. manually switching to a native * engine like libpinyin to compose directly in the remote * session), possibly well before this call - forcibly * reclaiming XrdpIme would yank that away mid-use for the sake * of a single, possibly stale or deferred commit (e.g. macOS * auto-flushing a composition left pending after the user * moved on to something else). Drop this commit instead and * leave the user's active choice alone. */ LOG(LOG_LEVEL_WARNING, "xrdp_input_enable: global engine is \"%s\", not XrdpIme or " "the original \"%s\" - leaving it alone rather than " "reclaiming it, dropping this commit", name, last_input_name_snapshot); g_free(last_input_name_snapshot); g_object_unref(desc); g_object_unref(local_bus); return FALSE; } /* Remember the user's engine only the first time we replace it. */ if (last_input_name_snapshot == NULL && name != NULL) { g_mutex_lock(&state_mutex); if (last_input_name == NULL) { last_input_name = g_strdup(name); } g_mutex_unlock(&state_mutex); LOG(LOG_LEVEL_INFO, "xrdp_input_enable: saving original IBus engine: %s", name); } g_free(last_input_name_snapshot); if (desc != NULL) { g_object_unref(desc); } g_mutex_lock(&state_mutex); last_engine_request_time = g_get_monotonic_time(); g_mutex_unlock(&state_mutex); result = ibus_bus_set_global_engine(local_bus, "XrdpIme"); if (!result) { LOG(LOG_LEVEL_ERROR, "xrdp_input_enable: failed to switch global engine to XrdpIme"); } g_object_unref(local_bus); return result; } /*****************************************************************************/ static gboolean xrdp_input_process_unicode_queue(gpointer data) { XrdpUnicodeEvent *event; /* This callback is always executed by the private IBus main loop. */ while (g_engine != NULL && (event = g_async_queue_try_pop(unicode_queue)) != NULL) { IBusText *text = ibus_text_new_from_unichar(event->unicode); if (text != NULL) { LOG(LOG_LEVEL_DEBUG, "IBUS-COMMIT: engine=%p committing U+%04X", g_engine, event->unicode); /* ibus_engine_commit_text() sinks and releases `text` itself * since it's still floating (IBusText derives from * GInitiallyUnowned) - do not unref it again here, that * would release an already-finalized object. */ ibus_engine_commit_text(g_engine, text); } g_free(event); } return G_SOURCE_CONTINUE; } /*****************************************************************************/ /* TRUE once queued unicode text is safe to commit: either raw key traffic * on this engine has been quiet for XRDP_INPUT_COMMIT_QUIET_US (the * client's own compensating keystrokes, if any, have had time to land), * or XRDP_INPUT_COMMIT_MAX_WAIT_US has elapsed since the queue first had * something pending, whichever comes first. If not yet ready, *wait_us * (when non-NULL) is set to how much longer until it will be. * * "Quiet" is measured from the later of the last raw key and the moment * the text was queued. Measuring from the last raw key alone fails when * the user has paused between typing the pinyin and confirming it: the * raw keys are then already "quiet" when the commit arrives, so it is * committed at once, and the client's cleanup Backspaces (which follow * the commit by a few ms to a few tens of ms) delete the text just * committed and keep going into what was there before. Measured against * a real Mac client: a 270 ms pause before confirming, commit followed * 2 ms later by as many Backspaces as leaked keys. Every commit therefore * waits at least the quiet window, and any Backspace arriving inside it * extends the wait. */ static gboolean xrdp_input_queue_ready(gint64 *wait_us) { gint64 now; gint64 deadline; if (g_engine == NULL || g_async_queue_length(unicode_queue) <= 0) { queue_wait_since = 0; return FALSE; } now = g_get_monotonic_time(); if (queue_wait_since == 0) { queue_wait_since = now; } deadline = MIN(MAX(last_raw_key_time, queue_wait_since) + XRDP_INPUT_COMMIT_QUIET_US, queue_wait_since + XRDP_INPUT_COMMIT_MAX_WAIT_US); if (now >= deadline) { return TRUE; } if (wait_us != NULL) { *wait_us = deadline - now; } return FALSE; } /*****************************************************************************/ static gboolean xrdp_input_queue_source_prepare(GSource *source, gint *timeout) { gint64 wait_us = 0; if (xrdp_input_queue_ready(&wait_us)) { *timeout = 0; return TRUE; } /* Either nothing pending (wait_us left at 0 - block until the next * g_main_context_wakeup(), same as the old *timeout = -1 behaviour), * or pending but not quiet yet (wake us up once it should be). */ *timeout = (wait_us > 0) ? (gint)((wait_us / 1000) + 1) : -1; return FALSE; } /*****************************************************************************/ static gboolean xrdp_input_queue_source_check(GSource *source) { return xrdp_input_queue_ready(NULL); } /*****************************************************************************/ static gboolean xrdp_input_queue_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { return callback != NULL ? callback(user_data) : G_SOURCE_CONTINUE; } static GSourceFuncs xrdp_input_queue_source_funcs = { xrdp_input_queue_source_prepare, xrdp_input_queue_source_check, xrdp_input_queue_source_dispatch, NULL, NULL, NULL }; /*****************************************************************************/ static void xrdp_input_install_queue_source(void) { GSource *source; source = g_source_new(&xrdp_input_queue_source_funcs, sizeof(GSource)); g_source_set_name(source, "xrdp-ibus-unicode-queue"); g_source_set_callback(source, xrdp_input_process_unicode_queue, NULL, NULL); g_source_attach(source, ibus_context); g_source_unref(source); } /*****************************************************************************/ /* Runs on the IBus thread when the RDP client disconnects. Puts back the * user's own engine and drops anything still queued, but deliberately does * NOT stop the loop or the thread. * * ibus_bus_new() returns a process-wide singleton here (see the note above * xrdp_input_ibus_bus_disconnected), and its GDBusConnection stays bound to * the GMainContext that was thread-default when it was first created. If * the thread and that context were torn down on every client disconnect, * the next connect would build a new private context but get the old * connection back: nothing would iterate the old context, so ibus-daemon's * CreateEngine call on our factory would never be dispatched and * ibus_bus_set_global_engine() would time out ("failed to switch global * engine to XrdpIme") on every reconnect. Keeping one IBus thread for the * life of chansrv avoids the whole class of problem. */ static gboolean xrdp_input_reset_cb(gpointer data) { XrdpUnicodeEvent *event; gchar *original_engine_name; LOG(LOG_LEVEL_INFO, "xrdp_input: client disconnected, resetting IBus state"); /* Take ownership of last_input_name under state_mutex rather than * reading then freeing it directly - xrdp_input_enable() on * chansrv's own thread can be reading or (re)writing it concurrently * (see the comment there), and this runs on the IBus thread. */ g_mutex_lock(&state_mutex); original_engine_name = last_input_name; last_input_name = NULL; g_mutex_unlock(&state_mutex); if (original_engine_name != NULL && bus != NULL && ibus_bus_is_connected(bus)) { LOG(LOG_LEVEL_INFO, "xrdp_input: restoring original IBus engine: %s", original_engine_name); if (!ibus_bus_set_global_engine(bus, original_engine_name)) { LOG(LOG_LEVEL_WARNING, "xrdp_input: failed to restore original IBus engine"); } } g_free(original_engine_name); while ((event = g_async_queue_try_pop(unicode_queue)) != NULL) { g_free(event); } g_mutex_lock(&state_mutex); last_engine_request_time = 0; ibus_reset_pending = FALSE; g_cond_broadcast(&state_cond); g_mutex_unlock(&state_mutex); return G_SOURCE_REMOVE; } /*****************************************************************************/ /* If the ibus daemon restarts or the socket otherwise goes away * mid-session, this fires on the IBus thread. This is expected to be a * rare event in practice (daemon crash, manual "ibus-restart", or a * package upgrade touching ibus mid-session). * * NOTE: this does NOT achieve a working reconnect (see #3230), only a * clean failure. ibus_bus_new() returns a process-wide singleton in * this libibus version - confirmed directly: two ibus_bus_new() calls * in the same process, no disconnect involved, return the identical * pointer. Once that singleton's connection dies, every later * ibus_bus_new() call - including from a brand new thread here - just * hands back the same dead object; ibus_bus_is_connected() on it stays * FALSE permanently, not a transient state that clears with a retry. * A real reconnect would mean bypassing IBusBus and talking to * ibus-daemon over a raw GDBusConnection instead, which is a * significant undertaking not justified by how rarely this fires. * * What quitting the loop here does still buy us: xrdp_input_main_loop() * falls through to thread_cleanup and tears everything down cleanly, * thread_exit clears ibus_ready, and the next xrdp_input_unicode_init() * call sees ibus_ready == FALSE, so it correctly spawns a new thread * and fails fast (is_connected() on the singleton is false) instead of * either hanging or silently taking the "already ready" fast path * against a connection that's actually dead. Unicode input stays * broken until the session is fully reconnected, but nothing hangs, * leaks, or corrupts state in the meantime. */ static void xrdp_input_ibus_bus_disconnected(IBusBus *ibus_bus, gpointer user_data) { LOG(LOG_LEVEL_WARNING, "xrdp_input_ibus_bus_disconnected: IBus connection lost - " "unicode input will stay unavailable until the session " "reconnects (see #3230 note above this function)"); if (ibus_loop != NULL) { g_main_loop_quit(ibus_loop); } } /*****************************************************************************/ static THREAD_RV THREAD_CC xrdp_input_main_loop(void *in_val) { IBusFactory *factory = NULL; IBusComponent *component = NULL; IBusEngineDesc *desc = NULL; gboolean thread_default_pushed = FALSE; LOG(LOG_LEVEL_DEBUG, "xrdp_input_main_loop: starting IBus thread"); ibus_init(); /* Create and push our private context as thread-default *before* * creating bus (or anything else that opens a GDBusConnection). * GDBusConnection binds its async I/O to whatever is thread-default * at the moment it's constructed - if bus were created first, its * connection would silently bind to the global default context * instead, which nothing here ever iterates. Symptom: things that * need sync round-trips still appear to work, but signals like * "disconnected" simply never fire, since nothing is polling that * connection's fd at all. */ ibus_context = g_main_context_new(); if (ibus_context == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: failed to create GMainContext"); goto thread_exit; } ibus_loop = g_main_loop_new(ibus_context, FALSE); if (ibus_loop == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: failed to create GMainLoop"); goto thread_cleanup; } g_main_context_push_thread_default(ibus_context); thread_default_pushed = TRUE; { IBusBus *new_bus = ibus_bus_new(); if (new_bus == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: ibus_bus_new failed"); goto thread_cleanup; } g_object_ref_sink(new_bus); /* Published under lock so xrdp_input_enable(), running on * chansrv's thread, never observes a partially-constructed * `bus`. */ g_mutex_lock(&state_mutex); bus = new_bus; g_mutex_unlock(&state_mutex); } if (!ibus_bus_is_connected(bus)) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: IBus connection failed"); goto thread_cleanup; } g_signal_connect(bus, "disconnected", G_CALLBACK(xrdp_input_ibus_bus_disconnected), NULL); factory = ibus_factory_new(ibus_bus_get_connection(bus)); if (factory == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: ibus_factory_new failed"); goto thread_cleanup; } 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", "Xrdp input method", "1.1", "MIT", "xrdp", "default", "default", "xrdpime"); desc = ibus_engine_desc_new( "XrdpIme", "unicode input method for xrdp", "unicode input method for xrdp", "unicode", "MIT", "xrdp", "default", "default"); if (component == NULL || desc == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: failed to create IBus component"); goto thread_cleanup; } ibus_component_add_engine(component, desc); if (!ibus_bus_register_component(bus, component)) { LOG(LOG_LEVEL_WARNING, "xrdp_input_main_loop: IBus component registration failed"); } xrdp_input_install_queue_source(); g_mutex_lock(&state_mutex); ibus_ready = TRUE; ibus_thread_exited = FALSE; g_cond_broadcast(&state_cond); g_mutex_unlock(&state_mutex); /* All IBus operations and callbacks now run on this private context. */ g_main_loop_run(ibus_loop); thread_cleanup: if (thread_default_pushed) { g_main_context_pop_thread_default(ibus_context); thread_default_pushed = FALSE; } { /* Same reasoning as the `bus` clear below: g_engine and * last_input_name are read cross-thread (unlocked) by * xrdp_input_enable() on chansrv's own thread, and this can run * on an unexpected daemon disconnect with no coordination from * that thread at all - clear the shared pointers under lock * before releasing what they pointed to. */ IBusEngine *old_engine; gchar *old_last_input_name; g_mutex_lock(&state_mutex); old_engine = g_engine; g_engine = NULL; old_last_input_name = last_input_name; last_input_name = NULL; g_mutex_unlock(&state_mutex); if (old_engine != NULL) { g_object_unref(old_engine); } g_free(old_last_input_name); } if (desc != NULL) { g_object_unref(desc); desc = NULL; } if (component != NULL) { g_object_unref(component); component = NULL; } if (factory != NULL) { g_object_unref(factory); factory = NULL; } { /* Clear the shared pointer under lock before dropping the * reference, so a concurrent xrdp_input_enable() ref (taken * under the same lock) always sees either the live object or * NULL, never a dangling one. */ IBusBus *old_bus; g_mutex_lock(&state_mutex); old_bus = bus; bus = NULL; g_mutex_unlock(&state_mutex); if (old_bus != NULL) { g_object_unref(old_bus); } } if (ibus_loop != NULL) { g_main_loop_unref(ibus_loop); ibus_loop = NULL; } if (ibus_context != NULL) { g_main_context_unref(ibus_context); ibus_context = NULL; } thread_exit: g_mutex_lock(&state_mutex); ibus_ready = FALSE; ibus_thread_exited = TRUE; g_cond_broadcast(&state_cond); g_mutex_unlock(&state_mutex); LOG(LOG_LEVEL_DEBUG, "xrdp_input_main_loop: IBus thread exited"); return 0; } /*****************************************************************************/ int xrdp_input_send_unicode(char32_t unicode) { XrdpUnicodeEvent *event; GMainContext *context; LOG(LOG_LEVEL_DEBUG, "xrdp_input_send_unicode: received U+%04X", (unsigned int)unicode); if (unicode == 0) { return 0; } /* Called directly from here (chansrv's own thread), not marshaled * onto the IBus thread - see the comment on xrdp_input_enable() * for why running it there deadlocks against ibus-daemon's nested * "create-engine" callback. */ if (!xrdp_input_enable()) { return 1; } g_mutex_lock(&state_mutex); if (!ibus_ready || ibus_shutting_down || unicode_queue == NULL || ibus_context == NULL) { g_mutex_unlock(&state_mutex); LOG(LOG_LEVEL_WARNING, "xrdp_input_send_unicode: IBus input is not ready"); return 1; } event = g_new0(XrdpUnicodeEvent, 1); if (event == NULL) { g_mutex_unlock(&state_mutex); LOG(LOG_LEVEL_ERROR, "xrdp_input_send_unicode: allocation failed"); return 1; } event->unicode = (gunichar)unicode; /* Take our own ref so the context can't be torn down by the IBus * thread between here and the wakeup call below, once we drop the * mutex. The drain source's prepare/check gate on g_engine != NULL, * so if engine creation (triggered by xrdp_input_enable() above) is * still in flight, this wakeup is a no-op and the "enable" signal * handler's own wakeup picks the queued character up once the * engine is actually ready - it isn't lost. */ context = g_main_context_ref(ibus_context); g_async_queue_push(unicode_queue, event); g_main_context_wakeup(context); g_mutex_unlock(&state_mutex); g_main_context_unref(context); return 0; } /*****************************************************************************/ int xrdp_input_unicode_init(void) { const char *addr; unsigned int cnt = 0; gboolean ready; if (unicode_queue == NULL) { unicode_queue = g_async_queue_new(); if (unicode_queue == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_unicode_init: failed to create queue"); return 1; } } g_mutex_lock(&state_mutex); if (ibus_ready) { /* ibus_ready normally tracks connection liveness exactly, * since the "disconnected" handler and thread_exit update it * together - but there's a window between the socket actually * dying and that signal being dispatched on the IBus thread. * Double-check here rather than trust a possibly-stale flag, * so a client that reconnects right into that window gets a * clean failure (and a real retry next time) instead of a * false "unicode input is supported" advertisement it can * never actually use. */ IBusBus *check_bus = (bus != NULL) ? g_object_ref(bus) : NULL; gboolean connected = (check_bus != NULL) && ibus_bus_is_connected(check_bus); g_clear_object(&check_bus); if (connected) { /* Normal reconnect: the IBus thread was kept alive by * xrdp_input_unicode_destroy(), just let it commit again. */ ibus_shutting_down = FALSE; g_mutex_unlock(&state_mutex); return 0; } /* Stale: the IBus thread hasn't noticed its connection is dead * yet. Ask it to shut down and wait for it to fully exit * before starting a new one below - starting a second thread * while the first is still alive would race both of them over * bus/g_engine. */ LOG(LOG_LEVEL_WARNING, "xrdp_input_unicode_init: stale IBus connection, " "restarting IBus thread"); if (ibus_loop != NULL) { g_main_loop_quit(ibus_loop); } while (!ibus_thread_exited) { g_cond_wait(&state_cond, &state_mutex); } } ibus_shutting_down = FALSE; ibus_thread_exited = FALSE; g_mutex_unlock(&state_mutex); addr = ibus_get_address(); while (addr == NULL && cnt < 10) { usleep(500 * 1000); addr = ibus_get_address(); ++cnt; } if (addr == NULL) { LOG(LOG_LEVEL_ERROR, "xrdp_input_unicode_init: timed out waiting for IBus daemon"); /* No thread was created, so nothing will ever broadcast * state_cond to wake a future destroy()/init() call waiting on * ibus_thread_exited - reset it back to TRUE (set FALSE above * in anticipation of the thread this function didn't end up * creating), or that later wait blocks forever. */ g_mutex_lock(&state_mutex); ibus_thread_exited = TRUE; g_mutex_unlock(&state_mutex); return 1; } LOG(LOG_LEVEL_INFO, "xrdp_input_unicode_init: starting IBus thread"); if (tc_thread_create(xrdp_input_main_loop, NULL) != 0) { LOG(LOG_LEVEL_ERROR, "xrdp_input_unicode_init: failed to create IBus thread"); g_mutex_lock(&state_mutex); ibus_thread_exited = TRUE; g_mutex_unlock(&state_mutex); return 1; } g_mutex_lock(&state_mutex); while (!ibus_ready && !ibus_thread_exited) { g_cond_wait(&state_cond, &state_mutex); } ready = ibus_ready; g_mutex_unlock(&state_mutex); if (!ready) { LOG(LOG_LEVEL_ERROR, "xrdp_input_unicode_init: IBus thread failed to initialize"); return 1; } return 0; } /*****************************************************************************/ int xrdp_input_unicode_destroy(void) { GMainContext *context; LOG(LOG_LEVEL_DEBUG, "xrdp_input_unicode_destroy: client gone, resetting IBus input"); g_mutex_lock(&state_mutex); ibus_shutting_down = TRUE; context = (ibus_ready && !ibus_thread_exited) ? ibus_context : NULL; ibus_reset_pending = (context != NULL); g_mutex_unlock(&state_mutex); if (context != NULL) { g_main_context_invoke_full( context, G_PRIORITY_HIGH, xrdp_input_reset_cb, NULL, NULL); g_main_context_wakeup(context); /* Wait for the reset so a reconnect arriving right behind this * cannot have its engine restored out from under it. Bounded, * and also ends if the IBus thread exits (which never runs the * callback), so this cannot hang. */ g_mutex_lock(&state_mutex); { gint64 end = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; while (ibus_reset_pending && !ibus_thread_exited) { if (!g_cond_wait_until(&state_cond, &state_mutex, end)) { LOG(LOG_LEVEL_WARNING, "xrdp_input_unicode_destroy: timed out waiting for " "the IBus thread to reset"); break; } } ibus_reset_pending = FALSE; } g_mutex_unlock(&state_mutex); } /* unicode_queue is left allocated: the IBus thread's queue source * keeps using it across client connections. */ return 0; }