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

This commit is contained in:
Cheng Zhao 2022-11-29 04:36:25 +09:00 коммит произвёл GitHub
Родитель bbb590b777
Коммит 16a7bd7102
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 126 добавлений и 948 удалений

Просмотреть файл

@ -632,16 +632,8 @@ source_set("electron_lib") {
sources += [
"shell/browser/certificate_manager_model.cc",
"shell/browser/certificate_manager_model.h",
"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",
]

Просмотреть файл

@ -27,17 +27,14 @@ app.whenReady().then(() => {
__Platform Considerations__
If you want to keep exact same behaviors on all platforms, you should not
rely on the `click` event; instead, always attach a context menu to the tray icon.
__Linux__
* On Linux distributions that only have app indicator support, you have to
install `libappindicator1` to make the tray icon work.
* The app indicator will be used if it is supported, otherwise
`GtkStatusIcon` will be used instead.
* App indicator will only be shown when it has a context menu.
* The `click` event is ignored when using the app indicator.
* Tray icon requires support of [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/)
in user's desktop environment.
* The `click` event is emitted when the tray icon receives activation from
user, however the StatusNotifierItem spec does not specify which action would
cause an activation, for some environments it is left mouse click, but for
some it might be double left mouse click.
* In order for changes made to individual `MenuItem`s to take effect,
you have to call `setContextMenu` again. For example:
@ -92,6 +89,9 @@ Returns:
Emitted when the tray icon is clicked.
Note that on Linux this event is emitted when the tray icon receives an
activation, which might not necessarily be left mouse click.
#### Event: 'right-click' _macOS_ _Windows_
Returns:

Просмотреть файл

@ -121,5 +121,6 @@ preconnect_manager.patch
fix_remove_caption-removing_style_call.patch
build_allow_electron_to_use_exec_script.patch
build_only_use_the_mas_build_config_in_the_required_components.patch
fix_tray_icon_gone_on_lock_screen.patch
chore_introduce_blocking_api_for_electron.patch
chore_patch_out_partition_attribute_dcheck_for_webviews.patch

Просмотреть файл

@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Cheng Zhao <zcbenz@gmail.com>
Date: Tue, 15 Nov 2022 09:38:25 +0900
Subject: Re-register status item when owner of status watcher is changed
https://chromium-review.googlesource.com/c/chromium/src/+/4022621
diff --git a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
index f3c9dfa9ca33496a9c45cd0c780d3d629aeb4663..387b59a1015b51690810b90a4ac65df862b337f3 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
+++ b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
@@ -381,6 +381,13 @@ void StatusIconLinuxDbus::OnInitialized(bool success) {
return;
}
+ watcher_->SetNameOwnerChangedCallback(
+ base::BindRepeating(&StatusIconLinuxDbus::NameOwnerChangedReceived,
+ weak_factory_.GetWeakPtr()));
+ RegisterStatusNotifierItem();
+}
+
+void StatusIconLinuxDbus::RegisterStatusNotifierItem() {
dbus::MethodCall method_call(kInterfaceStatusNotifierWatcher,
kMethodRegisterStatusNotifierItem);
dbus::MessageWriter writer(&method_call);
@@ -396,6 +403,14 @@ void StatusIconLinuxDbus::OnRegistered(dbus::Response* response) {
delegate_->OnImplInitializationFailed();
}
+void StatusIconLinuxDbus::NameOwnerChangedReceived(
+ const std::string& old_owner,
+ const std::string& new_owner) {
+ // Re-register the item when the StatusNotifierWatcher has a new owner.
+ if (!new_owner.empty())
+ RegisterStatusNotifierItem();
+}
+
void StatusIconLinuxDbus::OnActivate(
dbus::MethodCall* method_call,
dbus::ExportedObject::ResponseSender sender) {
diff --git a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h
index e7628de42f980fa3535cab9dfffd0deab30f8812..eae1c332a0972aefb8843cac947aeb2f4c48d360 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h
+++ b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h
@@ -74,10 +74,16 @@ class StatusIconLinuxDbus : public ui::StatusIconLinux,
const std::string& method_name,
bool success);
void OnInitialized(bool success);
+ void RegisterStatusNotifierItem();
// Step 5: register the StatusNotifierItem with the StatusNotifierWatcher.
void OnRegistered(dbus::Response* response);
+ // Called when the name owner of StatusNotifierWatcher has changed, which
+ // can happen when lock/unlock screen.
+ void NameOwnerChangedReceived(const std::string& old_owner,
+ const std::string& new_owner);
+
// DBus methods.
// Action -> KDE behavior:
// Left-click -> Activate

Просмотреть файл

@ -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

Просмотреть файл

@ -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_

Просмотреть файл

@ -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

Просмотреть файл

@ -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_

Просмотреть файл

@ -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

Просмотреть файл

@ -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_

Просмотреть файл

@ -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

Просмотреть файл

@ -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_

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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