fix: restore original GTK/appindicator implementation of tray icons (#23674)

This commit is contained in:
Samuel Attard 2020-06-02 17:00:20 -07:00 committed by GitHub
parent 087567655a
commit d8594f7a78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1337 additions and 31 deletions

View file

@ -322,6 +322,7 @@ source_set("electron_lib") {
"shell/common/api:mojo",
"//base:base_static",
"//base/allocator:buildflags",
"//chrome/app:command_ids",
"//chrome/app/resources:platform_locale_settings",
"//chrome/services/printing/public/mojom",
"//components/certificate_transparency",
@ -523,6 +524,16 @@ source_set("electron_lib") {
sources += filenames.lib_sources_nss
sources += [
"shell/browser/ui/gtk/app_indicator_icon.cc",
"shell/browser/ui/gtk/app_indicator_icon.h",
"shell/browser/ui/gtk/app_indicator_icon_menu.cc",
"shell/browser/ui/gtk/app_indicator_icon_menu.h",
"shell/browser/ui/gtk/gtk_status_icon.cc",
"shell/browser/ui/gtk/gtk_status_icon.h",
"shell/browser/ui/gtk/menu_util.cc",
"shell/browser/ui/gtk/menu_util.h",
"shell/browser/ui/gtk/status_icon.cc",
"shell/browser/ui/gtk/status_icon.h",
"shell/browser/ui/gtk_util.cc",
"shell/browser/ui/gtk_util.h",
]

View file

@ -0,0 +1,379 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk/app_indicator_icon.h"
#include <dlfcn.h>
#include <gtk/gtk.h>
#include <algorithm>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/hash/md5.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_thread.h"
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/base/models/menu_model.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.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 = nullptr;
app_indicator_new_with_path_func app_indicator_new_with_path = nullptr;
app_indicator_set_status_func app_indicator_set_status = nullptr;
app_indicator_set_attention_icon_full_func
app_indicator_set_attention_icon_full = nullptr;
app_indicator_set_menu_func app_indicator_set_menu = nullptr;
app_indicator_set_icon_full_func app_indicator_set_icon_full = nullptr;
app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path =
nullptr;
void EnsureLibAppIndicatorLoaded() {
if (g_attempted_load)
return;
g_attempted_load = true;
std::string lib_name =
"libappindicator" + base::NumberToString(GTK_MAJOR_VERSION) + ".so";
void* indicator_lib = dlopen(lib_name.c_str(), RTLD_LAZY);
if (!indicator_lib) {
lib_name += ".1";
indicator_lib = dlopen(lib_name.c_str(), RTLD_LAZY);
}
if (!indicator_lib)
return;
g_opened = true;
app_indicator_new = reinterpret_cast<app_indicator_new_func>(
dlsym(indicator_lib, "app_indicator_new"));
app_indicator_new_with_path =
reinterpret_cast<app_indicator_new_with_path_func>(
dlsym(indicator_lib, "app_indicator_new_with_path"));
app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
dlsym(indicator_lib, "app_indicator_set_status"));
app_indicator_set_attention_icon_full =
reinterpret_cast<app_indicator_set_attention_icon_full_func>(
dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
dlsym(indicator_lib, "app_indicator_set_menu"));
app_indicator_set_icon_full =
reinterpret_cast<app_indicator_set_icon_full_func>(
dlsym(indicator_lib, "app_indicator_set_icon_full"));
app_indicator_set_icon_theme_path =
reinterpret_cast<app_indicator_set_icon_theme_path_func>(
dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
}
// Writes |bitmap| to a file at |path|. Returns true if successful.
bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) {
std::vector<unsigned char> png_data;
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data))
return false;
int bytes_written = base::WriteFile(
path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
return (bytes_written == static_cast<int>(png_data.size()));
}
void DeleteTempDirectory(const base::FilePath& dir_path) {
if (dir_path.empty())
return;
base::DeleteFile(dir_path, true);
}
} // namespace
namespace electron {
namespace gtkui {
AppIndicatorIcon::AppIndicatorIcon(std::string id,
const gfx::ImageSkia& image,
const base::string16& tool_tip)
: id_(id), icon_(nullptr), menu_model_(nullptr), icon_change_count_(0) {
std::unique_ptr<base::Environment> env(base::Environment::Create());
desktop_env_ = base::nix::GetDesktopEnvironment(env.get());
EnsureLibAppIndicatorLoaded();
tool_tip_ = base::UTF16ToUTF8(tool_tip);
SetIcon(image);
}
AppIndicatorIcon::~AppIndicatorIcon() {
if (icon_) {
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
g_object_unref(icon_);
base::PostTask(
FROM_HERE,
{base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&DeleteTempDirectory, temp_dir_));
}
}
// static
bool AppIndicatorIcon::CouldOpen() {
EnsureLibAppIndicatorLoaded();
return g_opened;
}
void AppIndicatorIcon::SetIcon(const gfx::ImageSkia& image) {
if (!g_opened)
return;
++icon_change_count_;
// Copy the bitmap because it may be freed by the time it's accessed in
// another thread.
SkBitmap safe_bitmap = *image.bitmap();
const base::TaskTraits kTraits = {
base::ThreadPool(), base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
if (desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE4 ||
desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE5) {
base::PostTaskAndReplyWithResult(
FROM_HERE, kTraits,
base::BindOnce(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
safe_bitmap, temp_dir_),
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
weak_factory_.GetWeakPtr()));
} else {
base::PostTaskAndReplyWithResult(
FROM_HERE, kTraits,
base::BindOnce(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread,
safe_bitmap, icon_change_count_, id_),
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
weak_factory_.GetWeakPtr()));
}
}
void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
DCHECK(!tool_tip_.empty());
tool_tip_ = base::UTF16ToUTF8(tool_tip);
UpdateClickActionReplacementMenuItem();
}
void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
if (!g_opened)
return;
menu_model_ = model;
// The icon is created asynchronously so it might not exist when the menu is
// set.
if (icon_)
SetMenu();
}
void AppIndicatorIcon::RefreshPlatformContextMenu() {
menu_->Refresh();
}
// static
AppIndicatorIcon::SetImageFromFileParams
AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread(
const SkBitmap& bitmap,
const base::FilePath& existing_temp_dir) {
base::FilePath temp_dir = existing_temp_dir;
if (temp_dir.empty() &&
!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
LOG(WARNING) << "Could not create temporary directory";
return SetImageFromFileParams();
}
base::FilePath icon_theme_path = temp_dir.AppendASCII("icons");
// On KDE4, an image located in a directory ending with
// "icons/hicolor/22x22/apps" can be used as the app indicator image because
// "/usr/share/icons/hicolor/22x22/apps" exists.
base::FilePath image_dir =
icon_theme_path.AppendASCII("hicolor").AppendASCII("22x22").AppendASCII(
"apps");
if (!base::CreateDirectory(image_dir))
return SetImageFromFileParams();
// On KDE4, the name of the image file for each different looking bitmap must
// be unique. It must also be unique across runs of Chrome.
std::vector<unsigned char> bitmap_png_data;
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) {
LOG(WARNING) << "Could not encode icon";
return SetImageFromFileParams();
}
base::MD5Digest digest;
base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]),
bitmap_png_data.size(), &digest);
std::string icon_name = base::StringPrintf(
"electron_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str());
// If |bitmap| is smaller than 22x22, KDE does some really ugly resizing.
// Pad |bitmap| with transparent pixels to make it 22x22.
const int kMinimalSize = 22;
SkBitmap scaled_bitmap;
scaled_bitmap.allocN32Pixels(std::max(bitmap.width(), kMinimalSize),
std::max(bitmap.height(), kMinimalSize));
scaled_bitmap.eraseARGB(0, 0, 0, 0);
SkCanvas canvas(scaled_bitmap);
canvas.drawBitmap(bitmap, (scaled_bitmap.width() - bitmap.width()) / 2,
(scaled_bitmap.height() - bitmap.height()) / 2);
base::FilePath image_path = image_dir.Append(icon_name + ".png");
if (!WriteFile(image_path, scaled_bitmap))
return SetImageFromFileParams();
SetImageFromFileParams params;
params.parent_temp_dir = temp_dir;
params.icon_theme_path = icon_theme_path.value();
params.icon_name = icon_name;
return params;
}
// static
AppIndicatorIcon::SetImageFromFileParams
AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap,
int icon_change_count,
const std::string& id) {
// Create a new temporary directory for each image on Unity since using a
// single temporary directory seems to have issues when changing icons in
// quick succession.
base::FilePath temp_dir;
if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
LOG(WARNING) << "Could not create temporary directory";
return SetImageFromFileParams();
}
std::string icon_name =
base::StringPrintf("%s_%d", id.c_str(), icon_change_count);
base::FilePath image_path = temp_dir.Append(icon_name + ".png");
SetImageFromFileParams params;
if (WriteFile(image_path, bitmap)) {
params.parent_temp_dir = temp_dir;
params.icon_theme_path = temp_dir.value();
params.icon_name = icon_name;
}
return params;
}
void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (params.icon_theme_path.empty())
return;
if (!icon_) {
icon_ =
app_indicator_new_with_path(id_.c_str(), params.icon_name.c_str(),
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
params.icon_theme_path.c_str());
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
SetMenu();
} else {
app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str());
app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon");
}
if (temp_dir_ != params.parent_temp_dir) {
base::PostTask(
FROM_HERE,
{base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&DeleteTempDirectory, temp_dir_));
temp_dir_ = params.parent_temp_dir;
}
}
void AppIndicatorIcon::SetMenu() {
menu_ = std::make_unique<AppIndicatorIconMenu>(menu_model_);
UpdateClickActionReplacementMenuItem();
app_indicator_set_menu(icon_, menu_->GetGtkMenu());
}
void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
// The menu may not have been created yet.
if (!menu_.get())
return;
if (!delegate()->HasClickAction() && menu_model_)
return;
DCHECK(!tool_tip_.empty());
menu_->UpdateClickActionReplacementMenuItem(
tool_tip_.c_str(),
base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
base::Unretained(this)));
}
void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
if (delegate())
delegate()->OnClick();
}
} // namespace gtkui
} // namespace electron

