Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(27)

Side by Side Diff: chrome/browser/ui/libgtk2ui/app_indicator_icon.cc

Issue 2449243002: Gtk3 ui: Add libgtk3ui as a separate build component (Closed)
Patch Set: Add theme_properties dep to //chrome/browser/ui Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/threading/sequenced_worker_pool.h"
18 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "third_party/skia/include/core/SkBitmap.h"
21 #include "third_party/skia/include/core/SkCanvas.h"
22 #include "ui/base/models/menu_model.h"
23 #include "ui/gfx/codec/png_codec.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/gfx/image/image_skia.h"
26
27 namespace {
28
29 typedef enum {
30 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
31 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
32 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
33 APP_INDICATOR_CATEGORY_HARDWARE,
34 APP_INDICATOR_CATEGORY_OTHER
35 } AppIndicatorCategory;
36
37 typedef enum {
38 APP_INDICATOR_STATUS_PASSIVE,
39 APP_INDICATOR_STATUS_ACTIVE,
40 APP_INDICATOR_STATUS_ATTENTION
41 } AppIndicatorStatus;
42
43 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
44 const gchar* icon_name,
45 AppIndicatorCategory category);
46
47 typedef AppIndicator* (*app_indicator_new_with_path_func)(
48 const gchar* id,
49 const gchar* icon_name,
50 AppIndicatorCategory category,
51 const gchar* icon_theme_path);
52
53 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
54 AppIndicatorStatus status);
55
56 typedef void (*app_indicator_set_attention_icon_full_func)(
57 AppIndicator* self,
58 const gchar* icon_name,
59 const gchar* icon_desc);
60
61 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
62
63 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
64 const gchar* icon_name,
65 const gchar* icon_desc);
66
67 typedef void (*app_indicator_set_icon_theme_path_func)(
68 AppIndicator* self,
69 const gchar* icon_theme_path);
70
71 bool g_attempted_load = false;
72 bool g_opened = false;
73
74 // Retrieved functions from libappindicator.
75 app_indicator_new_func app_indicator_new = NULL;
76 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
77 app_indicator_set_status_func app_indicator_set_status = NULL;
78 app_indicator_set_attention_icon_full_func
79 app_indicator_set_attention_icon_full = NULL;
80 app_indicator_set_menu_func app_indicator_set_menu = NULL;
81 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
82 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
83
84 void EnsureMethodsLoaded() {
85 if (g_attempted_load)
86 return;
87
88 g_attempted_load = true;
89
90 // Only use libappindicator where it is needed to support dbus based status
91 // icons. In particular, libappindicator does not support a click action.
92 std::unique_ptr<base::Environment> env(base::Environment::Create());
93 base::nix::DesktopEnvironment environment =
94 base::nix::GetDesktopEnvironment(env.get());
95 if (environment != base::nix::DESKTOP_ENVIRONMENT_KDE4 &&
96 environment != base::nix::DESKTOP_ENVIRONMENT_KDE5 &&
97 environment != base::nix::DESKTOP_ENVIRONMENT_UNITY) {
98 return;
99 }
100
101 void* indicator_lib = nullptr;
102
103 // These include guards might be unnecessary, but let's keep them as a
104 // precaution since using gtk2 and gtk3 symbols in the same process is
105 // explicitly unsupported.
106 #if GTK_MAJOR_VERSION == 2
107 if (!indicator_lib)
108 indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
109
110 if (!indicator_lib)
111 indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
112
113 if (!indicator_lib)
114 indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
115 #endif
116
117 #if GTK_MAJOR_VERSION == 3
118 if (!indicator_lib)
119 indicator_lib = dlopen("libappindicator3.so", RTLD_LAZY);
120
121 if (!indicator_lib)
122 indicator_lib = dlopen("libappindicator3.so.1", RTLD_LAZY);
123 #endif
124
125 if (!indicator_lib)
126 return;
127
128 g_opened = true;
129
130 app_indicator_new = reinterpret_cast<app_indicator_new_func>(
131 dlsym(indicator_lib, "app_indicator_new"));
132
133 app_indicator_new_with_path =
134 reinterpret_cast<app_indicator_new_with_path_func>(
135 dlsym(indicator_lib, "app_indicator_new_with_path"));
136
137 app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
138 dlsym(indicator_lib, "app_indicator_set_status"));
139
140 app_indicator_set_attention_icon_full =
141 reinterpret_cast<app_indicator_set_attention_icon_full_func>(
142 dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
143
144 app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
145 dlsym(indicator_lib, "app_indicator_set_menu"));
146
147 app_indicator_set_icon_full =
148 reinterpret_cast<app_indicator_set_icon_full_func>(
149 dlsym(indicator_lib, "app_indicator_set_icon_full"));
150
151 app_indicator_set_icon_theme_path =
152 reinterpret_cast<app_indicator_set_icon_theme_path_func>(
153 dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
154 }
155
156 // Writes |bitmap| to a file at |path|. Returns true if successful.
157 bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) {
158 std::vector<unsigned char> png_data;
159 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data))
160 return false;
161 int bytes_written = base::WriteFile(
162 path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
163 return (bytes_written == static_cast<int>(png_data.size()));
164 }
165
166 void DeleteTempDirectory(const base::FilePath& dir_path) {
167 if (dir_path.empty())
168 return;
169 base::DeleteFile(dir_path, true);
170 }
171
172 } // namespace
173
174 namespace libgtk2ui {
175
176 AppIndicatorIcon::AppIndicatorIcon(std::string id,
177 const gfx::ImageSkia& image,
178 const base::string16& tool_tip)
179 : id_(id),
180 icon_(NULL),
181 menu_model_(NULL),
182 icon_change_count_(0),
183 weak_factory_(this) {
184 std::unique_ptr<base::Environment> env(base::Environment::Create());
185 desktop_env_ = base::nix::GetDesktopEnvironment(env.get());
186
187 EnsureMethodsLoaded();
188 tool_tip_ = base::UTF16ToUTF8(tool_tip);
189 SetImage(image);
190 }
191 AppIndicatorIcon::~AppIndicatorIcon() {
192 if (icon_) {
193 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
194 g_object_unref(icon_);
195 content::BrowserThread::GetBlockingPool()->PostTask(
196 FROM_HERE,
197 base::Bind(&DeleteTempDirectory, temp_dir_));
198 }
199 }
200
201 // static
202 bool AppIndicatorIcon::CouldOpen() {
203 EnsureMethodsLoaded();
204 return g_opened;
205 }
206
207 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
208 if (!g_opened)
209 return;
210
211 ++icon_change_count_;
212
213 // Copy the bitmap because it may be freed by the time it's accessed in
214 // another thread.
215 SkBitmap safe_bitmap = *image.bitmap();
216
217 scoped_refptr<base::TaskRunner> task_runner =
218 content::BrowserThread::GetBlockingPool()
219 ->GetTaskRunnerWithShutdownBehavior(
220 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
221 if (desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE4 ||
222 desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE5) {
223 base::PostTaskAndReplyWithResult(
224 task_runner.get(), FROM_HERE,
225 base::Bind(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
226 safe_bitmap, temp_dir_),
227 base::Bind(&AppIndicatorIcon::SetImageFromFile,
228 weak_factory_.GetWeakPtr()));
229 } else {
230 base::PostTaskAndReplyWithResult(
231 task_runner.get(), FROM_HERE,
232 base::Bind(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread,
233 safe_bitmap, icon_change_count_, id_),
234 base::Bind(&AppIndicatorIcon::SetImageFromFile,
235 weak_factory_.GetWeakPtr()));
236 }
237 }
238
239 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
240 DCHECK(!tool_tip_.empty());
241 tool_tip_ = base::UTF16ToUTF8(tool_tip);
242 UpdateClickActionReplacementMenuItem();
243 }
244
245 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
246 if (!g_opened)
247 return;
248
249 menu_model_ = model;
250
251 // The icon is created asynchronously so it might not exist when the menu is
252 // set.
253 if (icon_)
254 SetMenu();
255 }
256
257 void AppIndicatorIcon::RefreshPlatformContextMenu() {
258 menu_->Refresh();
259 }
260
261 // static
262 AppIndicatorIcon::SetImageFromFileParams
263 AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread(
264 const SkBitmap& bitmap,
265 const base::FilePath& existing_temp_dir) {
266 base::FilePath temp_dir = existing_temp_dir;
267 if (temp_dir.empty() &&
268 !base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
269 LOG(WARNING) << "Could not create temporary directory";
270 return SetImageFromFileParams();
271 }
272
273 base::FilePath icon_theme_path = temp_dir.AppendASCII("icons");
274
275 // On KDE4, an image located in a directory ending with
276 // "icons/hicolor/24x24/apps" can be used as the app indicator image because
277 // "/usr/share/icons/hicolor/24x24/apps" exists.
278 base::FilePath image_dir = icon_theme_path.AppendASCII("hicolor")
279 .AppendASCII("24x24")
280 .AppendASCII("apps");
281
282 if (!base::CreateDirectory(image_dir))
283 return SetImageFromFileParams();
284
285 // On KDE4, the name of the image file for each different looking bitmap must
286 // be unique. It must also be unique across runs of Chrome.
287 std::vector<unsigned char> bitmap_png_data;
288 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) {
289 LOG(WARNING) << "Could not encode icon";
290 return SetImageFromFileParams();
291 }
292 base::MD5Digest digest;
293 base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]),
294 bitmap_png_data.size(), &digest);
295 std::string icon_name = base::StringPrintf(
296 "chrome_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str());
297
298 // If |bitmap| is not 24x24, KDE does some really ugly resizing. Pad |bitmap|
299 // with transparent pixels to make it 24x24.
300 const int kDesiredSize = 24;
301 SkBitmap scaled_bitmap;
302 scaled_bitmap.allocN32Pixels(kDesiredSize, kDesiredSize);
303 scaled_bitmap.eraseARGB(0, 0, 0, 0);
304 SkCanvas canvas(scaled_bitmap);
305 canvas.drawBitmap(bitmap, (kDesiredSize - bitmap.width()) / 2,
306 (kDesiredSize - bitmap.height()) / 2);
307
308 base::FilePath image_path = image_dir.Append(icon_name + ".png");
309 if (!WriteFile(image_path, scaled_bitmap))
310 return SetImageFromFileParams();
311
312 SetImageFromFileParams params;
313 params.parent_temp_dir = temp_dir;
314 params.icon_theme_path = icon_theme_path.value();
315 params.icon_name = icon_name;
316 return params;
317 }
318
319 // static
320 AppIndicatorIcon::SetImageFromFileParams
321 AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap,
322 int icon_change_count,
323 const std::string& id) {
324 // Create a new temporary directory for each image on Unity since using a
325 // single temporary directory seems to have issues when changing icons in
326 // quick succession.
327 base::FilePath temp_dir;
328 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
329 LOG(WARNING) << "Could not create temporary directory";
330 return SetImageFromFileParams();
331 }
332
333 std::string icon_name =
334 base::StringPrintf("%s_%d", id.c_str(), icon_change_count);
335 base::FilePath image_path = temp_dir.Append(icon_name + ".png");
336 SetImageFromFileParams params;
337 if (WriteFile(image_path, bitmap)) {
338 params.parent_temp_dir = temp_dir;
339 params.icon_theme_path = temp_dir.value();
340 params.icon_name = icon_name;
341 }
342 return params;
343 }
344
345 void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) {
346 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
347 if (params.icon_theme_path.empty())
348 return;
349
350 if (!icon_) {
351 icon_ =
352 app_indicator_new_with_path(id_.c_str(),
353 params.icon_name.c_str(),
354 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
355 params.icon_theme_path.c_str());
356 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
357 SetMenu();
358 } else {
359 app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str());
360 app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon");
361 }
362
363 if (temp_dir_ != params.parent_temp_dir) {
364 content::BrowserThread::GetBlockingPool()->PostTask(
365 FROM_HERE,
366 base::Bind(&DeleteTempDirectory, temp_dir_));
367 temp_dir_ = params.parent_temp_dir;
368 }
369 }
370
371 void AppIndicatorIcon::SetMenu() {
372 menu_.reset(new AppIndicatorIconMenu(menu_model_));
373 UpdateClickActionReplacementMenuItem();
374 app_indicator_set_menu(icon_, menu_->GetGtkMenu());
375 }
376
377 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
378 // The menu may not have been created yet.
379 if (!menu_.get())
380 return;
381
382 if (!delegate()->HasClickAction() && menu_model_)
383 return;
384
385 DCHECK(!tool_tip_.empty());
386 menu_->UpdateClickActionReplacementMenuItem(
387 tool_tip_.c_str(),
388 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
389 base::Unretained(this)));
390 }
391
392 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
393 if (delegate())
394 delegate()->OnClick();
395 }
396
397 } // namespace libgtk2ui
OLDNEW
« no previous file with comments | « chrome/browser/ui/libgtk2ui/app_indicator_icon.h ('k') | chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698