mirror of
https://github.com/electron/electron.git
synced 2026-01-08 23:18:06 -05:00
feat: allow macOS tray to maintain position (#47838)
* feat: allow macOS tray to maintain position * refactor: just use guid * test: fixup tests * docs: clarify UUID format
This commit is contained in:
@@ -52,10 +52,12 @@ gin::DeprecatedWrapperInfo Tray::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
Tray::Tray(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> image,
|
||||
std::optional<UUID> guid)
|
||||
: tray_icon_(TrayIcon::Create(guid)) {
|
||||
std::optional<base::Uuid> guid)
|
||||
: guid_(guid), tray_icon_(TrayIcon::Create(guid)) {
|
||||
SetImage(isolate, image);
|
||||
tray_icon_->AddObserver(this);
|
||||
if (guid.has_value())
|
||||
tray_icon_->SetAutoSaveName(guid.value().AsLowercaseString());
|
||||
}
|
||||
|
||||
Tray::~Tray() = default;
|
||||
@@ -63,19 +65,17 @@ Tray::~Tray() = default;
|
||||
// static
|
||||
gin_helper::Handle<Tray> Tray::New(gin_helper::ErrorThrower thrower,
|
||||
v8::Local<v8::Value> image,
|
||||
std::optional<UUID> guid,
|
||||
std::optional<base::Uuid> guid,
|
||||
gin::Arguments* args) {
|
||||
if (!Browser::Get()->is_ready()) {
|
||||
thrower.ThrowError("Cannot create Tray before app is ready");
|
||||
return {};
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (!guid.has_value() && args->Length() > 1) {
|
||||
thrower.ThrowError("Invalid GUID format");
|
||||
thrower.ThrowError("Invalid GUID format - GUID must be a string");
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
// Error thrown by us will be dropped when entering V8.
|
||||
// Make sure to abort early and propagate the error to JS.
|
||||
@@ -392,6 +392,15 @@ gfx::Rect Tray::GetBounds() {
|
||||
return tray_icon_->GetBounds();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Tray::GetGUID() {
|
||||
if (!CheckAlive())
|
||||
return {};
|
||||
auto* isolate = JavascriptEnvironment::GetIsolate();
|
||||
if (!guid_)
|
||||
return v8::Null(isolate);
|
||||
return gin::ConvertToV8(isolate, guid_.value());
|
||||
}
|
||||
|
||||
bool Tray::CheckAlive() {
|
||||
if (!tray_icon_) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
@@ -424,6 +433,7 @@ void Tray::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetMethod("closeContextMenu", &Tray::CloseContextMenu)
|
||||
.SetMethod("setContextMenu", &Tray::SetContextMenu)
|
||||
.SetMethod("getBounds", &Tray::GetBounds)
|
||||
.SetMethod("getGUID", &Tray::GetGUID)
|
||||
.Build();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class Tray final : public gin_helper::DeprecatedWrappable<Tray>,
|
||||
// gin_helper::Constructible
|
||||
static gin_helper::Handle<Tray> New(gin_helper::ErrorThrower thrower,
|
||||
v8::Local<v8::Value> image,
|
||||
std::optional<UUID> guid,
|
||||
std::optional<base::Uuid> guid,
|
||||
gin::Arguments* args);
|
||||
|
||||
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
|
||||
@@ -65,7 +65,7 @@ class Tray final : public gin_helper::DeprecatedWrappable<Tray>,
|
||||
private:
|
||||
Tray(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> image,
|
||||
std::optional<UUID> guid);
|
||||
std::optional<base::Uuid> guid);
|
||||
~Tray() override;
|
||||
|
||||
// TrayIconObserver:
|
||||
@@ -111,10 +111,12 @@ class Tray final : public gin_helper::DeprecatedWrappable<Tray>,
|
||||
void SetContextMenu(gin_helper::ErrorThrower thrower,
|
||||
v8::Local<v8::Value> arg);
|
||||
gfx::Rect GetBounds();
|
||||
v8::Local<v8::Value> GetGUID();
|
||||
|
||||
bool CheckAlive();
|
||||
|
||||
v8::Global<v8::Value> menu_;
|
||||
std::optional<base::Uuid> guid_;
|
||||
std::unique_ptr<TrayIcon> tray_icon_;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ gfx::Rect TrayIcon::GetBounds() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void TrayIcon::SetAutoSaveName(const std::string& name) {}
|
||||
|
||||
void TrayIcon::NotifyClicked(const gfx::Rect& bounds,
|
||||
const gfx::Point& location,
|
||||
int modifiers) {
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace electron {
|
||||
|
||||
class TrayIcon {
|
||||
public:
|
||||
static TrayIcon* Create(std::optional<UUID> guid);
|
||||
static TrayIcon* Create(std::optional<base::Uuid> guid);
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
using ImageType = HICON;
|
||||
@@ -99,6 +99,8 @@ class TrayIcon {
|
||||
// Returns the bounds of tray icon.
|
||||
virtual gfx::Rect GetBounds();
|
||||
|
||||
virtual void SetAutoSaveName(const std::string& name);
|
||||
|
||||
void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); }
|
||||
void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); }
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class TrayIconCocoa : public TrayIcon {
|
||||
void CloseContextMenu() override;
|
||||
void SetContextMenu(raw_ptr<ElectronMenuModel> menu_model) override;
|
||||
gfx::Rect GetBounds() override;
|
||||
void SetAutoSaveName(const std::string& name) override;
|
||||
|
||||
base::WeakPtr<TrayIconCocoa> GetWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "base/message_loop/message_pump_apple.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/task/current_thread.h"
|
||||
#include "base/uuid.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "shell/browser/ui/cocoa/NSString+ANSI.h"
|
||||
@@ -68,6 +69,10 @@
|
||||
[self setFrame:[statusItem_ button].frame];
|
||||
}
|
||||
|
||||
- (void)setAutosaveName:(NSString*)name {
|
||||
statusItem_.autosaveName = name;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas {
|
||||
// Use NSTrackingArea for listening to mouseEnter, mouseExit, and mouseMove
|
||||
// events.
|
||||
@@ -420,8 +425,12 @@ gfx::Rect TrayIconCocoa::GetBounds() {
|
||||
return gfx::ScreenRectFromNSRect([status_item_view_ window].frame);
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetAutoSaveName(const std::string& name) {
|
||||
[status_item_view_ setAutosaveName:base::SysUTF8ToNSString(name)];
|
||||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create(std::optional<UUID> guid) {
|
||||
TrayIcon* TrayIcon::Create(std::optional<base::Uuid> guid) {
|
||||
return new TrayIconCocoa;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ ui::StatusIconLinux* TrayIconLinux::GetStatusIcon() {
|
||||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create(std::optional<UUID> guid) {
|
||||
TrayIcon* TrayIcon::Create(std::optional<base::Uuid> guid) {
|
||||
return new TrayIconLinux;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace electron {
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create(std::optional<UUID> guid) {
|
||||
TrayIcon* TrayIcon::Create(std::optional<base::Uuid> guid) {
|
||||
static NotifyIconHost host;
|
||||
return host.CreateNotifyIcon(guid);
|
||||
}
|
||||
|
||||
@@ -190,21 +190,32 @@ NotifyIconHost::~NotifyIconHost() {
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
NotifyIcon* NotifyIconHost::CreateNotifyIcon(std::optional<UUID> guid) {
|
||||
if (guid.has_value()) {
|
||||
for (NotifyIcons::const_iterator i(notify_icons_.begin());
|
||||
i != notify_icons_.end(); ++i) {
|
||||
auto* 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* NotifyIconHost::CreateNotifyIcon(std::optional<base::Uuid> guid) {
|
||||
std::string guid_str =
|
||||
guid.has_value() ? guid.value().AsLowercaseString() : "";
|
||||
UUID uid = GUID_NULL;
|
||||
if (!guid_str.empty()) {
|
||||
if (guid_str[0] == '{' && guid_str[guid_str.length() - 1] == '}') {
|
||||
guid_str = guid_str.substr(1, guid_str.length() - 2);
|
||||
}
|
||||
|
||||
unsigned char* uid_cstr = (unsigned char*)guid_str.c_str();
|
||||
RPC_STATUS result = UuidFromStringA(uid_cstr, &uid);
|
||||
if (result != RPC_S_INVALID_STRING_UUID) {
|
||||
for (NotifyIcons::const_iterator i(notify_icons_.begin());
|
||||
i != notify_icons_.end(); ++i) {
|
||||
auto* current_win_icon = static_cast<NotifyIcon*>(*i);
|
||||
if (current_win_icon->guid() == uid) {
|
||||
LOG(WARNING)
|
||||
<< "Guid already in use. Existing tray entry will be replaced.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* notify_icon =
|
||||
new NotifyIcon(this, NextIconId(), window_, kNotifyIconMessage,
|
||||
guid.has_value() ? guid.value() : GUID_DEFAULT);
|
||||
uid == GUID_NULL ? GUID_DEFAULT : uid);
|
||||
|
||||
notify_icons_.push_back(notify_icon);
|
||||
return notify_icon;
|
||||
|
||||
@@ -27,7 +27,7 @@ class NotifyIconHost {
|
||||
NotifyIconHost(const NotifyIconHost&) = delete;
|
||||
NotifyIconHost& operator=(const NotifyIconHost&) = delete;
|
||||
|
||||
NotifyIcon* CreateNotifyIcon(std::optional<UUID> guid);
|
||||
NotifyIcon* CreateNotifyIcon(std::optional<base::Uuid> guid);
|
||||
void Remove(NotifyIcon* notify_icon);
|
||||
|
||||
private:
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/uuid.h"
|
||||
#include "gin/converter.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
@@ -36,18 +38,40 @@ typedef struct {
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <>
|
||||
struct Converter<base::Uuid> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
base::Uuid* out) {
|
||||
std::string guid;
|
||||
if (!gin::ConvertFromV8(isolate, val, &guid))
|
||||
return false;
|
||||
|
||||
base::Uuid parsed = base::Uuid::ParseLowercase(base::ToLowerASCII(guid));
|
||||
if (!parsed.is_valid())
|
||||
return false;
|
||||
|
||||
*out = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, base::Uuid val) {
|
||||
const std::string guid = val.AsLowercaseString();
|
||||
return gin::ConvertToV8(isolate, guid);
|
||||
}
|
||||
};
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
template <>
|
||||
struct Converter<UUID> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
UUID* out) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
std::string guid;
|
||||
if (!gin::ConvertFromV8(isolate, val, &guid))
|
||||
return false;
|
||||
|
||||
UUID uid;
|
||||
|
||||
if (!guid.empty()) {
|
||||
if (guid[0] == '{' && guid[guid.length() - 1] == '}') {
|
||||
guid = guid.substr(1, guid.length() - 2);
|
||||
@@ -62,12 +86,8 @@ struct Converter<UUID> {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, UUID val) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
const GUID GUID_NULL = {};
|
||||
if (val == GUID_NULL) {
|
||||
return v8::String::Empty(isolate);
|
||||
@@ -75,11 +95,9 @@ struct Converter<UUID> {
|
||||
std::wstring uid = base::win::WStringFromGUID(val);
|
||||
return StringToV8(isolate, base::SysWideToUTF8(uid));
|
||||
}
|
||||
#else
|
||||
return v8::Undefined(isolate);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace gin
|
||||
|
||||
|
||||
Reference in New Issue
Block a user