77388dab8a
XrdpIme never actually became the active engine after the private main loop rewrite - ibus_bus_set_global_engine() returned FALSE every time. Confirmed the actual D-Bus error by bypassing the boolean-only wrapper and calling SetGlobalEngine directly: Set global engine failed: Timeout was reached Root cause: xrdp_input_enable() was being marshaled onto the IBus thread via g_main_context_invoke(), on the (wrong) theory that it "must run on the IBus thread" like commit_text does. But set_global_engine() blocks synchronously waiting for ibus-daemon's reply, and 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. Running xrdp_input_enable() on the IBus thread left that thread stuck blocked inside its own synchronous call, unable to service the nested callback ibus-daemon needed answered before it could reply - a self-deadlock, resolved only by ibus-daemon's own timeout. Calling xrdp_input_enable() directly from chansrv's thread instead (matching the original pre-rewrite code, which did this without apparent reason but happened to sidestep the deadlock) leaves the IBus thread free to answer the nested call while chansrv's thread blocks. GDBusConnection sync calls are documented thread-safe to issue from any thread, so this doesn't need marshaling despite `bus` otherwise being owned by the IBus thread. Verified end-to-end against a real ibus-daemon and a focused GTK entry widget (not just the D-Bus trace): ibus engine now correctly reports "XrdpIme" after a send, and 27 characters fired with zero delay between them - the exact rapid-fire pattern that used to drop most commits under the old design - landed in order with zero drops and zero duplicates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
731 lines
21 KiB
C
731 lines
21 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 this is fine
|
|
* despite `bus` otherwise being owned by the IBus thread. */
|
|
static gboolean
|
|
xrdp_input_enable(void)
|
|
{
|
|
IBusEngineDesc *desc;
|
|
const gchar *name;
|
|
|
|
if (bus == NULL || !ibus_bus_is_connected(bus))
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"xrdp_input_enable: IBus is not connected");
|
|
return FALSE;
|
|
}
|
|
|
|
desc = ibus_bus_get_global_engine(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);
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
if (!ibus_bus_set_global_engine(bus, "XrdpIme"))
|
|
{
|
|
LOG(LOG_LEVEL_ERROR,
|
|
"xrdp_input_enable: failed to switch global engine to XrdpIme");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
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;
|
|
|
|
bus = ibus_bus_new();
|
|
|
|
if (bus == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "xrdp_input_main_loop: ibus_bus_new failed");
|
|
goto thread_cleanup;
|
|
}
|
|
g_object_ref_sink(bus);
|
|
|
|
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;
|
|
}
|
|
if (bus != NULL)
|
|
{
|
|
g_object_unref(bus);
|
|
bus = NULL;
|
|
}
|
|
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)
|
|
{
|
|
g_mutex_unlock(&state_mutex);
|
|
return 0;
|
|
}
|
|
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");
|
|
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;
|
|
}
|