Files
xrdp/sesman/chansrv/input_ibus.c
T
Liyi Meng 82b4eb6556 chansrv: Stop reclaiming XrdpIme from a deliberately-chosen engine
xrdp_input_enable() unconditionally reasserted XrdpIme as the global
engine any time it wasn't already active, on the theory that ibus can
silently revert to the user's original engine on its own. That's true,
but the fix was too broad: it also fired whenever the user had
manually switched the session's engine to something else entirely
(e.g. libpinyin, to compose Chinese directly in the remote desktop
rather than via the client's local IME), yanking control away
mid-use. Confirmed happening live: a composition left pending in a
client-side IME and then abandoned got auto-flushed by the OS well
after the fact, silently reclaiming XrdpIme and interrupting an
unrelated libpinyin session already in progress.

Now only reclaims XrdpIme when the current engine is unset or matches
the original one remembered before ever switching away from it
(last_input_name) - the actual "ibus reverted on its own" case this
existed for. Any other named engine is treated as a deliberate choice
and left alone; the stale/deferred commit is dropped instead of
hijacking whatever the user is currently doing.

Also fixes a hang found while testing this: if xrdp_input_unicode_init()
times out waiting for ibus_get_address() (daemon unreachable), it
returned early without resetting ibus_thread_exited back to TRUE, even
though it had just set it FALSE in anticipation of a thread that was
never actually created. No thread means no broadcast ever comes, so
any later xrdp_input_unicode_destroy() or re-init() call blocks on
that condvar forever. Pre-existing in the private-loop rewrite, not
introduced by this change - just surfaced by testing this one harder.

Verified: 20 back-to-back init() calls on a healthy connection still
take the fast path with no false failures; manually switching to a
different engine and then sending a stale commit leaves that engine
untouched and fails the commit; reverting to the original engine still
correctly reasserts XrdpIme; 5 kill/restart cycles still show no
thread leak; an 18-char rapid-fire send against a real focused GTK
entry still lands every character in order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 08:13:45 +00:00

832 lines
26 KiB
C

/**
* 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 <config_ac.h>
#endif
#include <glib.h>
#include <glib-object.h>
#include <glib/gstdio.h>
#include <ibus.h>
#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;
static int engine_id = 0;
/*****************************************************************************/
static void
xrdp_input_ibus_engine_enable(IBusEngine *engine)
{
LOG(LOG_LEVEL_INFO,
"xrdp_ibus_engine_enable: IM enabled, engine=%p", engine);
if (g_engine != NULL && g_engine != engine)
{
g_object_unref(g_engine);
g_engine = NULL;
}
if (g_engine == NULL)
{
g_engine = g_object_ref(engine);
}
if (ibus_context != NULL)
{
g_main_context_wakeup(ibus_context);
}
}
/*****************************************************************************/
static void
xrdp_input_ibus_engine_disable(IBusEngine *engine)
{
LOG(LOG_LEVEL_INFO,
"xrdp_ibus_engine_disable: IM disabled, engine=%p", engine);
if (g_engine == engine)
{
g_object_unref(g_engine);
g_engine = NULL;
}
}
/*****************************************************************************/
static gboolean
engine_process_key_event_cb(IBusEngine *engine,
guint keyval,
guint keycode,
guint state)
{
/* XrdpIme is a Unicode commit bridge, not a keyboard-event IME. */
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;
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;
}
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 &&
g_engine != NULL)
{
g_object_unref(desc);
g_object_unref(local_bus);
return TRUE;
}
/* 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. If it's some OTHER named engine, that's 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 that was
* left pending after the user moved on to something else). Drop
* this commit instead and leave the user's active choice alone. */
if (last_input_name != NULL && name != NULL &&
g_ascii_strcasecmp(name, last_input_name) != 0)
{
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);
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 == NULL && name != NULL)
{
last_input_name = g_strdup(name);
LOG(LOG_LEVEL_INFO,
"xrdp_input_enable: saving original IBus engine: %s",
last_input_name);
}
if (desc != NULL)
{
g_object_unref(desc);
}
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)
{
/* 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;
}
/*****************************************************************************/
static gboolean
xrdp_input_queue_source_prepare(GSource *source, gint *timeout)
{
*timeout = -1;
return g_engine != NULL && g_async_queue_length(unicode_queue) > 0;
}
/*****************************************************************************/
static gboolean
xrdp_input_queue_source_check(GSource *source)
{
return g_engine != NULL && g_async_queue_length(unicode_queue) > 0;
}
/*****************************************************************************/
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);
}
/*****************************************************************************/
static gboolean
xrdp_input_shutdown_cb(gpointer data)
{
XrdpUnicodeEvent *event;
LOG(LOG_LEVEL_INFO, "xrdp_input: shutting down IBus");
ibus_shutting_down = TRUE;
if (last_input_name != NULL && bus != NULL &&
ibus_bus_is_connected(bus))
{
LOG(LOG_LEVEL_INFO,
"xrdp_input: restoring original IBus engine: %s",
last_input_name);
if (!ibus_bus_set_global_engine(bus, last_input_name))
{
LOG(LOG_LEVEL_WARNING,
"xrdp_input: failed to restore original IBus engine");
}
}
g_clear_pointer(&last_input_name, g_free);
while ((event = g_async_queue_try_pop(unicode_queue)) != NULL)
{
g_free(event);
}
if (ibus_loop != NULL)
{
g_main_loop_quit(ibus_loop);
}
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;
}
if (g_engine != NULL)
{
g_object_unref(g_engine);
g_engine = NULL;
}
g_clear_pointer(&last_input_name, g_free);
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)
{
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: destroying IBus input");
g_mutex_lock(&state_mutex);
ibus_shutting_down = TRUE;
context = ibus_context;
g_mutex_unlock(&state_mutex);
if (context != NULL)
{
g_main_context_invoke_full(
context,
G_PRIORITY_HIGH,
xrdp_input_shutdown_cb,
NULL,
NULL);
g_main_context_wakeup(context);
}
g_mutex_lock(&state_mutex);
while (!ibus_thread_exited)
{
g_cond_wait(&state_cond, &state_mutex);
}
g_mutex_unlock(&state_mutex);
if (unicode_queue != NULL)
{
XrdpUnicodeEvent *event;
while ((event = g_async_queue_try_pop(unicode_queue)) != NULL)
{
g_free(event);
}
g_async_queue_unref(unicode_queue);
unicode_queue = NULL;
}
return 0;
}