text: // Copyright 2010-2020, 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 "win32/base/tsf_registrar.h"
  
  #include <windows.h>
  #define _ATL_NO_AUTOMATIC_NAMESPACE
  #define _WTL_NO_AUTOMATIC_NAMESPACE
  #include <atlbase.h>
  #include <msctf.h>
  #include <objbase.h>
  
  #include <memory>
  
  #include "base/const.h"
  #include "base/logging.h"
  #include "base/util.h"
  #include "win32/base/display_name_resource.h"
  #include "win32/base/input_dll.h"
  #include "win32/base/tsf_profile.h"
  
  namespace mozc {
  namespace win32 {
  
  namespace {
  
  using std::unique_ptr;
  
  // Defines the constant strings used in the TsfRegistrar::RegisterCOMServer()
  // function and the TsfRegistrar::UnregisterCOMServer() function.
  // We define these strings as WCHAR arrays to retrieve the size of this
  // string with the ARRAYSIZE() macro.
  const wchar_t kTipInfoKeyPrefix[] = L"CLSID\\";
  const wchar_t kTipInProcServer32[] = L"InProcServer32";
  const wchar_t kTipThreadingModel[] = L"ThreadingModel";
  const wchar_t kTipTextServiceModel[] = L"Apartment";
  
  // The categories this text service is registered under.
  const GUID kCategories[] = {
      GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER,     // It supports inline input.
      GUID_TFCAT_TIPCAP_COMLESS,               // It's a COM-Less module.
      GUID_TFCAT_TIPCAP_INPUTMODECOMPARTMENT,  // It supports input mode.
      GUID_TFCAT_TIPCAP_UIELEMENTENABLED,      // It supports UI less mode.
      GUID_TFCAT_TIP_KEYBOARD,                 // It's a keyboard input method.
      GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT,      // It supports Metro mode.
      GUID_TFCAT_TIPCAP_SYSTRAYSUPPORT,        // It supports Win8 systray.
  };
  
  }  // namespace
  
  // Register this module as a COM server.
  // This function creates two registry entries for this module:
  //   * "HKCR\CLSID\{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}", and;
  //   * "HKCR\CLSID\{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}\InProcServer32".
  // Also, this function Writes:
  //   * The description of this module;
  //   * The path to this module, and;
  //   * The type of this module (threading moddel).
  HRESULT TsfRegistrar::RegisterCOMServer(const wchar_t *path, DWORD length) {
    if (length == 0) {
      return E_OUTOFMEMORY;
    }
    // Open the registry key "HKEY_CLASSES_ROOT\CLSID", which stores information
    // of all COM modules installed in this PC.
    // To register a COM module to Windows, we should create a key of its GUID
    // and fill the information below:
    //  * The description of this module (optional);
    //  * The ProgID of this module (optional);
    //  * The absolute path to this module;
    //  * The threading model of this module.
    ATL::CRegKey parent;
    LONG result = parent.Open(HKEY_CLASSES_ROOT, &kTipInfoKeyPrefix[0],
                              KEY_READ | KEY_WRITE);
    if (result != ERROR_SUCCESS) {
      return HRESULT_FROM_WIN32(result);
    }
  
    // Create a sub-key for this COM module and set its description.
    // These operations are allowed only for administrators.
    wchar_t ime_key[64] = {};
    if (!::StringFromGUID2(TsfProfile::GetTextServiceGuid(), &ime_key[0],
                           arraysize(ime_key))) {
      return E_OUTOFMEMORY;
    }
  
    ATL::CRegKey key;
    result = key.Create(parent, &ime_key[0], REG_NONE, REG_OPTION_NON_VOLATILE,
                        KEY_READ | KEY_WRITE, nullptr, 0);
    if (result != ERROR_SUCCESS) {
      return HRESULT_FROM_WIN32(result);
    }
  
    std::wstring description;
    mozc::Util::UTF8ToWide(mozc::kProductNameInEnglish, &description);
  
    result = key.SetStringValue(nullptr, description.c_str(), REG_SZ);
    if (result != ERROR_SUCCESS) {
      return HRESULT_FROM_WIN32(result);
    }
  
    // Write the absolute path to this module and its threading model.
    // Windows use these values to load this module and set it up.
    result = key.SetKeyValue(&kTipInProcServer32[0], &path[0], nullptr);
    if (result != ERROR_SUCCESS) {
      return HRESULT_FROM_WIN32(result);
    }
  
    result = key.SetKeyValue(&kTipInProcServer32[0], &kTipTextServiceModel[0],
                             &kTipThreadingModel[0]);
    if (result != ERROR_SUCCESS) {
      return HRESULT_FROM_WIN32(result);
    }
  
    return HRESULT_FROM_WIN32(key.Close());
  }
  
  // Unregisters this module from Windows.
  // This function just deletes the all registry keys under
  // "HKCR\CLSID\{xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}".
  void TsfRegistrar::UnregisterCOMServer() {
    // Create the registry key for this COM server.
    // Only Administrators can create keys under HKEY_CLASSES_ROOT.
    wchar_t ime_key[64] = {};
    if (!::StringFromGUID2(TsfProfile::GetTextServiceGuid(), &ime_key[0],
                           arraysize(ime_key))) {
      return;
    }
  
    ATL::CRegKey key;
    HRESULT result =
        key.Open(HKEY_CLASSES_ROOT, &kTipInfoKeyPrefix[0], KEY_READ | KEY_WRITE);
    if (result != ERROR_SUCCESS) {
      return;
    }
  
    key.RecurseDeleteKey(&ime_key[0]);
    key.Close();
  }
  
  // Register this COM server to the profile store for input processors.
  // After completing this operation, Windows can treat this module as a
  // text-input service.
  // To see the list of registers input processors:
  //  1. Open the "Control Panel";
  //  2. Select "Date, Time, Language and Regional Options";
  //  3. Select "Language and Regional Options";
  //  4. Click the "Languages" tab;
  //  5. Click "Details" in the "Text services and input languages" frame, and;
  //  6. All installed prossors are enumerated in the "Installed services"
  //     frame.
  HRESULT TsfRegistrar::RegisterProfiles(const wchar_t *path, DWORD path_length) {
    // Retrieve the profile store for input processors.
    // If you might want to create the manager object w/o calling the pair of
    // CoInitialize/CoUninitialize, there is a helper function to retrieve the
    // object.
    // http://msdn.microsoft.com/en-us/library/ms629059.aspx
    ATL::CComPtr<ITfInputProcessorProfiles> profiles;
    HRESULT result = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
    if (result != S_OK) {
      return result;
    }
  
    // Register this COM server as an input processor, and add this module as an
    // input processor for the language |kTextServiceLanguage|.
    result = profiles->Register(TsfProfile::GetTextServiceGuid());
    if (result == S_OK) {
      // We use English name here as culture-invariant description.
      // Localized name is specified later by SetLanguageProfileDisplayName.
      std::wstring description;
      mozc::Util::UTF8ToWide(mozc::kProductNameInEnglish, &description);
  
      result = profiles->AddLanguageProfile(
          TsfProfile::GetTextServiceGuid(), TsfProfile::GetLangId(),
          TsfProfile::GetProfileGuid(), description.c_str(), description.size(),
          path, path_length, TsfProfile::GetIconIndex());
  
      HRESULT set_display_name_result = S_OK;
      ATL::CComPtr<ITfInputProcessorProfilesEx> profiles_ex;
      set_display_name_result = profiles.QueryInterface(&profiles_ex);
      if (SUCCEEDED(set_display_name_result) && profiles_ex != nullptr) {
        // Unfortunately, the document of SetLanguageProfileDisplayName is very
        // poor but we can guess that the mechanism of MUI is similar to that of
        // IMM32. IMM32 uses registry value "Layout Text" and
        // "Layout Display Name", where the content of "Layout Display Name" is
        // used by SHLoadIndirectString to display appropriate string based on
        // the current UI language.
        // This mechanism is called "Registry String Redirection".
        //   http://msdn.microsoft.com/en-us/library/dd374120.aspx
        // You can find similar string based on "Registry String Redirection"
        // used by the TSF:
        //   HKLM\SOFTWARE\Microsoft\CTF\TIP\<TextService CLSID>\LanguageProfile
        //       \<LangID>\<Profile GUID>\Display Description
        // Therefore, it is considered that arguments "pchFile" and "pchFile" of
        // the SetLanguageProfileDisplayName is resource file name and string
        // resource id, respectively.
  
        // You should use a new resource ID when you need to update the MUI text
        // because SetLanguageProfileDisplayName does not support version
        // modifiers.  See b/2994558 and the following article for details.
        // http://msdn.microsoft.com/en-us/library/bb759919.aspx
        set_display_name_result = profiles_ex->SetLanguageProfileDisplayName(
            TsfProfile::GetTextServiceGuid(), TsfProfile::GetLangId(),
            TsfProfile::GetProfileGuid(), path, path_length,
            TsfProfile::GetDescriptionTextIndex());
        if (FAILED(set_display_name_result)) {
          LOG(ERROR) << "SetLanguageProfileDisplayName failed."
                     << " hr = " << set_display_name_result;
        }
      }
    }
  
    return result;
  }
  
  // Unregister this COM server from the text-service framework.
  void TsfRegistrar::UnregisterProfiles() {
    // If you might want to create the manager object w/o calling the pair of
    // CoInitialize/CoUninitialize, there is a helper function to retrieve the
    // object.
    // http://msdn.microsoft.com/en-us/library/ms629059.aspx
    ATL::CComPtr<ITfInputProcessorProfiles> profiles;
    HRESULT result = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
    if (result == S_OK) {
      result = profiles->Unregister(TsfProfile::GetTextServiceGuid());
    }
  }
  
  // Retrieves the category manager for text input processors, and
  // registers this module as a keyboard and a display attribute provider.
  HRESULT TsfRegistrar::RegisterCategories() {
    // If you might want to create the manager object w/o calling the pair of
    // CoInitialize/CoUninitialize, there is a helper function to retrieve the
    // object.
    // http://msdn.microsoft.com/en-us/library/aa383439.aspx
    ATL::CComPtr<ITfCategoryMgr> category;
    HRESULT result = category.CoCreateInstance(CLSID_TF_CategoryMgr);
    if (result == S_OK) {
      for (int i = 0; i < arraysize(kCategories); ++i) {
        result = category->RegisterCategory(TsfProfile::GetTextServiceGuid(),
                                            kCategories[i],
                                            TsfProfile::GetTextServiceGuid());
      }
    }
  
    return result;
  }
  
  // Retrieves the category manager for text input processors, and
  // unregisters this keyboard module.
  void TsfRegistrar::UnregisterCategories() {
    // If you might want to create the manager object w/o calling the pair of
    // CoInitialize/CoUninitialize, there is a helper function to retrieve the
    // object.
    // http://msdn.microsoft.com/en-us/library/aa383439.aspx
    ATL::CComPtr<ITfCategoryMgr> category;
    HRESULT result = category.CoCreateInstance(CLSID_TF_CategoryMgr);
    if (result == S_OK) {
      for (int i = 0; i < arraysize(kCategories); ++i) {
        result = category->UnregisterCategory(TsfProfile::GetTextServiceGuid(),
                                              kCategories[i],
                                              TsfProfile::GetTextServiceGuid());
      }
    }
  }
  
  HRESULT TsfRegistrar::GetProfileEnabled(BOOL *enabled) {
    BOOL dummy_bool = FALSE;
    if (enabled == nullptr) {
      enabled = &dummy_bool;
    }
    *enabled = FALSE;
  
    const int num_profiles =
        ::EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, nullptr, 0);
    unique_ptr<LAYOUTORTIPPROFILE[]> profiles(
        new LAYOUTORTIPPROFILE[num_profiles]);
    const int num_copied = ::EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr,
                                                    profiles.get(), num_profiles);
  
    for (size_t i = 0; i < num_copied; ++i) {
      if ((profiles[i].dwProfileType == LOTP_INPUTPROCESSOR) &&
          ::IsEqualGUID(profiles[i].clsid, TsfProfile::GetTextServiceGuid()) &&
          (profiles[i].langid == TsfProfile::GetLangId()) &&
          ::IsEqualGUID(profiles[i].guidProfile, TsfProfile::GetProfileGuid())) {
        // Found.
        *enabled = TRUE;
        return S_OK;
      }
    }
  
    // Not Found.
    *enabled = FALSE;
    return S_OK;
  }
  
  HRESULT TsfRegistrar::SetProfileEnabled(BOOL enable) {
    BOOL is_enabled = FALSE;
    HRESULT result = GetProfileEnabled(&is_enabled);
    if (FAILED(result)) {
      return result;
    }
  
    if ((is_enabled == FALSE) && (enable == FALSE)) {
      // Already disabled.  Nothing to do here.
      return S_OK;
    }
  
    // On Windows Vista or Windows 7 x64 editions,
    // ITfInputProcessorProfileMgr::DeactivateProfile seems to be more reliable
    // as opposed to ITfInputProcessorProfiles::EnableLanguageProfile, which
    // sometimes fails on those environment for some reason.  See b/3002349.
    if (enable == FALSE) {
      ATL::CComPtr<ITfInputProcessorProfileMgr> profile_mgr;
      result = profile_mgr.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
      if (SUCCEEDED(result)) {
        result = profile_mgr->DeactivateProfile(
            TF_PROFILETYPE_INPUTPROCESSOR, TsfProfile::GetLangId(),
            TsfProfile::GetTextServiceGuid(), TsfProfile::GetProfileGuid(),
            nullptr, TF_IPPMF_FORSESSION | TF_IPPMF_DISABLEPROFILE);
        if (SUCCEEDED(result)) {
          return result;
        }
      }
    }
  
    // Retrieve the profile store for input processors.
    // If you might want to create the manager object w/o calling the pair of
    // CoInitialize/CoUninitialize, there is a helper function to retrieve the
    // object.
    // http://msdn.microsoft.com/en-us/library/ms629059.aspx
    ATL::CComPtr<ITfInputProcessorProfiles> profiles;
    result = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
    if (FAILED(result)) {
      return result;
    }
  
    return profiles->EnableLanguageProfile(TsfProfile::GetTextServiceGuid(),
                                           TsfProfile::GetLangId(),
                                           TsfProfile::GetProfileGuid(), enable);
  }
  
  }  // namespace win32
  }  // namespace mozc
  