File: taskbar_decorator_win.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (229 lines) | stat: -rw-r--r-- 9,105 bytes parent folder | download | duplicates (5)
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