View file

@ -0,0 +1,116 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#define SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/nix/xdg_util.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/views/linux_ui/status_icon_linux.h"
typedef struct _AppIndicator AppIndicator;
typedef struct _GtkWidget GtkWidget;
class SkBitmap;
namespace gfx {
class ImageSkia;
}
namespace ui {
class MenuModel;
}
namespace electron {
namespace gtkui {
class AppIndicatorIconMenu;
// Status icon implementation which uses libappindicator.
class AppIndicatorIcon : public views::StatusIconLinux {
public:
// The id uniquely identifies the new status icon from other chrome status
// icons.
AppIndicatorIcon(std::string id,
const gfx::ImageSkia& image,
const base::string16& tool_tip);
~AppIndicatorIcon() override;
// Indicates whether libappindicator so could be opened.
static bool CouldOpen();
// Overridden from views::StatusIconLinux:
void SetIcon(const gfx::ImageSkia& image) override;
void SetToolTip(const base::string16& tool_tip) override;
void UpdatePlatformContextMenu(ui::MenuModel* menu) override;
void RefreshPlatformContextMenu() override;
private:
struct SetImageFromFileParams {
// The temporary directory in which the icon(s) were written.
base::FilePath parent_temp_dir;
// The icon theme path to pass to libappindicator.
std::string icon_theme_path;
// The icon name to pass to libappindicator.
std::string icon_name;
};
// Writes |bitmap| to a temporary directory on a worker thread. The temporary
// directory is selected based on KDE's quirks.
static SetImageFromFileParams WriteKDE4TempImageOnWorkerThread(
const SkBitmap& bitmap,
const base::FilePath& existing_temp_dir);
// Writes |bitmap| to a temporary directory on a worker thread. The temporary
// directory is selected based on Unity's quirks.
static SetImageFromFileParams WriteUnityTempImageOnWorkerThread(
const SkBitmap& bitmap,
int icon_change_count,
const std::string& id);
void SetImageFromFile(const SetImageFromFileParams& params);
void SetMenu();
// Sets a menu item at the top of the menu as a replacement for the status
// icon click action. Clicking on this menu item should simulate a status icon
// click by despatching a click event.
void UpdateClickActionReplacementMenuItem();
// Callback for when the status icon click replacement menu item is activated.
void OnClickActionReplacementMenuItemActivated();
std::string id_;
std::string tool_tip_;
// Used to select KDE or Unity for image setting.
base::nix::DesktopEnvironment desktop_env_;
// Gtk status icon wrapper
AppIndicator* icon_;
std::unique_ptr<AppIndicatorIconMenu> menu_;
ui::MenuModel* menu_model_;
base::FilePath temp_dir_;
int icon_change_count_;
base::WeakPtrFactory<AppIndicatorIcon> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(AppIndicatorIcon);
};
} // namespace gtkui
} // namespace electron
#endif // SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_

