1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/taskbar/taskbar_decorator_win.h"
#include <objbase.h>
#include <shobjidl.h>
#include <wrl/client.h>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/win/scoped_gdi_object.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/avatar_menu.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "skia/ext/font_utils.h"
#include "skia/ext/image_operations.h"
#include "skia/ext/legacy_display_globals.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkFont.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "third_party/skia/include/core/SkStream.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/image/image.h"
#include "ui/views/win/hwnd_util.h"
namespace taskbar {
namespace {
constexpr int kOverlayIconSize = 16;
// Responsible for invoking TaskbarList::SetOverlayIcon(). The call to
// TaskbarList::SetOverlayIcon() runs a nested run loop that proves
// problematic when called on the UI thread. Additionally it seems the call may
// take a while to complete. For this reason we call it on a worker thread.
//
// Docs for TaskbarList::SetOverlayIcon() say it does nothing if the HWND is not
// valid.
void SetOverlayIcon(HWND hwnd,
std::unique_ptr<SkBitmap> bitmap,
const std::string& alt_text) {
Microsoft::WRL::ComPtr<ITaskbarList3> taskbar;
HRESULT result = ::CoCreateInstance(
CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbar));
if (FAILED(result) || FAILED(taskbar->HrInit()))
return;
base::win::ScopedGDIObject<HICON> icon;
if (bitmap) {
DCHECK_GE(bitmap.get()->width(), bitmap.get()->height());
// Maintain aspect ratio on resize, but prefer more square.
// (We used to round down here, but rounding up produces nicer results.)
const int resized_height = base::ClampCeil(
kOverlayIconSize *
(static_cast<float>(bitmap.get()->height()) / bitmap.get()->width()));
DCHECK_GE(kOverlayIconSize, resized_height);
// Since the target size is so small, we use our best resizer.
SkBitmap sk_icon = skia::ImageOperations::Resize(
*bitmap.get(), skia::ImageOperations::RESIZE_LANCZOS3, kOverlayIconSize,
resized_height);
// Paint the resized icon onto a 16x16 canvas otherwise Windows will badly
// hammer it to 16x16. We'll use a circular clip to be consistent with the
// way profile icons are rendered in the profile switcher.
SkBitmap offscreen_bitmap;
offscreen_bitmap.allocN32Pixels(kOverlayIconSize, kOverlayIconSize);
SkCanvas offscreen_canvas(offscreen_bitmap, SkSurfaceProps{});
offscreen_canvas.clear(SK_ColorTRANSPARENT);
static const SkRRect overlay_icon_clip =
SkRRect::MakeOval(SkRect::MakeWH(kOverlayIconSize, kOverlayIconSize));
offscreen_canvas.clipRRect(overlay_icon_clip, true);
// Note: the original code used kOverlayIconSize - resized_height, but in
// order to center the icon in the circle clip area, we're going to center
// it in the paintable region instead, rounding up to the closest pixel to
// avoid smearing.
const int y_offset = std::ceilf((kOverlayIconSize - resized_height) / 2.0f);
offscreen_canvas.drawImage(sk_icon.asImage(), 0, y_offset);
icon = IconUtil::CreateHICONFromSkBitmap(offscreen_bitmap);
if (!icon.is_valid())
return;
}
taskbar->SetOverlayIcon(hwnd, icon.get(), base::UTF8ToWide(alt_text).c_str());
}
void PostSetOverlayIcon(HWND hwnd,
std::unique_ptr<SkBitmap> bitmap,
const std::string& alt_text) {
base::ThreadPool::CreateCOMSTATaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})
->PostTask(FROM_HERE, base::BindOnce(&SetOverlayIcon, hwnd,
std::move(bitmap), alt_text));
}
} // namespace
void DrawTaskbarDecorationString(gfx::NativeWindow window,
const std::string& content,
const std::string& alt_text) {
HWND hwnd = views::HWNDForNativeWindow(window);
// This is the color used by the Windows 10 Badge API, for platform
// consistency.
constexpr int kBackgroundColor = SkColorSetRGB(0x26, 0x25, 0x2D);
constexpr int kForegroundColor = SK_ColorWHITE;
constexpr int kRadius = kOverlayIconSize / 2;
// The minimum gap to have between our content and the edge of the badge.
constexpr int kMinMargin = 3;
// The amount of space we have to render the icon.
constexpr int kMaxBounds = kOverlayIconSize - 2 * kMinMargin;
constexpr int kMaxTextSize = 24; // Max size for our text.
constexpr int kMinTextSize = 7; // Min size for our text.
auto badge = std::make_unique<SkBitmap>();
badge->allocN32Pixels(kOverlayIconSize, kOverlayIconSize);
SkCanvas canvas(*badge.get(),
skia::LegacyDisplayGlobals::GetSkSurfaceProps());
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(kBackgroundColor);
canvas.clear(SK_ColorTRANSPARENT);
canvas.drawCircle(kRadius, kRadius, kRadius, paint);
paint.reset();
paint.setColor(kForegroundColor);
SkFont font = skia::DefaultFont();
SkRect bounds;
int text_size = kMaxTextSize;
// Find the largest |text_size| larger than |kMinTextSize| in which
// |content| fits into our 16x16px icon, with margins.
do {
font.setSize(text_size--);
font.measureText(content.c_str(), content.size(), SkTextEncoding::kUTF8,
&bounds);
} while (text_size >= kMinTextSize &&
(bounds.width() > kMaxBounds || bounds.height() > kMaxBounds));
canvas.drawSimpleText(content.c_str(), content.size(), SkTextEncoding::kUTF8,
kRadius - bounds.width() / 2 - bounds.x(),
kRadius - bounds.height() / 2 - bounds.y(), font,
paint);
PostSetOverlayIcon(hwnd, std::move(badge), alt_text);
}
void DrawTaskbarDecoration(gfx::NativeWindow window, const gfx::Image* image) {
HWND hwnd = views::HWNDForNativeWindow(window);
// SetOverlayIcon() does nothing if the window is not visible so testing here
// avoids all the wasted effort of the image resizing.
if (!::IsWindowVisible(hwnd))
return;
// Copy the image since we're going to use it on a separate thread and
// gfx::Image isn't thread safe.
std::unique_ptr<SkBitmap> bitmap;
if (image) {
// If `image` is an old avatar, then it's guaranteed to by 2x by code in
// ProfileAttributesEntry::GetAvatarIcon().
bitmap = std::make_unique<SkBitmap>(
profiles::GetWin2xAvatarIconAsSquare(*image->ToSkBitmap()));
}
PostSetOverlayIcon(hwnd, std::move(bitmap), "");
}
void UpdateTaskbarDecoration(Profile* profile, gfx::NativeWindow window) {
if (profile->IsGuestSession() ||
// Browser process and profile manager may be null in tests.
(g_browser_process && g_browser_process->profile_manager() &&
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetNumberOfProfiles() <= 1)) {
taskbar::DrawTaskbarDecoration(window, nullptr);
return;
}
// We need to draw the taskbar decoration. Even though we have an icon on the
// window's relaunch details, we draw over it because the user may have
// pinned the badge-less Chrome shortcut which will cause Windows to ignore
// the relaunch details.
// TODO(calamity): ideally this should not be necessary but due to issues
// with the default shortcut being pinned, we add the runtime badge for
// safety. See crbug.com/313800.
gfx::Image decoration;
AvatarMenu::ImageLoadStatus status = AvatarMenu::GetImageForMenuButton(
profile->GetPath(), &decoration, kOverlayIconSize);
// If the user is using a Gaia picture and the picture is still being loaded,
// wait until the load finishes. This taskbar decoration will be triggered
// again upon the finish of the picture load.
if (status == AvatarMenu::ImageLoadStatus::LOADING ||
status == AvatarMenu::ImageLoadStatus::PROFILE_DELETED ||
status == AvatarMenu::ImageLoadStatus::BROWSER_SHUTTING_DOWN) {
return;
}
taskbar::DrawTaskbarDecoration(window, &decoration);
}
} // namespace taskbar
|