Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / app_indicator_icon.cc
blob7ef1c4087680f67ad14768846e9ff816480741bd
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 base::WriteFile(new_file_path,
148 png_data->front_as<char>(), png_data->size());
150 if (bytes_written != static_cast<int>(png_data->size()))
151 return base::FilePath();
152 return new_file_path;
155 void DeleteTempImagePath(const base::FilePath& icon_file_path) {
156 if (icon_file_path.empty())
157 return;
158 base::DeleteFile(icon_file_path, true);
161 } // namespace
163 namespace libgtk2ui {
165 AppIndicatorIcon::AppIndicatorIcon(std::string id,
166 const gfx::ImageSkia& image,
167 const base::string16& tool_tip)
168 : id_(id),
169 icon_(NULL),
170 gtk_menu_(NULL),
171 menu_model_(NULL),
172 icon_change_count_(0),
173 block_activation_(false),
174 weak_factory_(this) {
175 EnsureMethodsLoaded();
176 tool_tip_ = base::UTF16ToUTF8(tool_tip);
177 SetImage(image);
179 AppIndicatorIcon::~AppIndicatorIcon() {
180 if (icon_) {
181 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
182 if (gtk_menu_)
183 DestroyMenu();
184 g_object_unref(icon_);
185 content::BrowserThread::GetBlockingPool()->PostTask(
186 FROM_HERE,
187 base::Bind(&DeleteTempImagePath, icon_file_path_.DirName()));
191 // static
192 bool AppIndicatorIcon::CouldOpen() {
193 EnsureMethodsLoaded();
194 return g_opened;
197 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
198 if (!g_opened)
199 return;
201 ++icon_change_count_;
203 // We create a deep copy of the image since it may have been freed by the time
204 // it's accessed in the other thread.
205 scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
206 base::PostTaskAndReplyWithResult(
207 content::BrowserThread::GetBlockingPool()
208 ->GetTaskRunnerWithShutdownBehavior(
209 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
210 FROM_HERE,
211 base::Bind(&CreateTempImageFile,
212 safe_image.release(),
213 icon_change_count_,
214 id_),
215 base::Bind(&AppIndicatorIcon::SetImageFromFile,
216 weak_factory_.GetWeakPtr()));
219 void AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) {
220 // Ignore pressed images, since the standard on Linux is to not highlight
221 // pressed status icons.
224 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
225 DCHECK(!tool_tip_.empty());
226 tool_tip_ = base::UTF16ToUTF8(tool_tip);
228 // We can set the click action label only if the icon exists. Also we only
229 // need to update the label if it is shown and it's only shown if we are sure
230 // that there is a click action or if there is no menu.
231 if (icon_ && (delegate()->HasClickAction() || menu_model_ == NULL)) {
232 GList* children = gtk_container_get_children(GTK_CONTAINER(gtk_menu_));
233 for (GList* child = children; child; child = g_list_next(child))
234 if (g_object_get_data(G_OBJECT(child->data), "click-action-item") !=
235 NULL) {
236 gtk_menu_item_set_label(GTK_MENU_ITEM(child->data),
237 tool_tip_.c_str());
238 break;
240 g_list_free(children);
244 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
245 if (!g_opened)
246 return;
248 if (gtk_menu_) {
249 DestroyMenu();
251 menu_model_ = model;
253 // The icon is created asynchronously so it might not exist when the menu is
254 // set.
255 if (icon_)
256 SetMenu();
259 void AppIndicatorIcon::RefreshPlatformContextMenu() {
260 gtk_container_foreach(
261 GTK_CONTAINER(gtk_menu_), SetMenuItemInfo, &block_activation_);
264 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
265 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
266 if (icon_file_path.empty())
267 return;
269 base::FilePath old_path = icon_file_path_;
270 icon_file_path_ = icon_file_path;
272 std::string icon_name =
273 icon_file_path_.BaseName().RemoveExtension().value();
274 std::string icon_dir = icon_file_path_.DirName().value();
275 if (!icon_) {
276 icon_ =
277 app_indicator_new_with_path(id_.c_str(),
278 icon_name.c_str(),
279 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
280 icon_dir.c_str());
281 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
282 SetMenu();
283 } else {
284 // Currently we are creating a new temp directory every time the icon is
285 // set. So we need to set the directory each time.
286 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
287 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
289 // Delete previous icon directory.
290 content::BrowserThread::GetBlockingPool()->PostTask(
291 FROM_HERE,
292 base::Bind(&DeleteTempImagePath, old_path.DirName()));
296 void AppIndicatorIcon::SetMenu() {
297 gtk_menu_ = gtk_menu_new();
299 if (delegate()->HasClickAction() || menu_model_ == NULL) {
300 CreateClickActionReplacement();
301 if (menu_model_) {
302 // Add separator before the other menu items.
303 GtkWidget* menu_item = gtk_separator_menu_item_new();
304 gtk_widget_show(menu_item);
305 gtk_menu_shell_append(GTK_MENU_SHELL(gtk_menu_), menu_item);
308 if (menu_model_) {
309 BuildSubmenuFromModel(menu_model_,
310 gtk_menu_,
311 G_CALLBACK(OnMenuItemActivatedThunk),
312 &block_activation_,
313 this);
314 RefreshPlatformContextMenu();
316 app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
319 void AppIndicatorIcon::CreateClickActionReplacement() {
320 DCHECK(!tool_tip_.empty());
322 // Add "click replacement menu item".
323 GtkWidget* menu_item = gtk_menu_item_new_with_mnemonic(tool_tip_.c_str());
324 g_object_set_data(
325 G_OBJECT(menu_item), "click-action-item", GINT_TO_POINTER(1));
326 g_signal_connect(menu_item, "activate", G_CALLBACK(OnClickThunk), this);
327 gtk_widget_show(menu_item);
328 gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
331 void AppIndicatorIcon::DestroyMenu() {
332 gtk_widget_destroy(gtk_menu_);
333 gtk_menu_ = NULL;
334 menu_model_ = NULL;
337 void AppIndicatorIcon::OnClick(GtkWidget* menu_item) {
338 if (delegate())
339 delegate()->OnClick();
342 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget* menu_item) {
343 if (block_activation_)
344 return;
346 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
347 if (!model) {
348 // There won't be a model for "native" submenus like the "Input Methods"
349 // context menu. We don't need to handle activation messages for submenus
350 // anyway, so we can just return here.
351 DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
352 return;
355 // The activate signal is sent to radio items as they get deselected;
356 // ignore it in this case.
357 if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
358 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
359 return;
362 int id;
363 if (!GetMenuItemID(menu_item, &id))
364 return;
366 // The menu item can still be activated by hotkeys even if it is disabled.
367 if (menu_model_->IsEnabledAt(id))
368 ExecuteCommand(model, id);
371 } // namespace libgtk2ui