blob: 0154fb37a1014dc59c847d0ad8efb5850257a687 [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 "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";
// GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT
const GUID KGuidTfcatTipcapImmersiveSupport = {
0x13a016df, 0x560b, 0x46cd, {0x94, 0x7a, 0x4c, 0x3a, 0xf1, 0xe0, 0xe3, 0x5d}
};
// GUID_TFCAT_TIPCAP_SYSTRAYSUPPORT
const GUID KGuidTfcatTipcapSystraySupport = {
0x25504fb4, 0x7bab, 0x4bc1, {0x9c, 0x69, 0xcf, 0x81, 0x89, 0x0f, 0x0e, 0xf5}
};
// 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.
KGuidTfcatTipcapImmersiveSupport, // It supports Metro mode.
KGuidTfcatTipcapSystraySupport, // 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);
}
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.
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