diff --git a/BUILD.gn b/BUILD.gn index 3c06c3608505b..c905891eb86fc 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -631,16 +631,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", ] diff --git a/docs/api/tray.md b/docs/api/tray.md index 0cb995ce2fb3d..f64d02debf93a 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -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: diff --git a/patches/chromium/.patches b/patches/chromium/.patches index c07995b093a12..50a0f9bc50e0c 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -121,3 +121,4 @@ preconnect_manager.patch fix_remove_caption-removing_style_call.patch build_allow_electron_to_use_exec_script.patch cherry-pick-7196a42b42ce.patch +fix_tray_icon_gone_on_lock_screen.patch diff --git a/patches/chromium/fix_tray_icon_gone_on_lock_screen.patch b/patches/chromium/fix_tray_icon_gone_on_lock_screen.patch new file mode 100644 index 0000000000000..d641b34bf3f1f --- /dev/null +++ b/patches/chromium/fix_tray_icon_gone_on_lock_screen.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cheng Zhao +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 diff --git a/shell/browser/ui/gtk/app_indicator_icon.cc b/shell/browser/ui/gtk/app_indicator_icon.cc deleted file mode 100644 index 602f2bf94ee72..0000000000000 --- a/shell/browser/ui/gtk/app_indicator_icon.cc +++ /dev/null @@ -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 -#include - -#include -#include -#include - -#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( - dlsym(indicator_lib, "app_indicator_new")); - - app_indicator_new_with_path = - reinterpret_cast( - dlsym(indicator_lib, "app_indicator_new_with_path")); - - app_indicator_set_status = reinterpret_cast( - dlsym(indicator_lib, "app_indicator_set_status")); - - app_indicator_set_attention_icon_full = - reinterpret_cast( - dlsym(indicator_lib, "app_indicator_set_attention_icon_full")); - - app_indicator_set_menu = reinterpret_cast( - dlsym(indicator_lib, "app_indicator_set_menu")); - - app_indicator_set_icon_full = - reinterpret_cast( - dlsym(indicator_lib, "app_indicator_set_icon_full")); - - app_indicator_set_icon_theme_path = - reinterpret_cast( - dlsym(indicator_lib, "app_indicator_set_icon_theme_path")); -} - -// Writes |bitmap| to a file at |path|. Returns true if successful. -bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) { - std::vector png_data; - if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data)) - return false; - int bytes_written = base::WriteFile( - path, reinterpret_cast(&png_data[0]), png_data.size()); - return (bytes_written == static_cast(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 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(&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(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 diff --git a/shell/browser/ui/gtk/app_indicator_icon.h b/shell/browser/ui/gtk/app_indicator_icon.h deleted file mode 100644 index 8695545d9b4d9..0000000000000 --- a/shell/browser/ui/gtk/app_indicator_icon.h +++ /dev/null @@ -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 -#include - -#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 menu_; - ui::MenuModel* menu_model_ = nullptr; - - base::FilePath temp_dir_; - int icon_change_count_ = 0; - - base::WeakPtrFactory weak_factory_{this}; -}; - -} // namespace electron::gtkui - -#endif // ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_ diff --git a/shell/browser/ui/gtk/app_indicator_icon_menu.cc b/shell/browser/ui/gtk/app_indicator_icon_menu.cc deleted file mode 100644 index 1d2cd04428e7e..0000000000000 --- a/shell/browser/ui/gtk/app_indicator_icon_menu.cc +++ /dev/null @@ -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 - -#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 diff --git a/shell/browser/ui/gtk/app_indicator_icon_menu.h b/shell/browser/ui/gtk/app_indicator_icon_menu.h deleted file mode 100644 index cafcb3c601de1..0000000000000 --- a/shell/browser/ui/gtk/app_indicator_icon_menu.h +++ /dev/null @@ -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_ diff --git a/shell/browser/ui/gtk/gtk_status_icon.cc b/shell/browser/ui/gtk/gtk_status_icon.cc deleted file mode 100644 index 69843f30b8ac3..0000000000000 --- a/shell/browser/ui/gtk/gtk_status_icon.cc +++ /dev/null @@ -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 - -#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(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 diff --git a/shell/browser/ui/gtk/gtk_status_icon.h b/shell/browser/ui/gtk/gtk_status_icon.h deleted file mode 100644 index 7d7163cea96bd..0000000000000 --- a/shell/browser/ui/gtk/gtk_status_icon.h +++ /dev/null @@ -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 - -#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 menu_; -}; - -} // namespace electron::gtkui - -#endif // ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_ diff --git a/shell/browser/ui/gtk/status_icon.cc b/shell/browser/ui/gtk/status_icon.cc deleted file mode 100644 index dea4a7f952794..0000000000000 --- a/shell/browser/ui/gtk/status_icon.cc +++ /dev/null @@ -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 - -#include - -#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 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( - base::StringPrintf("%s%d", id_prefix, indicators_count), image, - tool_tip); - } else { - return std::make_unique(image, tool_tip); - } -#endif -} - -} // namespace electron::gtkui diff --git a/shell/browser/ui/gtk/status_icon.h b/shell/browser/ui/gtk/status_icon.h deleted file mode 100644 index df80d03157e04..0000000000000 --- a/shell/browser/ui/gtk/status_icon.h +++ /dev/null @@ -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 - -#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 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_ diff --git a/shell/browser/ui/tray_icon_gtk.cc b/shell/browser/ui/tray_icon_gtk.cc index 67dbe7c3c2384..4302a45d2e302 100644 --- a/shell/browser/ui/tray_icon_gtk.cc +++ b/shell/browser/ui/tray_icon_gtk.cc @@ -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; - -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); +} -void TrayIconGtk::SetImage(const gfx::Image& image) { - image_ = image.AsImageSkia(); +} // namespace - if (icon_) { - icon_->SetIcon(image_); - return; - } +TrayIconGtk::TrayIconGtk() + : status_icon_(new StatusIconLinuxDbus), status_icon_type_(kTypeDbus) { + status_icon_->SetDelegate(this); +} - tool_tip_ = base::UTF8ToUTF16(GetApplicationName()); +TrayIconGtk::~TrayIconGtk() = default; - icon_ = gtkui::CreateLinuxStatusIcon(image_, tool_tip_, - Browser::Get()->GetName().c_str()); - icon_->SetDelegate(this); +void TrayIconGtk::SetImage(const gfx::Image& image) { + 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; } diff --git a/shell/browser/ui/tray_icon_gtk.h b/shell/browser/ui/tray_icon_gtk.h index 7da8890d6b702..11feec449a717 100644 --- a/shell/browser/ui/tray_icon_gtk.h +++ b/shell/browser/ui/tray_icon_gtk.h @@ -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 icon_; + enum StatusIconType { + kTypeDbus, + kTypeNone, + }; + + scoped_refptr status_icon_; + StatusIconType status_icon_type_; + gfx::ImageSkia image_; std::u16string tool_tip_; - ui::MenuModel* menu_model_; + raw_ptr menu_model_ = nullptr; }; } // namespace electron