blob: 3239f3a1dcab50d0863da2a66a16813e5e97340e [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "unix/ibus/mozc_engine.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <limits>
#include <map>
#include <sstream>
#include <string>
#include "base/const.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/protobuf/descriptor.h"
#include "base/protobuf/message.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "client/client.h"
#include "config/config.pb.h"
#include "session/commands.pb.h"
#include "session/ime_switch_util.h"
#include "unix/ibus/engine_registrar.h"
#include "unix/ibus/ibus_candidate_window_handler.h"
#include "unix/ibus/key_event_handler.h"
#include "unix/ibus/message_translator.h"
#include "unix/ibus/mozc_engine_property.h"
#include "unix/ibus/path_util.h"
#include "unix/ibus/preedit_handler.h"
#include "unix/ibus/property_handler.h"
#include "unix/ibus/surrounding_text_util.h"
#ifdef ENABLE_GTK_RENDERER
#include "renderer/renderer_client.h"
#include "unix/ibus/gtk_candidate_window_handler.h"
#endif // ENABLE_GTK_RENDERER
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
#include "unix/ibus/selection_monitor.h"
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
#ifdef ENABLE_GTK_RENDERER
DEFINE_bool(use_mozc_renderer, true,
"The engine tries to use mozc_renderer if available.");
#endif // ENABLE_GTK_RENDERER
namespace {
// The ID for candidates which are not associated with texts.
const int32 kBadCandidateId = -1;
#ifdef ENABLE_GTK_RENDERER
const char kMozcPanelSectionName[] = "panel";
#endif // ENABLE_GTK_RENDERER
// Default UI locale
const char kMozcDefaultUILocale[] = "en_US.UTF-8";
// for every 5 minutes, call SyncData
const uint64 kSyncDataInterval = 5 * 60;
const char *kUILocaleEnvNames[] = {
"LC_ALL",
"LC_MESSAGES",
"LANG",
};
string GetMessageLocale() {
for (size_t i = 0; i < arraysize(kUILocaleEnvNames); ++i) {
const char *env_ptr = ::getenv(kUILocaleEnvNames[i]);
if (env_ptr == NULL || env_ptr[0] == '\0') {
continue;
}
return env_ptr;
}
return kMozcDefaultUILocale;
}
bool GetString(GVariant *value, string *out_string) {
if (g_variant_classify(value) != G_VARIANT_CLASS_STRING) {
return false;
}
*out_string = static_cast<const char *>(g_variant_get_string(value, NULL));
return true;
}
bool GetBoolean(GVariant *value, bool *out_boolean) {
if (g_variant_classify(value) != G_VARIANT_CLASS_BOOLEAN) {
return false;
}
*out_boolean = (g_variant_get_boolean(value) != FALSE);
return true;
}
struct IBusMozcEngineClass {
IBusEngineClass parent;
};
struct IBusMozcEngine {
IBusEngine parent;
mozc::ibus::MozcEngine *engine;
};
IBusEngineClass *g_parent_class = NULL;
GObject *MozcEngineClassConstructor(
GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties) {
return G_OBJECT_CLASS(g_parent_class)->constructor(type,
n_construct_properties,
construct_properties);
}
void MozcEngineClassDestroy(IBusObject *engine) {
IBUS_OBJECT_CLASS(g_parent_class)->destroy(engine);
}
void MozcEngineClassInit(gpointer klass, gpointer class_data) {
IBusEngineClass *engine_class = IBUS_ENGINE_CLASS(klass);
VLOG(2) << "MozcEngineClassInit is called";
mozc::ibus::EngineRegistrar::Register(
mozc::Singleton<mozc::ibus::MozcEngine>::get(), engine_class);
g_parent_class = reinterpret_cast<IBusEngineClass*>(
g_type_class_peek_parent(klass));
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->constructor = MozcEngineClassConstructor;
IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS(klass);
ibus_object_class->destroy = MozcEngineClassDestroy;
}
void MozcEngineInstanceInit(GTypeInstance *instance, gpointer klass) {
IBusMozcEngine *engine = reinterpret_cast<IBusMozcEngine*>(instance);
engine->engine = mozc::Singleton<mozc::ibus::MozcEngine>::get();
}
} // namespace
namespace mozc {
namespace ibus {
namespace {
struct SurroundingTextInfo {
SurroundingTextInfo()
: relative_selected_length(0) {}
int32 relative_selected_length;
string preceding_text;
string selection_text;
string following_text;
};
bool GetSurroundingText(IBusEngine *engine,
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
SelectionMonitorInterface *selection_monitor,
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
SurroundingTextInfo *info) {
if (!(engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT)) {
VLOG(1) << "Give up CONVERT_REVERSE due to client_capabilities: "
<< engine->client_capabilities;
return false;
}
guint cursor_pos = 0;
guint anchor_pos = 0;
// DO NOT call g_object_unref against this.
// http://ibus.googlecode.com/svn/docs/ibus-1.4/IBusText.html
// http://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#gobject-The-Base-Object-Type.description
IBusText *text = NULL;
ibus_engine_get_surrounding_text(engine, &text, &cursor_pos,
&anchor_pos);
const string surrounding_text(ibus_text_get_text(text));
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
if (cursor_pos == anchor_pos && selection_monitor != NULL) {
const SelectionInfo &info = selection_monitor->GetSelectionInfo();
guint new_anchor_pos = 0;
if (SurroundingTextUtil::GetAnchorPosFromSelection(
surrounding_text, info.selected_text,
cursor_pos, &new_anchor_pos)) {
anchor_pos = new_anchor_pos;
}
}
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
if (!SurroundingTextUtil::GetSafeDelta(cursor_pos, anchor_pos,
&info->relative_selected_length)) {
LOG(ERROR) << "Too long text selection.";
return false;
}
const size_t selection_start = min(cursor_pos, anchor_pos);
const size_t selection_length = abs(info->relative_selected_length);
Util::SubStringPiece(surrounding_text, 0, selection_start)
.CopyToString(&info->preceding_text);
Util::SubStringPiece(surrounding_text, selection_start, selection_length)
.CopyToString(&info->selection_text);
Util::SubStringPiece(surrounding_text, selection_start + selection_length)
.CopyToString(&info->following_text);
return true;
}
std::unique_ptr<client::ClientInterface> CreateAndConfigureClient() {
std::unique_ptr<client::ClientInterface> client(
client::ClientFactory::NewClient());
// Currently client capability is fixed.
commands::Capability capability;
capability.set_text_deletion(commands::Capability::DELETE_PRECEDING_TEXT);
client->set_client_capability(capability);
return client;
}
} // namespace
MozcEngine::MozcEngine()
: last_sync_time_(Util::GetTime()),
key_event_handler_(new KeyEventHandler),
client_(CreateAndConfigureClient()),
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
selection_monitor_(SelectionMonitorFactory::Create(1024)),
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
property_handler_(new PropertyHandler(
new LocaleBasedMessageTranslator(GetMessageLocale()), client_.get())),
preedit_handler_(new PreeditHandler()),
#ifdef ENABLE_GTK_RENDERER
gtk_candidate_window_handler_(new GtkCandidateWindowHandler(
new renderer::RendererClient())),
#endif // ENABLE_GTK_RENDERER
ibus_candidate_window_handler_(new IBusCandidateWindowHandler()),
preedit_method_(config::Config::ROMAN) {
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
if (selection_monitor_.get() != NULL) {
selection_monitor_->StartMonitoring();
}
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
// TODO(yusukes): write a unit test to check if the capability is set
// as expected.
}
MozcEngine::~MozcEngine() {
SyncData(true);
}
void MozcEngine::CandidateClicked(
IBusEngine *engine,
guint index,
guint button,
guint state) {
if (index >= unique_candidate_ids_.size()) {
return;
}
const int32 id = unique_candidate_ids_[index];
if (id == kBadCandidateId) {
return;
}
commands::Output output;
commands::SessionCommand command;
command.set_type(commands::SessionCommand::SELECT_CANDIDATE);
command.set_id(id);
client_->SendCommand(command, &output);
UpdateAll(engine, output);
}
void MozcEngine::CursorDown(IBusEngine *engine) {
// TODO(mazda): Implement this.
}
void MozcEngine::CursorUp(IBusEngine *engine) {
// TODO(mazda): Implement this.
}
void MozcEngine::Disable(IBusEngine *engine) {
RevertSession(engine);
GetCandidateWindowHandler(engine)->Hide(engine);
key_event_handler_->Clear();
}
void MozcEngine::Enable(IBusEngine *engine) {
// Launch mozc_server
client_->EnsureConnection();
UpdatePreeditMethod();
// When ibus-mozc is disabled by the "next input method" hot key, ibus-daemon
// does not call MozcEngine::Disable(). Call RevertSession() here so the
// mozc_server could discard a preedit string before the hot key is pressed
// (crosbug.com/4596).
RevertSession(engine);
// If engine wants to use surrounding text, we should call
// ibus_engine_get_surrounding_text once when the engine enabled.
ibus_engine_get_surrounding_text(engine, NULL, NULL, NULL);
}
void MozcEngine::FocusIn(IBusEngine *engine) {
property_handler_->Register(engine);
UpdatePreeditMethod();
}
void MozcEngine::FocusOut(IBusEngine *engine) {
GetCandidateWindowHandler(engine)->Hide(engine);
property_handler_->ResetContentType(engine);
// Note that the preedit string (if any) will be committed by IBus runtime
// because we are specifying |IBUS_ENGINE_PREEDIT_COMMIT| flag to
// |ibus_engine_update_preedit_text_with_mode|. All we need to do here is
// simply resetting the current session in case there is a non-empty
// preedit text. Note that |RevertSession| is supposed to do nothing when
// there is no preedit text.
// See https://code.google.com/p/mozc/issues/detail?id=255 for details.
RevertSession(engine);
SyncData(false);
}
void MozcEngine::PageDown(IBusEngine *engine) {
// TODO(mazda,yusukes): Implement this to support arrow icons inside the Gtk+
// candidate window.
}
void MozcEngine::PageUp(IBusEngine *engine) {
// TODO(mazda,yusukes): Implement this to support arrow icons inside the Gtk+
// candidate window.
}
gboolean MozcEngine::ProcessKeyEvent(
IBusEngine *engine,
guint keyval,
guint keycode,
guint modifiers) {
VLOG(2) << "keyval: " << keyval
<< ", keycode: " << keycode
<< ", modifiers: " << modifiers;
if (property_handler_->IsDisabled()) {
return FALSE;
}
// Send current caret location to mozc_server to manage suggest window
// position.
// TODO(nona): Merge SendKey event to reduce IPC cost.
// TODO(nona): Add a unit test against b/6209562.
SendCaretLocation(engine->cursor_area.x,
engine->cursor_area.y,
engine->cursor_area.width,
engine->cursor_area.height);
// TODO(yusukes): use |layout| in IBusEngineDesc if possible.
const bool layout_is_jp =
!g_strcmp0(ibus_engine_get_name(engine), "mozc-jp");
commands::KeyEvent key;
if (!key_event_handler_->GetKeyEvent(
keyval, keycode, modifiers, preedit_method_, layout_is_jp, &key)) {
// Doesn't send a key event to mozc_server.
return FALSE;
}
VLOG(2) << key.DebugString();
if (!property_handler_->IsActivated() &&
!config::ImeSwitchUtil::IsDirectModeCommand(key)) {
return FALSE;
}
key.set_activated(property_handler_->IsActivated());
key.set_mode(property_handler_->GetOriginalCompositionMode());
commands::Context context;
SurroundingTextInfo surrounding_text_info;
if (GetSurroundingText(engine,
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
selection_monitor_.get(),
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
&surrounding_text_info)) {
context.set_preceding_text(surrounding_text_info.preceding_text);
context.set_following_text(surrounding_text_info.following_text);
}
commands::Output output;
if (!client_->SendKeyWithContext(key, context, &output)) {
LOG(ERROR) << "SendKey failed";
return FALSE;
}
VLOG(2) << output.DebugString();
UpdateAll(engine, output);
const bool consumed = output.consumed();
return consumed ? TRUE : FALSE;
}
void MozcEngine::PropertyActivate(IBusEngine *engine,
const gchar *property_name,
guint property_state) {
property_handler_->ProcessPropertyActivate(engine, property_name,
property_state);
}
void MozcEngine::PropertyHide(IBusEngine *engine,
const gchar *property_name) {
// We can ignore the signal.
}
void MozcEngine::PropertyShow(IBusEngine *engine,
const gchar *property_name) {
// We can ignore the signal.
}
void MozcEngine::Reset(IBusEngine *engine) {
RevertSession(engine);
}
void MozcEngine::SetCapabilities(IBusEngine *engine,
guint capabilities) {
// Do nothing.
}
void MozcEngine::SetCursorLocation(IBusEngine *engine,
gint x,
gint y,
gint w,
gint h) {
// Do nothing
}
void MozcEngine::SetContentType(IBusEngine *engine,
guint purpose,
guint hints) {
const bool prev_disabled =
property_handler_->IsDisabled();
property_handler_->UpdateContentType(engine);
if (!prev_disabled && property_handler_->IsDisabled()) {
// Make sure on-going composition is reverted.
RevertSession(engine);
}
}
GType MozcEngine::GetType() {
static GType type = 0;
static const GTypeInfo type_info = {
sizeof(IBusMozcEngineClass),
NULL,
NULL,
MozcEngineClassInit,
NULL,
NULL,
sizeof(IBusMozcEngine),
0,
MozcEngineInstanceInit,
};
if (type == 0) {
type = g_type_register_static(IBUS_TYPE_ENGINE,
"IBusMozcEngine",
&type_info,
static_cast<GTypeFlags>(0));
DCHECK_NE(type, 0) << "g_type_register_static failed";
}
return type;
}
// static
void MozcEngine::Disconnected(IBusBus *bus, gpointer user_data) {
ibus_quit();
}
void MozcEngine::ConfigValueChanged(IBusConfig *config,
const gchar *section,
const gchar *name,
GVariant *value,
gpointer user_data) {
// This function might be called _before_ MozcEngineClassInit is called if
// you press the "Configure..." button for Mozc before switching to the Mozc
// input method.
MozcEngine *engine = mozc::Singleton<MozcEngine>::get();
engine->UpdateConfig(section, name, value);
}
#ifdef ENABLE_GTK_RENDERER
void MozcEngine::InitRendererConfig(IBusConfig *config) {
GVariant *custom_font_value = ibus_config_get_value(config,
kMozcPanelSectionName,
"custom_font");
GVariant *use_custom_font_value = ibus_config_get_value(config,
kMozcPanelSectionName,
"use_custom_font");
bool use_custom_font;
if (GetBoolean(use_custom_font_value, &use_custom_font)) {
gtk_candidate_window_handler_->OnIBusUseCustomFontDescriptionChanged(
use_custom_font);
} else {
LOG(ERROR) << "Initialize Failed: "
<< "Cannot get panel:use_custom_font configuration.";
}
string font_description;
if (GetString(custom_font_value, &font_description)) {
gtk_candidate_window_handler_->OnIBusCustomFontDescriptionChanged(
font_description);
} else {
LOG(ERROR) << "Initialize Failed: "
<< "Cannot get panel:custom_font configuration.";
}
}
#endif // ENABLE_GTK_RENDERER
// TODO(mazda): Move the impelementation to an appropriate file.
void MozcEngine::InitConfig(IBusConfig *config) {
#ifdef ENABLE_GTK_RENDERER
MozcEngine *engine = mozc::Singleton<MozcEngine>::get();
engine->InitRendererConfig(config);
#endif // ENABLE_GTK_RENDERER
}
bool MozcEngine::UpdateAll(IBusEngine *engine, const commands::Output &output) {
UpdateDeletionRange(engine, output);
UpdateResult(engine, output);
preedit_handler_->Update(engine, output);
GetCandidateWindowHandler(engine)->Update(engine, output);
UpdateCandidateIDMapping(output);
property_handler_->Update(engine, output);
LaunchTool(output);
ExecuteCallback(engine, output);
return true;
}
bool MozcEngine::UpdateDeletionRange(IBusEngine *engine,
const commands::Output &output) {
if (output.has_deletion_range() &&
output.deletion_range().offset() < 0 &&
output.deletion_range().offset() + output.deletion_range().length() >=
0) {
// Nowadays 'ibus_engine_delete_surrounding_text' becomes functional on
// many of the major applications. Confirmed that it works on
// Firefox 10.0, LibreOffice 3.3.4 and GEdit 3.2.3.
ibus_engine_delete_surrounding_text(
engine,
output.deletion_range().offset(), output.deletion_range().length());
}
return true;
}
bool MozcEngine::UpdateResult(IBusEngine *engine,
const commands::Output &output) const {
if (!output.has_result()) {
VLOG(2) << "output doesn't contain result";
return true;
}
IBusText *text = ibus_text_new_from_string(output.result().value().c_str());
ibus_engine_commit_text(engine, text);
// |text| is released by ibus_engine_commit_text.
return true;
}
bool MozcEngine::UpdateCandidateIDMapping(const commands::Output &output) {
if (!output.has_candidates() || output.candidates().candidate_size() == 0) {
return true;
}
unique_candidate_ids_.clear();
const commands::Candidates &candidates = output.candidates();
for (int i = 0; i < candidates.candidate_size(); ++i) {
if (candidates.candidate(i).has_id()) {
const int32 id = candidates.candidate(i).id();
unique_candidate_ids_.push_back(id);
} else {
// The parent node of the cascading window does not have an id since the
// node does not contain a candidate word.
unique_candidate_ids_.push_back(kBadCandidateId);
}
}
return true;
}
void MozcEngine::UpdateConfig(const gchar *section,
const gchar *name,
GVariant *value) {
if (!section || !name || !value) {
return;
}
#ifdef ENABLE_GTK_RENDERER
// TODO(nona): Introduce ConfigHandler
if (g_strcmp0(section, kMozcPanelSectionName) == 0) {
if (g_strcmp0(name, "use_custom_font") == 0) {
bool use_custom_font;
if (!GetBoolean(value, &use_custom_font)) {
LOG(ERROR) << "Cannot get " << section << ":" << name << " value.";
return;
}
gtk_candidate_window_handler_->OnIBusUseCustomFontDescriptionChanged(
use_custom_font);
} else if (g_strcmp0(name, "custom_font") == 0) {
string font_description;
if (!GetString(value, &font_description)) {
LOG(ERROR) << "Cannot get " << section << ":" << name << " value.";
return;
}
gtk_candidate_window_handler_->OnIBusCustomFontDescriptionChanged(
font_description);
}
}
#endif // ENABLE_GTK_RENDERER
}
void MozcEngine::UpdatePreeditMethod() {
config::Config config;
if (!client_->GetConfig(&config)) {
LOG(ERROR) << "GetConfig failed";
return;
}
preedit_method_ = config.has_preedit_method() ?
config.preedit_method() : config::Config::ROMAN;
}
void MozcEngine::SyncData(bool force) {
if (client_.get() == NULL) {
return;
}
const uint64 current_time = Util::GetTime();
if (force ||
(current_time >= last_sync_time_ &&
current_time - last_sync_time_ >= kSyncDataInterval)) {
VLOG(1) << "Syncing data";
client_->SyncData();
last_sync_time_ = current_time;
}
}
bool MozcEngine::LaunchTool(const commands::Output &output) const {
if (!client_->LaunchToolWithProtoBuf(output)) {
VLOG(2) << output.DebugString() << " Launch Failed";
return false;
}
return true;
}
void MozcEngine::RevertSession(IBusEngine *engine) {
// TODO(team): We should skip following actions when there is no on-going
// omposition.
commands::SessionCommand command;
command.set_type(commands::SessionCommand::REVERT);
commands::Output output;
if (!client_->SendCommand(command, &output)) {
LOG(ERROR) << "RevertSession() failed";
return;
}
UpdateAll(engine, output);
}
bool MozcEngine::ExecuteCallback(IBusEngine *engine,
const commands::Output &output) {
if (!output.has_callback()) {
return false;
}
// TODO(nona): Make IBus interface class and add unittest for ibus APIs.
if (!output.callback().has_session_command()) {
LOG(ERROR) << "callback does not have session_command";
return false;
}
const commands::SessionCommand &callback_command =
output.callback().session_command();
if (!callback_command.has_type()) {
LOG(ERROR) << "callback_command has no type";
return false;
}
commands::SessionCommand session_command;
session_command.set_type(callback_command.type());
// TODO(nona): Make a function to handle CONVERT_REVERSE.
// Used by CONVERT_REVERSE and/or UNDO
// This value represents how many characters are selected as a relative
// distance of characters. Positive value represents forward text selection
// and negative value represents backword text selection.
// Note that you should not allow 0x80000000 for |relative_selected_length|
// because you cannot safely use |-relative_selected_length| nor
// |abs(relative_selected_length)| in this case due to integer overflow.
SurroundingTextInfo surrounding_text_info;
switch (callback_command.type()) {
case commands::SessionCommand::UNDO:
// As far as I've tested on Ubuntu 11.10, most of applications which
// accept 'ibus_engine_delete_surrounding_text' doe not set
// IBUS_CAP_SURROUNDING_TEXT bit.
// So we should carefully uncomment the following code.
// -----
// if (!(engine->client_capabilities & IBUS_CAP_SURROUNDING_TEXT)) {
// return false;
// }
break;
case commands::SessionCommand::CONVERT_REVERSE: {
if (!GetSurroundingText(engine,
#ifdef MOZC_ENABLE_X11_SELECTION_MONITOR
selection_monitor_.get(),
#endif // MOZC_ENABLE_X11_SELECTION_MONITOR
&surrounding_text_info)) {
return false;
}
session_command.set_text(surrounding_text_info.selection_text);
break;
}
default:
return false;
}
commands::Output new_output;
if (!client_->SendCommand(session_command, &new_output)) {
LOG(ERROR) << "Callback Command Failed";
return false;
}
if (callback_command.type() == commands::SessionCommand::CONVERT_REVERSE) {
// We need to remove selected text as a first step of reconversion.
commands::DeletionRange *range = new_output.mutable_deletion_range();
// Use DeletionRange field to remove the selected text.
// For forward selection (that is, |relative_selected_length > 0|), the
// offset should be a negative value to delete preceding text.
// For backward selection (that is, |relative_selected_length < 0|),
// IBus and/or some applications seem to expect |offset == 0| somehow.
const int32 offset = surrounding_text_info.relative_selected_length > 0
? -surrounding_text_info.relative_selected_length // forward selection
: 0; // backward selection
range->set_offset(offset);
range->set_length(abs(surrounding_text_info.relative_selected_length));
}
// Here uses recursion of UpdateAll but it's okay because the converter
// ensures that the second output never contains callback.
UpdateAll(engine, new_output);
return true;
}
CandidateWindowHandlerInterface *MozcEngine::GetCandidateWindowHandler(
IBusEngine *engine) {
#ifndef ENABLE_GTK_RENDERER
return ibus_candidate_window_handler_.get();
#else
if (!FLAGS_use_mozc_renderer) {
return ibus_candidate_window_handler_.get();
}
if (!(engine->client_capabilities & IBUS_CAP_PREEDIT_TEXT)) {
return ibus_candidate_window_handler_.get();
}
// TODO(nona): integrate with renderer/renderer_client.cc
const string renderer_path = FileUtil::JoinPath(
SystemUtil::GetServerDirectory(), "mozc_renderer");
if (!FileUtil::FileExists(renderer_path)) {
return ibus_candidate_window_handler_.get();
}
// TODO(nona): Check executable bit for renderer.
return gtk_candidate_window_handler_.get();
#endif // not ENABLE_GTK_RENDERER
}
void MozcEngine::SendCaretLocation(uint32 x, uint32 y, uint32 width,
uint32 height) {
commands::Output output;
commands::SessionCommand command;
command.set_type(commands::SessionCommand::SEND_CARET_LOCATION);
commands::Rectangle *caret_rectangle = command.mutable_caret_rectangle();
caret_rectangle->set_x(x);
caret_rectangle->set_y(y);
caret_rectangle->set_width(width);
caret_rectangle->set_height(height);
client_->SendCommand(command, &output);
}
} // namespace ibus
} // namespace mozc