Upstream version 10.39.225.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 <dlfcn.h>
8 #include <gtk/gtk.h>
9
10 #include "base/bind.h"
11 #include "base/environment.h"
12 #include "base/files/file_util.h"
13 #include "base/md5.h"
14 #include "base/memory/ref_counted_memory.h"
15 #include "base/nix/xdg_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/threading/sequenced_worker_pool.h"
19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "ui/base/models/menu_model.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/image/image_skia.h"
24
25 namespace {
26
27 typedef enum {
28   APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
29   APP_INDICATOR_CATEGORY_COMMUNICATIONS,
30   APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
31   APP_INDICATOR_CATEGORY_HARDWARE,
32   APP_INDICATOR_CATEGORY_OTHER
33 } AppIndicatorCategory;
34
35 typedef enum {
36   APP_INDICATOR_STATUS_PASSIVE,
37   APP_INDICATOR_STATUS_ACTIVE,
38   APP_INDICATOR_STATUS_ATTENTION
39 } AppIndicatorStatus;
40
41 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
42                                                 const gchar* icon_name,
43                                                 AppIndicatorCategory category);
44
45 typedef AppIndicator* (*app_indicator_new_with_path_func)(
46     const gchar* id,
47     const gchar* icon_name,
48     AppIndicatorCategory category,
49     const gchar* icon_theme_path);
50
51 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
52                                               AppIndicatorStatus status);
53
54 typedef void (*app_indicator_set_attention_icon_full_func)(
55     AppIndicator* self,
56     const gchar* icon_name,
57     const gchar* icon_desc);
58
59 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
60
61 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
62                                                  const gchar* icon_name,
63                                                  const gchar* icon_desc);
64
65 typedef void (*app_indicator_set_icon_theme_path_func)(
66     AppIndicator* self,
67     const gchar* icon_theme_path);
68
69 bool g_attempted_load = false;
70 bool g_opened = false;
71
72 // Retrieved functions from libappindicator.
73 app_indicator_new_func app_indicator_new = NULL;
74 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
75 app_indicator_set_status_func app_indicator_set_status = NULL;
76 app_indicator_set_attention_icon_full_func
77     app_indicator_set_attention_icon_full = NULL;
78 app_indicator_set_menu_func app_indicator_set_menu = NULL;
79 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
80 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
81
82 void EnsureMethodsLoaded() {
83   if (g_attempted_load)
84     return;
85
86   g_attempted_load = true;
87
88   // Only use libappindicator where it is needed to support dbus based status
89   // icons. In particular, libappindicator does not support a click action.
90   scoped_ptr<base::Environment> env(base::Environment::Create());
91   base::nix::DesktopEnvironment environment =
92       base::nix::GetDesktopEnvironment(env.get());
93   if (environment != base::nix::DESKTOP_ENVIRONMENT_KDE4 &&
94       environment != base::nix::DESKTOP_ENVIRONMENT_UNITY) {
95     return;
96   }
97
98   void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
99   if (!indicator_lib) {
100     indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
101   }
102   if (!indicator_lib) {
103     indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
104   }
105   if (!indicator_lib) {
106     return;
107   }
108
109   g_opened = true;
110
111   app_indicator_new = reinterpret_cast<app_indicator_new_func>(
112       dlsym(indicator_lib, "app_indicator_new"));
113
114   app_indicator_new_with_path =
115       reinterpret_cast<app_indicator_new_with_path_func>(
116           dlsym(indicator_lib, "app_indicator_new_with_path"));
117
118   app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
119       dlsym(indicator_lib, "app_indicator_set_status"));
120
121   app_indicator_set_attention_icon_full =
122       reinterpret_cast<app_indicator_set_attention_icon_full_func>(
123           dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
124
125   app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
126       dlsym(indicator_lib, "app_indicator_set_menu"));
127
128   app_indicator_set_icon_full =
129       reinterpret_cast<app_indicator_set_icon_full_func>(
130           dlsym(indicator_lib, "app_indicator_set_icon_full"));
131
132   app_indicator_set_icon_theme_path =
133       reinterpret_cast<app_indicator_set_icon_theme_path_func>(
134           dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
135 }
136
137 // Returns whether a temporary directory should be created for each app
138 // indicator image.
139 bool ShouldCreateTempDirectoryPerImage(bool using_kde4) {
140   // Create a new temporary directory for each image on Unity since using a
141   // single temporary directory seems to have issues when changing icons in
142   // quick succession.
143   return !using_kde4;
144 }
145
146 // Returns the subdirectory of |temp_dir| in which the app indicator image
147 // should be saved.
148 base::FilePath GetImageDirectoryPath(bool using_kde4,
149                                      const base::FilePath& temp_dir) {
150   // On KDE4, an image located in a directory ending with
151   // "icons/hicolor/16x16/apps" can be used as the app indicator image because
152   // "/usr/share/icons/hicolor/16x16/apps" exists.
153   return using_kde4 ?
154       temp_dir.AppendASCII("icons").AppendASCII("hicolor").AppendASCII("16x16").
155           AppendASCII("apps") :
156       temp_dir;
157 }
158
159 std::string GetImageFileNameForKDE4(
160     const scoped_refptr<base::RefCountedMemory>& png_data) {
161   // On KDE4, the name of the image file for each different looking bitmap must
162   // be unique. It must also be unique across runs of Chrome.
163   base::MD5Digest digest;
164   base::MD5Sum(png_data->front_as<char>(), png_data->size(), &digest);
165   return base::StringPrintf("chrome_app_indicator_%s.png",
166                             base::MD5DigestToBase16(digest).c_str());
167 }
168
169 std::string GetImageFileNameForNonKDE4(int icon_change_count,
170                                        const std::string& id) {
171   return base::StringPrintf("%s_%d.png", id.c_str(), icon_change_count);
172 }
173
174 // Returns the "icon theme path" given the file path of the app indicator image.
175 std::string GetIconThemePath(bool using_kde4,
176                              const base::FilePath& image_path) {
177   return using_kde4 ?
178       image_path.DirName().DirName().DirName().DirName().value() :
179       image_path.DirName().value();
180 }
181
182 base::FilePath CreateTempImageFile(bool using_kde4,
183                                    gfx::ImageSkia* image_ptr,
184                                    int icon_change_count,
185                                    std::string id,
186                                    const base::FilePath& previous_file_path) {
187   scoped_ptr<gfx::ImageSkia> image(image_ptr);
188
189   scoped_refptr<base::RefCountedMemory> png_data =
190       gfx::Image(*image.get()).As1xPNGBytes();
191   if (png_data->size() == 0) {
192     // If the bitmap could not be encoded to PNG format, skip it.
193     LOG(WARNING) << "Could not encode icon";
194     return base::FilePath();
195   }
196
197   base::FilePath new_file_path;
198   if (previous_file_path.empty() ||
199       ShouldCreateTempDirectoryPerImage(using_kde4)) {
200     base::FilePath tmp_dir;
201     if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &tmp_dir))
202       return base::FilePath();
203     new_file_path = GetImageDirectoryPath(using_kde4, tmp_dir);
204     if (new_file_path != tmp_dir) {
205       if (!base::CreateDirectory(new_file_path))
206         return base::FilePath();
207     }
208   } else {
209     new_file_path = previous_file_path.DirName();
210   }
211
212   new_file_path = new_file_path.Append(using_kde4 ?
213       GetImageFileNameForKDE4(png_data) :
214       GetImageFileNameForNonKDE4(icon_change_count, id));
215
216   int bytes_written =
217       base::WriteFile(new_file_path,
218                       png_data->front_as<char>(), png_data->size());
219
220   if (bytes_written != static_cast<int>(png_data->size()))
221     return base::FilePath();
222   return new_file_path;
223 }
224
225 void DeleteTempDirectory(const base::FilePath& dir_path) {
226   if (dir_path.empty())
227     return;
228   base::DeleteFile(dir_path, true);
229 }
230
231 }  // namespace
232
233 namespace libgtk2ui {
234
235 AppIndicatorIcon::AppIndicatorIcon(std::string id,
236                                    const gfx::ImageSkia& image,
237                                    const base::string16& tool_tip)
238     : id_(id),
239       using_kde4_(false),
240       icon_(NULL),
241       menu_model_(NULL),
242       icon_change_count_(0),
243       weak_factory_(this) {
244   scoped_ptr<base::Environment> env(base::Environment::Create());
245   using_kde4_ = base::nix::GetDesktopEnvironment(env.get()) ==
246       base::nix::DESKTOP_ENVIRONMENT_KDE4;
247
248   EnsureMethodsLoaded();
249   tool_tip_ = base::UTF16ToUTF8(tool_tip);
250   SetImage(image);
251 }
252 AppIndicatorIcon::~AppIndicatorIcon() {
253   if (icon_) {
254     app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
255     g_object_unref(icon_);
256     content::BrowserThread::GetBlockingPool()->PostTask(
257         FROM_HERE,
258         base::Bind(&DeleteTempDirectory, icon_file_path_.DirName()));
259   }
260 }
261
262 // static
263 bool AppIndicatorIcon::CouldOpen() {
264   EnsureMethodsLoaded();
265   return g_opened;
266 }
267
268 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
269   if (!g_opened)
270     return;
271
272   ++icon_change_count_;
273
274   // We create a deep copy of the image since it may have been freed by the time
275   // it's accessed in the other thread.
276   scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
277   base::PostTaskAndReplyWithResult(
278       content::BrowserThread::GetBlockingPool()
279           ->GetTaskRunnerWithShutdownBehavior(
280                 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
281       FROM_HERE,
282       base::Bind(&CreateTempImageFile,
283                  using_kde4_,
284                  safe_image.release(),
285                  icon_change_count_,
286                  id_,
287                  icon_file_path_),
288       base::Bind(&AppIndicatorIcon::SetImageFromFile,
289                  weak_factory_.GetWeakPtr()));
290 }
291
292 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
293   DCHECK(!tool_tip_.empty());
294   tool_tip_ = base::UTF16ToUTF8(tool_tip);
295   UpdateClickActionReplacementMenuItem();
296 }
297
298 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
299   if (!g_opened)
300     return;
301
302   menu_model_ = model;
303
304   // The icon is created asynchronously so it might not exist when the menu is
305   // set.
306   if (icon_)
307     SetMenu();
308 }
309
310 void AppIndicatorIcon::RefreshPlatformContextMenu() {
311   menu_->Refresh();
312 }
313
314 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
315   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
316   if (icon_file_path.empty())
317     return;
318
319   base::FilePath old_path = icon_file_path_;
320   icon_file_path_ = icon_file_path;
321
322   std::string icon_name =
323       icon_file_path_.BaseName().RemoveExtension().value();
324   std::string icon_dir = GetIconThemePath(using_kde4_, icon_file_path);
325   if (!icon_) {
326     icon_ =
327         app_indicator_new_with_path(id_.c_str(),
328                                     icon_name.c_str(),
329                                     APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
330                                     icon_dir.c_str());
331     app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
332     SetMenu();
333   } else {
334     // Currently we are creating a new temp directory every time the icon is
335     // set. So we need to set the directory each time.
336     app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
337     app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
338
339     if (ShouldCreateTempDirectoryPerImage(using_kde4_)) {
340       // Delete previous icon directory.
341       content::BrowserThread::GetBlockingPool()->PostTask(
342           FROM_HERE,
343           base::Bind(&DeleteTempDirectory, old_path.DirName()));
344     }
345   }
346 }
347
348 void AppIndicatorIcon::SetMenu() {
349   menu_.reset(new AppIndicatorIconMenu(menu_model_));
350   UpdateClickActionReplacementMenuItem();
351   app_indicator_set_menu(icon_, menu_->GetGtkMenu());
352 }
353
354 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
355   // The menu may not have been created yet.
356   if (!menu_.get())
357     return;
358
359   if (!delegate()->HasClickAction() && menu_model_)
360     return;
361
362   DCHECK(!tool_tip_.empty());
363   menu_->UpdateClickActionReplacementMenuItem(
364       tool_tip_.c_str(),
365       base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
366                  base::Unretained(this)));
367 }
368
369 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
370   if (delegate())
371     delegate()->OnClick();
372 }
373
374 }  // namespace libgtk2ui