Infobar material design refresh: layout
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / app_indicator_icon.cc
blob91f02727f36b427151e771742342e6ef3250f73d
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h"
7 #include <dlfcn.h>
8 #include <gtk/gtk.h>
10 #include "base/bind.h"
11 #include "base/environment.h"
12 #include "base/files/file_util.h"
13 #include "base/md5.h"
14 #include "base/memory/ref_counted_memory.h"
15 #include "base/nix/xdg_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/threading/sequenced_worker_pool.h"
19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "third_party/skia/include/core/SkCanvas.h"
23 #include "ui/base/models/menu_model.h"
24 #include "ui/gfx/codec/png_codec.h"
25 #include "ui/gfx/image/image.h"
26 #include "ui/gfx/image/image_skia.h"
28 namespace {
30 typedef enum {
31 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
32 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
33 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
34 APP_INDICATOR_CATEGORY_HARDWARE,
35 APP_INDICATOR_CATEGORY_OTHER
36 } AppIndicatorCategory;
38 typedef enum {
39 APP_INDICATOR_STATUS_PASSIVE,
40 APP_INDICATOR_STATUS_ACTIVE,
41 APP_INDICATOR_STATUS_ATTENTION
42 } AppIndicatorStatus;
44 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
45 const gchar* icon_name,
46 AppIndicatorCategory category);
48 typedef AppIndicator* (*app_indicator_new_with_path_func)(
49 const gchar* id,
50 const gchar* icon_name,
51 AppIndicatorCategory category,
52 const gchar* icon_theme_path);
54 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
55 AppIndicatorStatus status);
57 typedef void (*app_indicator_set_attention_icon_full_func)(
58 AppIndicator* self,
59 const gchar* icon_name,
60 const gchar* icon_desc);
62 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
64 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
65 const gchar* icon_name,
66 const gchar* icon_desc);
68 typedef void (*app_indicator_set_icon_theme_path_func)(
69 AppIndicator* self,
70 const gchar* icon_theme_path);
72 bool g_attempted_load = false;
73 bool g_opened = false;
75 // Retrieved functions from libappindicator.
76 app_indicator_new_func app_indicator_new = NULL;
77 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
78 app_indicator_set_status_func app_indicator_set_status = NULL;
79 app_indicator_set_attention_icon_full_func
80 app_indicator_set_attention_icon_full = NULL;
81 app_indicator_set_menu_func app_indicator_set_menu = NULL;
82 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
83 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
85 void EnsureMethodsLoaded() {
86 if (g_attempted_load)
87 return;
89 g_attempted_load = true;
91 // Only use libappindicator where it is needed to support dbus based status
92 // icons. In particular, libappindicator does not support a click action.
93 scoped_ptr<base::Environment> env(base::Environment::Create());
94 base::nix::DesktopEnvironment environment =
95 base::nix::GetDesktopEnvironment(env.get());
96 if (environment != base::nix::DESKTOP_ENVIRONMENT_KDE4 &&
97 environment != base::nix::DESKTOP_ENVIRONMENT_UNITY) {
98 return;
101 void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
102 if (!indicator_lib) {
103 indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
105 if (!indicator_lib) {
106 indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
108 if (!indicator_lib) {
109 return;
112 g_opened = true;
114 app_indicator_new = reinterpret_cast<app_indicator_new_func>(
115 dlsym(indicator_lib, "app_indicator_new"));
117 app_indicator_new_with_path =
118 reinterpret_cast<app_indicator_new_with_path_func>(
119 dlsym(indicator_lib, "app_indicator_new_with_path"));
121 app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
122 dlsym(indicator_lib, "app_indicator_set_status"));
124 app_indicator_set_attention_icon_full =
125 reinterpret_cast<app_indicator_set_attention_icon_full_func>(
126 dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
128 app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
129 dlsym(indicator_lib, "app_indicator_set_menu"));
131 app_indicator_set_icon_full =
132 reinterpret_cast<app_indicator_set_icon_full_func>(
133 dlsym(indicator_lib, "app_indicator_set_icon_full"));
135 app_indicator_set_icon_theme_path =
136 reinterpret_cast<app_indicator_set_icon_theme_path_func>(
137 dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
140 // Writes |bitmap| to a file at |path|. Returns true if successful.
141 bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) {
142 std::vector<unsigned char> png_data;
143 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data))
144 return false;
145 int bytes_written = base::WriteFile(
146 path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
147 return (bytes_written == static_cast<int>(png_data.size()));
150 void DeleteTempDirectory(const base::FilePath& dir_path) {
151 if (dir_path.empty())
152 return;
153 base::DeleteFile(dir_path, true);
156 } // namespace
158 namespace libgtk2ui {
160 AppIndicatorIcon::AppIndicatorIcon(std::string id,
161 const gfx::ImageSkia& image,
162 const base::string16& tool_tip)
163 : id_(id),
164 using_kde4_(false),
165 icon_(NULL),
166 menu_model_(NULL),
167 icon_change_count_(0),
168 weak_factory_(this) {
169 scoped_ptr<base::Environment> env(base::Environment::Create());
170 using_kde4_ = base::nix::GetDesktopEnvironment(env.get()) ==
171 base::nix::DESKTOP_ENVIRONMENT_KDE4;
173 EnsureMethodsLoaded();
174 tool_tip_ = base::UTF16ToUTF8(tool_tip);
175 SetImage(image);
177 AppIndicatorIcon::~AppIndicatorIcon() {
178 if (icon_) {
179 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
180 g_object_unref(icon_);
181 content::BrowserThread::GetBlockingPool()->PostTask(
182 FROM_HERE,
183 base::Bind(&DeleteTempDirectory, temp_dir_));
187 // static
188 bool AppIndicatorIcon::CouldOpen() {
189 EnsureMethodsLoaded();
190 return g_opened;
193 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
194 if (!g_opened)
195 return;
197 ++icon_change_count_;
199 // Copy the bitmap because it may be freed by the time it's accessed in
200 // another thread.
201 SkBitmap safe_bitmap = *image.bitmap();
203 scoped_refptr<base::TaskRunner> task_runner =
204 content::BrowserThread::GetBlockingPool()
205 ->GetTaskRunnerWithShutdownBehavior(
206 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
207 if (using_kde4_) {
208 base::PostTaskAndReplyWithResult(
209 task_runner.get(), FROM_HERE,
210 base::Bind(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
211 safe_bitmap, temp_dir_),
212 base::Bind(&AppIndicatorIcon::SetImageFromFile,
213 weak_factory_.GetWeakPtr()));
214 } else {
215 base::PostTaskAndReplyWithResult(
216 task_runner.get(), FROM_HERE,
217 base::Bind(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread,
218 safe_bitmap, icon_change_count_, id_),
219 base::Bind(&AppIndicatorIcon::SetImageFromFile,
220 weak_factory_.GetWeakPtr()));
224 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
225 DCHECK(!tool_tip_.empty());
226 tool_tip_ = base::UTF16ToUTF8(tool_tip);
227 UpdateClickActionReplacementMenuItem();
230 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
231 if (!g_opened)
232 return;
234 menu_model_ = model;
236 // The icon is created asynchronously so it might not exist when the menu is
237 // set.
238 if (icon_)
239 SetMenu();
242 void AppIndicatorIcon::RefreshPlatformContextMenu() {
243 menu_->Refresh();
246 // static
247 AppIndicatorIcon::SetImageFromFileParams
248 AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread(
249 const SkBitmap& bitmap,
250 const base::FilePath& existing_temp_dir) {
251 base::FilePath temp_dir = existing_temp_dir;
252 if (temp_dir.empty() &&
253 !base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
254 LOG(WARNING) << "Could not create temporary directory";
255 return SetImageFromFileParams();
258 base::FilePath icon_theme_path = temp_dir.AppendASCII("icons");
260 // On KDE4, an image located in a directory ending with
261 // "icons/hicolor/24x24/apps" can be used as the app indicator image because
262 // "/usr/share/icons/hicolor/24x24/apps" exists.
263 base::FilePath image_dir = icon_theme_path.AppendASCII("hicolor")
264 .AppendASCII("24x24")
265 .AppendASCII("apps");
267 if (!base::CreateDirectory(image_dir))
268 return SetImageFromFileParams();
270 // On KDE4, the name of the image file for each different looking bitmap must
271 // be unique. It must also be unique across runs of Chrome.
272 std::vector<unsigned char> bitmap_png_data;
273 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) {
274 LOG(WARNING) << "Could not encode icon";
275 return SetImageFromFileParams();
277 base::MD5Digest digest;
278 base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]),
279 bitmap_png_data.size(), &digest);
280 std::string icon_name = base::StringPrintf(
281 "chrome_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str());
283 // If |bitmap| is not 24x24, KDE does some really ugly resizing. Pad |bitmap|
284 // with transparent pixels to make it 24x24.
285 const int kDesiredSize = 24;
286 SkBitmap scaled_bitmap;
287 scaled_bitmap.allocN32Pixels(kDesiredSize, kDesiredSize);
288 scaled_bitmap.eraseARGB(0, 0, 0, 0);
289 SkCanvas canvas(scaled_bitmap);
290 canvas.drawBitmap(bitmap, (kDesiredSize - bitmap.width()) / 2,
291 (kDesiredSize - bitmap.height()) / 2);
293 base::FilePath image_path = image_dir.Append(icon_name + ".png");
294 if (!WriteFile(image_path, scaled_bitmap))
295 return SetImageFromFileParams();
297 SetImageFromFileParams params;
298 params.parent_temp_dir = temp_dir;
299 params.icon_theme_path = icon_theme_path.value();
300 params.icon_name = icon_name;
301 return params;
304 // static
305 AppIndicatorIcon::SetImageFromFileParams
306 AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap,
307 int icon_change_count,
308 const std::string& id) {
309 // Create a new temporary directory for each image on Unity since using a
310 // single temporary directory seems to have issues when changing icons in
311 // quick succession.
312 base::FilePath temp_dir;
313 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
314 LOG(WARNING) << "Could not create temporary directory";
315 return SetImageFromFileParams();
318 std::string icon_name =
319 base::StringPrintf("%s_%d", id.c_str(), icon_change_count);
320 base::FilePath image_path = temp_dir.Append(icon_name + ".png");
321 SetImageFromFileParams params;
322 if (WriteFile(image_path, bitmap)) {
323 params.parent_temp_dir = temp_dir;
324 params.icon_theme_path = temp_dir.value();
325 params.icon_name = icon_name;
327 return params;
330 void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) {
331 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
332 if (params.icon_theme_path.empty())
333 return;
335 if (!icon_) {
336 icon_ =
337 app_indicator_new_with_path(id_.c_str(),
338 params.icon_name.c_str(),
339 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
340 params.icon_theme_path.c_str());
341 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
342 SetMenu();
343 } else {
344 app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str());
345 app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon");
348 if (temp_dir_ != params.parent_temp_dir) {
349 content::BrowserThread::GetBlockingPool()->PostTask(
350 FROM_HERE,
351 base::Bind(&DeleteTempDirectory, temp_dir_));
352 temp_dir_ = params.parent_temp_dir;
356 void AppIndicatorIcon::SetMenu() {
357 menu_.reset(new AppIndicatorIconMenu(menu_model_));
358 UpdateClickActionReplacementMenuItem();
359 app_indicator_set_menu(icon_, menu_->GetGtkMenu());
362 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
363 // The menu may not have been created yet.
364 if (!menu_.get())
365 return;
367 if (!delegate()->HasClickAction() && menu_model_)
368 return;
370 DCHECK(!tool_tip_.empty());
371 menu_->UpdateClickActionReplacementMenuItem(
372 tool_tip_.c_str(),
373 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
374 base::Unretained(this)));
377 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
378 if (delegate())
379 delegate()->OnClick();
382 } // namespace libgtk2ui