Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / app_indicator_icon.cc
blob53b951c5bed6ed3551bbd51976463530c14bdf7a
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 <gtk/gtk.h>
8 #include <dlfcn.h>
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/memory/ref_counted_memory.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "chrome/browser/ui/libgtk2ui/menu_util.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "ui/base/models/menu_model.h"
19 #include "ui/gfx/image/image_skia.h"
21 namespace {
23 typedef enum {
24 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
25 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
26 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
27 APP_INDICATOR_CATEGORY_HARDWARE,
28 APP_INDICATOR_CATEGORY_OTHER
29 } AppIndicatorCategory;
31 typedef enum {
32 APP_INDICATOR_STATUS_PASSIVE,
33 APP_INDICATOR_STATUS_ACTIVE,
34 APP_INDICATOR_STATUS_ATTENTION
35 } AppIndicatorStatus;
37 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
38 const gchar* icon_name,
39 AppIndicatorCategory category);
41 typedef AppIndicator* (*app_indicator_new_with_path_func)(
42 const gchar* id,
43 const gchar* icon_name,
44 AppIndicatorCategory category,
45 const gchar* icon_theme_path);
47 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
48 AppIndicatorStatus status);
50 typedef void (*app_indicator_set_attention_icon_full_func)(
51 AppIndicator* self,
52 const gchar* icon_name,
53 const gchar* icon_desc);
55 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
57 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
58 const gchar* icon_name,
59 const gchar* icon_desc);
61 typedef void (*app_indicator_set_icon_theme_path_func)(
62 AppIndicator* self,
63 const gchar* icon_theme_path);
65 bool g_attempted_load = false;
66 bool g_opened = false;
68 // Retrieved functions from libappindicator.
69 app_indicator_new_func app_indicator_new = NULL;
70 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
71 app_indicator_set_status_func app_indicator_set_status = NULL;
72 app_indicator_set_attention_icon_full_func
73 app_indicator_set_attention_icon_full = NULL;
74 app_indicator_set_menu_func app_indicator_set_menu = NULL;
75 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
76 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
78 void EnsureMethodsLoaded() {
79 if (g_attempted_load)
80 return;
82 g_attempted_load = true;
84 void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
85 if (!indicator_lib) {
86 indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
88 if (!indicator_lib) {
89 indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
91 if (!indicator_lib) {
92 return;
95 g_opened = true;
97 app_indicator_new = reinterpret_cast<app_indicator_new_func>(
98 dlsym(indicator_lib, "app_indicator_new"));
100 app_indicator_new_with_path =
101 reinterpret_cast<app_indicator_new_with_path_func>(
102 dlsym(indicator_lib, "app_indicator_new_with_path"));
104 app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
105 dlsym(indicator_lib, "app_indicator_set_status"));
107 app_indicator_set_attention_icon_full =
108 reinterpret_cast<app_indicator_set_attention_icon_full_func>(
109 dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
111 app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
112 dlsym(indicator_lib, "app_indicator_set_menu"));
114 app_indicator_set_icon_full =
115 reinterpret_cast<app_indicator_set_icon_full_func>(
116 dlsym(indicator_lib, "app_indicator_set_icon_full"));
118 app_indicator_set_icon_theme_path =
119 reinterpret_cast<app_indicator_set_icon_theme_path_func>(
120 dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
123 base::FilePath CreateTempImageFile(gfx::ImageSkia* image_ptr,
124 int icon_change_count,
125 std::string id) {
126 scoped_ptr<gfx::ImageSkia> image(image_ptr);
128 scoped_refptr<base::RefCountedMemory> png_data =
129 gfx::Image(*image.get()).As1xPNGBytes();
130 if (png_data->size() == 0) {
131 // If the bitmap could not be encoded to PNG format, skip it.
132 LOG(WARNING) << "Could not encode icon";
133 return base::FilePath();
136 base::FilePath temp_dir;
137 base::FilePath new_file_path;
139 // Create a new temporary directory for each image since using a single
140 // temporary directory seems to have issues when changing icons in quick
141 // succession.
142 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir))
143 return base::FilePath();
144 new_file_path =
145 temp_dir.Append(id + base::StringPrintf("_%d.png", icon_change_count));
146 int bytes_written =
147 file_util::WriteFile(new_file_path,
148 reinterpret_cast<const char*>(png_data->front()),
149 png_data->size());
151 if (bytes_written != static_cast<int>(png_data->size()))
152 return base::FilePath();
153 return new_file_path;
156 void DeleteTempImagePath(const base::FilePath& icon_file_path) {
157 if (icon_file_path.empty())
158 return;
159 base::DeleteFile(icon_file_path, true);
162 } // namespace
164 namespace libgtk2ui {
166 AppIndicatorIcon::AppIndicatorIcon(std::string id,
167 const gfx::ImageSkia& image,
168 const base::string16& tool_tip)
169 : id_(id),
170 icon_(NULL),
171 gtk_menu_(NULL),
172 menu_model_(NULL),
173 icon_change_count_(0),
174 block_activation_(false),
175 weak_factory_(this) {
176 EnsureMethodsLoaded();
177 tool_tip_ = base::UTF16ToUTF8(tool_tip);
178 SetImage(image);
180 AppIndicatorIcon::~AppIndicatorIcon() {
181 if (icon_) {
182 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
183 if (gtk_menu_)
184 DestroyMenu();
185 g_object_unref(icon_);
186 content::BrowserThread::GetBlockingPool()->PostTask(
187 FROM_HERE,
188 base::Bind(&DeleteTempImagePath, icon_file_path_.DirName()));
192 // static
193 bool AppIndicatorIcon::CouldOpen() {
194 EnsureMethodsLoaded();
195 return g_opened;
198 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
199 if (!g_opened)
200 return;
202 ++icon_change_count_;
204 // We create a deep copy of the image since it may have been freed by the time
205 // it's accessed in the other thread.
206 scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
207 base::PostTaskAndReplyWithResult(
208 content::BrowserThread::GetBlockingPool()
209 ->GetTaskRunnerWithShutdownBehavior(
210 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
211 FROM_HERE,
212 base::Bind(&CreateTempImageFile,
213 safe_image.release(),
214 icon_change_count_,
215 id_),
216 base::Bind(&AppIndicatorIcon::SetImageFromFile,
217 weak_factory_.GetWeakPtr()));
220 void AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) {
221 // Ignore pressed images, since the standard on Linux is to not highlight
222 // pressed status icons.
225 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
226 DCHECK(!tool_tip_.empty());
227 tool_tip_ = base::UTF16ToUTF8(tool_tip);
229 // We can set the click action label only if the icon exists. Also we only
230 // need to update the label if it is shown and it's only shown if we are sure
231 // that there is a click action or if there is no menu.
232 if (icon_ && (delegate()->HasClickAction() || menu_model_ == NULL)) {
233 GList* children = gtk_container_get_children(GTK_CONTAINER(gtk_menu_));
234 for (GList* child = children; child; child = g_list_next(child))
235 if (g_object_get_data(G_OBJECT(child->data), "click-action-item") !=
236 NULL) {
237 gtk_menu_item_set_label(GTK_MENU_ITEM(child->data),
238 tool_tip_.c_str());
239 break;
241 g_list_free(children);
245 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
246 if (!g_opened)
247 return;
249 if (gtk_menu_) {
250 DestroyMenu();
252 menu_model_ = model;
254 // The icon is created asynchronously so it might not exist when the menu is
255 // set.
256 if (icon_)
257 SetMenu();
260 void AppIndicatorIcon::RefreshPlatformContextMenu() {
261 gtk_container_foreach(
262 GTK_CONTAINER(gtk_menu_), SetMenuItemInfo, &block_activation_);
265 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
266 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
267 if (icon_file_path.empty())
268 return;
270 base::FilePath old_path = icon_file_path_;
271 icon_file_path_ = icon_file_path;
273 std::string icon_name =
274 icon_file_path_.BaseName().RemoveExtension().value();
275 std::string icon_dir = icon_file_path_.DirName().value();
276 if (!icon_) {
277 icon_ =
278 app_indicator_new_with_path(id_.c_str(),
279 icon_name.c_str(),
280 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
281 icon_dir.c_str());
282 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
283 SetMenu();
284 } else {
285 // Currently we are creating a new temp directory every time the icon is
286 // set. So we need to set the directory each time.
287 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
288 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
290 // Delete previous icon directory.
291 content::BrowserThread::GetBlockingPool()->PostTask(
292 FROM_HERE,
293 base::Bind(&DeleteTempImagePath, old_path.DirName()));
297 void AppIndicatorIcon::SetMenu() {
298 gtk_menu_ = gtk_menu_new();
300 if (delegate()->HasClickAction() || menu_model_ == NULL) {
301 CreateClickActionReplacement();
302 if (menu_model_) {
303 // Add separator before the other menu items.
304 GtkWidget* menu_item = gtk_separator_menu_item_new();
305 gtk_widget_show(menu_item);
306 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_), menu_item);
309 if (menu_model_) {
310 BuildSubmenuFromModel(menu_model_,
311 gtk_menu_,
312 G_CALLBACK(OnMenuItemActivatedThunk),
313 &block_activation_,
314 this);
315 RefreshPlatformContextMenu();
317 app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
320 void AppIndicatorIcon::CreateClickActionReplacement() {
321 DCHECK(!tool_tip_.empty());
323 // Add "click replacement menu item".
324 GtkWidget* menu_item = gtk_menu_item_new_with_mnemonic(tool_tip_.c_str());
325 g_object_set_data(
326 G_OBJECT(menu_item), "click-action-item", GINT_TO_POINTER(1));
327 g_signal_connect(menu_item, "activate", G_CALLBACK(OnClickThunk), this);
328 gtk_widget_show(menu_item);
329 gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
332 void AppIndicatorIcon::DestroyMenu() {
333 gtk_widget_destroy(gtk_menu_);
334 gtk_menu_ = NULL;
335 menu_model_ = NULL;
338 void AppIndicatorIcon::OnClick(GtkWidget* menu_item) {
339 if (delegate())
340 delegate()->OnClick();
343 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget* menu_item) {
344 if (block_activation_)
345 return;
347 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
348 if (!model) {
349 // There won't be a model for "native" submenus like the "Input Methods"
350 // context menu. We don't need to handle activation messages for submenus
351 // anyway, so we can just return here.
352 DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
353 return;
356 // The activate signal is sent to radio items as they get deselected;
357 // ignore it in this case.
358 if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
359 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
360 return;
363 int id;
364 if (!GetMenuItemID(menu_item, &id))
365 return;
367 // The menu item can still be activated by hotkeys even if it is disabled.
368 if (menu_model_->IsEnabledAt(id))
369 ExecuteCommand(model, id);
372 } // namespace libgtk2ui