View file

@ -0,0 +1,123 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
#include <gtk/gtk.h>
#include "base/bind.h"
#include "base/debug/leak_annotations.h"
#include "shell/browser/ui/gtk/menu_util.h"
#include "ui/base/models/menu_model.h"
namespace electron {
namespace gtkui {
AppIndicatorIconMenu::AppIndicatorIconMenu(ui::MenuModel* model)
: menu_model_(model),
click_action_replacement_menu_item_added_(false),
gtk_menu_(nullptr),
block_activation_(false) {
{
ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/378770
gtk_menu_ = gtk_menu_new();
}
g_object_ref_sink(gtk_menu_);
if (menu_model_) {
BuildSubmenuFromModel(menu_model_, gtk_menu_,
G_CALLBACK(OnMenuItemActivatedThunk),
&block_activation_, this);
Refresh();
}
}
AppIndicatorIconMenu::~AppIndicatorIconMenu() {
gtk_widget_destroy(gtk_menu_);
g_object_unref(gtk_menu_);
}
void AppIndicatorIconMenu::UpdateClickActionReplacementMenuItem(
const char* label,
const base::Closure& callback) {
click_action_replacement_callback_ = callback;
if (click_action_replacement_menu_item_added_) {
GList* children = gtk_container_get_children(GTK_CONTAINER(gtk_menu_));
for (GList* child = children; child; child = g_list_next(child)) {
if (g_object_get_data(G_OBJECT(child->data), "click-action-item") !=
nullptr) {
gtk_menu_item_set_label(GTK_MENU_ITEM(child->data), label);
break;
}
}
g_list_free(children);
} else {
click_action_replacement_menu_item_added_ = true;
// If |menu_model_| is non empty, add a separator to separate the
// "click action replacement menu item" from the other menu items.
if (menu_model_ && menu_model_->GetItemCount() > 0) {
GtkWidget* menu_item = gtk_separator_menu_item_new();
gtk_widget_show(menu_item);
gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
}
GtkWidget* menu_item = gtk_menu_item_new_with_mnemonic(label);
g_object_set_data(G_OBJECT(menu_item), "click-action-item",
GINT_TO_POINTER(1));
g_signal_connect(menu_item, "activate",
G_CALLBACK(OnClickActionReplacementMenuItemActivatedThunk),
this);
gtk_widget_show(menu_item);
gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
}
}
void AppIndicatorIconMenu::Refresh() {
gtk_container_foreach(GTK_CONTAINER(gtk_menu_), SetMenuItemInfo,
&block_activation_);
}
GtkMenu* AppIndicatorIconMenu::GetGtkMenu() {
return GTK_MENU(gtk_menu_);
}
void AppIndicatorIconMenu::OnClickActionReplacementMenuItemActivated(
GtkWidget* menu_item) {
click_action_replacement_callback_.Run();
}
void AppIndicatorIconMenu::OnMenuItemActivated(GtkWidget* menu_item) {
if (block_activation_)
return;
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
if (!model) {
// There won't be a model for "native" submenus like the "Input Methods"
// context menu. We don't need to handle activation messages for submenus
// anyway, so we can just return here.
DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
return;
}
// The activate signal is sent to radio items as they get deselected;
// ignore it in this case.
if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
return;
}
int id;
if (!GetMenuItemID(menu_item, &id))
return;
// The menu item can still be activated by hotkeys even if it is disabled.
if (model->IsEnabledAt(id))
ExecuteCommand(model, id);
}
} // namespace gtkui
} // namespace electron

