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 file_util::WriteFile(new_file_path
,
148 reinterpret_cast<const char*>(png_data
->front()),
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())
159 base::DeleteFile(icon_file_path
, true);
164 namespace libgtk2ui
{
166 AppIndicatorIcon::AppIndicatorIcon(std::string id
,
167 const gfx::ImageSkia
& image
,
168 const base::string16
& tool_tip
)
173 icon_change_count_(0),
174 block_activation_(false),
175 weak_factory_(this) {
176 EnsureMethodsLoaded();
177 tool_tip_
= base::UTF16ToUTF8(tool_tip
);
180 AppIndicatorIcon::~AppIndicatorIcon() {
182 app_indicator_set_status(icon_
, APP_INDICATOR_STATUS_PASSIVE
);
185 g_object_unref(icon_
);
186 content::BrowserThread::GetBlockingPool()->PostTask(
188 base::Bind(&DeleteTempImagePath
, icon_file_path_
.DirName()));
193 bool AppIndicatorIcon::CouldOpen() {
194 EnsureMethodsLoaded();
198 void AppIndicatorIcon::SetImage(const gfx::ImageSkia
& image
) {
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(),
212 base::Bind(&CreateTempImageFile
,
213 safe_image
.release(),
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") !=
237 gtk_menu_item_set_label(GTK_MENU_ITEM(child
->data
),
241 g_list_free(children
);
245 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel
* model
) {
254 // The icon is created asynchronously so it might not exist when the menu is
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())
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();
278 app_indicator_new_with_path(id_
.c_str(),
280 APP_INDICATOR_CATEGORY_APPLICATION_STATUS
,
282 app_indicator_set_status(icon_
, APP_INDICATOR_STATUS_ACTIVE
);
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(
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();
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
);
310 BuildSubmenuFromModel(menu_model_
,
312 G_CALLBACK(OnMenuItemActivatedThunk
),
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());
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_
);
338 void AppIndicatorIcon::OnClick(GtkWidget
* menu_item
) {
340 delegate()->OnClick();
343 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget
* menu_item
) {
344 if (block_activation_
)
347 ui::MenuModel
* model
= ModelForMenuItem(GTK_MENU_ITEM(menu_item
));
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
)));
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
))) {
364 if (!GetMenuItemID(menu_item
, &id
))
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