Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / app_indicator_icon.cc
blobf2a30904843e2b96f4b4c1fe0af5c19a4f9fc5a2
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 "ui/base/models/menu_model.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/image/image_skia.h"
25 namespace {
27 typedef enum {
28 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
29 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
30 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
31 APP_INDICATOR_CATEGORY_HARDWARE,
32 APP_INDICATOR_CATEGORY_OTHER
33 } AppIndicatorCategory;
35 typedef enum {
36 APP_INDICATOR_STATUS_PASSIVE,
37 APP_INDICATOR_STATUS_ACTIVE,
38 APP_INDICATOR_STATUS_ATTENTION
39 } AppIndicatorStatus;
41 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
42 const gchar* icon_name,
43 AppIndicatorCategory category);
45 typedef AppIndicator* (*app_indicator_new_with_path_func)(
46 const gchar* id,
47 const gchar* icon_name,
48 AppIndicatorCategory category,
49 const gchar* icon_theme_path);
51 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
52 AppIndicatorStatus status);
54 typedef void (*app_indicator_set_attention_icon_full_func)(
55 AppIndicator* self,
56 const gchar* icon_name,
57 const gchar* icon_desc);
59 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
61 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
62 const gchar* icon_name,
63 const gchar* icon_desc);
65 typedef void (*app_indicator_set_icon_theme_path_func)(
66 AppIndicator* self,
67 const gchar* icon_theme_path);
69 bool g_attempted_load = false;
70 bool g_opened = false;
72 // Retrieved functions from libappindicator.
73 app_indicator_new_func app_indicator_new = NULL;
74 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
75 app_indicator_set_status_func app_indicator_set_status = NULL;
76 app_indicator_set_attention_icon_full_func
77 app_indicator_set_attention_icon_full = NULL;
78 app_indicator_set_menu_func app_indicator_set_menu = NULL;
79 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
80 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
82 void EnsureMethodsLoaded() {
83 if (g_attempted_load)
84 return;
86 g_attempted_load = true;
88 // Only use libappindicator where it is needed to support dbus based status
89 // icons. In particular, libappindicator does not support a click action.
90 scoped_ptr<base::Environment> env(base::Environment::Create());
91 base::nix::DesktopEnvironment environment =
92 base::nix::GetDesktopEnvironment(env.get());
93 if (environment != base::nix::DESKTOP_ENVIRONMENT_KDE4 &&
94 environment != base::nix::DESKTOP_ENVIRONMENT_UNITY) {
95 return;
98 void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
99 if (!indicator_lib) {
100 indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
102 if (!indicator_lib) {
103 indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
105 if (!indicator_lib) {
106 return;
109 g_opened = true;
111 app_indicator_new = reinterpret_cast<app_indicator_new_func>(
112 dlsym(indicator_lib, "app_indicator_new"));
114 app_indicator_new_with_path =
115 reinterpret_cast<app_indicator_new_with_path_func>(
116 dlsym(indicator_lib, "app_indicator_new_with_path"));
118 app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
119 dlsym(indicator_lib, "app_indicator_set_status"));
121 app_indicator_set_attention_icon_full =
122 reinterpret_cast<app_indicator_set_attention_icon_full_func>(
123 dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
125 app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
126 dlsym(indicator_lib, "app_indicator_set_menu"));
128 app_indicator_set_icon_full =
129 reinterpret_cast<app_indicator_set_icon_full_func>(
130 dlsym(indicator_lib, "app_indicator_set_icon_full"));
132 app_indicator_set_icon_theme_path =
133 reinterpret_cast<app_indicator_set_icon_theme_path_func>(
134 dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
137 // Returns whether a temporary directory should be created for each app
138 // indicator image.
139 bool ShouldCreateTempDirectoryPerImage(bool using_kde4) {
140 // Create a new temporary directory for each image on Unity since using a
141 // single temporary directory seems to have issues when changing icons in
142 // quick succession.
143 return !using_kde4;
146 // Returns the subdirectory of |temp_dir| in which the app indicator image
147 // should be saved.
148 base::FilePath GetImageDirectoryPath(bool using_kde4,
149 const base::FilePath& temp_dir) {
150 // On KDE4, an image located in a directory ending with
151 // "icons/hicolor/16x16/apps" can be used as the app indicator image because
152 // "/usr/share/icons/hicolor/16x16/apps" exists.
153 return using_kde4 ?
154 temp_dir.AppendASCII("icons").AppendASCII("hicolor").AppendASCII("16x16").
155 AppendASCII("apps") :
156 temp_dir;
159 std::string GetImageFileNameForKDE4(
160 const scoped_refptr<base::RefCountedMemory>& png_data) {
161 // On KDE4, the name of the image file for each different looking bitmap must
162 // be unique. It must also be unique across runs of Chrome.
163 base::MD5Digest digest;
164 base::MD5Sum(png_data->front_as<char>(), png_data->size(), &digest);
165 return base::StringPrintf("chrome_app_indicator_%s.png",
166 base::MD5DigestToBase16(digest).c_str());
169 std::string GetImageFileNameForNonKDE4(int icon_change_count,
170 const std::string& id) {
171 return base::StringPrintf("%s_%d.png", id.c_str(), icon_change_count);
174 // Returns the "icon theme path" given the file path of the app indicator image.
175 std::string GetIconThemePath(bool using_kde4,
176 const base::FilePath& image_path) {
177 return using_kde4 ?
178 image_path.DirName().DirName().DirName().DirName().value() :
179 image_path.DirName().value();
182 base::FilePath CreateTempImageFile(bool using_kde4,
183 gfx::ImageSkia* image_ptr,
184 int icon_change_count,
185 std::string id,
186 const base::FilePath& previous_file_path) {
187 scoped_ptr<gfx::ImageSkia> image(image_ptr);
189 scoped_refptr<base::RefCountedMemory> png_data =
190 gfx::Image(*image.get()).As1xPNGBytes();
191 if (png_data->size() == 0) {
192 // If the bitmap could not be encoded to PNG format, skip it.
193 LOG(WARNING) << "Could not encode icon";
194 return base::FilePath();
197 base::FilePath new_file_path;
198 if (previous_file_path.empty() ||
199 ShouldCreateTempDirectoryPerImage(using_kde4)) {
200 base::FilePath tmp_dir;
201 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &tmp_dir))
202 return base::FilePath();
203 new_file_path = GetImageDirectoryPath(using_kde4, tmp_dir);
204 if (new_file_path != tmp_dir) {
205 if (!base::CreateDirectory(new_file_path))
206 return base::FilePath();
208 } else {
209 new_file_path = previous_file_path.DirName();
212 new_file_path = new_file_path.Append(using_kde4 ?
213 GetImageFileNameForKDE4(png_data) :
214 GetImageFileNameForNonKDE4(icon_change_count, id));
216 int bytes_written =
217 base::WriteFile(new_file_path,
218 png_data->front_as<char>(), png_data->size());
220 if (bytes_written != static_cast<int>(png_data->size()))
221 return base::FilePath();
222 return new_file_path;
225 void DeleteTempDirectory(const base::FilePath& dir_path) {
226 if (dir_path.empty())
227 return;
228 base::DeleteFile(dir_path, true);
231 } // namespace
233 namespace libgtk2ui {
235 AppIndicatorIcon::AppIndicatorIcon(std::string id,
236 const gfx::ImageSkia& image,
237 const base::string16& tool_tip)
238 : id_(id),
239 using_kde4_(false),
240 icon_(NULL),
241 menu_model_(NULL),
242 icon_change_count_(0),
243 weak_factory_(this) {
244 scoped_ptr<base::Environment> env(base::Environment::Create());
245 using_kde4_ = base::nix::GetDesktopEnvironment(env.get()) ==
246 base::nix::DESKTOP_ENVIRONMENT_KDE4;
248 EnsureMethodsLoaded();
249 tool_tip_ = base::UTF16ToUTF8(tool_tip);
250 SetImage(image);
252 AppIndicatorIcon::~AppIndicatorIcon() {
253 if (icon_) {
254 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
255 g_object_unref(icon_);
256 content::BrowserThread::GetBlockingPool()->PostTask(
257 FROM_HERE,
258 base::Bind(&DeleteTempDirectory, icon_file_path_.DirName()));
262 // static
263 bool AppIndicatorIcon::CouldOpen() {
264 EnsureMethodsLoaded();
265 return g_opened;
268 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
269 if (!g_opened)
270 return;
272 ++icon_change_count_;
274 // We create a deep copy of the image since it may have been freed by the time
275 // it's accessed in the other thread.
276 scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
277 base::PostTaskAndReplyWithResult(
278 content::BrowserThread::GetBlockingPool()
279 ->GetTaskRunnerWithShutdownBehavior(
280 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
281 FROM_HERE,
282 base::Bind(&CreateTempImageFile,
283 using_kde4_,
284 safe_image.release(),
285 icon_change_count_,
286 id_,
287 icon_file_path_),
288 base::Bind(&AppIndicatorIcon::SetImageFromFile,
289 weak_factory_.GetWeakPtr()));
292 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
293 DCHECK(!tool_tip_.empty());
294 tool_tip_ = base::UTF16ToUTF8(tool_tip);
295 UpdateClickActionReplacementMenuItem();
298 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
299 if (!g_opened)
300 return;
302 menu_model_ = model;
304 // The icon is created asynchronously so it might not exist when the menu is
305 // set.
306 if (icon_)
307 SetMenu();
310 void AppIndicatorIcon::RefreshPlatformContextMenu() {
311 menu_->Refresh();
314 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
315 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
316 if (icon_file_path.empty())
317 return;
319 base::FilePath old_path = icon_file_path_;
320 icon_file_path_ = icon_file_path;
322 std::string icon_name =
323 icon_file_path_.BaseName().RemoveExtension().value();
324 std::string icon_dir = GetIconThemePath(using_kde4_, icon_file_path);
325 if (!icon_) {
326 icon_ =
327 app_indicator_new_with_path(id_.c_str(),
328 icon_name.c_str(),
329 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
330 icon_dir.c_str());
331 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
332 SetMenu();
333 } else {
334 // Currently we are creating a new temp directory every time the icon is
335 // set. So we need to set the directory each time.
336 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
337 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
339 if (ShouldCreateTempDirectoryPerImage(using_kde4_)) {
340 // Delete previous icon directory.
341 content::BrowserThread::GetBlockingPool()->PostTask(
342 FROM_HERE,
343 base::Bind(&DeleteTempDirectory, old_path.DirName()));
348 void AppIndicatorIcon::SetMenu() {
349 menu_.reset(new AppIndicatorIconMenu(menu_model_));
350 UpdateClickActionReplacementMenuItem();
351 app_indicator_set_menu(icon_, menu_->GetGtkMenu());
354 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
355 // The menu may not have been created yet.
356 if (!menu_.get())
357 return;
359 if (!delegate()->HasClickAction() && menu_model_)
360 return;
362 DCHECK(!tool_tip_.empty());
363 menu_->UpdateClickActionReplacementMenuItem(
364 tool_tip_.c_str(),
365 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
366 base::Unretained(this)));
369 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
370 if (delegate())
371 delegate()->OnClick();
374 } // namespace libgtk2ui