blob: 829ea43a8daba94fe8e2f127d91d6e90ccd99268 [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 "gui/base/win_util.h"
#ifdef OS_WIN
#define NTDDI_VERSION NTDDI_WIN7 // for JumpList.
#include <dwmapi.h>
#include <uxtheme.h>
#include <vssym32.h>
#include <windows.h>
#include <winuser.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>
#endif // OS_WIN
#include <QtCore/QFile>
#include <QtCore/QLibrary>
#include <QtCore/QList>
#include <QtCore/QPointer>
#include <QtGui/QApplication>
#include <QtGui/QDesktopWidget>
#include <QtGui/QPainter>
#include <QtGui/QPaintEngine>
#include <QtGui/QWidget>
#ifdef OS_WIN
#include "base/const.h"
#endif // OS_WIN
#include "base/logging.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/win_util.h"
#ifdef OS_WIN
#ifndef WM_DWMCOMPOSITIONCHANGED
#define WM_DWMCOMPOSITIONCHANGED 0x031E
#endif // WM_DWMCOMPOSITIONCHANGED
// DWM API
typedef HRESULT (WINAPI *FPDwmIsCompositionEnabled)
(BOOL *pfEnabled);
typedef HRESULT (WINAPI *FPDwmExtendFrameIntoClientArea)
(HWND hWnd,
const MARGINS *pMarInset);
typedef HRESULT (WINAPI *FPDwmEnableBlurBehindWindow)
(HWND hWnd,
const DWM_BLURBEHIND *pBlurBehind);
// Theme API
typedef HANDLE (WINAPI *FPOpenThemeData)
(HWND hwnd, LPCWSTR pszClassList);
typedef HRESULT (WINAPI *FPCloseThemeData)
(HANDLE hTheme);
typedef HRESULT (WINAPI *FPDrawThemeTextEx)
(HANDLE hTheme, HDC hdc, int iPartId, int iStateId,
LPCWSTR pszText, int cchText, DWORD dwTextFlags,
LPRECT pRect, const DTTOPTS *pOptions);
typedef HRESULT (WINAPI *FPGetThemeSysFont)
(HANDLE hTheme, int iFontId, LOGFONTW *plf);
#endif // OS_WIN
namespace mozc {
namespace gui {
#ifdef OS_WIN
namespace {
FPDwmIsCompositionEnabled gDwmIsCompositionEnabled = NULL;
FPDwmEnableBlurBehindWindow gDwmEnableBlurBehindWindow = NULL;
FPDwmExtendFrameIntoClientArea gDwmExtendFrameIntoClientArea = NULL;
FPOpenThemeData gOpenThemeData = NULL;
FPCloseThemeData gCloseThemeData = NULL;
FPDrawThemeTextEx gDrawThemeTextEx = NULL;
FPGetThemeSysFont gGetThemeSysFont = NULL;
class WindowNotifier : public QWidget {
public:
WindowNotifier() {
winId(); // to make a window handle
}
virtual ~WindowNotifier() {}
void AddWidget(QWidget *widget) {
widgets_.append(widget);
}
void RemoveWidget(QWidget *widget) {
widgets_.removeAll(widget);
}
bool winEvent(MSG *message, long *result);
void InstallStyleSheets(const QString &dwm_on_style,
const QString &dwm_off_style) {
dwm_on_style_ = dwm_on_style;
dwm_off_style_ = dwm_off_style;
}
static WindowNotifier *Get() {
return Singleton<WindowNotifier>::get();
}
private:
QWidgetList widgets_;
QString dwm_on_style_;
QString dwm_off_style_;
};
class DwmResolver {
public:
DwmResolver() : dwmlib_(NULL), themelib_(NULL) {
dwmlib_ = mozc::WinUtil::LoadSystemLibrary(L"dwmapi.dll");
themelib_ = mozc::WinUtil::LoadSystemLibrary(L"uxtheme.dll");
if (NULL != dwmlib_) {
gDwmIsCompositionEnabled =
reinterpret_cast<FPDwmIsCompositionEnabled>
(::GetProcAddress(dwmlib_,
"DwmIsCompositionEnabled"));
gDwmExtendFrameIntoClientArea =
reinterpret_cast<FPDwmExtendFrameIntoClientArea>
(::GetProcAddress(dwmlib_,
"DwmExtendFrameIntoClientArea"));
gDwmEnableBlurBehindWindow =
reinterpret_cast<FPDwmEnableBlurBehindWindow>
(::GetProcAddress(dwmlib_,
"DwmEnableBlurBehindWindow"));
}
if (NULL != themelib_) {
gOpenThemeData =
reinterpret_cast<FPOpenThemeData>(
::GetProcAddress(themelib_, "OpenThemeData"));
gCloseThemeData =
reinterpret_cast<FPCloseThemeData>(
::GetProcAddress(themelib_, "CloseThemeData"));
gDrawThemeTextEx =
reinterpret_cast<FPDrawThemeTextEx>(
::GetProcAddress(themelib_, "DrawThemeTextEx"));
gGetThemeSysFont =
reinterpret_cast<FPGetThemeSysFont>(
::GetProcAddress(themelib_, "GetThemeSysFont"));
}
}
bool IsAvailable() const {
return (gDwmIsCompositionEnabled != NULL &&
gDwmExtendFrameIntoClientArea != NULL &&
gDwmEnableBlurBehindWindow != NULL &&
gOpenThemeData != NULL &&
gCloseThemeData != NULL &&
gDrawThemeTextEx != NULL &&
gGetThemeSysFont != NULL);
}
static bool ResolveLibs() {
return Singleton<DwmResolver>::get()->IsAvailable();
}
private:
HMODULE dwmlib_;
HMODULE themelib_;
};
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 instanciate CLSID_ShellLink. hr = " << hr;
return NULL;
}
{
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 NULL;
}
}
{
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 NULL;
}
}
CComQIPtr<IPropertyStore> property_store(link);
if (property_store == NULL) {
DLOG(ERROR) << "QueryInterface failed.";
return NULL;
}
{
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 NULL;
}
hr = property_store->SetValue(PKEY_Title, prop_variant);
::PropVariantClear(&prop_variant);
}
if (FAILED(hr)) {
DLOG(ERROR) << "SetValue failed. hr = " << hr;
return NULL;
}
hr = property_store->Commit();
if (FAILED(hr)) {
DLOG(ERROR) << "Commit failed. hr = " << hr;
return NULL;
}
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 instanciate CLSID_EnumerableObjectCollection."
" hr = " << hr;
return false;
}
// TODO(yukawa): Investigate better way to localize strings.
const LinkInfo kLinks[] = {
// "手書き文字入力"
{"--mode=hand_writing",
"Hand Wrinting",
"\xE6\x89\x8B\xE6\x9B\xB8\xE3\x81\x8D\xE6\x96\x87\xE5\xAD\x97"
"\xE5\x85\xA5\xE5\x8A\x9B"},
// "文字パレット"
{"--mode=character_palette",
"Character Palette",
"\xE6\x96\x87\xE5\xAD\x97\xE3\x83\x91\xE3\x83\xAC\xE3\x83\x83"
"\xE3\x83\x88"},
// "辞書ツール"
{"--mode=dictionary_tool",
"Dictionary Tool",
"\xE8\xBE\x9E\xE6\x9B\xB8\xE3\x83\x84\xE3\x83\xBC\xE3\x83\xAB"},
// "単語登録"
{"--mode=word_register_dialog",
"Add Word",
"\xE5\x8D\x98\xE8\xAA\x9E\xE7\x99\xBB\xE9\x8C\xB2"},
// "プロパティ"
{"--mode=config_dialog",
"Properties",
"\xE3\x83\x97\xE3\x83\xAD\xE3\x83\x91\xE3\x83\x86\xE3\x82\xA3"},
};
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 != NULL) {
object_collection->AddObject(link);
}
}
CComQIPtr<IObjectArray> object_array(object_collection);
if (object_array == NULL) {
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 instanciate 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
bool WinUtil::IsCompositionEnabled() {
#ifdef OS_WIN
if (!DwmResolver::ResolveLibs()) {
return false;
}
HRESULT hr = S_OK;
BOOL is_enabled = false;
hr = gDwmIsCompositionEnabled(&is_enabled);
if (SUCCEEDED(hr)) {
return is_enabled;
} else {
LOG(ERROR) << "DwmIsCompositionEnabled() failed: "
<< static_cast<long>(hr);
}
#endif // OS_WIN
return false;
}
bool WinUtil::ExtendFrameIntoClientArea(QWidget *widget,
int left, int top,
int right, int bottom) {
DCHECK(widget);
#ifdef OS_WIN
if (!DwmResolver::ResolveLibs()) {
return false;
}
HRESULT hr = S_OK;
MARGINS margin = { left, top, right, bottom };
hr = gDwmExtendFrameIntoClientArea(widget->winId(), &margin);
if (SUCCEEDED(hr)) {
WindowNotifier::Get()->AddWidget(widget);
widget->setAttribute(Qt::WA_TranslucentBackground, true);
return true;
} else {
LOG(ERROR) << "DwmExtendFrameIntoClientArea() failed: "
<< static_cast<long>(hr);
}
#endif // OS_WIN
return false;
}
#ifdef OS_WIN
bool WindowNotifier::winEvent(MSG *message, long *result) {
if (message != NULL && message->message == WM_DWMCOMPOSITIONCHANGED) {
const bool composition_enabled = WinUtil::IsCompositionEnabled();
// switch styles if need be
if (!dwm_on_style_.isEmpty() && !dwm_off_style_.isEmpty()) {
if (composition_enabled) {
qApp->setStyleSheet(dwm_on_style_);
} else {
qApp->setStyleSheet(dwm_off_style_);
}
}
foreach(QWidget *widget, widgets_) {
if (widget != NULL) {
widget->setAttribute(Qt::WA_NoSystemBackground,
composition_enabled);
// TODO(taku): left/top/right/bottom are not updated.
// need to be fixed.
if (composition_enabled) {
MARGINS margin = { -1, 0, 0, 0 };
gDwmExtendFrameIntoClientArea(widget->winId(), &margin);
widget->setAttribute(Qt::WA_TranslucentBackground, true);
}
widget->update();
}
}
}
// call default
return QWidget::winEvent(message, result);
}
#endif // OS_WIN
QRect WinUtil::GetTextRect(QWidget *widget, const QString &text) {
DCHECK(widget);
const QFont font = QApplication::font(widget);
const QFontMetrics fontMetrics(font);
return fontMetrics.boundingRect(text);
}
void WinUtil::InstallStyleSheets(const QString &dwm_on_style,
const QString &dwm_off_style) {
#ifdef OS_WIN
WindowNotifier::Get()->InstallStyleSheets(dwm_on_style,
dwm_off_style);
#endif // OS_WIN
}
void WinUtil::InstallStyleSheetsFiles(const QString &dwm_on_style_file,
const QString &dwm_off_style_file) {
#ifdef OS_WIN
QFile file1(dwm_on_style_file);
file1.open(QFile::ReadOnly);
QFile file2(dwm_off_style_file);
file2.open(QFile::ReadOnly);
WinUtil::InstallStyleSheets(QLatin1String(file1.readAll()),
QLatin1String(file2.readAll()));
#endif // OS_WIN
}
void WinUtil::DrawThemeText(const QString &text,
const QRect &rect,
int glow_size,
QPainter *painter) {
DCHECK(painter);
#ifdef OS_WIN
if (!DwmResolver::ResolveLibs()) {
return;
}
HDC hdc = painter->paintEngine()->getDC();
if (NULL == hdc) {
LOG(ERROR) << "hdc is NULL";
return;
}
HANDLE theme = gOpenThemeData(qApp->desktop()->winId(), L"WINDOW");
if (theme == NULL) {
LOG(ERROR) << "::OpenThemaData() failed";
return;
}
// Set up a memory DC and bitmap that we'll draw into
HDC dc_mem = ::CreateCompatibleDC(hdc);
if (dc_mem == NULL) {
gCloseThemeData(theme);
LOG(ERROR) << "::CreateCompatibleDC() failed: " << ::GetLastError();
return;
}
BITMAPINFO dib = { 0 };
dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
dib.bmiHeader.biWidth = rect.width();
dib.bmiHeader.biHeight = -rect.height();
dib.bmiHeader.biPlanes = 1;
dib.bmiHeader.biBitCount = 32;
dib.bmiHeader.biCompression = BI_RGB;
HBITMAP bmp = ::CreateDIBSection(hdc, &dib,
DIB_RGB_COLORS, NULL, NULL, 0);
if (NULL == bmp) {
::DeleteDC(dc_mem);
gCloseThemeData(theme);
LOG(ERROR) << "::CreateDIBSection() failed: " << ::GetLastError();
return;
}
LOGFONT lf = { 0 };
if (NULL != theme) {
gGetThemeSysFont(theme, TMT_CAPTIONFONT, &lf);
} else {
NONCLIENTMETRICS ncm = { sizeof(NONCLIENTMETRICS) };
::SystemParametersInfo(SPI_GETNONCLIENTMETRICS,
sizeof(NONCLIENTMETRICS), &ncm, false);
lf = ncm.lfMessageFont;
}
HFONT caption_font = ::CreateFontIndirect(&lf);
HBITMAP old_bmp = reinterpret_cast<HBITMAP>(
::SelectObject(dc_mem,
reinterpret_cast<HGDIOBJ>(bmp)));
HFONT old_font = reinterpret_cast<HFONT>(
::SelectObject(dc_mem,
reinterpret_cast<HGDIOBJ>(caption_font)));
DTTOPTS dto = { sizeof(DTTOPTS) };
const UINT format = DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOPREFIX;
RECT rctext = { 0, 0, rect.width(), rect.height() };
dto.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;
dto.iGlowSize = glow_size;
const HRESULT hr =
gDrawThemeTextEx(theme, dc_mem, 0, 0,
reinterpret_cast<LPCWSTR>(text.utf16()),
-1, format, &rctext, &dto);
if (SUCCEEDED(hr)) {
// Copy to the painter's HDC
if (!::BitBlt(hdc, rect.left(), rect.top(), rect.width(), rect.height(),
dc_mem, 0, 0, SRCCOPY)) {
LOG(ERROR) << "::BitBlt() failed: " << ::GetLastError();
}
} else {
LOG(ERROR) << "::DrawThemeTextEx() failed: " << static_cast<long>(hr);
}
::SelectObject(dc_mem, reinterpret_cast<HGDIOBJ>(old_bmp));
::SelectObject(dc_mem, reinterpret_cast<HGDIOBJ>(old_font));
::DeleteObject(bmp);
::DeleteObject(caption_font);
::DeleteDC(dc_mem);
gCloseThemeData(theme);
#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);
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";
}
#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 NULL character.
// This is only the condition when this function
// can return |true|
if (ERROR_SUCCESS == result &&
num_chars < arraysize(data) &&
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(NULL,
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr)) {
DLOG(INFO) << "CoInitializeEx failed. hr = " << hr;
return;
}
InitializeJumpList();
::CoUninitialize();
#endif // OS_WIN
}
} // namespace gui
} // namespace mozc