fix: reimplement Tray with StatusIconLinuxDbus on Linux (#36333)

This commit is contained in:
Cheng Zhao 2022-11-29 04:36:25 +09:00 committed by GitHub
parent bbb590b777
commit 16a7bd7102
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 126 additions and 948 deletions

View file

@ -1,376 +0,0 @@
// 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/logging.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/thread_pool.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::DeletePathRecursively(dir_path);
}
} // namespace
namespace electron::gtkui {
AppIndicatorIcon::AppIndicatorIcon(std::string id,
const gfx::ImageSkia& image,
const std::u16string& tool_tip)
: id_(id) {
auto 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::ThreadPool::PostTask(
FROM_HERE, {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::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::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, kTraits,
base::BindOnce(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
safe_bitmap, temp_dir_),
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
weak_factory_.GetWeakPtr()));
} else {
base::ThreadPool::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 std::u16string& 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.drawImage(bitmap.asImage(),
(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::ThreadPool::PostTask(
FROM_HERE, {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::BindRepeating(
&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
base::Unretained(this)));
}
void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
if (delegate())
delegate()->OnClick();
}
} // namespace electron::gtkui

View file

@ -1,114 +0,0 @@
// 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 ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#define ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#include <memory>
#include <string>
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/nix/xdg_util.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/linux/status_icon_linux.h"
typedef struct _AppIndicator AppIndicator;
typedef struct _GtkWidget GtkWidget;
class SkBitmap;
namespace gfx {
class ImageSkia;
}
namespace ui {
class MenuModel;
}
namespace electron::gtkui {
class AppIndicatorIconMenu;
// Status icon implementation which uses libappindicator.
class AppIndicatorIcon : public ui::StatusIconLinux {
public:
// The id uniquely identifies the new status icon from other chrome status
// icons.
AppIndicatorIcon(std::string id,
const gfx::ImageSkia& image,
const std::u16string& tool_tip);
~AppIndicatorIcon() override;
// disable copy
AppIndicatorIcon(const AppIndicatorIcon&) = delete;
AppIndicatorIcon& operator=(const AppIndicatorIcon&) = delete;
// Indicates whether libappindicator so could be opened.
static bool CouldOpen();
// Overridden from ui::StatusIconLinux:
void SetIcon(const gfx::ImageSkia& image) override;
void SetToolTip(const std::u16string& 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_ = nullptr;
std::unique_ptr<AppIndicatorIconMenu> menu_;
ui::MenuModel* menu_model_ = nullptr;
base::FilePath temp_dir_;
int icon_change_count_ = 0;
base::WeakPtrFactory<AppIndicatorIcon> weak_factory_{this};
};
} // namespace electron::gtkui
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_

View file

@ -1,116 +0,0 @@
// 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::gtkui {
AppIndicatorIconMenu::AppIndicatorIconMenu(ui::MenuModel* model)
: menu_model_(model) {
{
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::RepeatingClosure& 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 electron::gtkui

View file

@ -1,73 +0,0 @@
// 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 ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
#define ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
#include "base/callback.h"
#include "ui/base/glib/glib_signal.h"
typedef struct _GtkMenu GtkMenu;
typedef struct _GtkWidget GtkWidget;
namespace ui {
class MenuModel;
}
namespace electron::gtkui {
// The app indicator icon's menu.
class AppIndicatorIconMenu {
public:
explicit AppIndicatorIconMenu(ui::MenuModel* model);
virtual ~AppIndicatorIconMenu();
// disable copy
AppIndicatorIconMenu(const AppIndicatorIconMenu&) = delete;
AppIndicatorIconMenu& operator=(const AppIndicatorIconMenu&) = delete;
// 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::RepeatingClosure& 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_ = false;
// 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::RepeatingClosure click_action_replacement_callback_;
GtkWidget* gtk_menu_ = nullptr;
bool block_activation_ = false;
};
} // namespace electron::gtkui
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_

View file

@ -1,82 +0,0 @@
// 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::gtkui {
GtkStatusIcon::GtkStatusIcon(const gfx::ImageSkia& image,
const std::u16string& 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 std::u16string& 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 electron::gtkui

View file

@ -1,62 +0,0 @@
// 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 ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
#define ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
#include <memory>
#include "ui/base/glib/glib_integers.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/linux/status_icon_linux.h"
typedef struct _GtkStatusIcon GtkStatusIcon;
namespace gfx {
class ImageSkia;
}
namespace ui {
class MenuModel;
}
namespace electron::gtkui {
class AppIndicatorIconMenu;
// Status icon implementation which uses the system tray X11 spec (via
// GtkStatusIcon).
class GtkStatusIcon : public ui::StatusIconLinux {
public:
GtkStatusIcon(const gfx::ImageSkia& image, const std::u16string& tool_tip);
~GtkStatusIcon() override;
// disable copy
GtkStatusIcon(const GtkStatusIcon&) = delete;
GtkStatusIcon& operator=(const GtkStatusIcon&) = delete;
// Overridden from ui::StatusIconLinux:
void SetIcon(const gfx::ImageSkia& image) override;
void SetToolTip(const std::u16string& 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_;
};
} // namespace electron::gtkui
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_

View file

@ -1,56 +0,0 @@
// 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::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<ui::StatusIconLinux> CreateLinuxStatusIcon(
const gfx::ImageSkia& image,
const std::u16string& tool_tip,
const char* id_prefix) {
#if GTK_CHECK_VERSION(3, 90, 0)
NOTIMPLEMENTED();
return nullptr;
#else
if (AppIndicatorIcon::CouldOpen()) {
++indicators_count;
return std::make_unique<AppIndicatorIcon>(
base::StringPrintf("%s%d", id_prefix, indicators_count), image,
tool_tip);
} else {
return std::make_unique<GtkStatusIcon>(image, tool_tip);
}
#endif
}
} // namespace electron::gtkui

View file

@ -1,28 +0,0 @@
// 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 ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
#define ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
#include <memory>
#include "base/strings/string_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/linux/status_icon_linux.h"
namespace electron::gtkui {
bool IsStatusIconSupported();
std::unique_ptr<ui::StatusIconLinux> CreateLinuxStatusIcon(
const gfx::ImageSkia& image,
const std::u16string& tool_tip,
const char* id_prefix);
} // namespace electron::gtkui
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_

View file

@ -4,43 +4,54 @@
#include "shell/browser/ui/tray_icon_gtk.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.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"
#include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h"
#include "ui/gfx/image/image_skia_rep.h"
namespace electron {
TrayIconGtk::TrayIconGtk() = default;
namespace {
gfx::ImageSkia GetBestImageRep(const gfx::ImageSkia& image) {
image.EnsureRepsForSupportedScales();
float best_scale = 0.0f;
SkBitmap best_rep;
for (const auto& rep : image.image_reps()) {
if (rep.scale() > best_scale) {
best_scale = rep.scale();
best_rep = rep.GetBitmap();
}
}
// All status icon implementations want the image in pixel coordinates, so use
// a scale factor of 1.
return gfx::ImageSkia::CreateFromBitmap(best_rep, 1.0f);
}
} // namespace
TrayIconGtk::TrayIconGtk()
: status_icon_(new StatusIconLinuxDbus), status_icon_type_(kTypeDbus) {
status_icon_->SetDelegate(this);
}
TrayIconGtk::~TrayIconGtk() = default;
void TrayIconGtk::SetImage(const gfx::Image& image) {
image_ = image.AsImageSkia();
if (icon_) {
icon_->SetIcon(image_);
return;
}
tool_tip_ = base::UTF8ToUTF16(GetApplicationName());
icon_ = gtkui::CreateLinuxStatusIcon(image_, tool_tip_,
Browser::Get()->GetName().c_str());
icon_->SetDelegate(this);
image_ = GetBestImageRep(image.AsImageSkia());
if (status_icon_)
status_icon_->SetIcon(image_);
}
void TrayIconGtk::SetToolTip(const std::string& tool_tip) {
tool_tip_ = base::UTF8ToUTF16(tool_tip);
icon_->SetToolTip(tool_tip_);
if (status_icon_)
status_icon_->SetToolTip(tool_tip_);
}
void TrayIconGtk::SetContextMenu(ElectronMenuModel* menu_model) {
menu_model_ = menu_model;
icon_->UpdatePlatformContextMenu(menu_model_);
if (status_icon_)
status_icon_->UpdatePlatformContextMenu(menu_model_);
}
const gfx::ImageSkia& TrayIconGtk::GetImage() const {
@ -55,13 +66,24 @@ ui::MenuModel* TrayIconGtk::GetMenuModel() const {
return menu_model_;
}
void TrayIconGtk::OnImplInitializationFailed() {}
void TrayIconGtk::OnImplInitializationFailed() {
switch (status_icon_type_) {
case kTypeDbus:
status_icon_ = nullptr;
status_icon_type_ = kTypeNone;
return;
case kTypeNone:
NOTREACHED();
}
}
void TrayIconGtk::OnClick() {
NotifyClicked();
}
bool TrayIconGtk::HasClickAction() {
// Returning true will make the tooltip show as an additional context menu
// item, which makes sense in Chrome but not in most Electron apps.
return false;
}

View file

@ -11,6 +11,8 @@
#include "shell/browser/ui/tray_icon.h"
#include "ui/linux/status_icon_linux.h"
class StatusIconLinuxDbus;
namespace electron {
class TrayIconGtk : public TrayIcon, public ui::StatusIconLinux::Delegate {
@ -34,10 +36,17 @@ class TrayIconGtk : public TrayIcon, public ui::StatusIconLinux::Delegate {
void OnImplInitializationFailed() override;
private:
std::unique_ptr<ui::StatusIconLinux> icon_;
enum StatusIconType {
kTypeDbus,
kTypeNone,
};
scoped_refptr<StatusIconLinuxDbus> status_icon_;
StatusIconType status_icon_type_;
gfx::ImageSkia image_;
std::u16string tool_tip_;
ui::MenuModel* menu_model_;
raw_ptr<ui::MenuModel> menu_model_ = nullptr;
};
} // namespace electron