// Copyright (c) 2014 GitHub, Inc. All rights reserved. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #include "atom/browser/ui/gtk/app_indicator_icon.h" #include #include #include "base/file_util.h" #include "base/guid.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/ui/gtk/menu_gtk.h" #include "content/public/browser/browser_thread.h" #include "ui/gfx/image/image.h" namespace { typedef enum { APP_INDICATOR_CATEGORY_APPLICATION_STATUS, APP_INDICATOR_CATEGORY_COMMUNICATIONS, APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, APP_INDICATOR_CATEGORY_HARDWARE, APP_INDICATOR_CATEGORY_OTHER } AppIndicatorCategory; typedef enum { APP_INDICATOR_STATUS_PASSIVE, APP_INDICATOR_STATUS_ACTIVE, APP_INDICATOR_STATUS_ATTENTION } AppIndicatorStatus; typedef AppIndicator* (*app_indicator_new_func)(const gchar* id, const gchar* icon_name, AppIndicatorCategory category); typedef AppIndicator* (*app_indicator_new_with_path_func)( const gchar* id, const gchar* icon_name, AppIndicatorCategory category, const gchar* icon_theme_path); typedef void (*app_indicator_set_status_func)(AppIndicator* self, AppIndicatorStatus status); typedef void (*app_indicator_set_attention_icon_full_func)( AppIndicator* self, const gchar* icon_name, const gchar* icon_desc); typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu); typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self, const gchar* icon_name, const gchar* icon_desc); typedef void (*app_indicator_set_icon_theme_path_func)( AppIndicator* self, const gchar* icon_theme_path); bool g_attempted_load = false; bool g_opened = false; // Retrieved functions from libappindicator. app_indicator_new_func app_indicator_new = NULL; app_indicator_new_with_path_func app_indicator_new_with_path = NULL; app_indicator_set_status_func app_indicator_set_status = NULL; app_indicator_set_attention_icon_full_func app_indicator_set_attention_icon_full = NULL; app_indicator_set_menu_func app_indicator_set_menu = NULL; app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL; app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL; void EnsureMethodsLoaded() { if (g_attempted_load) return; g_attempted_load = true; void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY); if (!indicator_lib) { indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY); } if (!indicator_lib) { indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY); } if (!indicator_lib) { return; } g_opened = true; app_indicator_new = reinterpret_cast( dlsym(indicator_lib, "app_indicator_new")); app_indicator_new_with_path = reinterpret_cast( dlsym(indicator_lib, "app_indicator_new_with_path")); app_indicator_set_status = reinterpret_cast( dlsym(indicator_lib, "app_indicator_set_status")); app_indicator_set_attention_icon_full = reinterpret_cast( dlsym(indicator_lib, "app_indicator_set_attention_icon_full")); app_indicator_set_menu = reinterpret_cast( dlsym(indicator_lib, "app_indicator_set_menu")); app_indicator_set_icon_full = reinterpret_cast( dlsym(indicator_lib, "app_indicator_set_icon_full")); app_indicator_set_icon_theme_path = reinterpret_cast( dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); } base::FilePath CreateTempImageFile(gfx::ImageSkia* image_ptr, int icon_change_count, std::string id) { scoped_ptr image(image_ptr); scoped_refptr png_data = gfx::Image(*image.get()).As1xPNGBytes(); if (png_data->size() == 0) { // If the bitmap could not be encoded to PNG format, skip it. LOG(WARNING) << "Could not encode icon"; return base::FilePath(); } base::FilePath temp_dir; base::FilePath new_file_path; // Create a new temporary directory for each image since using a single // temporary directory seems to have issues when changing icons in quick // succession. if (!file_util::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) return base::FilePath(); new_file_path = temp_dir.Append(id + base::StringPrintf("_%d.png", icon_change_count)); int bytes_written = file_util::WriteFile( new_file_path, reinterpret_cast(png_data->front()), png_data->size()); if (bytes_written != static_cast(png_data->size())) return base::FilePath(); return new_file_path; } void DeleteTempImagePath(const base::FilePath& icon_file_path) { if (icon_file_path.empty()) return; base::DeleteFile(icon_file_path, true); } } // namespace namespace atom { AppIndicatorIcon::AppIndicatorIcon() : icon_(NULL), id_(base::GenerateGUID()), icon_change_count_(0), weak_factory_(this) { } AppIndicatorIcon::~AppIndicatorIcon() { if (icon_) { app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE); // if (gtk_menu_) // DestroyMenu(); g_object_unref(icon_); content::BrowserThread::GetBlockingPool()->PostTask( FROM_HERE, base::Bind(&DeleteTempImagePath, icon_file_path_.DirName())); } } bool AppIndicatorIcon::CouldOpen() { EnsureMethodsLoaded(); return g_opened; } void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) { if (!g_opened) return; ++icon_change_count_; // We create a deep copy of the image since it may have been freed by the time // it's accessed in the other thread. scoped_ptr safe_image(image.DeepCopy()); base::PostTaskAndReplyWithResult( content::BrowserThread::GetBlockingPool() ->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(), FROM_HERE, base::Bind(&CreateTempImageFile, safe_image.release(), icon_change_count_, id_), base::Bind(&AppIndicatorIcon::SetImageFromFile, weak_factory_.GetWeakPtr())); } void AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) { // Ignore pressed images, since the standard on Linux is to not highlight // pressed status icons. } void AppIndicatorIcon::SetToolTip(const std::string& tool_tip) { // App indicator doesn't have tooltips: // https://bugs.launchpad.net/indicator-application/+bug/527458 } void AppIndicatorIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_.reset(new MenuGtk(NULL, menu_model)); app_indicator_set_menu(icon_, GTK_MENU(menu_->widget())); } void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); if (icon_file_path.empty()) return; base::FilePath old_path = icon_file_path_; icon_file_path_ = icon_file_path; std::string icon_name = icon_file_path_.BaseName().RemoveExtension().value(); std::string icon_dir = icon_file_path_.DirName().value(); if (!icon_) { icon_ = app_indicator_new_with_path(id_.c_str(), icon_name.c_str(), APP_INDICATOR_CATEGORY_APPLICATION_STATUS, icon_dir.c_str()); app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE); } else { // Currently we are creating a new temp directory every time the icon is // set. So we need to set the directory each time. app_indicator_set_icon_theme_path(icon_, icon_dir.c_str()); app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon"); // Delete previous icon directory. content::BrowserThread::GetBlockingPool()->PostTask( FROM_HERE, base::Bind(&DeleteTempImagePath, old_path.DirName())); } } } // namespace atom