Browse Source

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

Cheng Zhao 2 years ago
parent
commit
16a7bd7102

+ 0 - 8
BUILD.gn

@@ -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",
     ]

+ 9 - 9
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:

+ 1 - 0
patches/chromium/.patches

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

+ 61 - 0
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 <[email protected]>
+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

+ 0 - 376
shell/browser/ui/gtk/app_indicator_icon.cc

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

+ 0 - 114
shell/browser/ui/gtk/app_indicator_icon.h

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

+ 0 - 116
shell/browser/ui/gtk/app_indicator_icon_menu.cc

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

+ 0 - 73
shell/browser/ui/gtk/app_indicator_icon_menu.h

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

+ 0 - 82
shell/browser/ui/gtk/gtk_status_icon.cc

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

+ 0 - 62
shell/browser/ui/gtk/gtk_status_icon.h

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

+ 0 - 56
shell/browser/ui/gtk/status_icon.cc

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

+ 0 - 28
shell/browser/ui/gtk/status_icon.h

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

+ 44 - 22
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;
 }
 

+ 11 - 2
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<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