View file

@ -0,0 +1,75 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
#define SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
#include "base/callback.h"
#include "base/macros.h"
#include "ui/base/glib/glib_signal.h"
typedef struct _GtkMenu GtkMenu;
typedef struct _GtkWidget GtkWidget;
namespace ui {
class MenuModel;
}
namespace electron {
namespace gtkui {
// The app indicator icon's menu.
class AppIndicatorIconMenu {
public:
explicit AppIndicatorIconMenu(ui::MenuModel* model);
virtual ~AppIndicatorIconMenu();
// Sets a menu item at the top of |gtk_menu_| as a replacement for the app
// indicator icon's click action. |callback| is called when the menu item
// is activated.
void UpdateClickActionReplacementMenuItem(const char* label,
const base::Closure& callback);
// Refreshes all the menu item labels and menu item checked/enabled states.
void Refresh();
GtkMenu* GetGtkMenu();
private:
// Callback for when the "click action replacement" menu item is activated.
CHROMEG_CALLBACK_0(AppIndicatorIconMenu,
void,
OnClickActionReplacementMenuItemActivated,
GtkWidget*);
// Callback for when a menu item is activated.
CHROMEG_CALLBACK_0(AppIndicatorIconMenu,
void,
OnMenuItemActivated,
GtkWidget*);
// Not owned.
ui::MenuModel* menu_model_;
// Whether a "click action replacement" menu item has been added to the menu.
bool click_action_replacement_menu_item_added_;
// Called when the click action replacement menu item is activated. When a
// menu item from |menu_model_| is activated, MenuModel::ActivatedAt() is
// invoked and is assumed to do any necessary processing.
base::Closure click_action_replacement_callback_;
GtkWidget* gtk_menu_;
bool block_activation_;
DISALLOW_COPY_AND_ASSIGN(AppIndicatorIconMenu);
};
} // namespace gtkui
} // namespace electron
#endif // SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_

View file

