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:
parent
2955c67c4e
commit
89eb309d0b
13 changed files with 143 additions and 15 deletions
|
@ -58,9 +58,10 @@ If you want to keep exact same behaviors on all platforms, you should not
|
|||
rely on the `click` event and always attach a context menu to the tray icon.
|
||||
|
||||
|
||||
### `new Tray(image)`
|
||||
### `new Tray(image, [guid])`
|
||||
|
||||
* `image` ([NativeImage](native-image.md) | String)
|
||||
* `guid` String (optional) _Windows_ - Assigns a GUID to the tray icon. If the executable is signed and the signature contains an organization in the subject line then the GUID is permanently associated with that signature. OS level settings like the position of the tray icon in the system tray will persist even if the path to the executable changes. If the executable is not code-signed then the GUID is permanently associated with the path to the executable. Changing the path to the executable will break the creation of the tray icon and a new GUID must be used. However, it is highly recommended to use the GUID parameter only in conjunction with code-signed executable. If an App defines multiple tray icons then each icon must use a separate GUID.
|
||||
|
||||
Creates a new tray icon associated with the `image`.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -341,7 +341,7 @@ gfx::Rect TrayIconCocoa::GetBounds() {
|
|||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create() {
|
||||
TrayIcon* TrayIcon::Create(base::Optional<UUID> guid) {
|
||||
return new TrayIconCocoa;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ bool TrayIconGtk::HasClickAction() {
|
|||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create() {
|
||||
TrayIcon* TrayIcon::Create(base::Optional<UUID> guid) {
|
||||
return new TrayIconGtk;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
55
shell/common/gin_converters/guid_converter.h
Normal file
55
shell/common/gin_converters/guid_converter.h
Normal 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_
|
|
@ -20,6 +20,18 @@ describe('tray module', () => {
|
|||
tray = new Tray(badPath)
|
||||
}).to.throw(/Image could not be created from .*/)
|
||||
})
|
||||
|
||||
ifit(process.platform === 'win32')('throws a descriptive error if an invlaid guid is given', () => {
|
||||
expect(() => {
|
||||
tray = new Tray(nativeImage.createEmpty(), 'I am not a guid')
|
||||
}).to.throw('Invalid GUID format')
|
||||
})
|
||||
|
||||
ifit(process.platform === 'win32')('accepts a valid guid', () => {
|
||||
expect(() => {
|
||||
tray = new Tray(nativeImage.createEmpty(), '0019A433-3526-48BA-A66C-676742C0FEFB')
|
||||
}).to.not.throw()
|
||||
})
|
||||
})
|
||||
|
||||
ifdescribe(process.platform === 'darwin')('tray get/set ignoreDoubleClickEvents', () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue