Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / libgtk2ui / app_indicator_icon.cc
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.
4
5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h"
6
7 #include <gtk/gtk.h>
8 #include <dlfcn.h>
9
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"
20
21 namespace {
22
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;
30
31 typedef enum {
32   APP_INDICATOR_STATUS_PASSIVE,
33   APP_INDICATOR_STATUS_ACTIVE,
34   APP_INDICATOR_STATUS_ATTENTION
35 } AppIndicatorStatus;
36
37 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
38                                                 const gchar* icon_name,
39                                                 AppIndicatorCategory category);
40
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);
46
47 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
48                                               AppIndicatorStatus status);
49
50 typedef void (*app_indicator_set_attention_icon_full_func)(
51     AppIndicator* self,
52     const gchar* icon_name,
53     const gchar* icon_desc);
54
55 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
56
57 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
58                                                  const gchar* icon_name,
59                                                  const gchar* icon_desc);
60
61 typedef void (*app_indicator_set_icon_theme_path_func)(
62     AppIndicator* self,
63     const gchar* icon_theme_path);
64
65 bool g_attempted_load = false;
66 bool g_opened = false;
67
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;
77
78 void EnsureMethodsLoaded() {
79   if (g_attempted_load)
80     return;
81
82   g_attempted_load = true;
83
84   void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
85   if (!indicator_lib) {
86     indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
87   }
88   if (!indicator_lib) {
89     indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
90   }
91   if (!indicator_lib) {
92     return;
93   }
94
95   g_opened = true;
96
97   app_indicator_new = reinterpret_cast<app_indicator_new_func>(
98       dlsym(indicator_lib, "app_indicator_new"));
99
100   app_indicator_new_with_path =
101       reinterpret_cast<app_indicator_new_with_path_func>(
102           dlsym(indicator_lib, "app_indicator_new_with_path"));
103
104   app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
105       dlsym(indicator_lib, "app_indicator_set_status"));
106
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"));
110
111   app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
112       dlsym(indicator_lib, "app_indicator_set_menu"));
113
114   app_indicator_set_icon_full =
115       reinterpret_cast<app_indicator_set_icon_full_func>(
116           dlsym(indicator_lib, "app_indicator_set_icon_full"));
117
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"));
121 }
122
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);
127
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();
134   }
135
136   base::FilePath temp_dir;
137   base::FilePath new_file_path;
138
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                            png_data->front_as<char>(),
149                            png_data->size());
150
151   if (bytes_written != static_cast<int>(png_data->size()))
152     return base::FilePath();
153   return new_file_path;
154 }
155
156 void DeleteTempImagePath(const base::FilePath& icon_file_path) {
157   if (icon_file_path.empty())
158     return;
159   base::DeleteFile(icon_file_path, true);
160 }
161
162 }  // namespace
163
164 namespace libgtk2ui {
165
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);
179 }
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()));
189   }
190 }
191
192 // static
193 bool AppIndicatorIcon::CouldOpen() {
194   EnsureMethodsLoaded();
195   return g_opened;
196 }
197
198 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
199   if (!g_opened)
200     return;
201
202   ++icon_change_count_;
203
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()));
218 }
219
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.
223 }
224
225 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
226   DCHECK(!tool_tip_.empty());
227   tool_tip_ = base::UTF16ToUTF8(tool_tip);
228
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;
240       }
241     g_list_free(children);
242   }
243 }
244
245 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
246   if (!g_opened)
247     return;
248
249   if (gtk_menu_) {
250     DestroyMenu();
251   }
252   menu_model_ = model;
253
254   // The icon is created asynchronously so it might not exist when the menu is
255   // set.
256   if (icon_)
257     SetMenu();
258 }
259
260 void AppIndicatorIcon::RefreshPlatformContextMenu() {
261   gtk_container_foreach(
262       GTK_CONTAINER(gtk_menu_), SetMenuItemInfo, &block_activation_);
263 }
264
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;
269
270   base::FilePath old_path = icon_file_path_;
271   icon_file_path_ = icon_file_path;
272
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");
289
290     // Delete previous icon directory.
291     content::BrowserThread::GetBlockingPool()->PostTask(
292         FROM_HERE,
293         base::Bind(&DeleteTempImagePath, old_path.DirName()));
294   }
295 }
296
297 void AppIndicatorIcon::SetMenu() {
298   gtk_menu_ = gtk_menu_new();
299
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);
307     }
308   }
309   if (menu_model_) {
310     BuildSubmenuFromModel(menu_model_,
311                           gtk_menu_,
312                           G_CALLBACK(OnMenuItemActivatedThunk),
313                           &block_activation_,
314                           this);
315     RefreshPlatformContextMenu();
316   }
317   app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
318 }
319
320 void AppIndicatorIcon::CreateClickActionReplacement() {
321   DCHECK(!tool_tip_.empty());
322
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);
330 }
331
332 void AppIndicatorIcon::DestroyMenu() {
333   gtk_widget_destroy(gtk_menu_);
334   gtk_menu_ = NULL;
335   menu_model_ = NULL;
336 }
337
338 void AppIndicatorIcon::OnClick(GtkWidget* menu_item) {
339   if (delegate())
340     delegate()->OnClick();
341 }
342
343 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget* menu_item) {
344   if (block_activation_)
345     return;
346
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;
354   }
355
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;
361   }
362
363   int id;
364   if (!GetMenuItemID(menu_item, &id))
365     return;
366
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);
370 }
371
372 }  // namespace libgtk2ui