@ -0,0 +1,86 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk/gtk_status_icon.h"
#include <gtk/gtk.h>
#include "base/debug/leak_annotations.h"
#include "base/strings/utf_string_conversions.h"
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
#include "shell/browser/ui/gtk_util.h"
#include "ui/base/models/menu_model.h"
#include "ui/gfx/image/image_skia.h"
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
namespace electron {
namespace gtkui {
GtkStatusIcon::GtkStatusIcon(const gfx::ImageSkia& image,
const base::string16& tool_tip) {
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*image.bitmap());
{
// GTK has a bug that leaks 384 bytes when creating a GtkStatusIcon. It
// will not be fixed since the status icon was deprecated in version 3.14.
// Luckily, Chromium doesn't need to create a status icon very often, if at
// all.
ANNOTATE_SCOPED_MEMORY_LEAK;
gtk_status_icon_ = gtk_status_icon_new_from_pixbuf(pixbuf);
}
g_object_unref(pixbuf);
g_signal_connect(gtk_status_icon_, "activate", G_CALLBACK(OnClickThunk),
this);
g_signal_connect(gtk_status_icon_, "popup_menu",
G_CALLBACK(OnContextMenuRequestedThunk), this);
SetToolTip(tool_tip);
}
GtkStatusIcon::~GtkStatusIcon() {
gtk_status_icon_set_visible(gtk_status_icon_, FALSE);
g_object_unref(gtk_status_icon_);
}
void GtkStatusIcon::SetIcon(const gfx::ImageSkia& image) {
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*image.bitmap());
gtk_status_icon_set_from_pixbuf(gtk_status_icon_, pixbuf);
g_object_unref(pixbuf);
}
void GtkStatusIcon::SetToolTip(const base::string16& tool_tip) {
gtk_status_icon_set_tooltip_text(gtk_status_icon_,
base::UTF16ToUTF8(tool_tip).c_str());
}
void GtkStatusIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
menu_.reset();
if (model)
menu_ = std::make_unique<AppIndicatorIconMenu>(model);
}
void GtkStatusIcon::RefreshPlatformContextMenu() {
if (menu_.get())
menu_->Refresh();
}
void GtkStatusIcon::OnClick(GtkStatusIcon* status_icon) {
if (delegate())
delegate()->OnClick();
}
void GtkStatusIcon::OnContextMenuRequested(GtkStatusIcon* status_icon,
guint button,
guint32 activate_time) {
if (menu_.get()) {
gtk_menu_popup(menu_->GetGtkMenu(), nullptr, nullptr,
gtk_status_icon_position_menu, gtk_status_icon_, button,
activate_time);
}
}
} // namespace gtkui
} // namespace electron

View file

@ -0,0 +1,65 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
#define SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
#include <memory>
#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/base/glib/glib_integers.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/views/linux_ui/status_icon_linux.h"
typedef struct _GtkStatusIcon GtkStatusIcon;
namespace gfx {
class ImageSkia;
}
namespace ui {
class MenuModel;
}
namespace electron {
namespace gtkui {
class AppIndicatorIconMenu;
// Status icon implementation which uses the system tray X11 spec (via
// GtkStatusIcon).
class GtkStatusIcon : public views::StatusIconLinux {
public:
GtkStatusIcon(const gfx::ImageSkia& image, const base::string16& tool_tip);
~GtkStatusIcon() override;
// Overridden from views::StatusIconLinux:
void SetIcon(const gfx::ImageSkia& image) override;
void SetToolTip(const base::string16& tool_tip) override;
void UpdatePlatformContextMenu(ui::MenuModel* menu) override;
void RefreshPlatformContextMenu() override;
private:
CHROMEG_CALLBACK_0(GtkStatusIcon, void, OnClick, GtkStatusIcon*);
CHROMEG_CALLBACK_2(GtkStatusIcon,
void,
OnContextMenuRequested,
GtkStatusIcon*,
guint,
guint);
::GtkStatusIcon* gtk_status_icon_;
std::unique_ptr<AppIndicatorIconMenu> menu_;
DISALLOW_COPY_AND_ASSIGN(GtkStatusIcon);
};
} // namespace gtkui
} // namespace electron
#endif // SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_

View file

