blob: 0b7786d684847d0f19858ffcaf099a5862eba3d7 [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/uninstall_helper.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlcom.h>
#include <msctf.h>
#include <strsafe.h>
#include <objbase.h>
#include <iomanip>
#include <map>
#include <memory>
#include <sstream>
#include "base/logging.h"
#include "base/scoped_handle.h"
#include "base/system_util.h"
#include "base/win_util.h"
#include "win32/base/imm_registrar.h"
#include "win32/base/imm_util.h"
#include "win32/base/immdev.h"
#include "win32/base/tsf_profile.h"
namespace mozc {
namespace win32 {
using ATL::CComPtr;
using ATL::CRegKey;
using std::unique_ptr;
namespace {
typedef map<int, DWORD> PreloadOrderToKLIDMap;
// Windows NT 6.0, 6.1 and 6.2
const CLSID CLSID_IMJPTIP = {
0x03b5835f, 0xf03c, 0x411b, {0x9c, 0xe2, 0xaa, 0x23, 0xe1, 0x17, 0x1e, 0x36}
};
const GUID GUID_IMJPTIP = {
0xa76c93d9, 0x5523, 0x4e90, {0xaa, 0xfa, 0x4d, 0xb1, 0x12, 0xf9, 0xac, 0x76}
};
const LANGID kLANGJaJP = MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);
const wchar_t kRegKeyboardLayouts[] =
L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts";
const wchar_t kPreloadKeyName[] = L"Keyboard Layout\\Preload";
// 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;
// Converts an unsigned integer to a wide string.
wstring utow(unsigned int i) {
wstringstream ss;
ss << i;
return ss.str();
}
wstring GetIMEFileNameFromKeyboardLayout(
const CRegKey &key, const KeyboardLayoutID &klid) {
CRegKey subkey;
LONG result = subkey.Open(key, klid.ToString().c_str(), KEY_READ);
if (ERROR_SUCCESS != result) {
return L"";
}
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 <= 1)) {
return L"";
}
const ULONG filename_length = (filename_length_including_null - 1);
const wstring filename(filename_buffer);
// Note that |filename_length| does not contain NUL character.
DCHECK_EQ(filename_length, filename.size());
return filename;
}
bool GenerateKeyboardLayoutList(vector<KeyboardLayoutInfo> *keyboard_layouts) {
if (keyboard_layouts == nullptr) {
return false;
}
keyboard_layouts->clear();
CRegKey key;
LONG result = key.Open(
HKEY_LOCAL_MACHINE, kRegKeyboardLayouts, KEY_READ);
if (ERROR_SUCCESS != result) {
return false;
}
wchar_t value_name[kMaxValueNameLength];
for (DWORD enum_reg_index = 0;; ++enum_reg_index) {
DWORD value_name_length = kMaxValueNameLength;
result = key.EnumKey(
enum_reg_index,
value_name,
&value_name_length);
if (ERROR_NO_MORE_ITEMS == result) {
return true;
}
if (ERROR_SUCCESS != result) {
return true;
}
// 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;
}
KeyboardLayoutInfo info;
info.klid = klid.id();
info.ime_filename = GetIMEFileNameFromKeyboardLayout(key, klid);
keyboard_layouts->push_back(info);
}
return true;
}
bool GenerateKeyboardLayoutMap(map<DWORD, wstring> *keyboard_layouts) {
if (keyboard_layouts == nullptr) {
return false;
}
vector<KeyboardLayoutInfo> keyboard_layout_list;
if (!GenerateKeyboardLayoutList(&keyboard_layout_list)) {
return false;
}
for (size_t i = 0; i < keyboard_layout_list.size(); ++i) {
const KeyboardLayoutInfo &layout = keyboard_layout_list[i];
(*keyboard_layouts)[layout.klid] = layout.ime_filename;
}
return true;
}
wstring GetIMEFileName(HKL hkl) {
const UINT num_chars_without_null = ::ImmGetIMEFileName(hkl, nullptr, 0);
const size_t num_chars_with_null = num_chars_without_null + 1;
unique_ptr<wchar_t[]> buffer(new wchar_t[num_chars_with_null]);
const UINT num_copied =
::ImmGetIMEFileName(hkl, buffer.get(), num_chars_with_null);
// |num_copied| does not include terminating null character.
return wstring(buffer.get(), buffer.get() + num_copied);
}
bool GetInstalledProfilesByLanguageForTSF(
LANGID langid,
vector<LayoutProfileInfo> *installed_profiles) {
ScopedCOMInitializer com_initializer;
if (FAILED(com_initializer.error_code())) {
return false;
}
HRESULT hr = S_OK;
CComPtr<ITfInputProcessorProfiles> profiles;
hr = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
if (FAILED(hr)) {
return false;
}
CComPtr<IEnumTfLanguageProfiles> enum_profiles;
hr = profiles->EnumLanguageProfiles(langid, &enum_profiles);
if (FAILED(hr)) {
return false;
}
while (true) {
ULONG num_fetched = 0;
TF_LANGUAGEPROFILE src = {0};
hr = enum_profiles->Next(1, &src, &num_fetched);
if (FAILED(hr)) {
return false;
}
if ((hr == S_FALSE) || (num_fetched != 1)) {
break;
}
if (src.catid != GUID_TFCAT_TIP_KEYBOARD) {
continue;
}
LayoutProfileInfo profile;
profile.langid = src.langid;
profile.is_default = (src.fActive != FALSE);
BOOL enabled = FALSE;
hr = profiles->IsEnabledLanguageProfile(
src.clsid, langid, src.guidProfile, &enabled);
if (SUCCEEDED(hr)) {
profile.is_enabled = (enabled != FALSE);
}
profile.clsid = src.clsid;
profile.profile_guid = src.guidProfile;
profile.is_tip = true;
installed_profiles->push_back(profile);
}
return true;
}
bool GetInstalledProfilesByLanguageForIMM32(
LANGID langid,
vector<LayoutProfileInfo> *installed_profiles) {
vector<KeyboardLayoutInfo> keyboard_layouts;
if (!GenerateKeyboardLayoutList(&keyboard_layouts)) {
DLOG(ERROR) << "GenerateKeyboardLayoutList failed.";
return false;
}
for (size_t i = 0; i < keyboard_layouts.size(); ++i) {
const KeyboardLayoutInfo &info = keyboard_layouts[i];
const LANGID info_langid = static_cast<LANGID>(info.klid & 0xffff);
if (info_langid == langid) {
LayoutProfileInfo profile;
profile.langid = langid;
profile.is_tip = false;
profile.klid = info.klid;
// TODO(yukawa): determine |profile.is_default|
// TODO(yukawa): determine |profile.is_enabled|
profile.ime_filename = info.ime_filename;
installed_profiles->push_back(profile);
}
}
return true;
}
bool GetPreloadLayoutsMain(PreloadOrderToKLIDMap *preload_map) {
if (preload_map == nullptr) {
return false;
}
// Retrieve keys under kPreloadKeyName.
CRegKey preload_key;
LONG result = preload_key.Open(HKEY_CURRENT_USER, kPreloadKeyName);
if (ERROR_SUCCESS != result) {
return false;
}
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;
result = RegEnumValue(preload_key,
i,
value_name,
&value_name_length,
nullptr, // reserved (must be nullptr)
nullptr, // type (optional)
value,
&value_length);
if (ERROR_NO_MORE_ITEMS == result) {
break;
}
if (ERROR_SUCCESS != result) {
return false;
}
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;
}
(*preload_map)[ivalue_name] = klid.id();
}
return true;
}
wstring GUIDToString(const GUID &guid) {
wchar_t buffer[256];
const int character_length_with_null =
::StringFromGUID2(guid, buffer, arraysize(buffer));
if (character_length_with_null <= 0) {
return L"";
}
const size_t character_length_without_null =
character_length_with_null - 1;
return wstring(buffer, buffer + character_length_without_null);
}
wstring LANGIDToString(LANGID langid) {
wchar_t buffer[5];
HRESULT hr = ::StringCchPrintf(buffer, arraysize(buffer), L"%04x", langid);
if (FAILED(hr)) {
return L"";
}
return buffer;
}
bool BroadcastNewIME(const KeyboardLayoutInfo &layout) {
KeyboardLayoutID klid(layout.klid);
// We cannot use const HKL because |&mozc_hkl| will be cast into PVOID.
HKL hkl = ::LoadKeyboardLayout(klid.ToString().c_str(), KLF_ACTIVATE);
// SPI_SETDEFAULTINPUTLANG ensures that new process in this session will
// use |hkl| by default but this setting is volatile even if you specified
// SPIF_UPDATEINIFILE flag.
// SPI_SETDEFAULTINPUTLANG does not work perfectly for a HKL substituted
// by a TIP on Windows XP. It works for notepad but wordpad still uses
// the previous layout. Consider to use
// ITfInputProcessorProfiles::SetDefaultLanguageProfile for TIP backed
// layout.
if (::SystemParametersInfo(
SPI_SETDEFAULTINPUTLANG, 0, &hkl, SPIF_SENDCHANGE) == FALSE) {
LOG(ERROR) << "SystemParameterInfo failed: " << GetLastError();
return false;
}
// Broadcasting WM_INPUTLANGCHANGEREQUEST so that existing process in the
// current session will change their input method to |hkl|. This mechanism
// also works against a HKL which is substituted by a TIP on Windows XP.
// Note: we have virtually the same code in imm_util.cc too.
// TODO(yukawa): Make a common function around WM_INPUTLANGCHANGEREQUEST.
DWORD recipients = BSM_APPLICATIONS;
const LONG result = ::BroadcastSystemMessage(
BSF_POSTMESSAGE,
&recipients,
WM_INPUTLANGCHANGEREQUEST,
INPUTLANGCHANGE_SYSCHARSET,
reinterpret_cast<LPARAM>(hkl));
if (result == 0) {
const int error = ::GetLastError();
LOG(ERROR) << "BroadcastSystemMessage failed. error = " << error;
return false;
}
return true;
}
bool BroadcastNewTIPOnVista(const LayoutProfileInfo &profile) {
ScopedCOMInitializer com_initializer;
if (FAILED(com_initializer.error_code())) {
return false;
}
CComPtr<ITfInputProcessorProfileMgr> profile_manager;
HRESULT hr = S_OK;
hr = profile_manager.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
if (FAILED(hr)) {
return false;
}
const DWORD activate_flags =
TF_IPPMF_FORSESSION | TF_IPPMF_ENABLEPROFILE |
TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE;
hr = profile_manager->ActivateProfile(
TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, profile.clsid,
profile.profile_guid, nullptr, activate_flags);
if (FAILED(hr)) {
return false;
}
return true;
}
bool EnableAndBroadcastNewLayout(
const LayoutProfileInfo &profile, bool broadcast_change) {
if (profile.is_tip) {
ScopedCOMInitializer com_initializer;
if (FAILED(com_initializer.error_code())) {
return false;
}
CComPtr<ITfInputProcessorProfileMgr> profile_manager;
HRESULT hr = S_OK;
hr = profile_manager.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
if (FAILED(hr)) {
return false;
}
DWORD activate_flags = TF_IPPMF_ENABLEPROFILE |
TF_IPPMF_DONTCARECURRENTINPUTLANGUAGE;
if (broadcast_change) {
activate_flags |= TF_IPPMF_FORSESSION;
}
hr = profile_manager->ActivateProfile(
TF_PROFILETYPE_INPUTPROCESSOR, profile.langid, profile.clsid,
profile.profile_guid, nullptr, activate_flags);
if (FAILED(hr)) {
DLOG(ERROR) << "ActivateProfile failed";
return false;
}
return true;
}
// |profile| is IME.
if (!broadcast_change) {
return true;
}
KeyboardLayoutInfo layout;
layout.klid = profile.klid;
layout.ime_filename = profile.ime_filename;
if (!BroadcastNewIME(layout)) {
DLOG(ERROR) << "BroadcastNewIME failed";
return false;
}
return true;
}
bool GetActiveKeyboardLayouts(vector<HKL> *keyboard_layouts) {
if (keyboard_layouts == nullptr) {
return false;
}
keyboard_layouts->clear();
const int num_keyboard_layout = ::GetKeyboardLayoutList(0, nullptr);
unique_ptr<HKL[]> buffer(new HKL[num_keyboard_layout]);
const int num_copied = ::GetKeyboardLayoutList(num_keyboard_layout,
buffer.get());
keyboard_layouts->assign(buffer.get(), buffer.get() + num_copied);
return true;
}
void EnableAndSetDefaultIfLayoutIsTIP(const KeyboardLayoutInfo &layout) {
ScopedCOMInitializer com_initializer;
if (FAILED(com_initializer.error_code())) {
return;
}
HRESULT hr = S_OK;
CComPtr<ITfInputProcessorProfiles> profiles;
hr = profiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles);
if (FAILED(hr)) {
return;
}
CComPtr<ITfInputProcessorProfileSubstituteLayout> substitute_layout;
hr = profiles.QueryInterface(&substitute_layout);
if (FAILED(hr)) {
return;
}
CComPtr<IEnumTfLanguageProfiles> enum_profiles;
const LANGID langid = static_cast<LANGID>(layout.klid);
hr = profiles->EnumLanguageProfiles(langid, &enum_profiles);
if (FAILED(hr)) {
return;
}
while (true) {
ULONG num_fetched = 0;
TF_LANGUAGEPROFILE profile = {0};
hr = enum_profiles->Next(1, &profile, &num_fetched);
if (FAILED(hr)) {
return;
}
if ((hr == S_FALSE) || (num_fetched != 1)) {
return;
}
if (profile.catid != GUID_TFCAT_TIP_KEYBOARD) {
continue;
}
HKL hkl = nullptr;
hr = substitute_layout->GetSubstituteKeyboardLayout(profile.clsid,
profile.langid,
profile.guidProfile,
&hkl);
if (FAILED(hr)) {
DLOG(ERROR) << "GetSubstituteKeyboardLayout failed";
continue;
}
if (!WinUtil::SystemEqualString(
GetIMEFileName(hkl), layout.ime_filename, true)) {
continue;
}
BOOL enabled = TRUE;
hr = profiles->EnableLanguageProfile(profile.clsid,
profile.langid,
profile.guidProfile,
TRUE);
if (FAILED(hr)) {
DLOG(ERROR) << "EnableLanguageProfile failed";
continue;
}
hr = profiles->SetDefaultLanguageProfile(profile.langid,
profile.clsid,
profile.guidProfile);
if (FAILED(hr)) {
DLOG(ERROR) << "SetDefaultLanguageProfile failed";
continue;
}
return;
}
return;
}
// This function lists all the active keyboard layouts up and unloads each
// layout based on the specified condition. If |exclude| is true, this
// function unloads any active IME if it is included in |ime_filenames|.
// If |exclude| is false, this function unloads any active IME unless it is
// included in |ime_filenames|.
void UnloadActivatedKeyboardMain(const vector<wstring> &ime_filenames,
bool exclude) {
vector<HKL> loaded_layouts;
if (!GetActiveKeyboardLayouts(&loaded_layouts)) {
return;
}
for (size_t i = 0; i < loaded_layouts.size(); ++i) {
const HKL hkl = loaded_layouts[i];
const wstring ime_filename = GetIMEFileName(hkl);
if (ime_filename.empty()) {
continue;
}
bool can_unload = !exclude;
for (size_t j = 0; j < ime_filenames.size(); ++j) {
if (WinUtil::SystemEqualString(ime_filename, ime_filenames[j], true)) {
can_unload = exclude;
break;
}
}
if (can_unload) {
::UnloadKeyboardLayout(hkl);
}
}
}
void UnloadProfilesForVista(
const vector<LayoutProfileInfo> &profiles_to_be_removed) {
vector <wstring> ime_filenames;
for (size_t i = 0; i < profiles_to_be_removed.size(); ++i) {
ime_filenames.push_back(profiles_to_be_removed[i].ime_filename);
}
UnloadActivatedKeyboardMain(ime_filenames, true);
}
bool IsEqualProfile(const LayoutProfileInfo &lhs,
const LayoutProfileInfo &rhs) {
// Check if the profile type (TIP or IME) is the same.
if (lhs.is_tip != rhs.is_tip) {
return false;
}
// Check if the target language is the same.
if (lhs.langid != rhs.langid) {
return false;
}
if (lhs.is_tip) {
// If both of them are TIP, check if they have the same CLSID and Profile
// GUID. Otherwise, they are different from each other.
DCHECK(rhs.is_tip);
if (::IsEqualCLSID(lhs.clsid, rhs.clsid) == 0) {
return false;
}
if (::IsEqualGUID(lhs.profile_guid, rhs.profile_guid) == 0) {
return false;
}
return true;
}
// If both of them are IME, check if they have the same KLID and IME file
// name (if any). Otherwise, they are different from each other.
DCHECK(!lhs.is_tip);
DCHECK(!rhs.is_tip);
if (lhs.klid != rhs.klid) {
return false;
}
if (!WinUtil::SystemEqualString(
lhs.ime_filename.c_str(), rhs.ime_filename.c_str(), true)) {
return false;
}
return true;
}
bool IsEqualPreload(const PreloadOrderToKLIDMap &current_preload_map,
const vector<KeyboardLayoutInfo> &new_preload_layouts) {
if (current_preload_map.size() != new_preload_layouts.size()) {
return false;
}
int index = 0;
for (PreloadOrderToKLIDMap::const_iterator it = current_preload_map.begin();
it != current_preload_map.end(); ++it) {
const DWORD klid = it->second;
DCHECK_GE(index, 0);
DCHECK_LT(index, new_preload_layouts.size());
if (klid != new_preload_layouts[index].klid) {
return false;
}
++index;
}
return true;
}
// Currently only keyboard layouts which have IME filename are supported.
bool RemoveHotKeyForIME(
const vector<KeyboardLayoutInfo> &layouts_to_be_removed) {
bool succeeded = true;
for (DWORD id = IME_HOTKEY_DSWITCH_FIRST; id <= IME_HOTKEY_DSWITCH_LAST;
++id) {
UINT modifiers = 0;
UINT virtual_key = 0;
HKL hkl = nullptr;
BOOL result = ::ImmGetHotKey(id, &modifiers, &virtual_key, &hkl);
if (result == FALSE) {
continue;
}
if (hkl == nullptr) {
continue;
}
const wstring ime_name = GetIMEFileName(hkl);
for (size_t i = 0; i < layouts_to_be_removed.size(); ++i) {
const KeyboardLayoutInfo &layout = layouts_to_be_removed[i];
if (layout.ime_filename.empty()) {
continue;
}
if (!WinUtil::SystemEqualString(layout.ime_filename, ime_name, true)) {
continue;
}
// ImmSetHotKey fails when both 2nd and 3rd arguments are valid while 4th
// argument is nullptr. To remove the HotKey, pass 0 to them.
result = ::ImmSetHotKey(id, 0, 0, nullptr);
if (result == FALSE) {
succeeded = false;
}
break;
}
}
return succeeded;
}
// Currently this function is Mozc-specific.
// TODO(yukawa): Generalize this function for any IME.
void RemoveHotKeyForVista(const vector<LayoutProfileInfo> &installed_profiles) {
vector<KeyboardLayoutInfo> hotkey_remove_targets;
for (size_t i = 0; i < installed_profiles.size(); ++i) {
const LayoutProfileInfo &profile = installed_profiles[i];
if (!profile.is_tip && WinUtil::SystemEqualString(
profile.ime_filename, ImmRegistrar::GetFileNameForIME(), true)) {
// This is the full IMM32 version of Google Japanese Input.
KeyboardLayoutInfo info;
info.klid = profile.klid;
info.ime_filename = profile.ime_filename;
hotkey_remove_targets.push_back(info);
continue;
}
}
if (!RemoveHotKeyForIME(hotkey_remove_targets)) {
DLOG(ERROR) << "RemoveHotKeyForIME failed.";
}
}
} // namespace
KeyboardLayoutInfo::KeyboardLayoutInfo()
: klid(0) {}
LayoutProfileInfo::LayoutProfileInfo()
: langid(0),
clsid(CLSID_NULL),
profile_guid(GUID_NULL),
klid(0),
is_default(false),
is_tip(false),
is_enabled(false) {}
// Currently this function is Mozc-specific.
// TODO(yukawa): Generalize this function for any IME and/or TIP.
bool UninstallHelper::GetNewEnabledProfileForVista(
const vector<LayoutProfileInfo> &current_profiles,
const vector<LayoutProfileInfo> &installed_profiles,
LayoutProfileInfo *current_default,
LayoutProfileInfo *new_default,
vector<LayoutProfileInfo> *removed_profiles) {
if (current_default == nullptr) {
return false;
}
// Initialize in case no entry is marked as default.
*current_default = LayoutProfileInfo();
if (new_default == nullptr) {
return false;
}
if (removed_profiles == nullptr) {
return false;
}
removed_profiles->clear();
bool default_found = false;
bool default_set = false;
for (size_t i = 0; i < current_profiles.size(); ++i) {
const LayoutProfileInfo &profile = current_profiles[i];
if (profile.is_default) {
*current_default = profile;
}
if (!profile.is_tip &&
WinUtil::SystemEqualString(
profile.ime_filename, ImmRegistrar::GetFileNameForIME(), true)) {
// This is the full IMM32 version of Google Japanese Input.
removed_profiles->push_back(profile);
continue;
}
if (profile.is_tip &&
::IsEqualCLSID(TsfProfile::GetTextServiceGuid(), profile.clsid) &&
::IsEqualGUID(TsfProfile::GetProfileGuid(), profile.profile_guid)) {
// This is the full TSF version of Google Japanese Input.
removed_profiles->push_back(profile);
continue;
}
if (!default_found && profile.is_enabled && profile.is_default) {
default_found = true;
default_set = true;
*new_default = profile;
}
if (!default_found && !default_set && profile.is_enabled) {
default_set = true;
*new_default = profile;
}
}
if (!default_set) {
// TODO(yukawa): Consider this case.
// Use MS-IME as a fallback.
new_default->langid = MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);
new_default->clsid = CLSID_IMJPTIP;
new_default->profile_guid = GUID_IMJPTIP;
new_default->klid = 0;
new_default->ime_filename.clear();
new_default->is_default = true;
new_default->is_tip = true;
new_default->is_enabled = false;
}
return true;
}
bool UninstallHelper::GetInstalledProfilesByLanguage(
LANGID langid,
vector<LayoutProfileInfo> *installed_profiles) {
if (installed_profiles == nullptr) {
return false;
}
installed_profiles->clear();
if (!GetInstalledProfilesByLanguageForTSF(langid, installed_profiles)) {
// Actually this can fail if user have explicitly unregistered TSF modules
// like b/2636769. We do not return false because it would be better to
// continue with the result of GetInstalledProfilesByLanguageForIMM32.
LOG(ERROR) << "GetInstalledProfilesByLanguageForTSF failed.";
}
if (!GetInstalledProfilesByLanguageForIMM32(langid, installed_profiles)) {
LOG(ERROR) << "GetInstalledProfilesByLanguageForIMM32 failed.";
return false;
}
return true;
}
bool UninstallHelper::GetCurrentProfilesForVista(
vector<LayoutProfileInfo> *current_profiles) {
if (current_profiles == nullptr) {
return false;
}
current_profiles->clear();
map<DWORD, wstring> keyboard_layouts;
if (!GenerateKeyboardLayoutMap(&keyboard_layouts)) {
return false;
}
{
const UINT num_element = ::EnumEnabledLayoutOrTip(
nullptr, nullptr, nullptr, nullptr, 0);
unique_ptr<LAYOUTORTIPPROFILE[]> buffer(
new LAYOUTORTIPPROFILE[num_element]);
const UINT num_copied = ::EnumEnabledLayoutOrTip(
nullptr, nullptr, nullptr, buffer.get(), num_element);
for (size_t i = 0; i < num_copied; ++i) {
const LAYOUTORTIPPROFILE &src = buffer[i];
if (src.catid != GUID_TFCAT_TIP_KEYBOARD) {
continue;
}
LayoutProfileInfo profile;
profile.langid = src.langid;
profile.is_default = ((src.dwFlags & LOT_DEFAULT) == LOT_DEFAULT);
profile.is_enabled = !((src.dwFlags & LOT_DISABLED) == LOT_DISABLED);
profile.clsid = src.clsid;
profile.profile_guid = src.guidProfile;
if ((src.dwProfileType & LOTP_INPUTPROCESSOR) == LOTP_INPUTPROCESSOR) {
profile.is_tip = true;
current_profiles->push_back(profile);
continue;
}
if ((src.dwProfileType & LOTP_KEYBOARDLAYOUT) == LOTP_KEYBOARDLAYOUT) {
const wstring id(src.szId);
// A valid |profile.szId| should consists of language ID (LANGID) and
// keyboard layout ID (KILD) as follows.
// <LangID 1>:<KLID 1>
// "0411:E0200411"
// Check if |id.size()| is expected.
if (id.size() != 13) {
continue;
}
// Extract KLID. It should be 8-letter hexadecimal code begins at 6th
// character in the |id|, that is, |id.substr(5, 8)|.
const KeyboardLayoutID klid(id.substr(5, 8));
if (!klid.has_id()) {
continue;
}
profile.klid = klid.id();
if (keyboard_layouts.find(profile.klid) != keyboard_layouts.end()) {
profile.ime_filename = keyboard_layouts[profile.klid];
}
profile.is_tip = false;
current_profiles->push_back(profile);
continue;
}
}
} // release |buffer|
return true;
}
bool UninstallHelper::RemoveProfilesForVista(
const vector<LayoutProfileInfo> &profiles_to_be_removed) {
if (profiles_to_be_removed.size() == 0) {
// Nothing to do.
return true;
}
const wstring &profile_string = ComposeProfileStringForVista(
profiles_to_be_removed);
const BOOL result = ::InstallLayoutOrTipUserReg(
nullptr, nullptr, nullptr, profile_string.c_str(), ILOT_UNINSTALL);
return result != FALSE;
}
wstring UninstallHelper::ComposeProfileStringForVista(
const vector<LayoutProfileInfo> &profiles) {
wstringstream ss;
for (size_t i = 0; i < profiles.size(); ++i) {
const LayoutProfileInfo &info = profiles[i];
if (i != 0) {
ss << L";";
}
const wstring &langid_string = LANGIDToString(info.langid);
if (langid_string.empty()) {
continue;
}
if (info.is_tip) {
const wstring &clsid_string = GUIDToString(info.clsid);
const wstring &guid_string = GUIDToString(info.profile_guid);
if (clsid_string.empty() || guid_string.empty()) {
continue;
}
ss << langid_string << L":" << clsid_string << guid_string;
} else {
const KeyboardLayoutID klid(info.klid);
ss << langid_string << L":" << klid.ToString();
}
}
return ss.str();
}
bool UninstallHelper::SetDefaultForVista(
const LayoutProfileInfo &current_default,
const LayoutProfileInfo &new_default, bool broadcast_change) {
if (current_default.is_default && current_default.is_enabled &&
IsEqualProfile(current_default, new_default)) {
// |new_default| is alreasy default and enabled.
return true;
}
if (!EnableAndBroadcastNewLayout(new_default, broadcast_change)) {
// We do not return false here because the main task of this function is
// setting the specified profile to default.
DLOG(ERROR) << "EnableAndBroadcastNewLayout failed.";
}
vector<LayoutProfileInfo> profile_list;
profile_list.push_back(new_default);
const wstring profile_string = ComposeProfileStringForVista(profile_list);
if (profile_string.empty()) {
return false;
}
DWORD flag = 0;
if (!broadcast_change) {
// If |broadcast_change| prevent the SetDefaultLayoutOrTip API from
// disturbing the current session in case the thread is impersonated.
flag = SDLOT_NOAPPLYTOCURRENTSESSION;
}
if (!::SetDefaultLayoutOrTip(profile_string.c_str(), flag)) {
DLOG(ERROR) << "SetDefaultLayoutOrTip failed";
return false;
}
return true;
}
bool UninstallHelper::RestoreUserIMEEnvironmentForVista(bool broadcast_change) {
vector<LayoutProfileInfo> installed_profiles;
if (!GetInstalledProfilesByLanguage(kLANGJaJP, &installed_profiles)) {
return false;
}
RemoveHotKeyForVista(installed_profiles);
vector<LayoutProfileInfo> current_profiles;
if (!GetCurrentProfilesForVista(&current_profiles)) {
return false;
}
LayoutProfileInfo current_default;
LayoutProfileInfo new_default;
vector<LayoutProfileInfo> removed_profiles;
if (!GetNewEnabledProfileForVista(current_profiles, installed_profiles,
&current_default, &new_default,
&removed_profiles)) {
return false;
}
if (!SetDefaultForVista(current_default, new_default, broadcast_change)) {
DLOG(ERROR) << "SetDefaultForVista failed.";
}
if (!RemoveProfilesForVista(removed_profiles)) {
DLOG(ERROR) << "RemoveProfilesForVista failed.";
}
if (broadcast_change) {
// Unload unnecessary keyboard layouts.
UnloadProfilesForVista(removed_profiles);
}
return true;
}
bool UninstallHelper::RestoreUserIMEEnvironmentMain() {
// Basically this function is called from the "non-deferred" custom action
// with "non-elevated" user privileges, which is enough and prefarable to
// update entries under HKCU. Assuming the desktop is available,
// broadcast messages into existing processes so that these processes
// start to use the new default IME.
const bool kBroadcastNewIME = true;
return RestoreUserIMEEnvironmentForVista(kBroadcastNewIME);
}
bool UninstallHelper::EnsureIMEIsRemovedForCurrentUser(
bool disable_hkcu_cache) {
if (disable_hkcu_cache) {
// For some reason, HKCU in a deferred, non-impersonated custom action is
// occasionally mapped not to the HKU/.Default but to the active user's
// profile as if the current thread is impersonated to the active user.
// To fix up this, we should disable per-process registry cache by calling
// RegDisablePredefinedCache API.
const LRESULT result = ::RegDisablePredefinedCache();
if (result != ERROR_SUCCESS) {
DLOG(ERROR) << "IsServiceThread failed. result = " << result;
return false;
}
}
// Since this function is targeting for the service account, IME change
// notification will not be sent in case it causes unwilling side-effects
// against other important processes running in the service session.
const bool kBroadcastNewIME = false;
return RestoreUserIMEEnvironmentForVista(kBroadcastNewIME);
}
} // namespace win32
} // namespace mozc