OLD | NEW |
---|---|
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h" | 5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h" |
6 | 6 |
7 #include <dlfcn.h> | 7 #include <dlfcn.h> |
8 #include <gtk/gtk.h> | 8 #include <gtk/gtk.h> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/environment.h" | 11 #include "base/environment.h" |
12 #include "base/files/file_util.h" | 12 #include "base/files/file_util.h" |
13 #include "base/md5.h" | 13 #include "base/md5.h" |
14 #include "base/memory/ref_counted_memory.h" | 14 #include "base/memory/ref_counted_memory.h" |
15 #include "base/nix/xdg_util.h" | 15 #include "base/nix/xdg_util.h" |
16 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" |
17 #include "base/strings/utf_string_conversions.h" | 17 #include "base/strings/utf_string_conversions.h" |
18 #include "base/threading/sequenced_worker_pool.h" | 18 #include "base/threading/sequenced_worker_pool.h" |
19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h" | 19 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h" |
20 #include "content/public/browser/browser_thread.h" | 20 #include "content/public/browser/browser_thread.h" |
21 #include "third_party/skia/include/core/SkBitmap.h" | |
22 #include "third_party/skia/include/core/SkCanvas.h" | |
21 #include "ui/base/models/menu_model.h" | 23 #include "ui/base/models/menu_model.h" |
24 #include "ui/gfx/codec/png_codec.h" | |
22 #include "ui/gfx/image/image.h" | 25 #include "ui/gfx/image/image.h" |
23 #include "ui/gfx/image/image_skia.h" | 26 #include "ui/gfx/image/image_skia.h" |
24 | 27 |
25 namespace { | 28 namespace { |
26 | 29 |
27 typedef enum { | 30 typedef enum { |
28 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 31 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, |
29 APP_INDICATOR_CATEGORY_COMMUNICATIONS, | 32 APP_INDICATOR_CATEGORY_COMMUNICATIONS, |
30 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, | 33 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, |
31 APP_INDICATOR_CATEGORY_HARDWARE, | 34 APP_INDICATOR_CATEGORY_HARDWARE, |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
127 | 130 |
128 app_indicator_set_icon_full = | 131 app_indicator_set_icon_full = |
129 reinterpret_cast<app_indicator_set_icon_full_func>( | 132 reinterpret_cast<app_indicator_set_icon_full_func>( |
130 dlsym(indicator_lib, "app_indicator_set_icon_full")); | 133 dlsym(indicator_lib, "app_indicator_set_icon_full")); |
131 | 134 |
132 app_indicator_set_icon_theme_path = | 135 app_indicator_set_icon_theme_path = |
133 reinterpret_cast<app_indicator_set_icon_theme_path_func>( | 136 reinterpret_cast<app_indicator_set_icon_theme_path_func>( |
134 dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); | 137 dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); |
135 } | 138 } |
136 | 139 |
137 // Returns whether a temporary directory should be created for each app | 140 // Writes |bitmap| to a file at |path|. Returns true if successful. |
138 // indicator image. | 141 bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) { |
139 bool ShouldCreateTempDirectoryPerImage(bool using_kde4) { | 142 std::vector<unsigned char> png_data; |
140 // Create a new temporary directory for each image on Unity since using a | 143 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data)) |
141 // single temporary directory seems to have issues when changing icons in | 144 return false; |
142 // quick succession. | 145 int bytes_written = base::WriteFile( |
143 return !using_kde4; | 146 path, reinterpret_cast<char*>(&png_data[0]), png_data.size()); |
144 } | 147 return (bytes_written == static_cast<int>(png_data.size())); |
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 } | 148 } |
224 | 149 |
225 void DeleteTempDirectory(const base::FilePath& dir_path) { | 150 void DeleteTempDirectory(const base::FilePath& dir_path) { |
226 if (dir_path.empty()) | 151 if (dir_path.empty()) |
227 return; | 152 return; |
228 base::DeleteFile(dir_path, true); | 153 base::DeleteFile(dir_path, true); |
229 } | 154 } |
230 | 155 |
231 } // namespace | 156 } // namespace |
232 | 157 |
(...skipping 15 matching lines...) Expand all Loading... | |
248 EnsureMethodsLoaded(); | 173 EnsureMethodsLoaded(); |
249 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 174 tool_tip_ = base::UTF16ToUTF8(tool_tip); |
250 SetImage(image); | 175 SetImage(image); |
251 } | 176 } |
252 AppIndicatorIcon::~AppIndicatorIcon() { | 177 AppIndicatorIcon::~AppIndicatorIcon() { |
253 if (icon_) { | 178 if (icon_) { |
254 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE); | 179 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE); |
255 g_object_unref(icon_); | 180 g_object_unref(icon_); |
256 content::BrowserThread::GetBlockingPool()->PostTask( | 181 content::BrowserThread::GetBlockingPool()->PostTask( |
257 FROM_HERE, | 182 FROM_HERE, |
258 base::Bind(&DeleteTempDirectory, icon_file_path_.DirName())); | 183 base::Bind(&DeleteTempDirectory, temp_dir_.DirName())); |
259 } | 184 } |
260 } | 185 } |
261 | 186 |
262 // static | 187 // static |
263 bool AppIndicatorIcon::CouldOpen() { | 188 bool AppIndicatorIcon::CouldOpen() { |
264 EnsureMethodsLoaded(); | 189 EnsureMethodsLoaded(); |
265 return g_opened; | 190 return g_opened; |
266 } | 191 } |
267 | 192 |
268 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) { | 193 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) { |
269 if (!g_opened) | 194 if (!g_opened) |
270 return; | 195 return; |
271 | 196 |
272 ++icon_change_count_; | 197 ++icon_change_count_; |
273 | 198 |
274 // We create a deep copy of the image since it may have been freed by the time | 199 // Copy the bitmap because it may be freed by the time it's accessed in |
275 // it's accessed in the other thread. | 200 // another thread. |
276 scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy()); | 201 SkBitmap safe_bitmap = *image.bitmap(); |
277 base::PostTaskAndReplyWithResult( | 202 |
203 scoped_refptr<base::TaskRunner> task_runner = | |
278 content::BrowserThread::GetBlockingPool() | 204 content::BrowserThread::GetBlockingPool() |
279 ->GetTaskRunnerWithShutdownBehavior( | 205 ->GetTaskRunnerWithShutdownBehavior( |
280 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(), | 206 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); |
281 FROM_HERE, | 207 if (using_kde4_) { |
282 base::Bind(&CreateTempImageFile, | 208 base::PostTaskAndReplyWithResult( |
283 using_kde4_, | 209 task_runner.get(), FROM_HERE, |
284 safe_image.release(), | 210 base::Bind(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread, |
285 icon_change_count_, | 211 safe_bitmap, temp_dir_), |
286 id_, | 212 base::Bind(&AppIndicatorIcon::SetImageFromFile, |
287 icon_file_path_), | 213 weak_factory_.GetWeakPtr())); |
288 base::Bind(&AppIndicatorIcon::SetImageFromFile, | 214 } else { |
289 weak_factory_.GetWeakPtr())); | 215 base::PostTaskAndReplyWithResult( |
216 task_runner.get(), FROM_HERE, | |
217 base::Bind(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread, | |
218 safe_bitmap, icon_change_count_, id_), | |
219 base::Bind(&AppIndicatorIcon::SetImageFromFile, | |
220 weak_factory_.GetWeakPtr())); | |
221 } | |
290 } | 222 } |
291 | 223 |
292 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) { | 224 void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) { |
293 DCHECK(!tool_tip_.empty()); | 225 DCHECK(!tool_tip_.empty()); |
294 tool_tip_ = base::UTF16ToUTF8(tool_tip); | 226 tool_tip_ = base::UTF16ToUTF8(tool_tip); |
295 UpdateClickActionReplacementMenuItem(); | 227 UpdateClickActionReplacementMenuItem(); |
296 } | 228 } |
297 | 229 |
298 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) { | 230 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) { |
299 if (!g_opened) | 231 if (!g_opened) |
300 return; | 232 return; |
301 | 233 |
302 menu_model_ = model; | 234 menu_model_ = model; |
303 | 235 |
304 // The icon is created asynchronously so it might not exist when the menu is | 236 // The icon is created asynchronously so it might not exist when the menu is |
305 // set. | 237 // set. |
306 if (icon_) | 238 if (icon_) |
307 SetMenu(); | 239 SetMenu(); |
308 } | 240 } |
309 | 241 |
310 void AppIndicatorIcon::RefreshPlatformContextMenu() { | 242 void AppIndicatorIcon::RefreshPlatformContextMenu() { |
311 menu_->Refresh(); | 243 menu_->Refresh(); |
312 } | 244 } |
313 | 245 |
314 void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) { | 246 // static |
247 AppIndicatorIcon::SetImageFromFileParams | |
248 AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread( | |
249 const SkBitmap& bitmap, | |
250 const base::FilePath& existing_temp_dir) { | |
251 base::FilePath temp_dir = existing_temp_dir; | |
252 if (temp_dir.empty() && | |
253 !base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) { | |
254 LOG(WARNING) << "Could not create temporary directory"; | |
255 return SetImageFromFileParams(); | |
256 } | |
257 | |
258 base::FilePath icon_theme_path = temp_dir.AppendASCII("icons"); | |
259 | |
260 // On KDE4, the name of the image file for each different looking bitmap must | |
261 // be unique. It must also be unique across runs of Chrome. | |
262 std::vector<unsigned char> bitmap_png_data; | |
263 if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) { | |
264 LOG(WARNING) << "Could not encode icon"; | |
265 return SetImageFromFileParams(); | |
266 } | |
267 base::MD5Digest digest; | |
268 base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]), | |
269 bitmap_png_data.size(), &digest); | |
270 std::string icon_name = base::StringPrintf( | |
271 "chrome_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str()); | |
pkotwicz
2014/11/13 05:02:39
This is "chrome_app_indicator2" on purpose. This i
| |
272 | |
273 // On KDE4, an image located in a directory ending with | |
274 // "icons/hicolor/24x24/apps" can be used as the app indicator image because | |
275 // "/usr/share/icons/hicolor/24x24/apps" exists. | |
276 base::FilePath image_dir = icon_theme_path.AppendASCII("hicolor") | |
277 .AppendASCII("24x24") | |
278 .AppendASCII("apps"); | |
279 | |
280 if (!base::CreateDirectory(image_dir)) | |
281 return SetImageFromFileParams(); | |
282 | |
283 // If |bitmap| is not 24x24, KDE does some really ugly resizing. Pad |bitmap| | |
284 // with transparent pixels to make it 24x24. | |
285 const int kDesiredSize = 24; | |
286 SkBitmap scaled_bitmap; | |
287 scaled_bitmap.allocN32Pixels(kDesiredSize, kDesiredSize); | |
288 scaled_bitmap.eraseARGB(0, 0, 0, 0); | |
289 SkCanvas canvas(scaled_bitmap); | |
290 canvas.drawBitmap(bitmap, (kDesiredSize - bitmap.width()) / 2, | |
291 (kDesiredSize - bitmap.height()) / 2); | |
292 | |
293 base::FilePath image_path = image_dir.Append(icon_name + ".png"); | |
294 if (!WriteFile(image_path, scaled_bitmap)) | |
295 return SetImageFromFileParams(); | |
296 | |
297 SetImageFromFileParams params; | |
298 params.parent_temp_dir = temp_dir; | |
299 params.icon_theme_path = icon_theme_path.value(); | |
300 params.icon_name = icon_name; | |
301 return params; | |
302 } | |
303 | |
304 // static | |
305 AppIndicatorIcon::SetImageFromFileParams | |
306 AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap, | |
307 int icon_change_count, | |
308 const std::string& id) { | |
309 // Create a new temporary directory for each image on Unity since using a | |
310 // single temporary directory seems to have issues when changing icons in | |
311 // quick succession. | |
312 base::FilePath temp_dir; | |
313 if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) { | |
314 LOG(WARNING) << "Could not create temporary directory"; | |
315 return SetImageFromFileParams(); | |
316 } | |
317 | |
318 std::string icon_name = | |
319 base::StringPrintf("%s_%d", id.c_str(), icon_change_count); | |
320 base::FilePath image_path = temp_dir.Append(icon_name + ".png"); | |
321 SetImageFromFileParams params; | |
322 if (WriteFile(image_path, bitmap)) { | |
323 params.parent_temp_dir = temp_dir; | |
324 params.icon_theme_path = temp_dir.value(); | |
325 params.icon_name = icon_name; | |
326 } | |
327 return params; | |
328 } | |
329 | |
330 void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) { | |
315 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 331 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
316 if (icon_file_path.empty()) | 332 if (params.icon_theme_path.empty()) |
317 return; | 333 return; |
318 | 334 |
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_) { | 335 if (!icon_) { |
326 icon_ = | 336 icon_ = |
327 app_indicator_new_with_path(id_.c_str(), | 337 app_indicator_new_with_path(id_.c_str(), |
328 icon_name.c_str(), | 338 params.icon_name.c_str(), |
329 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, | 339 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, |
330 icon_dir.c_str()); | 340 params.icon_theme_path.c_str()); |
331 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE); | 341 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE); |
332 SetMenu(); | 342 SetMenu(); |
333 } else { | 343 } else { |
334 // Currently we are creating a new temp directory every time the icon is | 344 app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str()); |
335 // set. So we need to set the directory each time. | 345 app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon"); |
336 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str()); | 346 } |
337 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon"); | |
338 | 347 |
339 if (ShouldCreateTempDirectoryPerImage(using_kde4_)) { | 348 if (temp_dir_ != params.parent_temp_dir) { |
340 // Delete previous icon directory. | 349 content::BrowserThread::GetBlockingPool()->PostTask( |
341 content::BrowserThread::GetBlockingPool()->PostTask( | 350 FROM_HERE, |
342 FROM_HERE, | 351 base::Bind(&DeleteTempDirectory, temp_dir_)); |
343 base::Bind(&DeleteTempDirectory, old_path.DirName())); | 352 temp_dir_ = params.parent_temp_dir; |
344 } | |
345 } | 353 } |
346 } | 354 } |
347 | 355 |
348 void AppIndicatorIcon::SetMenu() { | 356 void AppIndicatorIcon::SetMenu() { |
349 menu_.reset(new AppIndicatorIconMenu(menu_model_)); | 357 menu_.reset(new AppIndicatorIconMenu(menu_model_)); |
350 UpdateClickActionReplacementMenuItem(); | 358 UpdateClickActionReplacementMenuItem(); |
351 app_indicator_set_menu(icon_, menu_->GetGtkMenu()); | 359 app_indicator_set_menu(icon_, menu_->GetGtkMenu()); |
352 } | 360 } |
353 | 361 |
354 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() { | 362 void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() { |
(...skipping 10 matching lines...) Expand all Loading... | |
365 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated, | 373 base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated, |
366 base::Unretained(this))); | 374 base::Unretained(this))); |
367 } | 375 } |
368 | 376 |
369 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() { | 377 void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() { |
370 if (delegate()) | 378 if (delegate()) |
371 delegate()->OnClick(); | 379 delegate()->OnClick(); |
372 } | 380 } |
373 | 381 |
374 } // namespace libgtk2ui | 382 } // namespace libgtk2ui |
OLD | NEW |