@ -0,0 +1,320 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk/menu_util.h"
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <map>
#include <string>
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "shell/browser/ui/gtk_util.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkUnPreMultiply.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_model.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
namespace electron {
namespace gtkui {
namespace {
int EventFlagsFromGdkState(guint state) {
int flags = ui::EF_NONE;
flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : ui::EF_NONE;
flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_ON : ui::EF_NONE;
flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : ui::EF_NONE;
flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_MOUSE_BUTTON : ui::EF_NONE;
flags |=
(state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_MOUSE_BUTTON : ui::EF_NONE;
flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_MOUSE_BUTTON : ui::EF_NONE;
return flags;
}
guint GetGdkKeyCodeForAccelerator(const ui::Accelerator& accelerator) {
// The second parameter is false because accelerator keys are expressed in
// terms of the non-shift-modified key.
return XKeysymForWindowsKeyCode(accelerator.key_code(), false);
}
GdkModifierType GetGdkModifierForAccelerator(
const ui::Accelerator& accelerator) {
int event_flag = accelerator.modifiers();
int modifier = 0;
if (event_flag & ui::EF_SHIFT_DOWN)
modifier |= GDK_SHIFT_MASK;
if (event_flag & ui::EF_CONTROL_DOWN)
modifier |= GDK_CONTROL_MASK;
if (event_flag & ui::EF_ALT_DOWN)
modifier |= GDK_MOD1_MASK;
return static_cast<GdkModifierType>(modifier);
}
} // namespace
GtkWidget* BuildMenuItemWithImage(const std::string& label, GtkWidget* image) {
// GTK4 removed support for image menu items.
#if GTK_CHECK_VERSION(3, 90, 0)
return gtk_menu_item_new_with_mnemonic(label.c_str());
#else
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
GtkWidget* menu_item = gtk_image_menu_item_new_with_mnemonic(label.c_str());
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
G_GNUC_END_IGNORE_DEPRECATIONS;
return menu_item;
#endif
}
GtkWidget* BuildMenuItemWithImage(const std::string& label,
const gfx::Image& icon) {
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*icon.ToSkBitmap());
GtkWidget* menu_item =
BuildMenuItemWithImage(label, gtk_image_new_from_pixbuf(pixbuf));
g_object_unref(pixbuf);
return menu_item;
}
GtkWidget* BuildMenuItemWithLabel(const std::string& label) {
return gtk_menu_item_new_with_mnemonic(label.c_str());
}
ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
return reinterpret_cast<ui::MenuModel*>(
g_object_get_data(G_OBJECT(menu_item), "model"));
}
GtkWidget* AppendMenuItemToMenu(int index,
ui::MenuModel* model,
GtkWidget* menu_item,
GtkWidget* menu,
bool connect_to_activate,
GCallback item_activated_cb,
void* this_ptr) {
// Set the ID of a menu item.
// Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
g_object_set_data(G_OBJECT(menu_item), "menu-id", GINT_TO_POINTER(index + 1));
// Native menu items do their own thing, so only selectively listen for the
// activate signal.
if (connect_to_activate) {
g_signal_connect(menu_item, "activate", item_activated_cb, this_ptr);
}
// AppendMenuItemToMenu is used both internally when we control menu creation
// from a model (where the model can choose to hide certain menu items), and
// with immediate commands which don't provide the option.
if (model) {
if (model->IsVisibleAt(index))
gtk_widget_show(menu_item);
} else {
gtk_widget_show(menu_item);
}
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
return menu_item;
}
bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) {
gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id");
if (id_ptr != nullptr) {
*menu_id = GPOINTER_TO_INT(id_ptr) - 1;
return true;
}
return false;
}
void ExecuteCommand(ui::MenuModel* model, int id) {
GdkEvent* event = gtk_get_current_event();
int event_flags = 0;
if (event && event->type == GDK_BUTTON_RELEASE)
event_flags = EventFlagsFromGdkState(event->button.state);
model->ActivatedAt(id, event_flags);
if (event)
gdk_event_free(event);
}
void BuildSubmenuFromModel(ui::MenuModel* model,
GtkWidget* menu,
GCallback item_activated_cb,
bool* block_activation,
void* this_ptr) {
std::map<int, GtkWidget*> radio_groups;
GtkWidget* menu_item = nullptr;
for (int i = 0; i < model->GetItemCount(); ++i) {
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
base::UTF16ToUTF8(model->GetLabelAt(i)));
bool connect_to_activate = true;
switch (model->GetTypeAt(i)) {
case ui::MenuModel::TYPE_SEPARATOR:
menu_item = gtk_separator_menu_item_new();
break;
case ui::MenuModel::TYPE_CHECK:
menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
break;
case ui::MenuModel::TYPE_RADIO: {
auto iter = radio_groups.find(model->GetGroupIdAt(i));
if (iter == radio_groups.end()) {
menu_item =
gtk_radio_menu_item_new_with_mnemonic(nullptr, label.c_str());
radio_groups[model->GetGroupIdAt(i)] = menu_item;
} else {
menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
}
break;
}
case ui::MenuModel::TYPE_BUTTON_ITEM: {
NOTIMPLEMENTED();
break;
}
case ui::MenuModel::TYPE_SUBMENU:
case ui::MenuModel::TYPE_COMMAND: {
auto icon_model = model->GetIconAt(i);
if (!icon_model.IsEmpty())
menu_item = BuildMenuItemWithImage(label, icon_model.GetImage());
else
menu_item = BuildMenuItemWithLabel(label);
#if !GTK_CHECK_VERSION(3, 90, 0)
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
if (GTK_IS_IMAGE_MENU_ITEM(menu_item)) {
gtk_image_menu_item_set_always_show_image(
GTK_IMAGE_MENU_ITEM(menu_item), TRUE);
}
G_GNUC_END_IGNORE_DEPRECATIONS;
#endif
break;
}
default:
NOTREACHED();
}
if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
GtkWidget* submenu = gtk_menu_new();
ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i);
BuildSubmenuFromModel(submenu_model, submenu, item_activated_cb,
block_activation, this_ptr);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
// Update all the menu item info in the newly-generated menu.
gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo,
block_activation);
submenu_model->MenuWillShow();
connect_to_activate = false;
}
#if defined(USE_X11)
ui::Accelerator accelerator;
if (model->GetAcceleratorAt(i, &accelerator)) {
gtk_widget_add_accelerator(menu_item, "activate", nullptr,
GetGdkKeyCodeForAccelerator(accelerator),
GetGdkModifierForAccelerator(accelerator),
GTK_ACCEL_VISIBLE);
}
#endif
g_object_set_data(G_OBJECT(menu_item), "model", model);
AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate,
item_activated_cb, this_ptr);
menu_item = nullptr;
}
}
void SetMenuItemInfo(GtkWidget* widget, void* block_activation_ptr) {
if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
// We need to explicitly handle this case because otherwise we'll ask the
// menu delegate about something with an invalid id.
return;
}
int id;
if (!GetMenuItemID(widget, &id))
return;
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
if (!model) {
// If we're not providing the sub menu, then there's no model. For
// example, the IME submenu doesn't have a model.
return;
}
bool* block_activation = static_cast<bool*>(block_activation_ptr);
if (GTK_IS_CHECK_MENU_ITEM(widget)) {
GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
// gtk_check_menu_item_set_active() will send the activate signal. Touching
// the underlying "active" property will also call the "activate" handler
// for this menu item. So we prevent the "activate" handler from
// being called while we set the checkbox.
// Why not use one of the glib signal-blocking functions? Because when we
// toggle a radio button, it will deactivate one of the other radio buttons,
// which we don't have a pointer to.
*block_activation = true;
gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
*block_activation = false;
}
if (GTK_IS_MENU_ITEM(widget)) {
gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
if (model->IsVisibleAt(id)) {
// Update the menu item label if it is dynamic.
if (model->IsItemDynamicAt(id)) {
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
base::UTF16ToUTF8(model->GetLabelAt(id)));
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
#if !GTK_CHECK_VERSION(3, 90, 0)
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
auto icon_model = model->GetIconAt(id);
if (!icon_model.IsEmpty()) {
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(
*icon_model.GetImage().ToSkBitmap());
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
gtk_image_new_from_pixbuf(pixbuf));
g_object_unref(pixbuf);
} else {
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr);
}
}
G_GNUC_END_IGNORE_DEPRECATIONS;
#endif
}
gtk_widget_show(widget);
} else {
gtk_widget_hide(widget);
}
GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
if (submenu) {
gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
block_activation_ptr);
}
}
}
} // namespace gtkui
} // namespace electron

