feat: allow GUID parameter to avoid systray demotion on Windows (#21891)

* fix: systray icon demotion

Adding support for GUID parameter in Tray API.
In combination with signed binaries this allows to maintain
the position in the systray on Windows.

* unit tests

* make mac and linux compile
This commit is contained in:
bitdisaster 2020-01-30 21:37:03 -08:00 committed by GitHub
parent 2955c67c4e
commit 89eb309d0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 143 additions and 15 deletions

View file

@ -11,6 +11,7 @@
#include "shell/browser/browser.h"
#include "shell/common/api/atom_api_native_image.h"
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_converters/guid_converter.h"
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
@ -54,8 +55,10 @@ namespace electron {
namespace api {
Tray::Tray(gin::Handle<NativeImage> image, gin_helper::Arguments* args)
: tray_icon_(TrayIcon::Create()) {
Tray::Tray(gin::Handle<NativeImage> image,
base::Optional<UUID> guid,
gin_helper::Arguments* args)
: tray_icon_(TrayIcon::Create(guid)) {
SetImage(args->isolate(), image);
tray_icon_->AddObserver(this);
@ -67,12 +70,21 @@ Tray::~Tray() = default;
// static
gin_helper::WrappableBase* Tray::New(gin_helper::ErrorThrower thrower,
gin::Handle<NativeImage> image,
base::Optional<UUID> guid,
gin_helper::Arguments* args) {
if (!Browser::Get()->is_ready()) {
thrower.ThrowError("Cannot create Tray before app is ready");
return nullptr;
}
return new Tray(image, args);
#if defined(OS_WIN)
if (!guid.has_value() && args->Length() > 1) {
thrower.ThrowError("Invalid GUID format");
return nullptr;
}
#endif
return new Tray(image, guid, args);
}
void Tray::OnClicked(const gfx::Rect& bounds,

View file

@ -12,6 +12,7 @@
#include "gin/handle.h"
#include "shell/browser/ui/tray_icon.h"
#include "shell/browser/ui/tray_icon_observer.h"
#include "shell/common/gin_converters/guid_converter.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/gin_helper/trackable_object.h"
@ -36,13 +37,16 @@ class Tray : public gin_helper::TrackableObject<Tray>, public TrayIconObserver {
public:
static gin_helper::WrappableBase* New(gin_helper::ErrorThrower thrower,
gin::Handle<NativeImage> image,
base::Optional<UUID> guid,
gin_helper::Arguments* args);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
protected:
Tray(gin::Handle<NativeImage> image, gin_helper::Arguments* args);
Tray(gin::Handle<NativeImage> image,
base::Optional<UUID> guid,
gin_helper::Arguments* args);
~Tray() override;
// TrayIconObserver:

View file

@ -11,13 +11,14 @@
#include "base/observer_list.h"
#include "shell/browser/ui/atom_menu_model.h"
#include "shell/browser/ui/tray_icon_observer.h"
#include "shell/common/gin_converters/guid_converter.h"
#include "ui/gfx/geometry/rect.h"
namespace electron {
class TrayIcon {
public:
static TrayIcon* Create();
static TrayIcon* Create(base::Optional<UUID> guid);
#if defined(OS_WIN)
using ImageType = HICON;

View file

@ -341,7 +341,7 @@ gfx::Rect TrayIconCocoa::GetBounds() {
}
// static
TrayIcon* TrayIcon::Create() {
TrayIcon* TrayIcon::Create(base::Optional<UUID> guid) {
return new TrayIconCocoa;
}

View file

@ -91,7 +91,7 @@ bool TrayIconGtk::HasClickAction() {
}
// static
TrayIcon* TrayIcon::Create() {
TrayIcon* TrayIcon::Create(base::Optional<UUID> guid) {
return new TrayIconGtk;
}

View file

@ -8,9 +8,9 @@
namespace electron {
// static
TrayIcon* TrayIcon::Create() {
TrayIcon* TrayIcon::Create(base::Optional<UUID> guid) {
static NotifyIconHost host;
return host.CreateNotifyIcon();
return host.CreateNotifyIcon(guid);
}
} // namespace electron

View file

@ -4,6 +4,7 @@
#include "shell/browser/ui/win/notify_icon.h"
#include <objbase.h>
#include <utility>
#include "base/strings/string_number_conversions.h"
@ -43,12 +44,18 @@ UINT ConvertIconType(electron::TrayIcon::IconType type) {
namespace electron {
NotifyIcon::NotifyIcon(NotifyIconHost* host, UINT id, HWND window, UINT message)
NotifyIcon::NotifyIcon(NotifyIconHost* host,
UINT id,
HWND window,
UINT message,
GUID guid)
: host_(host),
icon_id_(id),
window_(window),
message_id_(message),
weak_factory_(this) {
guid_ = guid;
is_using_guid_ = guid != GUID_DEFAULT;
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
icon_data.uFlags |= NIF_MESSAGE;
@ -246,6 +253,9 @@ gfx::Rect NotifyIcon::GetBounds() {
icon_id.uID = icon_id_;
icon_id.hWnd = window_;
icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER);
if (is_using_guid_) {
icon_id.guidItem = guid_;
}
RECT rect = {0};
Shell_NotifyIconGetRect(&icon_id, &rect);
@ -257,6 +267,10 @@ void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) {
icon_data->cbSize = sizeof(NOTIFYICONDATA);
icon_data->hWnd = window_;
icon_data->uID = icon_id_;
if (is_using_guid_) {
icon_data->uFlags = NIF_GUID;
icon_data->guidItem = guid_;
}
}
void NotifyIcon::OnContextMenuClosed() {

View file

@ -17,6 +17,7 @@
#include "base/memory/weak_ptr.h"
#include "base/win/scoped_gdi_object.h"
#include "shell/browser/ui/tray_icon.h"
#include "shell/browser/ui/win/notify_icon_host.h"
namespace gfx {
class Point;
@ -34,7 +35,11 @@ class NotifyIconHost;
class NotifyIcon : public TrayIcon {
public:
// Constructor which provides this icon's unique ID and messaging window.
NotifyIcon(NotifyIconHost* host, UINT id, HWND window, UINT message);
NotifyIcon(NotifyIconHost* host,
UINT id,
HWND window,
UINT message,
GUID guid);
~NotifyIcon() override;
// Handles a click event from the user - if |left_button_click| is true and
@ -53,6 +58,7 @@ class NotifyIcon : public TrayIcon {
UINT icon_id() const { return icon_id_; }
HWND window() const { return window_; }
UINT message_id() const { return message_id_; }
GUID guid() const { return guid_; }
// Overridden from TrayIcon:
void SetImage(HICON image) override;
@ -89,6 +95,12 @@ class NotifyIcon : public TrayIcon {
// The context menu.
AtomMenuModel* menu_model_ = nullptr;
// An optional GUID used for identifying tray entries on Windows
GUID guid_ = GUID_DEFAULT;
// indicates whether the tray entry is associated with a guid
bool is_using_guid_ = false;
// Context menu associated with this icon (if any).
std::unique_ptr<views::MenuRunner> menu_runner_;

View file

@ -83,9 +83,22 @@ NotifyIconHost::~NotifyIconHost() {
delete ptr;
}
NotifyIcon* NotifyIconHost::CreateNotifyIcon() {
NotifyIcon* NotifyIconHost::CreateNotifyIcon(base::Optional<UUID> guid) {
if (guid.has_value()) {
for (NotifyIcons::const_iterator i(notify_icons_.begin());
i != notify_icons_.end(); ++i) {
NotifyIcon* current_win_icon = static_cast<NotifyIcon*>(*i);
if (current_win_icon->guid() == guid.value()) {
LOG(WARNING)
<< "Guid already in use. Existing tray entry will be replaced.";
}
}
}
NotifyIcon* notify_icon =
new NotifyIcon(this, NextIconId(), window_, kNotifyIconMessage);
new NotifyIcon(this, NextIconId(), window_, kNotifyIconMessage,
guid.has_value() ? guid.value() : GUID_DEFAULT);
notify_icons_.push_back(notify_icon);
return notify_icon;
}

View file

@ -10,6 +10,10 @@
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "shell/common/gin_converters/guid_converter.h"
const GUID GUID_DEFAULT = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}};
namespace electron {
@ -20,7 +24,7 @@ class NotifyIconHost {
NotifyIconHost();
~NotifyIconHost();
NotifyIcon* CreateNotifyIcon();
NotifyIcon* CreateNotifyIcon(base::Optional<UUID> guid);
void Remove(NotifyIcon* notify_icon);
private:

View file

@ -0,0 +1,55 @@
// Copyright (c) 2020 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_COMMON_GIN_CONVERTERS_GUID_CONVERTER_H_
#define SHELL_COMMON_GIN_CONVERTERS_GUID_CONVERTER_H_
#if defined(OS_WIN)
#include <rpc.h>
#endif
#include <string>
#include "gin/converter.h"
#if defined(OS_WIN)
typedef GUID UUID;
#else
typedef struct {
} UUID;
#endif
namespace gin {
template <>
struct Converter<UUID> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
UUID* out) {
#if defined(OS_WIN)
std::string guid;
if (!gin::ConvertFromV8(isolate, val, &guid))
return false;
UUID uid;
if (guid.length() > 0) {
unsigned char* uid_cstr = (unsigned char*)guid.c_str();
RPC_STATUS result = UuidFromStringA(uid_cstr, &uid);
if (result == RPC_S_INVALID_STRING_UUID) {
return false;
} else {
*out = uid;
return true;
}
}
return false;
#else
return false;
#endif
}
};
} // namespace gin
#endif // SHELL_COMMON_GIN_CONVERTERS_GUID_CONVERTER_H_