fix: reimplement Tray with StatusIconLinuxDbus on Linux (#36333)
This commit is contained in:
parent
bbb590b777
commit
16a7bd7102
14 changed files with 126 additions and 948 deletions
8
BUILD.gn
8
BUILD.gn
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
61
patches/chromium/fix_tray_icon_gone_on_lock_screen.patch
Normal file
61
patches/chromium/fix_tray_icon_gone_on_lock_screen.patch
Normal 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
|
|
@ -1,376 +0,0 @@
|
||||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#include "shell/browser/ui/gtk/app_indicator_icon.h"
|
|
||||||
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "base/bind.h"
|
|
||||||
#include "base/environment.h"
|
|
||||||
#include "base/files/file_util.h"
|
|
||||||
#include "base/hash/md5.h"
|
|
||||||
#include "base/logging.h"
|
|
||||||
#include "base/memory/ref_counted_memory.h"
|
|
||||||
#include "base/strings/string_number_conversions.h"
|
|
||||||
#include "base/strings/stringprintf.h"
|
|
||||||
#include "base/strings/utf_string_conversions.h"
|
|
||||||
#include "base/task/thread_pool.h"
|
|
||||||
#include "content/public/browser/browser_thread.h"
|
|
||||||
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
|
|
||||||
#include "third_party/skia/include/core/SkBitmap.h"
|
|
||||||
#include "third_party/skia/include/core/SkCanvas.h"
|
|
||||||
#include "ui/base/models/menu_model.h"
|
|
||||||
#include "ui/gfx/codec/png_codec.h"
|
|
||||||
#include "ui/gfx/image/image.h"
|
|
||||||
#include "ui/gfx/image/image_skia.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
|
|
||||||
APP_INDICATOR_CATEGORY_COMMUNICATIONS,
|
|
||||||
APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
|
|
||||||
APP_INDICATOR_CATEGORY_HARDWARE,
|
|
||||||
APP_INDICATOR_CATEGORY_OTHER
|
|
||||||
} AppIndicatorCategory;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
APP_INDICATOR_STATUS_PASSIVE,
|
|
||||||
APP_INDICATOR_STATUS_ACTIVE,
|
|
||||||
APP_INDICATOR_STATUS_ATTENTION
|
|
||||||
} AppIndicatorStatus;
|
|
||||||
|
|
||||||
typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
|
|
||||||
const gchar* icon_name,
|
|
||||||
AppIndicatorCategory category);
|
|
||||||
|
|
||||||
typedef AppIndicator* (*app_indicator_new_with_path_func)(
|
|
||||||
const gchar* id,
|
|
||||||
const gchar* icon_name,
|
|
||||||
AppIndicatorCategory category,
|
|
||||||
const gchar* icon_theme_path);
|
|
||||||
|
|
||||||
typedef void (*app_indicator_set_status_func)(AppIndicator* self,
|
|
||||||
AppIndicatorStatus status);
|
|
||||||
|
|
||||||
typedef void (*app_indicator_set_attention_icon_full_func)(
|
|
||||||
AppIndicator* self,
|
|
||||||
const gchar* icon_name,
|
|
||||||
const gchar* icon_desc);
|
|
||||||
|
|
||||||
typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
|
|
||||||
|
|
||||||
typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
|
|
||||||
const gchar* icon_name,
|
|
||||||
const gchar* icon_desc);
|
|
||||||
|
|
||||||
typedef void (*app_indicator_set_icon_theme_path_func)(
|
|
||||||
AppIndicator* self,
|
|
||||||
const gchar* icon_theme_path);
|
|
||||||
|
|
||||||
bool g_attempted_load = false;
|
|
||||||
bool g_opened = false;
|
|
||||||
|
|
||||||
// Retrieved functions from libappindicator.
|
|
||||||
app_indicator_new_func app_indicator_new = nullptr;
|
|
||||||
app_indicator_new_with_path_func app_indicator_new_with_path = nullptr;
|
|
||||||
app_indicator_set_status_func app_indicator_set_status = nullptr;
|
|
||||||
app_indicator_set_attention_icon_full_func
|
|
||||||
app_indicator_set_attention_icon_full = nullptr;
|
|
||||||
app_indicator_set_menu_func app_indicator_set_menu = nullptr;
|
|
||||||
app_indicator_set_icon_full_func app_indicator_set_icon_full = nullptr;
|
|
||||||
app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path =
|
|
||||||
nullptr;
|
|
||||||
|
|
||||||
void EnsureLibAppIndicatorLoaded() {
|
|
||||||
if (g_attempted_load)
|
|
||||||
return;
|
|
||||||
|
|
||||||
g_attempted_load = true;
|
|
||||||
|
|
||||||
std::string lib_name =
|
|
||||||
"libappindicator" + base::NumberToString(GTK_MAJOR_VERSION) + ".so";
|
|
||||||
void* indicator_lib = dlopen(lib_name.c_str(), RTLD_LAZY);
|
|
||||||
|
|
||||||
if (!indicator_lib) {
|
|
||||||
lib_name += ".1";
|
|
||||||
indicator_lib = dlopen(lib_name.c_str(), RTLD_LAZY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!indicator_lib)
|
|
||||||
return;
|
|
||||||
|
|
||||||
g_opened = true;
|
|
||||||
|
|
||||||
app_indicator_new = reinterpret_cast<app_indicator_new_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_new"));
|
|
||||||
|
|
||||||
app_indicator_new_with_path =
|
|
||||||
reinterpret_cast<app_indicator_new_with_path_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_new_with_path"));
|
|
||||||
|
|
||||||
app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_set_status"));
|
|
||||||
|
|
||||||
app_indicator_set_attention_icon_full =
|
|
||||||
reinterpret_cast<app_indicator_set_attention_icon_full_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
|
|
||||||
|
|
||||||
app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_set_menu"));
|
|
||||||
|
|
||||||
app_indicator_set_icon_full =
|
|
||||||
reinterpret_cast<app_indicator_set_icon_full_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_set_icon_full"));
|
|
||||||
|
|
||||||
app_indicator_set_icon_theme_path =
|
|
||||||
reinterpret_cast<app_indicator_set_icon_theme_path_func>(
|
|
||||||
dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes |bitmap| to a file at |path|. Returns true if successful.
|
|
||||||
bool WriteFile(const base::FilePath& path, const SkBitmap& bitmap) {
|
|
||||||
std::vector<unsigned char> png_data;
|
|
||||||
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data))
|
|
||||||
return false;
|
|
||||||
int bytes_written = base::WriteFile(
|
|
||||||
path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
|
|
||||||
return (bytes_written == static_cast<int>(png_data.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeleteTempDirectory(const base::FilePath& dir_path) {
|
|
||||||
if (dir_path.empty())
|
|
||||||
return;
|
|
||||||
base::DeletePathRecursively(dir_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
AppIndicatorIcon::AppIndicatorIcon(std::string id,
|
|
||||||
const gfx::ImageSkia& image,
|
|
||||||
const std::u16string& tool_tip)
|
|
||||||
: id_(id) {
|
|
||||||
auto env = base::Environment::Create();
|
|
||||||
desktop_env_ = base::nix::GetDesktopEnvironment(env.get());
|
|
||||||
|
|
||||||
EnsureLibAppIndicatorLoaded();
|
|
||||||
tool_tip_ = base::UTF16ToUTF8(tool_tip);
|
|
||||||
SetIcon(image);
|
|
||||||
}
|
|
||||||
AppIndicatorIcon::~AppIndicatorIcon() {
|
|
||||||
if (icon_) {
|
|
||||||
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
|
|
||||||
g_object_unref(icon_);
|
|
||||||
base::ThreadPool::PostTask(
|
|
||||||
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
|
|
||||||
base::BindOnce(&DeleteTempDirectory, temp_dir_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
bool AppIndicatorIcon::CouldOpen() {
|
|
||||||
EnsureLibAppIndicatorLoaded();
|
|
||||||
return g_opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::SetIcon(const gfx::ImageSkia& image) {
|
|
||||||
if (!g_opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
++icon_change_count_;
|
|
||||||
|
|
||||||
// Copy the bitmap because it may be freed by the time it's accessed in
|
|
||||||
// another thread.
|
|
||||||
SkBitmap safe_bitmap = *image.bitmap();
|
|
||||||
|
|
||||||
const base::TaskTraits kTraits = {
|
|
||||||
base::MayBlock(), base::TaskPriority::USER_VISIBLE,
|
|
||||||
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
|
|
||||||
|
|
||||||
if (desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE4 ||
|
|
||||||
desktop_env_ == base::nix::DESKTOP_ENVIRONMENT_KDE5) {
|
|
||||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
|
||||||
FROM_HERE, kTraits,
|
|
||||||
base::BindOnce(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
|
|
||||||
safe_bitmap, temp_dir_),
|
|
||||||
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
|
|
||||||
weak_factory_.GetWeakPtr()));
|
|
||||||
} else {
|
|
||||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
|
||||||
FROM_HERE, kTraits,
|
|
||||||
base::BindOnce(AppIndicatorIcon::WriteUnityTempImageOnWorkerThread,
|
|
||||||
safe_bitmap, icon_change_count_, id_),
|
|
||||||
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
|
|
||||||
weak_factory_.GetWeakPtr()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::SetToolTip(const std::u16string& tool_tip) {
|
|
||||||
DCHECK(!tool_tip_.empty());
|
|
||||||
tool_tip_ = base::UTF16ToUTF8(tool_tip);
|
|
||||||
UpdateClickActionReplacementMenuItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
|
|
||||||
if (!g_opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
menu_model_ = model;
|
|
||||||
|
|
||||||
// The icon is created asynchronously so it might not exist when the menu is
|
|
||||||
// set.
|
|
||||||
if (icon_)
|
|
||||||
SetMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::RefreshPlatformContextMenu() {
|
|
||||||
menu_->Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
AppIndicatorIcon::SetImageFromFileParams
|
|
||||||
AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread(
|
|
||||||
const SkBitmap& bitmap,
|
|
||||||
const base::FilePath& existing_temp_dir) {
|
|
||||||
base::FilePath temp_dir = existing_temp_dir;
|
|
||||||
if (temp_dir.empty() &&
|
|
||||||
!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
|
|
||||||
LOG(WARNING) << "Could not create temporary directory";
|
|
||||||
return SetImageFromFileParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
base::FilePath icon_theme_path = temp_dir.AppendASCII("icons");
|
|
||||||
|
|
||||||
// On KDE4, an image located in a directory ending with
|
|
||||||
// "icons/hicolor/22x22/apps" can be used as the app indicator image because
|
|
||||||
// "/usr/share/icons/hicolor/22x22/apps" exists.
|
|
||||||
base::FilePath image_dir =
|
|
||||||
icon_theme_path.AppendASCII("hicolor").AppendASCII("22x22").AppendASCII(
|
|
||||||
"apps");
|
|
||||||
|
|
||||||
if (!base::CreateDirectory(image_dir))
|
|
||||||
return SetImageFromFileParams();
|
|
||||||
|
|
||||||
// On KDE4, the name of the image file for each different looking bitmap must
|
|
||||||
// be unique. It must also be unique across runs of Chrome.
|
|
||||||
std::vector<unsigned char> bitmap_png_data;
|
|
||||||
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_png_data)) {
|
|
||||||
LOG(WARNING) << "Could not encode icon";
|
|
||||||
return SetImageFromFileParams();
|
|
||||||
}
|
|
||||||
base::MD5Digest digest;
|
|
||||||
base::MD5Sum(reinterpret_cast<char*>(&bitmap_png_data[0]),
|
|
||||||
bitmap_png_data.size(), &digest);
|
|
||||||
std::string icon_name = base::StringPrintf(
|
|
||||||
"electron_app_indicator2_%s", base::MD5DigestToBase16(digest).c_str());
|
|
||||||
|
|
||||||
// If |bitmap| is smaller than 22x22, KDE does some really ugly resizing.
|
|
||||||
// Pad |bitmap| with transparent pixels to make it 22x22.
|
|
||||||
const int kMinimalSize = 22;
|
|
||||||
SkBitmap scaled_bitmap;
|
|
||||||
scaled_bitmap.allocN32Pixels(std::max(bitmap.width(), kMinimalSize),
|
|
||||||
std::max(bitmap.height(), kMinimalSize));
|
|
||||||
scaled_bitmap.eraseARGB(0, 0, 0, 0);
|
|
||||||
SkCanvas canvas(scaled_bitmap);
|
|
||||||
canvas.drawImage(bitmap.asImage(),
|
|
||||||
(scaled_bitmap.width() - bitmap.width()) / 2,
|
|
||||||
(scaled_bitmap.height() - bitmap.height()) / 2);
|
|
||||||
|
|
||||||
base::FilePath image_path = image_dir.Append(icon_name + ".png");
|
|
||||||
if (!WriteFile(image_path, scaled_bitmap))
|
|
||||||
return SetImageFromFileParams();
|
|
||||||
|
|
||||||
SetImageFromFileParams params;
|
|
||||||
params.parent_temp_dir = temp_dir;
|
|
||||||
params.icon_theme_path = icon_theme_path.value();
|
|
||||||
params.icon_name = icon_name;
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
AppIndicatorIcon::SetImageFromFileParams
|
|
||||||
AppIndicatorIcon::WriteUnityTempImageOnWorkerThread(const SkBitmap& bitmap,
|
|
||||||
int icon_change_count,
|
|
||||||
const std::string& id) {
|
|
||||||
// Create a new temporary directory for each image on Unity since using a
|
|
||||||
// single temporary directory seems to have issues when changing icons in
|
|
||||||
// quick succession.
|
|
||||||
base::FilePath temp_dir;
|
|
||||||
if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir)) {
|
|
||||||
LOG(WARNING) << "Could not create temporary directory";
|
|
||||||
return SetImageFromFileParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string icon_name =
|
|
||||||
base::StringPrintf("%s_%d", id.c_str(), icon_change_count);
|
|
||||||
base::FilePath image_path = temp_dir.Append(icon_name + ".png");
|
|
||||||
SetImageFromFileParams params;
|
|
||||||
if (WriteFile(image_path, bitmap)) {
|
|
||||||
params.parent_temp_dir = temp_dir;
|
|
||||||
params.icon_theme_path = temp_dir.value();
|
|
||||||
params.icon_name = icon_name;
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::SetImageFromFile(const SetImageFromFileParams& params) {
|
|
||||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
|
||||||
if (params.icon_theme_path.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!icon_) {
|
|
||||||
icon_ =
|
|
||||||
app_indicator_new_with_path(id_.c_str(), params.icon_name.c_str(),
|
|
||||||
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
|
|
||||||
params.icon_theme_path.c_str());
|
|
||||||
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
|
|
||||||
SetMenu();
|
|
||||||
} else {
|
|
||||||
app_indicator_set_icon_theme_path(icon_, params.icon_theme_path.c_str());
|
|
||||||
app_indicator_set_icon_full(icon_, params.icon_name.c_str(), "icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (temp_dir_ != params.parent_temp_dir) {
|
|
||||||
base::ThreadPool::PostTask(
|
|
||||||
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
|
|
||||||
base::BindOnce(&DeleteTempDirectory, temp_dir_));
|
|
||||||
temp_dir_ = params.parent_temp_dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::SetMenu() {
|
|
||||||
menu_ = std::make_unique<AppIndicatorIconMenu>(menu_model_);
|
|
||||||
UpdateClickActionReplacementMenuItem();
|
|
||||||
app_indicator_set_menu(icon_, menu_->GetGtkMenu());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
|
|
||||||
// The menu may not have been created yet.
|
|
||||||
if (!menu_.get())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!delegate()->HasClickAction() && menu_model_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
DCHECK(!tool_tip_.empty());
|
|
||||||
menu_->UpdateClickActionReplacementMenuItem(
|
|
||||||
tool_tip_.c_str(),
|
|
||||||
base::BindRepeating(
|
|
||||||
&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
|
|
||||||
base::Unretained(this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
|
|
||||||
if (delegate())
|
|
||||||
delegate()->OnClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
|
@ -1,114 +0,0 @@
|
||||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#ifndef ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
|
||||||
#define ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "base/files/file_path.h"
|
|
||||||
#include "base/memory/weak_ptr.h"
|
|
||||||
#include "base/nix/xdg_util.h"
|
|
||||||
#include "third_party/skia/include/core/SkImage.h"
|
|
||||||
#include "ui/base/glib/glib_signal.h"
|
|
||||||
#include "ui/linux/status_icon_linux.h"
|
|
||||||
|
|
||||||
typedef struct _AppIndicator AppIndicator;
|
|
||||||
typedef struct _GtkWidget GtkWidget;
|
|
||||||
|
|
||||||
class SkBitmap;
|
|
||||||
|
|
||||||
namespace gfx {
|
|
||||||
class ImageSkia;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
class MenuModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
class AppIndicatorIconMenu;
|
|
||||||
|
|
||||||
// Status icon implementation which uses libappindicator.
|
|
||||||
class AppIndicatorIcon : public ui::StatusIconLinux {
|
|
||||||
public:
|
|
||||||
// The id uniquely identifies the new status icon from other chrome status
|
|
||||||
// icons.
|
|
||||||
AppIndicatorIcon(std::string id,
|
|
||||||
const gfx::ImageSkia& image,
|
|
||||||
const std::u16string& tool_tip);
|
|
||||||
~AppIndicatorIcon() override;
|
|
||||||
|
|
||||||
// disable copy
|
|
||||||
AppIndicatorIcon(const AppIndicatorIcon&) = delete;
|
|
||||||
AppIndicatorIcon& operator=(const AppIndicatorIcon&) = delete;
|
|
||||||
|
|
||||||
// Indicates whether libappindicator so could be opened.
|
|
||||||
static bool CouldOpen();
|
|
||||||
|
|
||||||
// Overridden from ui::StatusIconLinux:
|
|
||||||
void SetIcon(const gfx::ImageSkia& image) override;
|
|
||||||
void SetToolTip(const std::u16string& tool_tip) override;
|
|
||||||
void UpdatePlatformContextMenu(ui::MenuModel* menu) override;
|
|
||||||
void RefreshPlatformContextMenu() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct SetImageFromFileParams {
|
|
||||||
// The temporary directory in which the icon(s) were written.
|
|
||||||
base::FilePath parent_temp_dir;
|
|
||||||
|
|
||||||
// The icon theme path to pass to libappindicator.
|
|
||||||
std::string icon_theme_path;
|
|
||||||
|
|
||||||
// The icon name to pass to libappindicator.
|
|
||||||
std::string icon_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Writes |bitmap| to a temporary directory on a worker thread. The temporary
|
|
||||||
// directory is selected based on KDE's quirks.
|
|
||||||
static SetImageFromFileParams WriteKDE4TempImageOnWorkerThread(
|
|
||||||
const SkBitmap& bitmap,
|
|
||||||
const base::FilePath& existing_temp_dir);
|
|
||||||
|
|
||||||
// Writes |bitmap| to a temporary directory on a worker thread. The temporary
|
|
||||||
// directory is selected based on Unity's quirks.
|
|
||||||
static SetImageFromFileParams WriteUnityTempImageOnWorkerThread(
|
|
||||||
const SkBitmap& bitmap,
|
|
||||||
int icon_change_count,
|
|
||||||
const std::string& id);
|
|
||||||
|
|
||||||
void SetImageFromFile(const SetImageFromFileParams& params);
|
|
||||||
void SetMenu();
|
|
||||||
|
|
||||||
// Sets a menu item at the top of the menu as a replacement for the status
|
|
||||||
// icon click action. Clicking on this menu item should simulate a status icon
|
|
||||||
// click by despatching a click event.
|
|
||||||
void UpdateClickActionReplacementMenuItem();
|
|
||||||
|
|
||||||
// Callback for when the status icon click replacement menu item is activated.
|
|
||||||
void OnClickActionReplacementMenuItemActivated();
|
|
||||||
|
|
||||||
std::string id_;
|
|
||||||
std::string tool_tip_;
|
|
||||||
|
|
||||||
// Used to select KDE or Unity for image setting.
|
|
||||||
base::nix::DesktopEnvironment desktop_env_;
|
|
||||||
|
|
||||||
// Gtk status icon wrapper
|
|
||||||
AppIndicator* icon_ = nullptr;
|
|
||||||
|
|
||||||
std::unique_ptr<AppIndicatorIconMenu> menu_;
|
|
||||||
ui::MenuModel* menu_model_ = nullptr;
|
|
||||||
|
|
||||||
base::FilePath temp_dir_;
|
|
||||||
int icon_change_count_ = 0;
|
|
||||||
|
|
||||||
base::WeakPtrFactory<AppIndicatorIcon> weak_factory_{this};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
|
|
@ -1,116 +0,0 @@
|
||||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
|
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include "base/bind.h"
|
|
||||||
#include "base/debug/leak_annotations.h"
|
|
||||||
#include "shell/browser/ui/gtk/menu_util.h"
|
|
||||||
#include "ui/base/models/menu_model.h"
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
AppIndicatorIconMenu::AppIndicatorIconMenu(ui::MenuModel* model)
|
|
||||||
: menu_model_(model) {
|
|
||||||
{
|
|
||||||
ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/378770
|
|
||||||
gtk_menu_ = gtk_menu_new();
|
|
||||||
}
|
|
||||||
g_object_ref_sink(gtk_menu_);
|
|
||||||
if (menu_model_) {
|
|
||||||
BuildSubmenuFromModel(menu_model_, gtk_menu_,
|
|
||||||
G_CALLBACK(OnMenuItemActivatedThunk),
|
|
||||||
&block_activation_, this);
|
|
||||||
Refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppIndicatorIconMenu::~AppIndicatorIconMenu() {
|
|
||||||
gtk_widget_destroy(gtk_menu_);
|
|
||||||
g_object_unref(gtk_menu_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIconMenu::UpdateClickActionReplacementMenuItem(
|
|
||||||
const char* label,
|
|
||||||
const base::RepeatingClosure& callback) {
|
|
||||||
click_action_replacement_callback_ = callback;
|
|
||||||
|
|
||||||
if (click_action_replacement_menu_item_added_) {
|
|
||||||
GList* children = gtk_container_get_children(GTK_CONTAINER(gtk_menu_));
|
|
||||||
for (GList* child = children; child; child = g_list_next(child)) {
|
|
||||||
if (g_object_get_data(G_OBJECT(child->data), "click-action-item") !=
|
|
||||||
nullptr) {
|
|
||||||
gtk_menu_item_set_label(GTK_MENU_ITEM(child->data), label);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_list_free(children);
|
|
||||||
} else {
|
|
||||||
click_action_replacement_menu_item_added_ = true;
|
|
||||||
|
|
||||||
// If |menu_model_| is non empty, add a separator to separate the
|
|
||||||
// "click action replacement menu item" from the other menu items.
|
|
||||||
if (menu_model_ && menu_model_->GetItemCount() > 0) {
|
|
||||||
GtkWidget* menu_item = gtk_separator_menu_item_new();
|
|
||||||
gtk_widget_show(menu_item);
|
|
||||||
gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
GtkWidget* menu_item = gtk_menu_item_new_with_mnemonic(label);
|
|
||||||
g_object_set_data(G_OBJECT(menu_item), "click-action-item",
|
|
||||||
GINT_TO_POINTER(1));
|
|
||||||
g_signal_connect(menu_item, "activate",
|
|
||||||
G_CALLBACK(OnClickActionReplacementMenuItemActivatedThunk),
|
|
||||||
this);
|
|
||||||
gtk_widget_show(menu_item);
|
|
||||||
gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIconMenu::Refresh() {
|
|
||||||
gtk_container_foreach(GTK_CONTAINER(gtk_menu_), SetMenuItemInfo,
|
|
||||||
&block_activation_);
|
|
||||||
}
|
|
||||||
|
|
||||||
GtkMenu* AppIndicatorIconMenu::GetGtkMenu() {
|
|
||||||
return GTK_MENU(gtk_menu_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIconMenu::OnClickActionReplacementMenuItemActivated(
|
|
||||||
GtkWidget* menu_item) {
|
|
||||||
click_action_replacement_callback_.Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppIndicatorIconMenu::OnMenuItemActivated(GtkWidget* menu_item) {
|
|
||||||
if (block_activation_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
|
|
||||||
if (!model) {
|
|
||||||
// There won't be a model for "native" submenus like the "Input Methods"
|
|
||||||
// context menu. We don't need to handle activation messages for submenus
|
|
||||||
// anyway, so we can just return here.
|
|
||||||
DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The activate signal is sent to radio items as they get deselected;
|
|
||||||
// ignore it in this case.
|
|
||||||
if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
|
|
||||||
!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int id;
|
|
||||||
if (!GetMenuItemID(menu_item, &id))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The menu item can still be activated by hotkeys even if it is disabled.
|
|
||||||
if (model->IsEnabledAt(id))
|
|
||||||
ExecuteCommand(model, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
|
@ -1,73 +0,0 @@
|
||||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#ifndef ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
|
||||||
#define ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
|
||||||
|
|
||||||
#include "base/callback.h"
|
|
||||||
#include "ui/base/glib/glib_signal.h"
|
|
||||||
|
|
||||||
typedef struct _GtkMenu GtkMenu;
|
|
||||||
typedef struct _GtkWidget GtkWidget;
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
class MenuModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
// The app indicator icon's menu.
|
|
||||||
class AppIndicatorIconMenu {
|
|
||||||
public:
|
|
||||||
explicit AppIndicatorIconMenu(ui::MenuModel* model);
|
|
||||||
virtual ~AppIndicatorIconMenu();
|
|
||||||
|
|
||||||
// disable copy
|
|
||||||
AppIndicatorIconMenu(const AppIndicatorIconMenu&) = delete;
|
|
||||||
AppIndicatorIconMenu& operator=(const AppIndicatorIconMenu&) = delete;
|
|
||||||
|
|
||||||
// Sets a menu item at the top of |gtk_menu_| as a replacement for the app
|
|
||||||
// indicator icon's click action. |callback| is called when the menu item
|
|
||||||
// is activated.
|
|
||||||
void UpdateClickActionReplacementMenuItem(
|
|
||||||
const char* label,
|
|
||||||
const base::RepeatingClosure& callback);
|
|
||||||
|
|
||||||
// Refreshes all the menu item labels and menu item checked/enabled states.
|
|
||||||
void Refresh();
|
|
||||||
|
|
||||||
GtkMenu* GetGtkMenu();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Callback for when the "click action replacement" menu item is activated.
|
|
||||||
CHROMEG_CALLBACK_0(AppIndicatorIconMenu,
|
|
||||||
void,
|
|
||||||
OnClickActionReplacementMenuItemActivated,
|
|
||||||
GtkWidget*);
|
|
||||||
|
|
||||||
// Callback for when a menu item is activated.
|
|
||||||
CHROMEG_CALLBACK_0(AppIndicatorIconMenu,
|
|
||||||
void,
|
|
||||||
OnMenuItemActivated,
|
|
||||||
GtkWidget*);
|
|
||||||
|
|
||||||
// Not owned.
|
|
||||||
ui::MenuModel* menu_model_;
|
|
||||||
|
|
||||||
// Whether a "click action replacement" menu item has been added to the menu.
|
|
||||||
bool click_action_replacement_menu_item_added_ = false;
|
|
||||||
|
|
||||||
// Called when the click action replacement menu item is activated. When a
|
|
||||||
// menu item from |menu_model_| is activated, MenuModel::ActivatedAt() is
|
|
||||||
// invoked and is assumed to do any necessary processing.
|
|
||||||
base::RepeatingClosure click_action_replacement_callback_;
|
|
||||||
|
|
||||||
GtkWidget* gtk_menu_ = nullptr;
|
|
||||||
|
|
||||||
bool block_activation_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
|
|
@ -1,82 +0,0 @@
|
||||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#include "shell/browser/ui/gtk/gtk_status_icon.h"
|
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include "base/debug/leak_annotations.h"
|
|
||||||
#include "base/strings/utf_string_conversions.h"
|
|
||||||
#include "shell/browser/ui/gtk/app_indicator_icon_menu.h"
|
|
||||||
#include "shell/browser/ui/gtk_util.h"
|
|
||||||
#include "ui/base/models/menu_model.h"
|
|
||||||
#include "ui/gfx/image/image_skia.h"
|
|
||||||
|
|
||||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
GtkStatusIcon::GtkStatusIcon(const gfx::ImageSkia& image,
|
|
||||||
const std::u16string& tool_tip) {
|
|
||||||
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*image.bitmap());
|
|
||||||
{
|
|
||||||
// GTK has a bug that leaks 384 bytes when creating a GtkStatusIcon. It
|
|
||||||
// will not be fixed since the status icon was deprecated in version 3.14.
|
|
||||||
// Luckily, Chromium doesn't need to create a status icon very often, if at
|
|
||||||
// all.
|
|
||||||
ANNOTATE_SCOPED_MEMORY_LEAK;
|
|
||||||
gtk_status_icon_ = gtk_status_icon_new_from_pixbuf(pixbuf);
|
|
||||||
}
|
|
||||||
g_object_unref(pixbuf);
|
|
||||||
|
|
||||||
g_signal_connect(gtk_status_icon_, "activate", G_CALLBACK(OnClickThunk),
|
|
||||||
this);
|
|
||||||
g_signal_connect(gtk_status_icon_, "popup_menu",
|
|
||||||
G_CALLBACK(OnContextMenuRequestedThunk), this);
|
|
||||||
SetToolTip(tool_tip);
|
|
||||||
}
|
|
||||||
|
|
||||||
GtkStatusIcon::~GtkStatusIcon() {
|
|
||||||
gtk_status_icon_set_visible(gtk_status_icon_, FALSE);
|
|
||||||
g_object_unref(gtk_status_icon_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::SetIcon(const gfx::ImageSkia& image) {
|
|
||||||
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*image.bitmap());
|
|
||||||
gtk_status_icon_set_from_pixbuf(gtk_status_icon_, pixbuf);
|
|
||||||
g_object_unref(pixbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::SetToolTip(const std::u16string& tool_tip) {
|
|
||||||
gtk_status_icon_set_tooltip_text(gtk_status_icon_,
|
|
||||||
base::UTF16ToUTF8(tool_tip).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
|
|
||||||
menu_.reset();
|
|
||||||
if (model)
|
|
||||||
menu_ = std::make_unique<AppIndicatorIconMenu>(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::RefreshPlatformContextMenu() {
|
|
||||||
if (menu_.get())
|
|
||||||
menu_->Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::OnClick(GtkStatusIcon* status_icon) {
|
|
||||||
if (delegate())
|
|
||||||
delegate()->OnClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GtkStatusIcon::OnContextMenuRequested(GtkStatusIcon* status_icon,
|
|
||||||
guint button,
|
|
||||||
guint32 activate_time) {
|
|
||||||
if (menu_.get()) {
|
|
||||||
gtk_menu_popup(menu_->GetGtkMenu(), nullptr, nullptr,
|
|
||||||
gtk_status_icon_position_menu, gtk_status_icon_, button,
|
|
||||||
activate_time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
|
@ -1,62 +0,0 @@
|
||||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#ifndef ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
|
||||||
#define ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "ui/base/glib/glib_integers.h"
|
|
||||||
#include "ui/base/glib/glib_signal.h"
|
|
||||||
#include "ui/linux/status_icon_linux.h"
|
|
||||||
|
|
||||||
typedef struct _GtkStatusIcon GtkStatusIcon;
|
|
||||||
|
|
||||||
namespace gfx {
|
|
||||||
class ImageSkia;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ui {
|
|
||||||
class MenuModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
class AppIndicatorIconMenu;
|
|
||||||
|
|
||||||
// Status icon implementation which uses the system tray X11 spec (via
|
|
||||||
// GtkStatusIcon).
|
|
||||||
class GtkStatusIcon : public ui::StatusIconLinux {
|
|
||||||
public:
|
|
||||||
GtkStatusIcon(const gfx::ImageSkia& image, const std::u16string& tool_tip);
|
|
||||||
~GtkStatusIcon() override;
|
|
||||||
|
|
||||||
// disable copy
|
|
||||||
GtkStatusIcon(const GtkStatusIcon&) = delete;
|
|
||||||
GtkStatusIcon& operator=(const GtkStatusIcon&) = delete;
|
|
||||||
|
|
||||||
// Overridden from ui::StatusIconLinux:
|
|
||||||
void SetIcon(const gfx::ImageSkia& image) override;
|
|
||||||
void SetToolTip(const std::u16string& tool_tip) override;
|
|
||||||
void UpdatePlatformContextMenu(ui::MenuModel* menu) override;
|
|
||||||
void RefreshPlatformContextMenu() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
CHROMEG_CALLBACK_0(GtkStatusIcon, void, OnClick, GtkStatusIcon*);
|
|
||||||
|
|
||||||
CHROMEG_CALLBACK_2(GtkStatusIcon,
|
|
||||||
void,
|
|
||||||
OnContextMenuRequested,
|
|
||||||
GtkStatusIcon*,
|
|
||||||
guint,
|
|
||||||
guint);
|
|
||||||
|
|
||||||
::GtkStatusIcon* gtk_status_icon_;
|
|
||||||
|
|
||||||
std::unique_ptr<AppIndicatorIconMenu> menu_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
|
|
@ -1,56 +0,0 @@
|
||||||
// Copyright (c) 2020 Slack Technologies, Inc.
|
|
||||||
// Use of this source code is governed by the MIT license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#include "shell/browser/ui/gtk/status_icon.h"
|
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "base/strings/stringprintf.h"
|
|
||||||
#include "shell/browser/ui/gtk/app_indicator_icon.h"
|
|
||||||
#include "shell/browser/ui/gtk/gtk_status_icon.h"
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
int indicators_count = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsStatusIconSupported() {
|
|
||||||
#if GTK_CHECK_VERSION(3, 90, 0)
|
|
||||||
NOTIMPLEMENTED();
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ui::StatusIconLinux> CreateLinuxStatusIcon(
|
|
||||||
const gfx::ImageSkia& image,
|
|
||||||
const std::u16string& tool_tip,
|
|
||||||
const char* id_prefix) {
|
|
||||||
#if GTK_CHECK_VERSION(3, 90, 0)
|
|
||||||
NOTIMPLEMENTED();
|
|
||||||
return nullptr;
|
|
||||||
#else
|
|
||||||
if (AppIndicatorIcon::CouldOpen()) {
|
|
||||||
++indicators_count;
|
|
||||||
|
|
||||||
return std::make_unique<AppIndicatorIcon>(
|
|
||||||
base::StringPrintf("%s%d", id_prefix, indicators_count), image,
|
|
||||||
tool_tip);
|
|
||||||
} else {
|
|
||||||
return std::make_unique<GtkStatusIcon>(image, tool_tip);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
|
@ -1,28 +0,0 @@
|
||||||
// Copyright (c) 2020 Slack Technologies, Inc.
|
|
||||||
// Use of this source code is governed by the MIT license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
#ifndef ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
|
||||||
#define ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "base/strings/string_util.h"
|
|
||||||
#include "ui/gfx/image/image_skia.h"
|
|
||||||
#include "ui/linux/status_icon_linux.h"
|
|
||||||
|
|
||||||
namespace electron::gtkui {
|
|
||||||
|
|
||||||
bool IsStatusIconSupported();
|
|
||||||
std::unique_ptr<ui::StatusIconLinux> CreateLinuxStatusIcon(
|
|
||||||
const gfx::ImageSkia& image,
|
|
||||||
const std::u16string& tool_tip,
|
|
||||||
const char* id_prefix);
|
|
||||||
|
|
||||||
} // namespace electron::gtkui
|
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
|
|
@ -4,43 +4,54 @@
|
||||||
|
|
||||||
#include "shell/browser/ui/tray_icon_gtk.h"
|
#include "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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue