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

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

View file

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

View file

@ -121,5 +121,6 @@ preconnect_manager.patch
fix_remove_caption-removing_style_call.patch fix_remove_caption-removing_style_call.patch
build_allow_electron_to_use_exec_script.patch build_allow_electron_to_use_exec_script.patch
build_only_use_the_mas_build_config_in_the_required_components.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_introduce_blocking_api_for_electron.patch
chore_patch_out_partition_attribute_dcheck_for_webviews.patch chore_patch_out_partition_attribute_dcheck_for_webviews.patch

View file

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

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 "shell/browser/ui/tray_icon_gtk.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "shell/browser/browser.h" #include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h"
#include "shell/browser/ui/gtk/status_icon.h" #include "ui/gfx/image/image_skia_rep.h"
#include "shell/common/application_info.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace electron { 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; TrayIconGtk::~TrayIconGtk() = default;
void TrayIconGtk::SetImage(const gfx::Image& image) { void TrayIconGtk::SetImage(const gfx::Image& image) {
image_ = image.AsImageSkia(); image_ = GetBestImageRep(image.AsImageSkia());
if (status_icon_)
if (icon_) { status_icon_->SetIcon(image_);
icon_->SetIcon(image_);
return;
}
tool_tip_ = base::UTF8ToUTF16(GetApplicationName());
icon_ = gtkui::CreateLinuxStatusIcon(image_, tool_tip_,
Browser::Get()->GetName().c_str());
icon_->SetDelegate(this);
} }
void TrayIconGtk::SetToolTip(const std::string& tool_tip) { void TrayIconGtk::SetToolTip(const std::string& tool_tip) {
tool_tip_ = base::UTF8ToUTF16(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) { void TrayIconGtk::SetContextMenu(ElectronMenuModel* menu_model) {
menu_model_ = menu_model; menu_model_ = menu_model;
icon_->UpdatePlatformContextMenu(menu_model_); if (status_icon_)
status_icon_->UpdatePlatformContextMenu(menu_model_);
} }
const gfx::ImageSkia& TrayIconGtk::GetImage() const { const gfx::ImageSkia& TrayIconGtk::GetImage() const {
@ -55,13 +66,24 @@ ui::MenuModel* TrayIconGtk::GetMenuModel() const {
return menu_model_; 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() { void TrayIconGtk::OnClick() {
NotifyClicked(); NotifyClicked();
} }
bool TrayIconGtk::HasClickAction() { 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; return false;
} }

View file

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