// 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 "gui/base/win_util.h"
#ifdef OS_WIN
// clang-format off
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlstr.h>
#include <atlwin.h>
#include <knownfolders.h>
#include <objectarray.h>
#include <propkey.h>
#include <propvarutil.h>
#include <shlobj.h>
#include <shobjidl.h>
// clang-format on
#endif // OS_WIN
#ifdef OS_WIN
#include "base/const.h"
#endif // OS_WIN
#include "base/logging.h"
#include "base/system_util.h"
#include "base/util.h"
namespace mozc {
namespace gui {
#ifdef OS_WIN
namespace {
CComPtr<IShellLink> InitializeShellLinkItem(const char *argument,
const char *item_title) {
HRESULT hr = S_OK;
CComPtr<IShellLink> link;
hr = link.CoCreateInstance(CLSID_ShellLink);
if (FAILED(hr)) {
DLOG(INFO) << "Failed to instantiate CLSID_ShellLink. hr = " << hr;
return nullptr;
}
{
std::wstring mozc_tool_path_wide;
Util::UTF8ToWide(SystemUtil::GetToolPath(), &mozc_tool_path_wide);
hr = link->SetPath(mozc_tool_path_wide.c_str());
if (FAILED(hr)) {
DLOG(ERROR) << "SetPath failed. hr = " << hr;
return nullptr;
}
}
{
std::wstring argument_wide;
Util::UTF8ToWide(argument, &argument_wide);
hr = link->SetArguments(argument_wide.c_str());
if (FAILED(hr)) {
DLOG(ERROR) << "SetArguments failed. hr = " << hr;
return nullptr;
}
}
CComQIPtr<IPropertyStore> property_store(link);
if (property_store == nullptr) {
DLOG(ERROR) << "QueryInterface failed.";
return nullptr;
}
{
std::wstring item_title_wide;
Util::UTF8ToWide(item_title, &item_title_wide);
PROPVARIANT prop_variant;
hr = ::InitPropVariantFromString(item_title_wide.c_str(), &prop_variant);
if (FAILED(hr)) {
DLOG(ERROR) << "QueryInterface failed. hr = " << hr;
return nullptr;
}
hr = property_store->SetValue(PKEY_Title, prop_variant);
::PropVariantClear(&prop_variant);
}
if (FAILED(hr)) {
DLOG(ERROR) << "SetValue failed. hr = " << hr;
return nullptr;
}
hr = property_store->Commit();
if (FAILED(hr)) {
DLOG(ERROR) << "Commit failed. hr = " << hr;
return nullptr;
}
return link;
}
struct LinkInfo {
const char *argument;
const char *title_english;
const char *title_japanese;
};
bool AddTasksToList(CComPtr<ICustomDestinationList> destination_list) {
HRESULT hr = S_OK;
CComPtr<IObjectCollection> object_collection;
hr = object_collection.CoCreateInstance(CLSID_EnumerableObjectCollection);
if (FAILED(hr)) {
DLOG(INFO) << "Failed to instantiate CLSID_EnumerableObjectCollection."
" hr = "
<< hr;
return false;
}
// TODO(yukawa): Investigate better way to localize strings.
const LinkInfo kLinks[] = {
{"--mode=dictionary_tool", "Dictionary Tool", "辞書ツール"},
{"--mode=word_register_dialog", "Add Word", "単語登録"},
{"--mode=config_dialog", "Properties", "プロパティ"},
};
const LANGID kJapaneseLangId =
MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN);
const bool use_japanese_ui =
(kJapaneseLangId == ::GetUserDefaultUILanguage());
for (size_t i = 0; i < arraysize(kLinks); ++i) {
CComPtr<IShellLink> link;
if (use_japanese_ui) {
link =
InitializeShellLinkItem(kLinks[i].argument, kLinks[i].title_japanese);
} else {
link =
InitializeShellLinkItem(kLinks[i].argument, kLinks[i].title_english);
}
if (link != nullptr) {
object_collection->AddObject(link);
}
}
CComQIPtr<IObjectArray> object_array(object_collection);
if (object_array == nullptr) {
DLOG(ERROR) << "QueryInterface failed.";
return false;
}
hr = destination_list->AddUserTasks(object_array);
if (FAILED(hr)) {
DLOG(ERROR) << "AddUserTasks failed. hr = " << hr;
return false;
}
return true;
}
void InitializeJumpList() {
HRESULT hr = S_OK;
CComPtr<ICustomDestinationList> destination_list;
hr = destination_list.CoCreateInstance(CLSID_DestinationList);
if (FAILED(hr)) {
DLOG(INFO) << "Failed to instantiate CLSID_DestinationList. hr = " << hr;
return;
}
UINT min_slots = 0;
CComPtr<IObjectArray> removed_objects;
hr = destination_list->BeginList(&min_slots, IID_IObjectArray,
reinterpret_cast<void **>(&removed_objects));
if (FAILED(hr)) {
DLOG(INFO) << "BeginList failed. hr = " << hr;
return;
}
if (!AddTasksToList(destination_list)) {
return;
}
hr = destination_list->CommitList();
if (FAILED(hr)) {
DLOG(INFO) << "Commit failed. hr = " << hr;
return;
}
}
} // namespace
#endif // OS_WIN
#ifdef OS_WIN
namespace {
struct FindVisibleWindowInfo {
HWND found_window_handle;
DWORD target_process_id;
};
BOOL CALLBACK FindVisibleWindowProc(HWND hwnd, LPARAM lp) {
DWORD process_id = 0;
::GetWindowThreadProcessId(hwnd, &process_id);
FindVisibleWindowInfo *info = reinterpret_cast<FindVisibleWindowInfo *>(lp);
if (process_id != info->target_process_id) {
// continue enum
return TRUE;
}
if (::IsWindowVisible(hwnd) == FALSE) {
// continue enum
return TRUE;
}
info->found_window_handle = hwnd;
return FALSE;
}
} // namespace
#endif // OS_WIN
void WinUtil::ActivateWindow(uint32 process_id) {
#ifdef OS_WIN
FindVisibleWindowInfo info = {};
info.target_process_id = process_id;
// The target process may contain several top-level windows.
// We do not care about the invisible windows.
if (::EnumWindows(FindVisibleWindowProc, reinterpret_cast<LPARAM>(&info)) !=
0) {
LOG(ERROR) << "Could not find the exsisting window.";
}
const CWindow window(info.found_window_handle);
std::wstring window_title_wide;
{
CString buf;
window.GetWindowTextW(buf);
window_title_wide.assign(buf.GetString(), buf.GetLength());
}
string window_title_utf8;
Util::WideToUTF8(window_title_wide, &window_title_utf8);
LOG(INFO) << "A visible window found. hwnd: " << window.m_hWnd
<< ", title: " << window_title_utf8;
// SetForegroundWindow API does not automatically restore the minimized
// window. Use explicitly OpenIcon API in this case.
if (window.IsIconic()) {
if (::OpenIcon(window.m_hWnd) == FALSE) {
LOG(ERROR) << "::OpenIcon() failed.";
}
}
// SetForegroundWindow API works wll iff the caller process satisfies the
// condition described in the following document.
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms633539.aspx
// Never use AttachThreadInput API to work around this restriction.
// http://blogs.msdn.com/b/oldnewthing/archive/2008/08/01/8795860.aspx
if (::SetForegroundWindow(window.m_hWnd) == FALSE) {
LOG(ERROR) << "::SetForegroundWindow() failed.";
}
#endif // OS_WIN
}
#ifdef OS_WIN
namespace {
const wchar_t kIMEHotKeyEntryKey[] = L"Keyboard Layout\\Toggle";
const wchar_t kIMEHotKeyEntryValue[] = L"Layout Hotkey";
const wchar_t kIMEHotKeyEntryData[] = L"3";
} // namespace
#endif // OS_WIN
// static
bool WinUtil::GetIMEHotKeyDisabled() {
#ifdef OS_WIN
CRegKey key;
LONG result = key.Open(HKEY_CURRENT_USER, kIMEHotKeyEntryKey, KEY_READ);
// When the key doesn't exist, can return |false| as well.
if (ERROR_SUCCESS != result) {
return false;
}
wchar_t data[4] = {};
ULONG num_chars = arraysize(data);
result = key.QueryStringValue(kIMEHotKeyEntryValue, data, &num_chars);
// Returned |num_char| includes nullptr character.
// This is only the condition when this function
// can return |true|
if (ERROR_SUCCESS == result && num_chars < arraysize(data) &&
std::wstring(data) == kIMEHotKeyEntryData) {
return true;
}
return false;
#else // OS_WIN
return false;
#endif // OS_WIN
}
// static
bool WinUtil::SetIMEHotKeyDisabled(bool disabled) {
#ifdef OS_WIN
if (WinUtil::GetIMEHotKeyDisabled() == disabled) {
// Do not need to update this entry.
return true;
}
if (disabled) {
CRegKey key;
LONG result = key.Create(HKEY_CURRENT_USER, kIMEHotKeyEntryKey);
if (ERROR_SUCCESS != result) {
return false;
}
// set "3"
result = key.SetStringValue(kIMEHotKeyEntryValue, kIMEHotKeyEntryData);
return ERROR_SUCCESS == result;
} else {
CRegKey key;
LONG result =
key.Open(HKEY_CURRENT_USER, kIMEHotKeyEntryKey, KEY_SET_VALUE | DELETE);
if (result == ERROR_FILE_NOT_FOUND) {
return true; // default value will be used.
}
if (ERROR_SUCCESS != result) {
return false;
}
result = key.DeleteValue(kIMEHotKeyEntryValue);
return (ERROR_SUCCESS == result || ERROR_FILE_NOT_FOUND == result);
}
#endif // OS_WIN
return false;
}
void WinUtil::KeepJumpListUpToDate() {
#ifdef OS_WIN
HRESULT hr = S_OK;
hr = ::CoInitializeEx(nullptr,
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) {
DLOG(INFO) << "CoInitializeEx failed. hr = " << hr;
return;
}
InitializeJumpList();
::CoUninitialize();
#endif // OS_WIN
}
} // namespace gui
} // namespace mozc