text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chromeos/dbus/ibus/ibus_input_context_client.h"
  
  #include <string>
  #include "base/bind.h"
  #include "base/callback.h"
  #include "chromeos/dbus/ibus/ibus_constants.h"
  #include "chromeos/dbus/ibus/ibus_engine_service.h"
  #include "chromeos/dbus/ibus/ibus_panel_service.h"
  #include "chromeos/dbus/ibus/ibus_text.h"
  #include "chromeos/ime/ibus_bridge.h"
  #include "dbus/bus.h"
  #include "dbus/message.h"
  #include "dbus/object_path.h"
  #include "dbus/object_proxy.h"
  
  namespace chromeos {
  
  using chromeos::IBusText;
  
  namespace {
  
  // The IBusInputContextClient implementation.
  class IBusInputContextClientImpl : public IBusInputContextClient {
   public:
    IBusInputContextClientImpl()
        : proxy_(NULL),
          is_xkb_layout_(true),
          weak_ptr_factory_(this) {
    }
  
    virtual ~IBusInputContextClientImpl() {}
  
   public:
    // IBusInputContextClient override.
    virtual void Initialize(dbus::Bus* bus,
                            const dbus::ObjectPath& object_path) OVERRIDE {
      if (proxy_ != NULL) {
        LOG(ERROR) << "IBusInputContextClient is already initialized.";
        return;
      }
      proxy_ = bus->GetObjectProxy(ibus::kServiceName, object_path);
  
      ConnectSignals();
    }
  
    // IBusInputContextClient override.
    virtual void SetInputContextHandler(
        IBusInputContextHandlerInterface* handler) OVERRIDE {
      handler_ = handler;
    }
  
    // IBusInputContextClient override.
    virtual void SetSetCursorLocationHandler(
        const SetCursorLocationHandler& set_cursor_location_handler) OVERRIDE {
      DCHECK(!set_cursor_location_handler.is_null());
      set_cursor_location_handler_ = set_cursor_location_handler;
    }
  
    // IBusInputContextClient override.
    virtual void UnsetSetCursorLocationHandler() OVERRIDE {
      set_cursor_location_handler_.Reset();
    }
  
    // IBusInputContextClient override.
    virtual void ResetObjectProxy() OVERRIDE {
      // Do not delete proxy here, proxy object is managed by dbus::Bus object.
      proxy_ = NULL;
    }
  
    // IBusInputContextClient override.
    virtual bool IsObjectProxyReady() const OVERRIDE {
      return proxy_ != NULL;
    }
  
    // IBusInputContextClient override.
    virtual void SetCapabilities(uint32 capabilities) OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kSetCapabilitiesMethod);
      dbus::MessageWriter writer(&method_call);
      writer.AppendUint32(capabilities);
      CallNoResponseMethod(&method_call,
                           ibus::input_context::kSetCapabilitiesMethod);
    }
  
    // IBusInputContextClient override.
    virtual void FocusIn() OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kFocusInMethod);
      CallNoResponseMethod(&method_call, ibus::input_context::kFocusInMethod);
    }
  
    // IBusInputContextClient override.
    virtual void FocusOut() OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kFocusOutMethod);
      CallNoResponseMethod(&method_call, ibus::input_context::kFocusOutMethod);
    }
  
    // IBusInputContextClient override.
    virtual void Reset() OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kResetMethod);
      CallNoResponseMethod(&method_call, ibus::input_context::kResetMethod);
    }
  
    // IBusInputContextClient override.
    virtual void SetCursorLocation(const ibus::Rect& cursor_location,
                                   const ibus::Rect& composition_head) OVERRIDE {
      if (!set_cursor_location_handler_.is_null())
        set_cursor_location_handler_.Run(cursor_location, composition_head);
    }
  
    // IBusInputContextClient override.
    virtual void ProcessKeyEvent(
        uint32 keyval,
        uint32 keycode,
        uint32 state,
        const ProcessKeyEventCallback& callback,
        const ErrorCallback& error_callback) OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kProcessKeyEventMethod);
      dbus::MessageWriter writer(&method_call);
      writer.AppendUint32(keyval);
      writer.AppendUint32(keycode);
      writer.AppendUint32(state);
      proxy_->CallMethodWithErrorCallback(
          &method_call,
          dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
          base::Bind(&IBusInputContextClientImpl::OnProcessKeyEvent,
                     callback,
                     error_callback),
          base::Bind(&IBusInputContextClientImpl::OnProecessKeyEventFail,
                     error_callback));
    }
  
    // IBusInputContextClient override.
    virtual void SetSurroundingText(const std::string& text,
                                    uint32 start_index,
                                    uint32 end_index) OVERRIDE {
      dbus::MethodCall method_call(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kSetSurroundingTextMethod);
      dbus::MessageWriter writer(&method_call);
      AppendStringAsIBusText(text, &writer);
      writer.AppendUint32(start_index);
      writer.AppendUint32(end_index);
      CallNoResponseMethod(&method_call,
                           ibus::input_context::kSetSurroundingTextMethod);
    }
  
    // IBusInputContextClient override.
    virtual void PropertyActivate(const std::string& key,
                                  ibus::IBusPropertyState state) OVERRIDE {
      dbus::MethodCall method_call(ibus::input_context::kServiceInterface,
                                   ibus::input_context::kPropertyActivateMethod);
      dbus::MessageWriter writer(&method_call);
      writer.AppendString(key);
      if (state == ibus::IBUS_PROPERTY_STATE_CHECKED) {
        writer.AppendUint32(ibus::IBUS_PROPERTY_STATE_CHECKED);
      } else {
        writer.AppendUint32(ibus::IBUS_PROPERTY_STATE_UNCHECKED);
      }
      CallNoResponseMethod(&method_call,
                           ibus::input_context::kPropertyActivateMethod);
    }
  
    // IBusInputContextClient override.
    virtual bool IsXKBLayout() OVERRIDE {
      return is_xkb_layout_;
    }
  
    // IBusInputContextClient override.
    virtual void SetIsXKBLayout(bool is_xkb_layout) OVERRIDE {
      is_xkb_layout_ = is_xkb_layout;
    }
  
   private:
    void CallNoResponseMethod(dbus::MethodCall* method_call,
                              const std::string& method_name) {
      proxy_->CallMethodWithErrorCallback(
          method_call,
          dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
          base::Bind(&IBusInputContextClientImpl::DefaultCallback,
                     method_name),
          base::Bind(&IBusInputContextClientImpl::DefaultErrorCallback,
                     method_name));
    }
  
    // Handles no response method call reply.
    static void DefaultCallback(const std::string& method_name,
                                dbus::Response* response) {
      if (!response) {
        LOG(ERROR) << "Failed to call method: " << method_name;
        return;
      }
    }
  
    // Handles error response of default method call.
    static void DefaultErrorCallback(const std::string& method_name,
                                     dbus::ErrorResponse* response) {
      LOG(ERROR) << "Failed to call method: " << method_name;
    }
  
    // Handles ProcessKeyEvent method call reply.
    static void OnProcessKeyEvent(const ProcessKeyEventCallback& callback,
                                  const ErrorCallback& error_callback,
                                  dbus::Response* response) {
      if (!response) {
        LOG(ERROR) << "Cannot get input context: " << response->ToString();
        error_callback.Run();
        return;
      }
      dbus::MessageReader reader(response);
      bool is_keyevent_used;
      if (!reader.PopBool(&is_keyevent_used)) {
        // The IBus message structure may be changed.
        LOG(ERROR) << "Invalid response: " << response->ToString();
        error_callback.Run();
        return;
      }
      DCHECK(!callback.is_null());
      callback.Run(is_keyevent_used);
    }
  
    // Handles error response of ProcessKeyEvent method call.
    static void OnProecessKeyEventFail(const ErrorCallback& error_callback,
                                       dbus::ErrorResponse* response) {
      error_callback.Run();
    }
  
    // Handles CommitText signal.
    void OnCommitText(dbus::Signal* signal) {
      if (!handler_)
        return;
      dbus::MessageReader reader(signal);
      IBusText ibus_text;
      if (!PopIBusText(&reader, &ibus_text)) {
        // The IBus message structure may be changed.
        LOG(ERROR) << "Invalid signal: " << signal->ToString();
        return;
      }
      handler_->CommitText(ibus_text);
    }
  
    // Handles ForwardKeyEvetn signal.
    void OnForwardKeyEvent(dbus::Signal* signal) {
      if (!handler_)
        return;
      dbus::MessageReader reader(signal);
      uint32 keyval = 0;
      uint32 keycode = 0;
      uint32 state = 0;
      if (!reader.PopUint32(&keyval) ||
          !reader.PopUint32(&keycode) ||
          !reader.PopUint32(&state)) {
        // The IBus message structure may be changed.
        LOG(ERROR) << "Invalid signal: " << signal->ToString();
        return;
      }
      handler_->ForwardKeyEvent(keyval, keycode, state);
    }
  
    // Handles UpdatePreeditText signal.
    void OnUpdatePreeditText(dbus::Signal* signal) {
      if (!handler_)
        return;
      dbus::MessageReader reader(signal);
      IBusText ibus_text;
      uint32 cursor_pos = 0;
      bool visible = true;
      if (!PopIBusText(&reader, &ibus_text) ||
          !reader.PopUint32(&cursor_pos) ||
          !reader.PopBool(&visible)) {
        // The IBus message structure may be changed.
        LOG(ERROR) << "Invalid signal: " << signal->ToString();
        return;
      }
      handler_->UpdatePreeditText(ibus_text, cursor_pos, visible);
    }
  
    // Handles ShowPreeditText signal.
    void OnShowPreeditText(dbus::Signal* signal) {
      if (handler_)
        handler_->ShowPreeditText();
    }
  
    // Handles HidePreeditText signal.
    void OnHidePreeditText(dbus::Signal* signal) {
      if (handler_)
        handler_->HidePreeditText();
    }
  
    // Handle DeleteSurroundingText signal.
    void OnDeleteSurroundingText(dbus::Signal* signal) {
      if (!handler_)
        return;
  
      dbus::MessageReader reader(signal);
      int32 offset = 0;
      uint32 length = 0;
      if (!reader.PopInt32(&offset) || !reader.PopUint32(&length)) {
        // The IBus message structure may be changed.
        LOG(ERROR) << "Invalid signal: " << signal->ToString();
        return;
      }
      handler_->DeleteSurroundingText(offset, length);
    }
  
    // Connects signals to signal handlers.
    void ConnectSignals() {
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kCommitTextSignal,
          base::Bind(&IBusInputContextClientImpl::OnCommitText,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
  
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kForwardKeyEventSignal,
          base::Bind(&IBusInputContextClientImpl::OnForwardKeyEvent,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
  
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kUpdatePreeditTextSignal,
          base::Bind(&IBusInputContextClientImpl::OnUpdatePreeditText,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
  
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kShowPreeditTextSignal,
          base::Bind(&IBusInputContextClientImpl::OnShowPreeditText,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
  
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kHidePreeditTextSignal,
          base::Bind(&IBusInputContextClientImpl::OnHidePreeditText,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
  
      proxy_->ConnectToSignal(
          ibus::input_context::kServiceInterface,
          ibus::input_context::kDeleteSurroundingTextSignal,
          base::Bind(&IBusInputContextClientImpl::OnDeleteSurroundingText,
                     weak_ptr_factory_.GetWeakPtr()),
          base::Bind(&IBusInputContextClientImpl::OnSignalConnected,
                     weak_ptr_factory_.GetWeakPtr()));
    }
  
    // Handles the result of signal connection setup.
    void OnSignalConnected(const std::string& interface,
                           const std::string& signal,
                           bool succeeded) {
      LOG_IF(ERROR, !succeeded) << "Connect to " << interface << " "
                                << signal << " failed.";
    }
  
    dbus::ObjectProxy* proxy_;
  
    // The pointer for input context handler. This can be NULL.
    IBusInputContextHandlerInterface* handler_;
  
    SetCursorLocationHandler set_cursor_location_handler_;
  
    // True if the current input method is xkb layout.
    bool is_xkb_layout_;
  
    base::WeakPtrFactory<IBusInputContextClientImpl> weak_ptr_factory_;
  
    DISALLOW_COPY_AND_ASSIGN(IBusInputContextClientImpl);
  };
  
  // An implementation of IBusInputContextClient without ibus-daemon interaction.
  // Currently this class is used only on linux desktop.
  // TODO(nona): Use this on ChromeOS device once crbug.com/171351 is fixed.
  class IBusInputContextClientDaemonlessImpl : public IBusInputContextClient {
   public:
    IBusInputContextClientDaemonlessImpl()
        : is_xkb_layout_(true),
          initialized_(false)
    {}
    virtual ~IBusInputContextClientDaemonlessImpl() {}
  
    // IBusInputContextClient override.
    virtual void Initialize(dbus::Bus* bus,
                            const dbus::ObjectPath& object_path) OVERRIDE {
      initialized_ = true;
    }
  
    virtual void SetInputContextHandler(
        IBusInputContextHandlerInterface* handler) OVERRIDE {
      IBusBridge::Get()->SetInputContextHandler(handler);
    }
  
    virtual void SetSetCursorLocationHandler(
        const SetCursorLocationHandler& set_cursor_location_handler) OVERRIDE {
    }
  
    virtual void UnsetSetCursorLocationHandler() OVERRIDE {
    }
  
    virtual void ResetObjectProxy() OVERRIDE {
      initialized_ = false;
    }
  
    virtual bool IsObjectProxyReady() const OVERRIDE {
      return initialized_;
    }
  
    virtual void SetCapabilities(uint32 capability) OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->SetCapability(
            static_cast<IBusEngineHandlerInterface::IBusCapability>(capability));
    }
  
    virtual void FocusIn() OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->FocusIn();
    }
  
    virtual void FocusOut() OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->FocusOut();
    }
  
    virtual void Reset() OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->Reset();
    }
  
    virtual void SetCursorLocation(const ibus::Rect& cursor_location,
                                   const ibus::Rect& composition_head) OVERRIDE {
      IBusPanelCandidateWindowHandlerInterface* candidate_window =
          IBusBridge::Get()->GetCandidateWindowHandler();
  
      if (candidate_window)
        candidate_window->SetCursorLocation(cursor_location, composition_head);
    }
  
    virtual void ProcessKeyEvent(
        uint32 keyval,
        uint32 keycode,
        uint32 state,
        const ProcessKeyEventCallback& callback,
        const ErrorCallback& error_callback) OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->ProcessKeyEvent(keyval, keycode, state, callback);
    }
  
    virtual void SetSurroundingText(const std::string& text,
                                    uint32 start_index,
                                    uint32 end_index) OVERRIDE {
      // TODO(nona): Implement this.
    }
  
    virtual void PropertyActivate(const std::string& key,
                                  ibus::IBusPropertyState state) OVERRIDE {
      IBusEngineHandlerInterface* engine = IBusBridge::Get()->GetEngineHandler();
      if (engine)
        engine->PropertyActivate(key,
                                 static_cast<ibus::IBusPropertyState>(state));
    }
  
    virtual bool IsXKBLayout() OVERRIDE {
      return is_xkb_layout_;
    }
  
    virtual void SetIsXKBLayout(bool is_xkb_layout) OVERRIDE {
      is_xkb_layout_ = is_xkb_layout;
    }
  
   private:
    // True if the current input method is xkb layout.
    bool is_xkb_layout_;
    bool initialized_;
  
    DISALLOW_COPY_AND_ASSIGN(IBusInputContextClientDaemonlessImpl);
  };
  
  }  // namespace
  
  ///////////////////////////////////////////////////////////////////////////////
  // IBusInputContextClient
  
  IBusInputContextClient::IBusInputContextClient() {}
  
  IBusInputContextClient::~IBusInputContextClient() {}
  
  // static
  IBusInputContextClient* IBusInputContextClient::Create(
      DBusClientImplementationType type) {
    if (type == REAL_DBUS_CLIENT_IMPLEMENTATION) {
      return new IBusInputContextClientImpl();
    }
    DCHECK_EQ(STUB_DBUS_CLIENT_IMPLEMENTATION, type);
    return new IBusInputContextClientDaemonlessImpl();
  }
  }  // namespace chromeos
  