fix: restore original GTK/appindicator implementation of tray icons (#23674)
This commit is contained in:
parent
087567655a
commit
d8594f7a78
13 changed files with 1337 additions and 31 deletions
11
BUILD.gn
11
BUILD.gn
|
@ -322,6 +322,7 @@ source_set("electron_lib") {
|
|||
"shell/common/api:mojo",
|
||||
"//base:base_static",
|
||||
"//base/allocator:buildflags",
|
||||
"//chrome/app:command_ids",
|
||||
"//chrome/app/resources:platform_locale_settings",
|
||||
"//chrome/services/printing/public/mojom",
|
||||
"//components/certificate_transparency",
|
||||
|
@ -523,6 +524,16 @@ source_set("electron_lib") {
|
|||
|
||||
sources += filenames.lib_sources_nss
|
||||
sources += [
|
||||
"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",
|
||||
]
|
||||
|
|
379
shell/browser/ui/gtk/app_indicator_icon.cc
Normal file
379
shell/browser/ui/gtk/app_indicator_icon.cc
Normal file
|
@ -0,0 +1,379 @@
|
|||
// 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/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/post_task.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::DeleteFile(dir_path, true);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
AppIndicatorIcon::AppIndicatorIcon(std::string id,
|
||||
const gfx::ImageSkia& image,
|
||||
const base::string16& tool_tip)
|
||||
: id_(id), icon_(nullptr), menu_model_(nullptr), icon_change_count_(0) {
|
||||
std::unique_ptr<base::Environment> 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::PostTask(
|
||||
FROM_HERE,
|
||||
{base::ThreadPool(), 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::ThreadPool(), 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::PostTaskAndReplyWithResult(
|
||||
FROM_HERE, kTraits,
|
||||
base::BindOnce(AppIndicatorIcon::WriteKDE4TempImageOnWorkerThread,
|
||||
safe_bitmap, temp_dir_),
|
||||
base::BindOnce(&AppIndicatorIcon::SetImageFromFile,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
base::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 base::string16& 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.drawBitmap(bitmap, (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::PostTask(
|
||||
FROM_HERE,
|
||||
{base::ThreadPool(), 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::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
|
||||
base::Unretained(this)));
|
||||
}
|
||||
|
||||
void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
|
||||
if (delegate())
|
||||
delegate()->OnClick();
|
||||
}
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
116
shell/browser/ui/gtk/app_indicator_icon.h
Normal file
116
shell/browser/ui/gtk/app_indicator_icon.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
// 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 SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
||||
#define SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
#include "ui/views/linux_ui/status_icon_linux.h"
|
||||
|
||||
typedef struct _AppIndicator AppIndicator;
|
||||
typedef struct _GtkWidget GtkWidget;
|
||||
|
||||
class SkBitmap;
|
||||
|
||||
namespace gfx {
|
||||
class ImageSkia;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
class AppIndicatorIconMenu;
|
||||
|
||||
// Status icon implementation which uses libappindicator.
|
||||
class AppIndicatorIcon : public views::StatusIconLinux {
|
||||
public:
|
||||
// The id uniquely identifies the new status icon from other chrome status
|
||||
// icons.
|
||||
AppIndicatorIcon(std::string id,
|
||||
const gfx::ImageSkia& image,
|
||||
const base::string16& tool_tip);
|
||||
~AppIndicatorIcon() override;
|
||||
|
||||
// Indicates whether libappindicator so could be opened.
|
||||
static bool CouldOpen();
|
||||
|
||||
// Overridden from views::StatusIconLinux:
|
||||
void SetIcon(const gfx::ImageSkia& image) override;
|
||||
void SetToolTip(const base::string16& 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_;
|
||||
|
||||
std::unique_ptr<AppIndicatorIconMenu> menu_;
|
||||
ui::MenuModel* menu_model_;
|
||||
|
||||
base::FilePath temp_dir_;
|
||||
int icon_change_count_;
|
||||
|
||||
base::WeakPtrFactory<AppIndicatorIcon> weak_factory_{this};
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AppIndicatorIcon);
|
||||
};
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
|
123
shell/browser/ui/gtk/app_indicator_icon_menu.cc
Normal file
123
shell/browser/ui/gtk/app_indicator_icon_menu.cc
Normal file
|
@ -0,0 +1,123 @@
|
|||
// 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 {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
AppIndicatorIconMenu::AppIndicatorIconMenu(ui::MenuModel* model)
|
||||
: menu_model_(model),
|
||||
click_action_replacement_menu_item_added_(false),
|
||||
gtk_menu_(nullptr),
|
||||
block_activation_(false) {
|
||||
{
|
||||
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::Closure& 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 gtkui
|
||||
|
||||
} // namespace electron
|
75
shell/browser/ui/gtk/app_indicator_icon_menu.h
Normal file
75
shell/browser/ui/gtk/app_indicator_icon_menu.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
// 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 SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
||||
#define SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/macros.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
|
||||
typedef struct _GtkMenu GtkMenu;
|
||||
typedef struct _GtkWidget GtkWidget;
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
// The app indicator icon's menu.
|
||||
class AppIndicatorIconMenu {
|
||||
public:
|
||||
explicit AppIndicatorIconMenu(ui::MenuModel* model);
|
||||
virtual ~AppIndicatorIconMenu();
|
||||
|
||||
// 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::Closure& 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_;
|
||||
|
||||
// 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::Closure click_action_replacement_callback_;
|
||||
|
||||
GtkWidget* gtk_menu_;
|
||||
|
||||
bool block_activation_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AppIndicatorIconMenu);
|
||||
};
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_UI_GTK_APP_INDICATOR_ICON_MENU_H_
|
86
shell/browser/ui/gtk/gtk_status_icon.cc
Normal file
86
shell/browser/ui/gtk/gtk_status_icon.cc
Normal file
|
@ -0,0 +1,86 @@
|
|||
// 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 {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
GtkStatusIcon::GtkStatusIcon(const gfx::ImageSkia& image,
|
||||
const base::string16& 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 base::string16& 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 gtkui
|
||||
|
||||
} // namespace electron
|
65
shell/browser/ui/gtk/gtk_status_icon.h
Normal file
65
shell/browser/ui/gtk/gtk_status_icon.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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 SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
||||
#define SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "ui/base/glib/glib_integers.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
#include "ui/views/linux_ui/status_icon_linux.h"
|
||||
|
||||
typedef struct _GtkStatusIcon GtkStatusIcon;
|
||||
|
||||
namespace gfx {
|
||||
class ImageSkia;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
class AppIndicatorIconMenu;
|
||||
|
||||
// Status icon implementation which uses the system tray X11 spec (via
|
||||
// GtkStatusIcon).
|
||||
class GtkStatusIcon : public views::StatusIconLinux {
|
||||
public:
|
||||
GtkStatusIcon(const gfx::ImageSkia& image, const base::string16& tool_tip);
|
||||
~GtkStatusIcon() override;
|
||||
|
||||
// Overridden from views::StatusIconLinux:
|
||||
void SetIcon(const gfx::ImageSkia& image) override;
|
||||
void SetToolTip(const base::string16& 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_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(GtkStatusIcon);
|
||||
};
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_UI_GTK_GTK_STATUS_ICON_H_
|
320
shell/browser/ui/gtk/menu_util.cc
Normal file
320
shell/browser/ui/gtk/menu_util.cc
Normal file
|
@ -0,0 +1,320 @@
|
|||
// 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/menu_util.h"
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/app/chrome_command_ids.h"
|
||||
#include "shell/browser/ui/gtk_util.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "third_party/skia/include/core/SkUnPreMultiply.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/events/event_constants.h"
|
||||
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
namespace {
|
||||
|
||||
int EventFlagsFromGdkState(guint state) {
|
||||
int flags = ui::EF_NONE;
|
||||
flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_ON : ui::EF_NONE;
|
||||
flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_MOUSE_BUTTON : ui::EF_NONE;
|
||||
flags |=
|
||||
(state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_MOUSE_BUTTON : ui::EF_NONE;
|
||||
flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_MOUSE_BUTTON : ui::EF_NONE;
|
||||
return flags;
|
||||
}
|
||||
|
||||
guint GetGdkKeyCodeForAccelerator(const ui::Accelerator& accelerator) {
|
||||
// The second parameter is false because accelerator keys are expressed in
|
||||
// terms of the non-shift-modified key.
|
||||
return XKeysymForWindowsKeyCode(accelerator.key_code(), false);
|
||||
}
|
||||
|
||||
GdkModifierType GetGdkModifierForAccelerator(
|
||||
const ui::Accelerator& accelerator) {
|
||||
int event_flag = accelerator.modifiers();
|
||||
int modifier = 0;
|
||||
if (event_flag & ui::EF_SHIFT_DOWN)
|
||||
modifier |= GDK_SHIFT_MASK;
|
||||
if (event_flag & ui::EF_CONTROL_DOWN)
|
||||
modifier |= GDK_CONTROL_MASK;
|
||||
if (event_flag & ui::EF_ALT_DOWN)
|
||||
modifier |= GDK_MOD1_MASK;
|
||||
return static_cast<GdkModifierType>(modifier);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label, GtkWidget* image) {
|
||||
// GTK4 removed support for image menu items.
|
||||
#if GTK_CHECK_VERSION(3, 90, 0)
|
||||
return gtk_menu_item_new_with_mnemonic(label.c_str());
|
||||
#else
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
||||
GtkWidget* menu_item = gtk_image_menu_item_new_with_mnemonic(label.c_str());
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS;
|
||||
return menu_item;
|
||||
#endif
|
||||
}
|
||||
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label,
|
||||
const gfx::Image& icon) {
|
||||
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*icon.ToSkBitmap());
|
||||
|
||||
GtkWidget* menu_item =
|
||||
BuildMenuItemWithImage(label, gtk_image_new_from_pixbuf(pixbuf));
|
||||
g_object_unref(pixbuf);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
GtkWidget* BuildMenuItemWithLabel(const std::string& label) {
|
||||
return gtk_menu_item_new_with_mnemonic(label.c_str());
|
||||
}
|
||||
|
||||
ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
|
||||
return reinterpret_cast<ui::MenuModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "model"));
|
||||
}
|
||||
|
||||
GtkWidget* AppendMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
bool connect_to_activate,
|
||||
GCallback item_activated_cb,
|
||||
void* this_ptr) {
|
||||
// Set the ID of a menu item.
|
||||
// Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
|
||||
g_object_set_data(G_OBJECT(menu_item), "menu-id", GINT_TO_POINTER(index + 1));
|
||||
|
||||
// Native menu items do their own thing, so only selectively listen for the
|
||||
// activate signal.
|
||||
if (connect_to_activate) {
|
||||
g_signal_connect(menu_item, "activate", item_activated_cb, this_ptr);
|
||||
}
|
||||
|
||||
// AppendMenuItemToMenu is used both internally when we control menu creation
|
||||
// from a model (where the model can choose to hide certain menu items), and
|
||||
// with immediate commands which don't provide the option.
|
||||
if (model) {
|
||||
if (model->IsVisibleAt(index))
|
||||
gtk_widget_show(menu_item);
|
||||
} else {
|
||||
gtk_widget_show(menu_item);
|
||||
}
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) {
|
||||
gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id");
|
||||
if (id_ptr != nullptr) {
|
||||
*menu_id = GPOINTER_TO_INT(id_ptr) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExecuteCommand(ui::MenuModel* model, int id) {
|
||||
GdkEvent* event = gtk_get_current_event();
|
||||
int event_flags = 0;
|
||||
|
||||
if (event && event->type == GDK_BUTTON_RELEASE)
|
||||
event_flags = EventFlagsFromGdkState(event->button.state);
|
||||
model->ActivatedAt(id, event_flags);
|
||||
|
||||
if (event)
|
||||
gdk_event_free(event);
|
||||
}
|
||||
|
||||
void BuildSubmenuFromModel(ui::MenuModel* model,
|
||||
GtkWidget* menu,
|
||||
GCallback item_activated_cb,
|
||||
bool* block_activation,
|
||||
void* this_ptr) {
|
||||
std::map<int, GtkWidget*> radio_groups;
|
||||
GtkWidget* menu_item = nullptr;
|
||||
for (int i = 0; i < model->GetItemCount(); ++i) {
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(i)));
|
||||
|
||||
bool connect_to_activate = true;
|
||||
|
||||
switch (model->GetTypeAt(i)) {
|
||||
case ui::MenuModel::TYPE_SEPARATOR:
|
||||
menu_item = gtk_separator_menu_item_new();
|
||||
break;
|
||||
|
||||
case ui::MenuModel::TYPE_CHECK:
|
||||
menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
|
||||
break;
|
||||
|
||||
case ui::MenuModel::TYPE_RADIO: {
|
||||
auto iter = radio_groups.find(model->GetGroupIdAt(i));
|
||||
|
||||
if (iter == radio_groups.end()) {
|
||||
menu_item =
|
||||
gtk_radio_menu_item_new_with_mnemonic(nullptr, label.c_str());
|
||||
radio_groups[model->GetGroupIdAt(i)] = menu_item;
|
||||
} else {
|
||||
menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
|
||||
GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ui::MenuModel::TYPE_BUTTON_ITEM: {
|
||||
NOTIMPLEMENTED();
|
||||
break;
|
||||
}
|
||||
case ui::MenuModel::TYPE_SUBMENU:
|
||||
case ui::MenuModel::TYPE_COMMAND: {
|
||||
auto icon_model = model->GetIconAt(i);
|
||||
if (!icon_model.IsEmpty())
|
||||
menu_item = BuildMenuItemWithImage(label, icon_model.GetImage());
|
||||
else
|
||||
menu_item = BuildMenuItemWithLabel(label);
|
||||
#if !GTK_CHECK_VERSION(3, 90, 0)
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
||||
if (GTK_IS_IMAGE_MENU_ITEM(menu_item)) {
|
||||
gtk_image_menu_item_set_always_show_image(
|
||||
GTK_IMAGE_MENU_ITEM(menu_item), TRUE);
|
||||
}
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
|
||||
GtkWidget* submenu = gtk_menu_new();
|
||||
ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i);
|
||||
BuildSubmenuFromModel(submenu_model, submenu, item_activated_cb,
|
||||
block_activation, this_ptr);
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
|
||||
|
||||
// Update all the menu item info in the newly-generated menu.
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo,
|
||||
block_activation);
|
||||
submenu_model->MenuWillShow();
|
||||
connect_to_activate = false;
|
||||
}
|
||||
|
||||
#if defined(USE_X11)
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAt(i, &accelerator)) {
|
||||
gtk_widget_add_accelerator(menu_item, "activate", nullptr,
|
||||
GetGdkKeyCodeForAccelerator(accelerator),
|
||||
GetGdkModifierForAccelerator(accelerator),
|
||||
GTK_ACCEL_VISIBLE);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_object_set_data(G_OBJECT(menu_item), "model", model);
|
||||
AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate,
|
||||
item_activated_cb, this_ptr);
|
||||
|
||||
menu_item = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SetMenuItemInfo(GtkWidget* widget, void* block_activation_ptr) {
|
||||
if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
|
||||
// We need to explicitly handle this case because otherwise we'll ask the
|
||||
// menu delegate about something with an invalid id.
|
||||
return;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (!GetMenuItemID(widget, &id))
|
||||
return;
|
||||
|
||||
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
|
||||
if (!model) {
|
||||
// If we're not providing the sub menu, then there's no model. For
|
||||
// example, the IME submenu doesn't have a model.
|
||||
return;
|
||||
}
|
||||
bool* block_activation = static_cast<bool*>(block_activation_ptr);
|
||||
|
||||
if (GTK_IS_CHECK_MENU_ITEM(widget)) {
|
||||
GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
|
||||
|
||||
// gtk_check_menu_item_set_active() will send the activate signal. Touching
|
||||
// the underlying "active" property will also call the "activate" handler
|
||||
// for this menu item. So we prevent the "activate" handler from
|
||||
// being called while we set the checkbox.
|
||||
// Why not use one of the glib signal-blocking functions? Because when we
|
||||
// toggle a radio button, it will deactivate one of the other radio buttons,
|
||||
// which we don't have a pointer to.
|
||||
*block_activation = true;
|
||||
gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
|
||||
*block_activation = false;
|
||||
}
|
||||
|
||||
if (GTK_IS_MENU_ITEM(widget)) {
|
||||
gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
|
||||
|
||||
if (model->IsVisibleAt(id)) {
|
||||
// Update the menu item label if it is dynamic.
|
||||
if (model->IsItemDynamicAt(id)) {
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(id)));
|
||||
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
|
||||
#if !GTK_CHECK_VERSION(3, 90, 0)
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
||||
if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
|
||||
auto icon_model = model->GetIconAt(id);
|
||||
if (!icon_model.IsEmpty()) {
|
||||
GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(
|
||||
*icon_model.GetImage().ToSkBitmap());
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
|
||||
gtk_image_new_from_pixbuf(pixbuf));
|
||||
g_object_unref(pixbuf);
|
||||
} else {
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), nullptr);
|
||||
}
|
||||
}
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS;
|
||||
#endif
|
||||
}
|
||||
|
||||
gtk_widget_show(widget);
|
||||
} else {
|
||||
gtk_widget_hide(widget);
|
||||
}
|
||||
|
||||
GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
|
||||
if (submenu) {
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
|
||||
block_activation_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
63
shell/browser/ui/gtk/menu_util.h
Normal file
63
shell/browser/ui/gtk/menu_util.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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 SHELL_BROWSER_UI_GTK_MENU_UTIL_H_
|
||||
#define SHELL_BROWSER_UI_GTK_MENU_UTIL_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
// Builds GtkImageMenuItems.
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label, GtkWidget* image);
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label,
|
||||
const gfx::Image& icon);
|
||||
GtkWidget* BuildMenuItemWithLabel(const std::string& label);
|
||||
|
||||
ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item);
|
||||
|
||||
// This method is used to build the menu dynamically. The return value is the
|
||||
// new menu item.
|
||||
GtkWidget* AppendMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
bool connect_to_activate,
|
||||
GCallback item_activated_cb,
|
||||
void* this_ptr);
|
||||
|
||||
// Gets the ID of a menu item.
|
||||
// Returns true if the menu item has an ID.
|
||||
bool GetMenuItemID(GtkWidget* menu_item, int* menu_id);
|
||||
|
||||
// Execute command associated with specified id.
|
||||
void ExecuteCommand(ui::MenuModel* model, int id);
|
||||
|
||||
// Creates a GtkMenu from |model_|. block_activation_ptr is used to disable
|
||||
// the item_activated_callback while we set up the set up the menu items.
|
||||
// See comments in definition of SetMenuItemInfo for more info.
|
||||
void BuildSubmenuFromModel(ui::MenuModel* model,
|
||||
GtkWidget* menu,
|
||||
GCallback item_activated_cb,
|
||||
bool* block_activation,
|
||||
void* this_ptr);
|
||||
|
||||
// Sets the check mark, enabled/disabled state and dynamic labels on menu items.
|
||||
void SetMenuItemInfo(GtkWidget* widget, void* block_activation_ptr);
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_UI_GTK_MENU_UTIL_H_
|
62
shell/browser/ui/gtk/status_icon.cc
Normal file
62
shell/browser/ui/gtk/status_icon.cc
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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 {
|
||||
|
||||
namespace 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<views::StatusIconLinux> CreateLinuxStatusIcon(
|
||||
const gfx::ImageSkia& image,
|
||||
const base::string16& tool_tip,
|
||||
const char* id_prefix) {
|
||||
#if GTK_CHECK_VERSION(3, 90, 0)
|
||||
NOTIMPLEMENTED();
|
||||
return nullptr;
|
||||
#else
|
||||
if (AppIndicatorIcon::CouldOpen()) {
|
||||
++indicators_count;
|
||||
|
||||
return std::unique_ptr<views::StatusIconLinux>(new AppIndicatorIcon(
|
||||
base::StringPrintf("%s%d", id_prefix, indicators_count), image,
|
||||
tool_tip));
|
||||
} else {
|
||||
return std::unique_ptr<views::StatusIconLinux>(
|
||||
new GtkStatusIcon(image, tool_tip));
|
||||
}
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
32
shell/browser/ui/gtk/status_icon.h
Normal file
32
shell/browser/ui/gtk/status_icon.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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 SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
||||
#define SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/strings/string_util.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
#include "ui/views/linux_ui/status_icon_linux.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace gtkui {
|
||||
|
||||
bool IsStatusIconSupported();
|
||||
std::unique_ptr<views::StatusIconLinux> CreateLinuxStatusIcon(
|
||||
const gfx::ImageSkia& image,
|
||||
const base::string16& tool_tip,
|
||||
const char* id_prefix);
|
||||
|
||||
} // namespace gtkui
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_UI_GTK_STATUS_ICON_H_
|
|
@ -6,44 +6,20 @@
|
|||
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.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"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
gfx::ImageSkia GetIconFromImage(const gfx::Image& image) {
|
||||
auto icon = image.AsImageSkia();
|
||||
auto size = icon.size();
|
||||
|
||||
// Systray icons are historically 22 pixels tall, e.g. on Ubuntu GNOME,
|
||||
// KDE, and xfce. Taller icons are causing incorrect sizing issues -- e.g.
|
||||
// a 1x1 icon -- so for now, pin the height manually. Similar behavior to
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1042098 ?
|
||||
static constexpr int DESIRED_HEIGHT = 22;
|
||||
if ((size.height() != 0) && (size.height() != DESIRED_HEIGHT)) {
|
||||
const double ratio = DESIRED_HEIGHT / static_cast<double>(size.height());
|
||||
size = gfx::Size(static_cast<int>(ratio * size.width()),
|
||||
static_cast<int>(ratio * size.height()));
|
||||
icon = gfx::ImageSkiaOperations::CreateResizedImage(
|
||||
icon, skia::ImageOperations::RESIZE_BEST, size);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TrayIconGtk::TrayIconGtk() = default;
|
||||
|
||||
TrayIconGtk::~TrayIconGtk() = default;
|
||||
|
||||
void TrayIconGtk::SetImage(const gfx::Image& image) {
|
||||
image_ = GetIconFromImage(image);
|
||||
image_ = image.AsImageSkia();
|
||||
|
||||
if (icon_) {
|
||||
icon_->SetIcon(image_);
|
||||
|
@ -52,9 +28,8 @@ void TrayIconGtk::SetImage(const gfx::Image& image) {
|
|||
|
||||
tool_tip_ = base::UTF8ToUTF16(GetApplicationName());
|
||||
|
||||
icon_ = base::MakeRefCounted<StatusIconLinuxDbus>();
|
||||
icon_->SetIcon(image_);
|
||||
icon_->SetToolTip(tool_tip_);
|
||||
icon_ = gtkui::CreateLinuxStatusIcon(image_, tool_tip_,
|
||||
Browser::Get()->GetName().c_str());
|
||||
icon_->SetDelegate(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h"
|
||||
#include "shell/browser/ui/tray_icon.h"
|
||||
#include "ui/views/linux_ui/status_icon_linux.h"
|
||||
|
||||
|
@ -39,7 +38,7 @@ class TrayIconGtk : public TrayIcon, public views::StatusIconLinux::Delegate {
|
|||
void OnImplInitializationFailed() override;
|
||||
|
||||
private:
|
||||
scoped_refptr<StatusIconLinuxDbus> icon_;
|
||||
std::unique_ptr<views::StatusIconLinux> icon_;
|
||||
gfx::ImageSkia image_;
|
||||
base::string16 tool_tip_;
|
||||
ui::MenuModel* menu_model_;
|
||||
|
|
Loading…
Reference in a new issue