Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fallback to GtkStatusIcon when app indicator is not supported #37034

Merged
merged 2 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,6 @@ source_set("electron_lib") {
sources += [
"shell/browser/certificate_manager_model.cc",
"shell/browser/certificate_manager_model.h",
"shell/browser/ui/gtk/menu_util.cc",
"shell/browser/ui/gtk/menu_util.h",
"shell/browser/ui/gtk_util.cc",
"shell/browser/ui/gtk_util.h",
]
Expand Down
5 changes: 3 additions & 2 deletions docs/api/tray.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ __Platform Considerations__

__Linux__

* Tray icon requires support of [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/)
in user's desktop environment.
* Tray icon uses [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/)
by default, when it is not available in user's desktop environment the
`GtkStatusIcon` will be used instead.
* The `click` event is emitted when the tray icon receives activation from
user, however the StatusNotifierItem spec does not specify which action would
cause an activation, for some environments it is left mouse click, but for
Expand Down
10 changes: 8 additions & 2 deletions filenames.gni
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ filenames = {
"shell/browser/relauncher_linux.cc",
"shell/browser/ui/electron_desktop_window_tree_host_linux.cc",
"shell/browser/ui/file_dialog_gtk.cc",
"shell/browser/ui/gtk/menu_gtk.cc",
"shell/browser/ui/gtk/menu_gtk.h",
"shell/browser/ui/gtk/menu_util.cc",
"shell/browser/ui/gtk/menu_util.h",
"shell/browser/ui/message_box_gtk.cc",
"shell/browser/ui/tray_icon_gtk.cc",
"shell/browser/ui/tray_icon_gtk.h",
"shell/browser/ui/status_icon_gtk.cc",
"shell/browser/ui/status_icon_gtk.h",
"shell/browser/ui/tray_icon_linux.cc",
"shell/browser/ui/tray_icon_linux.h",
"shell/browser/ui/views/client_frame_view_linux.cc",
"shell/browser/ui/views/client_frame_view_linux.h",
"shell/common/application_info_linux.cc",
Expand Down
70 changes: 70 additions & 0 deletions shell/browser/ui/gtk/menu_gtk.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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/menu_gtk.h"

#include <gtk/gtk.h>

#include "shell/browser/ui/gtk/menu_util.h"
#include "ui/base/models/menu_model.h"

namespace electron {

namespace gtkui {

MenuGtk::MenuGtk(ui::MenuModel* model)
: menu_model_(model), gtk_menu_(TakeGObject(gtk_menu_new())) {
if (menu_model_) {
BuildSubmenuFromModel(menu_model_, gtk_menu_,
G_CALLBACK(OnMenuItemActivatedThunk),
&block_activation_, this);
Refresh();
}
}

MenuGtk::~MenuGtk() {
gtk_widget_destroy(gtk_menu_);
}

void MenuGtk::Refresh() {
gtk_container_foreach(GTK_CONTAINER(gtk_menu_.get()), SetMenuItemInfo,
&block_activation_);
}

GtkMenu* MenuGtk::GetGtkMenu() {
return GTK_MENU(gtk_menu_.get());
}

void MenuGtk::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
48 changes: 48 additions & 0 deletions shell/browser/ui/gtk/menu_gtk.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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_MENU_GTK_H_
#define ELECTRON_SHELL_BROWSER_UI_GTK_MENU_GTK_H_

#include "base/callback.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/base/glib/scoped_gobject.h"

typedef struct _GtkMenu GtkMenu;
typedef struct _GtkWidget GtkWidget;

namespace ui {
class MenuModel;
}

namespace electron {

namespace gtkui {

class MenuGtk {
public:
explicit MenuGtk(ui::MenuModel* model);
virtual ~MenuGtk();

// Refreshes all the menu item labels and menu item checked/enabled states.
void Refresh();

GtkMenu* GetGtkMenu();

private:
// Callback for when a menu item is activated.
CHROMEG_CALLBACK_0(MenuGtk, void, OnMenuItemActivated, GtkWidget*);

raw_ptr<ui::MenuModel> menu_model_; // not owned
ScopedGObject<GtkWidget> gtk_menu_;

bool block_activation_ = false;
};

} // namespace gtkui

} // namespace electron

#endif // ELECTRON_SHELL_BROWSER_UI_GTK_MENU_GTK_H_
68 changes: 68 additions & 0 deletions shell/browser/ui/status_icon_gtk.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2023 Microsoft, Inc.
// Copyright (c) 2011 The Chromium Authors.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "shell/browser/ui/status_icon_gtk.h"

#include <gtk/gtk.h>

#include "base/strings/utf_string_conversions.h"
#include "shell/browser/ui/gtk/menu_gtk.h"
#include "shell/browser/ui/gtk_util.h"
#include "ui/gfx/image/image_skia.h"

namespace electron {

StatusIconGtk::StatusIconGtk() : icon_(TakeGObject(gtk_status_icon_new())) {
g_signal_connect(icon_, "activate", G_CALLBACK(OnClickThunk), this);
g_signal_connect(icon_, "popup_menu", G_CALLBACK(OnContextMenuRequestedThunk),
this);
}

StatusIconGtk::~StatusIconGtk() = default;

void StatusIconGtk::SetIcon(const gfx::ImageSkia& image) {
if (image.isNull())
return;

GdkPixbuf* pixbuf = gtk_util::GdkPixbufFromSkBitmap(*image.bitmap());
gtk_status_icon_set_from_pixbuf(icon_, pixbuf);
g_object_unref(pixbuf);
}

void StatusIconGtk::SetToolTip(const std::u16string& tool_tip) {
gtk_status_icon_set_tooltip_text(icon_, base::UTF16ToUTF8(tool_tip).c_str());
}

void StatusIconGtk::UpdatePlatformContextMenu(ui::MenuModel* model) {
if (model)
menu_ = std::make_unique<gtkui::MenuGtk>(model);
}

void StatusIconGtk::RefreshPlatformContextMenu() {
if (menu_)
menu_->Refresh();
}

void StatusIconGtk::OnSetDelegate() {
SetIcon(delegate_->GetImage());
SetToolTip(delegate_->GetToolTip());
UpdatePlatformContextMenu(delegate_->GetMenuModel());
gtk_status_icon_set_visible(icon_, TRUE);
}

void StatusIconGtk::OnClick(GtkStatusIcon* status_icon) {
delegate_->OnClick();
}

void StatusIconGtk::OnContextMenuRequested(GtkStatusIcon* status_icon,
guint button,
guint32 activate_time) {
if (menu_.get()) {
gtk_menu_popup(menu_->GetGtkMenu(), nullptr, nullptr,
gtk_status_icon_position_menu, icon_, button, activate_time);
}
}

} // namespace electron
52 changes: 52 additions & 0 deletions shell/browser/ui/status_icon_gtk.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2023 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#ifndef ELECTRON_SHELL_BROWSER_UI_STATUS_ICON_GTK_H_
#define ELECTRON_SHELL_BROWSER_UI_STATUS_ICON_GTK_H_

#include <memory>

#include "ui/base/glib/glib_integers.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/base/glib/scoped_gobject.h"
#include "ui/linux/status_icon_linux.h"

typedef struct _GtkStatusIcon GtkStatusIcon;

namespace electron {

namespace gtkui {
class MenuGtk;
}

class StatusIconGtk : public ui::StatusIconLinux {
public:
StatusIconGtk();
StatusIconGtk(const StatusIconGtk&) = delete;
StatusIconGtk& operator=(const StatusIconGtk&) = delete;
~StatusIconGtk() override;

// ui::StatusIconLinux:
void SetIcon(const gfx::ImageSkia& image) override;
void SetToolTip(const std::u16string& tool_tip) override;
void UpdatePlatformContextMenu(ui::MenuModel* model) override;
void RefreshPlatformContextMenu() override;
void OnSetDelegate() override;

private:
CHROMEG_CALLBACK_0(StatusIconGtk, void, OnClick, GtkStatusIcon*);
CHROMEG_CALLBACK_2(StatusIconGtk,
void,
OnContextMenuRequested,
GtkStatusIcon*,
guint,
guint);

std::unique_ptr<gtkui::MenuGtk> menu_;
ScopedGObject<GtkStatusIcon> icon_;
};

} // namespace electron

#endif // ELECTRON_SHELL_BROWSER_UI_STATUS_ICON_GTK_H_
95 changes: 0 additions & 95 deletions shell/browser/ui/tray_icon_gtk.cc

This file was deleted.