blob: 52e5d1fcbabadd2e96f0d52f5b62c12b2b354dcc [file] [log] [blame]
// Copyright 2010-2014, 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/imm_registrar.h"
#include <windows.h>
#include <WinNls32.h>
// Workaround against KB813540
#include <atlbase_mozc.h>
#include <strsafe.h>
#include <iomanip>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include "base/const.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/win_util.h"
#include "win32/base/display_name_resource.h"
#include "win32/base/imm_util.h"
#include "win32/base/immdev.h"
#include "win32/base/keyboard_layout_id.h"
namespace mozc {
namespace win32 {
namespace {
using std::unique_ptr;
const wchar_t kRegKeyboardLayouts[] =
L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts";
const wchar_t kLayoutDisplayNameKey[] = L"Layout Display Name";
const wchar_t kLayoutDisplayNamePattern[] = L"@%s,-%d";
const wchar_t kPreloadKeyName[] = L"Keyboard Layout\\Preload";
const wchar_t kPreloadTopValueName[] = L"1";
typedef map<unsigned int, DWORD> PreloadValueMap;
// Converts an unsigned integer to a wide string.
wstring utow(unsigned int i) {
wstringstream ss;
ss << i;
return ss.str();
}
wstring GetSystemRegKeyName(const KeyboardLayoutID &klid) {
return wstring(kRegKeyboardLayouts) + L"\\" + klid.ToString();
}
// Set the layout display name with the Registry String Redirection format
// to the specified keyboard layout.
// See the following documents for details.
// http://blogs.msdn.com/michkap/archive/2006/05/06/591174.aspx
// http://blogs.msdn.com/michkap/archive/2007/01/05/1387397.aspx
// http://blogs.msdn.com/michkap/archive/2007/08/25/4564548.aspx
// http://msdn.microsoft.com/en-us/library/dd374120.aspx
HRESULT SetLayoutDisplayName(const KeyboardLayoutID &klid,
const wstring &layout_display_name_resource_path,
int layout_display_name_resource_id) {
if (!klid.has_id()) {
return E_FAIL;
}
const wstring &key_name = GetSystemRegKeyName(klid);
CRegKey keybord_layout_key;
LRESULT result = keybord_layout_key.Open(
HKEY_LOCAL_MACHINE, key_name.c_str(), KEY_READ | KEY_WRITE);
if (result != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to open the registry key"
<< " result = " << result;
return HRESULT_FROM_WIN32(result);
}
wchar_t layout_name[MAX_PATH];
HRESULT hr = StringCchPrintf(layout_name, arraysize(layout_name),
kLayoutDisplayNamePattern,
layout_display_name_resource_path.c_str(),
layout_display_name_resource_id);
if (FAILED(hr)) {
LOG(ERROR) << "StringCchPrintf failed"
<< " hr = " << hr;
return hr;
}
result = keybord_layout_key.SetStringValue(&kLayoutDisplayNameKey[0],
layout_name, REG_SZ);
if (result != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set a registry value"
<< " result = " << result;
return HRESULT_FROM_WIN32(result);
}
return S_OK;
}
// ImmInstallIME has a bug in 64-bit Windows. It can't recoganize SysWOW64
// folder as a system folder, so it will refuse to install our IME. The
// solution here is to combine the 64-bit System32 folder and our filename
// to make ImmInstallIME happy.
wstring GetFullPathForSystem(const string& basename) {
string system_dir;
if (Util::WideToUTF8(SystemUtil::GetSystemDir(), &system_dir) <= 0) {
return L"";
}
const string fullpath = FileUtil::JoinPath(system_dir, basename);
wstring wfullpath;
if (Util::UTF8ToWide(fullpath.c_str(), &wfullpath) <= 0) {
return L"";
}
return wfullpath;
}
// Retrieves values under the preload key and stores the result to |keys|.
// Returns ERROR_SUCCESS if the operation completes successfully.
LONG RetrievePreloadValues(HKEY preload_key,
PreloadValueMap *keys) {
if (nullptr == keys) {
return ERROR_INVALID_PARAMETER;
}
// Registry element size limits are described in the link below.
// http://msdn.microsoft.com/en-us/library/ms724872(VS.85).aspx
const DWORD kMaxValueNameLength = 16383;
wchar_t value_name[kMaxValueNameLength];
const DWORD kMaxValueLength = 256;
BYTE value[kMaxValueLength];
for (DWORD i = 0;; ++i) {
DWORD value_name_length = kMaxValueNameLength;
DWORD value_length = kMaxValueLength;
LONG result = RegEnumValue(preload_key,
i,
value_name,
&value_name_length,
nullptr, // reserved (must be NULL)
nullptr, // type (optional)
value,
&value_length);
if (ERROR_NO_MORE_ITEMS == result) {
break;
} else if (ERROR_SUCCESS != result) {
return result;
}
const int ivalue_name = _wtoi(value_name);
const wstring wvalue(reinterpret_cast<wchar_t*>(value),
(value_length / sizeof(wchar_t)) - 1);
KeyboardLayoutID klid(wvalue);
if (!klid.has_id()) {
continue;
}
(*keys)[ivalue_name] = klid.id();
}
return ERROR_SUCCESS;
}
// Returns the index of |klid| in |preload_values|.
// This function works well on 64-bit Windows.
unsigned int GetPreloadIndex(const KeyboardLayoutID &klid,
const PreloadValueMap &preload_values) {
unsigned int index = 0;
for (PreloadValueMap::const_iterator i = preload_values.begin();
i != preload_values.end(); ++i) {
if ((*i).second == klid.id()) {
index = (*i).first;
}
}
return index;
}
wstring ToWideString(const string &str) {
wstring wide;
if (Util::UTF8ToWide(str.c_str(), &wide) <= 0) {
return L"";
}
return wide;
}
bool RemoveHotKey(HKL hkl) {
if (hkl == nullptr) {
return false;
}
bool succeeded = true;
for (DWORD id = IME_HOTKEY_DSWITCH_FIRST; id <= IME_HOTKEY_DSWITCH_LAST;
++id) {
UINT modifiers = 0;
UINT virtual_key = 0;
HKL assigned_hkl = nullptr;
BOOL result = ::ImmGetHotKey(id, &modifiers, &virtual_key, &assigned_hkl);
if (result == FALSE) {
continue;
}
if (assigned_hkl != hkl) {
continue;
}
// ImmSetHotKey fails when both 2nd and 3rd arguments are valid while 4th
// argument is NULL. To remove the HotKey, pass 0 to them.
result = ::ImmSetHotKey(id, 0, 0, nullptr);
if (result == FALSE) {
succeeded = false;
}
}
return succeeded;
}
} // anonymous namespace
HRESULT ImmRegistrar::Register(const wstring &ime_filename,
const wstring &layout_name,
const wstring &layout_display_name_resource_path,
int layout_display_name_resource_id,
HKL* hkl) {
HKL dummy_hkl = nullptr;
if (hkl == nullptr) {
hkl = &dummy_hkl;
}
// If the IME is already registered, return directly. If we install 32-bit
// and 64-bit IME side-by-side in a 64-bit Windows, the ImmInstallIME
// function should be called only once, either for the 32-bit or 64-bit
// version of the IME DLL.
{
const KeyboardLayoutID klid = GetKLIDFromFileName(ime_filename);
if (klid.has_id()) {
// already registered.
*hkl = ::LoadKeyboardLayoutW(klid.ToString().c_str(), KLF_ACTIVATE);
return S_OK;
}
} // |klid| is no longer needed.
IMEPROW dummy_ime_property = { 0 };
const wstring &fullpath(
wstring(SystemUtil::GetSystemDir()) + L"\\" + ime_filename);
// The path name of IME has hard limit. (http://b/2072809)
if (fullpath.size() + 1 > arraysize(dummy_ime_property.szName)) {
// Path name is too long. It will be truncated.
return E_FAIL;
}
// The description of IME has hard limit. (http://b/2072809)
if (layout_name.size() + 1 > arraysize(dummy_ime_property.szDescription)) {
// Description is too long. It will be truncated.
return E_FAIL;
}
// On 64-bit Windows, it would better to use native (64-bit) version of
// ImmInstallIME instead of WOW (32-bit) version. See b/2931871 for details.
const HKL installed_hkl =
::ImmInstallIME(fullpath.c_str(), layout_name.c_str());
if (!installed_hkl) {
return E_FAIL;
}
// Remove HotKey (if any), which is likely to be an unregistered HotKey
// used for the previous IME.
if (!RemoveHotKey(installed_hkl)) {
DLOG(ERROR) << "RemoveUnregisteredHotKey failed.";
// Removing the hotkey is an optional, nice-to-have task so its failure
// is not critical. Go to the next step.
}
*hkl = installed_hkl;
const KeyboardLayoutID installed_klid = GetKLIDFromFileName(ime_filename);
if (!installed_klid.has_id()) {
// ImmInstallIME returned a HKL but KLID is not found. Something wrong.
return E_FAIL;
}
// SetLayoutDisplayName is not mandatory so that we do nothing if it fails.
if (FAILED(SetLayoutDisplayName(installed_klid,
layout_display_name_resource_path,
layout_display_name_resource_id))) {
DLOG(ERROR) << "SetLayoutDisplayName failed.";
}
return S_OK;
}
// Uninstall module by deleting a registry key under kRegKeyboardLayouts.
HRESULT ImmRegistrar::Unregister(const wstring &ime_filename) {
const KeyboardLayoutID &klid = GetKLIDFromFileName(ime_filename);
if (!klid.has_id()) {
// already unregistered?
return S_OK;
}
// Ensure the target IME is unloaded.
{
const int num_keyboard_layout = ::GetKeyboardLayoutList(0, nullptr);
unique_ptr<HKL[]> keyboard_layouts(new HKL[num_keyboard_layout]);
const size_t num_copied = ::GetKeyboardLayoutList(num_keyboard_layout,
keyboard_layouts.get());
for (size_t i = 0; i < num_copied; ++i) {
const HKL hkl = keyboard_layouts[i];
if (!ImmRegistrar::IsIME(hkl, ime_filename)) {
continue;
}
::UnloadKeyboardLayout(hkl);
break;
}
}
// Remove IME registry key.
{
CRegKey keyboard_layouts;
LONG result = keyboard_layouts.Open(
HKEY_LOCAL_MACHINE, kRegKeyboardLayouts, KEY_READ | KEY_WRITE);
if (ERROR_SUCCESS != result) {
return HRESULT_FROM_WIN32(result);
}
result = keyboard_layouts.RecurseDeleteKey(klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
return HRESULT_FROM_WIN32(result);
}
}
return S_OK;
}
bool ImmRegistrar::IsIME(HKL hkl, const wstring &ime_filename) {
if (hkl == nullptr) {
return false;
}
wchar_t buf[MAX_PATH];
if (!::ImmGetIMEFileNameW(hkl, buf, MAX_PATH)) {
LOG(WARNING) << "Failed to get ime file name";
return false;
}
// TODO(yukawa): Support short filename. See b/2977730
return WinUtil::SystemEqualString(buf, ime_filename, true);
}
wstring ImmRegistrar::GetFileNameForIME() {
return ToWideString(mozc::kIMEFile);
}
KeyboardLayoutID ImmRegistrar::GetKLIDForIME() {
return GetKLIDFromFileName(GetFileNameForIME());
}
KeyboardLayoutID ImmRegistrar::GetKLIDFromFileName(
const wstring &ime_filename) {
if (ime_filename.empty()) {
return KeyboardLayoutID();
}
CRegKey keyboard_layouts;
LONG result = keyboard_layouts.Open(
HKEY_LOCAL_MACHINE, kRegKeyboardLayouts, KEY_READ);
if (ERROR_SUCCESS != result) {
return KeyboardLayoutID();
}
// Registry element size limits are described in the link below.
// http://msdn.microsoft.com/en-us/library/ms724872(VS.85).aspx
const DWORD kMaxValueNameLength = 16383;
wchar_t value_name[kMaxValueNameLength];
for (DWORD enum_reg_index = 0;; ++enum_reg_index) {
DWORD value_name_length = kMaxValueNameLength;
result = keyboard_layouts.EnumKey(
enum_reg_index,
value_name,
&value_name_length);
if (ERROR_NO_MORE_ITEMS == result) {
break;
} else if (ERROR_SUCCESS != result) {
break;
}
// Note that |value_name_length| does not contain NUL character.
const KeyboardLayoutID klid(
wstring(value_name, value_name + value_name_length));
if (!klid.has_id()) {
continue;
}
CRegKey subkey;
result = subkey.Open(keyboard_layouts, klid.ToString().c_str(), KEY_READ);
if (ERROR_SUCCESS != result) {
continue;
}
wchar_t filename_buffer[kMaxValueNameLength];
ULONG filename_length_including_null = kMaxValueNameLength;
result = subkey.QueryStringValue(
L"Ime File", filename_buffer, &filename_length_including_null);
// Note that |filename_length_including_null| contains NUL terminator.
if (ERROR_SUCCESS != result || (filename_length_including_null == 0)) {
continue;
}
const ULONG filename_length = (filename_length_including_null - 1);
// Note that |filename_length| does not contain NUL character.
const wstring target_basename(
filename_buffer, filename_buffer + filename_length);
// TODO(yukawa): Support short filename. See b/2977730
if (WinUtil::SystemEqualString(target_basename, ime_filename, true)) {
return klid;
}
}
return KeyboardLayoutID();
}
wstring ImmRegistrar::GetFullPathForIME() {
return GetFullPathForSystem(mozc::kIMEFile);
}
wstring ImmRegistrar::GetLayoutName() {
wstring layout_name;
// We use English name here as culture-invariant layout name.
if (Util::UTF8ToWide(kProductNameInEnglish, &layout_name) <= 0) {
return L"";
}
return layout_name;
}
int ImmRegistrar::GetLayoutDisplayNameResourceId() {
return IDS_IME_DISPLAYNAME;
}
// Removes a value equal to hkl from HKCU\Keyboard Layout\\Preload and decrement
// value names which are more than hkl.
HRESULT ImmRegistrar::RemoveKeyFromPreload(
const KeyboardLayoutID &klid, const KeyboardLayoutID &defaultKLID) {
// Retrieve keys under kPreloadKeyName.
CRegKey preload_key;
LONG result = preload_key.Open(HKEY_CURRENT_USER, kPreloadKeyName);
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
PreloadValueMap preload_values;
result = RetrievePreloadValues(preload_key, &preload_values);
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
const unsigned int preload_index = GetPreloadIndex(klid, preload_values);
if (0 == preload_index) {
// Not found. Already removed?
return S_OK;
}
// Write the default HKL if the deleted value was the last one.
if (preload_values.size() == 1) {
_ASSERT((*preload_values.begin()).first == preload_index);
result = preload_key.SetStringValue(kPreloadTopValueName,
defaultKLID.ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
} else {
// Remove values whose names are less than |preload_index|.
PreloadValueMap::iterator target_iter =
preload_values.find(preload_index);
preload_values.erase(preload_values.begin(), target_iter);
for (PreloadValueMap::iterator i = preload_values.begin();
i != preload_values.end();
++i) {
const wstring& value_name = utow((*i).first);
const KeyboardLayoutID target_klid((*i).second);
result = preload_key.DeleteValue(value_name.c_str());
if ((*i).first == preload_index) {
continue;
}
const wstring& new_value_name = utow((*i).first - 1);
preload_key.SetStringValue(new_value_name.c_str(),
target_klid.ToString().c_str());
}
}
return S_OK;
}
HRESULT ImmRegistrar::RestorePreload(const KeyboardLayoutID &klid) {
if (!klid.has_id()) {
return E_FAIL;
}
CRegKey preload_key;
LONG result = preload_key.Open(HKEY_CURRENT_USER, kPreloadKeyName);
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
PreloadValueMap preload_values;
result = RetrievePreloadValues(preload_key, &preload_values);
if (ERROR_SUCCESS != result ||
preload_values.find(1) == preload_values.end()) {
// The value corresponding to |hkl| or the value on the top does not exist.
return E_FAIL;
}
const unsigned int preload_index = GetPreloadIndex(klid, preload_values);
if (0 != preload_index) {
// |klid| already exists in the preload list.
// nothing to do.
return S_OK;
}
if (preload_values.size() == 0) {
result = preload_key.SetStringValue(
kPreloadTopValueName, klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
return S_OK;
}
const unsigned int new_preload_index = (preload_values.rbegin()->first + 1);
result = preload_key.SetStringValue(utow(new_preload_index).c_str(),
klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
return S_OK;
}
// NOTE: There are several ways to reorder the values other than |klid| after
// |klid| is moved to the top.
// The current implementation just swaps the order of |klid| for the top.
HRESULT ImmRegistrar::MovePreloadValueToTop(const KeyboardLayoutID &klid) {
CRegKey preload_key;
LONG result = preload_key.Open(HKEY_CURRENT_USER, kPreloadKeyName);
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
PreloadValueMap preload_values;
result = RetrievePreloadValues(preload_key, &preload_values);
if (ERROR_SUCCESS != result ||
preload_values.find(1) == preload_values.end()) {
// The value corresponding to |klid| or the value on the top does not exist.
return E_FAIL;
}
const unsigned int preload_index = GetPreloadIndex(klid, preload_values);
if (0 == preload_index) {
if (preload_values.size() == 0) {
// It is not necessary to move values since there is no preload key.
result = preload_key.SetStringValue(
kPreloadTopValueName, klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
return S_OK;
}
// Duplicate the first entry into the end of the list.
unsigned int new_preload_index = (preload_values.rbegin()->first + 1);
result = preload_key.SetStringValue(
utow(new_preload_index).c_str(),
KeyboardLayoutID(preload_values[1]).ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
// Overwrite the first entry with the target keyboard layout ID.
result = preload_key.SetStringValue(kPreloadTopValueName,
klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
// Attempt rollback when the second call fails.
preload_key.DeleteValue(utow(new_preload_index).c_str());
return E_FAIL;
}
return S_OK;
}
// It is not necessary to move values since the target hkl is already listed
// on the top.
if (1 == preload_index) {
return S_OK;
}
result = preload_key.SetStringValue(
kPreloadTopValueName,
KeyboardLayoutID(preload_values[preload_index]).ToString().c_str());
if (ERROR_SUCCESS != result) {
return E_FAIL;
}
const KeyboardLayoutID first_klid(preload_values[1]);
result = preload_key.SetStringValue(utow(preload_index).c_str(),
first_klid.ToString().c_str());
if (ERROR_SUCCESS != result) {
// Attempt rollback when the second call fails.
preload_key.SetStringValue(
kPreloadTopValueName, first_klid.ToString().c_str());
return E_FAIL;
}
return S_OK;
}
} // namespace win32
} // namespace mozc