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"
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"
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
;
32 APP_INDICATOR_STATUS_PASSIVE
,
33 APP_INDICATOR_STATUS_ACTIVE
,
34 APP_INDICATOR_STATUS_ATTENTION
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
)(
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
)(
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
)(
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() {
82 g_attempted_load
= true;
84 void* indicator_lib
= dlopen("libappindicator.so", RTLD_LAZY
);
86 indicator_lib
= dlopen("libappindicator.so.1", RTLD_LAZY
);
89 indicator_lib
= dlopen("libappindicator.so.0", RTLD_LAZY
);
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
,
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
142 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir
))
143 return base::FilePath();
145 temp_dir
.Append(id
+ base::StringPrintf("_%d.png", icon_change_count
));
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())
158 base::DeleteFile(icon_file_path
, true);
163 namespace libgtk2ui
{
165 AppIndicatorIcon::AppIndicatorIcon(std::string id
,
166 const gfx::ImageSkia
& image
,
167 const base::string16
& tool_tip
)
172 icon_change_count_(0),
173 block_activation_(false),
174 weak_factory_(this) {
175 EnsureMethodsLoaded();
176 tool_tip_
= base::UTF16ToUTF8(tool_tip
);
179 AppIndicatorIcon::~AppIndicatorIcon() {
181 app_indicator_set_status(icon_
, APP_INDICATOR_STATUS_PASSIVE
);
184 g_object_unref(icon_
);
185 content::BrowserThread::GetBlockingPool()->PostTask(
187 base::Bind(&DeleteTempImagePath
, icon_file_path_
.DirName()));
192 bool AppIndicatorIcon::CouldOpen() {
193 EnsureMethodsLoaded();
197 void AppIndicatorIcon::SetImage(const gfx::ImageSkia
& image
) {
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(),
211 base::Bind(&CreateTempImageFile
,
212 safe_image
.release(),
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") !=
236 gtk_menu_item_set_label(GTK_MENU_ITEM(child
->data
),
240 g_list_free(children
);
244 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel
* model
) {
253 // The icon is created asynchronously so it might not exist when the menu is
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())
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();
277 app_indicator_new_with_path(id_
.c_str(),
279 APP_INDICATOR_CATEGORY_APPLICATION_STATUS
,
281 app_indicator_set_status(icon_
, APP_INDICATOR_STATUS_ACTIVE
);
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(
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();
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
);
309 BuildSubmenuFromModel(menu_model_
,
311 G_CALLBACK(OnMenuItemActivatedThunk
),
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());
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_
);
337 void AppIndicatorIcon::OnClick(GtkWidget
* menu_item
) {
339 delegate()->OnClick();
342 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget
* menu_item
) {
343 if (block_activation_
)
346 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(menu_item
));
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
)));
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
))) {
363 if (!GetMenuItemID(menu_item
, &id
))
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