Files
xrdp/sesman/chansrv/input_ibus.c
T
Liyi Meng 55a4f81083 chansrv: Fix regression from the previous commit breaking Model A entirely
The "don't reclaim a deliberately-chosen engine" guard from the last
commit had a logic bug that broke ordinary Model A composition
completely, not just the edge case it was meant to fix.

ibus_bus_set_global_engine() sets the global engine *name*
synchronously, but the actual engine instance (g_engine) is created
asynchronously afterward. During that brief window, the global engine
name is already "XrdpIme" but g_engine is still NULL - the existing
fast path (name == "XrdpIme" && g_engine != NULL) correctly falls
through in that case, same as always. But the new guard only checked
whether the current name differed from last_input_name, and "XrdpIme"
is *always* different from last_input_name by construction (that's
the name of whatever we switched away from). So every commit that
landed in that async window got misclassified as "a different engine
took over" and silently dropped. Composing even one phrase sends
several characters in quick succession, so this was most of them -
not just the intended edge case.

Fixed by checking name == "XrdpIme" first, independent of g_engine's
readiness, before ever reaching the "is this some other engine"
guard - that window means "still connecting", not "something else
took over".

Verified against the exact failure condition: sent a 9-character
phrase rapid-fire immediately after xrdp_input_unicode_init() (no
settle time), landing correctly as a single unbroken phrase with zero
drops. Also re-ran all four trigger-happy scenarios from the previous
commit, plus a new check sending 5 characters rapid-fire immediately
after a from-original reassertion - all pass.

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

846 lines
27 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)
{
if (g_engine != NULL)
{
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). Fall through and let the
* queue/prepare-check mechanism wait for g_engine as usual. */
}
else if (last_input_name != NULL && name != NULL &&
g_ascii_strcasecmp(name, last_input_name) != 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);
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;
}