View file

@ -0,0 +1,63 @@
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_GTK_MENU_UTIL_H_
#define SHELL_BROWSER_UI_GTK_MENU_UTIL_H_
#include <gtk/gtk.h>
#include <string>
#include "ui/gfx/image/image.h"
namespace ui {
class MenuModel;
}
namespace electron {
namespace gtkui {
// Builds GtkImageMenuItems.
GtkWidget* BuildMenuItemWithImage(const std::string& label, GtkWidget* image);
GtkWidget* BuildMenuItemWithImage(const std::string& label,
const gfx::Image& icon);
GtkWidget* BuildMenuItemWithLabel(const std::string& label);
ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item);
// This method is used to build the menu dynamically. The return value is the
// new menu item.
GtkWidget* AppendMenuItemToMenu(int index,
ui::MenuModel* model,
GtkWidget* menu_item,
GtkWidget* menu,
bool connect_to_activate,
GCallback item_activated_cb,
void* this_ptr);
// Gets the ID of a menu item.
// Returns true if the menu item has an ID.
bool GetMenuItemID(GtkWidget* menu_item, int* menu_id);
// Execute command associated with specified id.
void ExecuteCommand(ui::MenuModel* model, int id);
// Creates a GtkMenu from |model_|. block_activation_ptr is used to disable
// the item_activated_callback while we set up the set up the menu items.
// See comments in definition of SetMenuItemInfo for more info.
void BuildSubmenuFromModel(ui::MenuModel* model,
GtkWidget* menu,
GCallback item_activated_cb,
bool* block_activation,
void* this_ptr);
// Sets the check mark, enabled/disabled state and dynamic labels on menu items.
void SetMenuItemInfo(GtkWidget* widget, void* block_activation_ptr);
} // namespace gtkui
} // namespace electron
#endif // SHELL_BROWSER_UI_GTK_MENU_UTIL_H_

View file

@ -0,0 +1,62 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/gtk/status_icon.h"
#include <gtk/gtk.h>
#include <memory>
#include "base/strings/stringprintf.h"
#include "shell/browser/ui/gtk/app_indicator_icon.h"
#include "shell/browser/ui/gtk/gtk_status_icon.h"
namespace electron {
namespace gtkui {
namespace {
int indicators_count = 0;
}
bool IsStatusIconSupported() {
#if GTK_CHECK_VERSION(3, 90, 0)
NOTIMPLEMENTED();
return false;
#else
return true;
#endif
}
std::unique_ptr<views::StatusIconLinux> CreateLinuxStatusIcon(
const gfx::ImageSkia& image,
const base::string16& tool_tip,
const char* id_prefix) {
#if GTK_CHECK_VERSION(3, 90, 0)
NOTIMPLEMENTED();
return nullptr;
#else
if (AppIndicatorIcon::CouldOpen()) {
++indicators_count;
return std::unique_ptr<views::StatusIconLinux>(new AppIndicatorIcon(
base::StringPrintf("%s%d", id_prefix, indicators_count), image,
tool_tip));
} else {
return std::unique_ptr<views::StatusIconLinux>(
new GtkStatusIcon(image, tool_tip));
}
return nullptr;
#endif
}
} // namespace gtkui
} // namespace electron

View file

@ -0,0 +1,32 @@
// Copyright (c) 2020 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
#define SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
#include <memory>
#include "base/strings/string_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/linux_ui/status_icon_linux.h"
namespace electron {
namespace gtkui {
bool IsStatusIconSupported();
std::unique_ptr<views::StatusIconLinux> CreateLinuxStatusIcon(
const gfx::ImageSkia& image,
const base::string16& tool_tip,
const char* id_prefix);
} // namespace gtkui
} // namespace electron
#endif // SHELL_BROWSER_UI_GTK_STATUS_ICON_H_

View file

@ -6,44 +6,20 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h"
#include "shell/browser/browser.h"
#include "shell/browser/ui/gtk/status_icon.h"
#include "shell/common/application_info.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace electron {
namespace {
gfx::ImageSkia GetIconFromImage(const gfx::Image& image) {
auto icon = image.AsImageSkia();
auto size = icon.size();
// Systray icons are historically 22 pixels tall, e.g. on Ubuntu GNOME,
// KDE, and xfce. Taller icons are causing incorrect sizing issues -- e.g.
// a 1x1 icon -- so for now, pin the height manually. Similar behavior to
// https://bugs.chromium.org/p/chromium/issues/detail?id=1042098 ?
static constexpr int DESIRED_HEIGHT = 22;
if ((size.height() != 0) && (size.height() != DESIRED_HEIGHT)) {
const double ratio = DESIRED_HEIGHT / static_cast<double>(size.height());
size = gfx::Size(static_cast<int>(ratio * size.width()),
static_cast<int>(ratio * size.height()));
icon = gfx::ImageSkiaOperations::CreateResizedImage(
icon, skia::ImageOperations::RESIZE_BEST, size);
}
return icon;
}
} // namespace
TrayIconGtk::TrayIconGtk() = default;
TrayIconGtk::~TrayIconGtk() = default;
void TrayIconGtk::SetImage(const gfx::Image& image) {
image_ = GetIconFromImage(image);
image_ = image.AsImageSkia();
if (icon_) {
icon_->SetIcon(image_);
@ -52,9 +28,8 @@ void TrayIconGtk::SetImage(const gfx::Image& image) {
tool_tip_ = base::UTF8ToUTF16(GetApplicationName());
icon_ = base::MakeRefCounted<StatusIconLinuxDbus>();
icon_->SetIcon(image_);
icon_->SetToolTip(tool_tip_);
icon_ = gtkui::CreateLinuxStatusIcon(image_, tool_tip_,
Browser::Get()->GetName().c_str());
icon_->SetDelegate(this);
}

View file

@ -8,7 +8,6 @@
#include <memory>
#include <string>
#include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h"
#include "shell/browser/ui/tray_icon.h"
#include "ui/views/linux_ui/status_icon_linux.h"
@ -39,7 +38,7 @@ class TrayIconGtk : public TrayIcon, public views::StatusIconLinux::Delegate {
void OnImplInitializationFailed() override;
private:
scoped_refptr<StatusIconLinuxDbus> icon_;
std::unique_ptr<views::StatusIconLinux> icon_;
gfx::ImageSkia image_;
base::string16 tool_tip_;
ui::MenuModel* menu_model_;