diff --git a/docs/api/tray.md b/docs/api/tray.md index 487dcc025c..84e1b6bd30 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -187,7 +187,7 @@ Returns: Emitted when the mouse clicks the tray icon. -#### Event: 'mouse-enter' _macOS_ +#### Event: 'mouse-enter' _macOS_ _Windows_ Returns: @@ -196,7 +196,7 @@ Returns: Emitted when the mouse enters the tray icon. -#### Event: 'mouse-leave' _macOS_ +#### Event: 'mouse-leave' _macOS_ _Windows_ Returns: diff --git a/shell/browser/ui/win/notify_icon.cc b/shell/browser/ui/win/notify_icon.cc index 71a937a937..2c70659feb 100644 --- a/shell/browser/ui/win/notify_icon.cc +++ b/shell/browser/ui/win/notify_icon.cc @@ -102,6 +102,16 @@ void NotifyIcon::HandleMouseMoveEvent(int modifiers) { NotifyMouseMoved(cursorPos, modifiers); } +void NotifyIcon::HandleMouseEntered(int modifiers) { + gfx::Point cursor_pos = display::Screen::GetScreen()->GetCursorScreenPoint(); + NotifyMouseEntered(cursor_pos, modifiers); +} + +void NotifyIcon::HandleMouseExited(int modifiers) { + gfx::Point cursor_pos = display::Screen::GetScreen()->GetCursorScreenPoint(); + NotifyMouseExited(cursor_pos, modifiers); +} + void NotifyIcon::ResetIcon() { NOTIFYICONDATA icon_data; InitIconData(&icon_data); diff --git a/shell/browser/ui/win/notify_icon.h b/shell/browser/ui/win/notify_icon.h index fa3a3f3902..9649923811 100644 --- a/shell/browser/ui/win/notify_icon.h +++ b/shell/browser/ui/win/notify_icon.h @@ -50,6 +50,8 @@ class NotifyIcon : public TrayIcon { // Handles a mouse move event from the user. void HandleMouseMoveEvent(int modifiers); + void HandleMouseEntered(int modifiers); + void HandleMouseExited(int modifiers); // Re-creates the status tray icon now after the taskbar has been created. void ResetIcon(); diff --git a/shell/browser/ui/win/notify_icon_host.cc b/shell/browser/ui/win/notify_icon_host.cc index 389ebcc1ef..7cc95c85f7 100644 --- a/shell/browser/ui/win/notify_icon_host.cc +++ b/shell/browser/ui/win/notify_icon_host.cc @@ -11,11 +11,13 @@ #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/stl_util.h" +#include "base/timer/timer.h" #include "base/win/win_util.h" #include "base/win/windows_types.h" #include "base/win/wrapped_window_proc.h" #include "content/public/browser/browser_thread.h" #include "shell/browser/ui/win/notify_icon.h" +#include "ui/display/screen.h" #include "ui/events/event_constants.h" #include "ui/events/win/system_event_state_lookup.h" #include "ui/gfx/win/hwnd_util.h" @@ -31,6 +33,8 @@ const UINT kBaseIconId = 2; const wchar_t kNotifyIconHostWindowClass[] = L"Electron_NotifyIconHostWindow"; +constexpr unsigned int kMouseLeaveCheckFrequency = 250; + bool IsWinPressed() { return ((::GetKeyState(VK_LWIN) & 0x8000) == 0x8000) || ((::GetKeyState(VK_RWIN) & 0x8000) == 0x8000); @@ -51,6 +55,103 @@ int GetKeyboardModifiers() { } // namespace +// Helper class used to detect mouse entered and mouse exited events based on +// mouse move event. +class NotifyIconHost::MouseEnteredExitedDetector { + public: + MouseEnteredExitedDetector() = default; + ~MouseEnteredExitedDetector() = default; + + // disallow copy + MouseEnteredExitedDetector(const MouseEnteredExitedDetector&) = delete; + MouseEnteredExitedDetector& operator=(const MouseEnteredExitedDetector&) = + delete; + + // disallow move + MouseEnteredExitedDetector(MouseEnteredExitedDetector&&) = delete; + MouseEnteredExitedDetector& operator=(MouseEnteredExitedDetector&&) = delete; + + void MouseMoveEvent(raw_ptr icon) { + if (!icon) + return; + + // If cursor is out of icon then skip this move event. + if (!IsCursorOverIcon(icon)) + return; + + // If user moved cursor to other icon then send mouse exited event for + // old icon. + if (current_mouse_entered_ && + current_mouse_entered_->icon_id() != icon->icon_id()) { + SendExitedEvent(); + } + + // If timer is runnig then cursor is arelady over icon and + // CheckCursorPositionOverIcon will be repeadly checking when to send + // mouse exited event. + if (mouse_exit_timer_.IsRunning()) + return; + + SendEnteredEvent(icon); + + // Start repeadly checking when to send mouse exited event. + StartObservingIcon(icon); + } + + void IconRemoved(raw_ptr icon) { + if (current_mouse_entered_ && + current_mouse_entered_->icon_id() == icon->icon_id()) { + SendExitedEvent(); + } + } + + private: + void SendEnteredEvent(raw_ptr icon) { + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&NotifyIcon::HandleMouseEntered, + icon->GetWeakPtr(), GetKeyboardModifiers())); + } + + void SendExitedEvent() { + mouse_exit_timer_.Stop(); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&NotifyIcon::HandleMouseExited, + std::move(current_mouse_entered_), + GetKeyboardModifiers())); + } + + bool IsCursorOverIcon(raw_ptr icon) { + gfx::Point cursor_pos = + display::Screen::GetScreen()->GetCursorScreenPoint(); + return icon->GetBounds().Contains(cursor_pos); + } + + void CheckCursorPositionOverIcon() { + if (!current_mouse_entered_ || + IsCursorOverIcon(current_mouse_entered_.get())) + return; + + SendExitedEvent(); + } + + void StartObservingIcon(raw_ptr icon) { + current_mouse_entered_ = icon->GetWeakPtr(); + mouse_exit_timer_.Start( + FROM_HERE, base::Milliseconds(kMouseLeaveCheckFrequency), + base::BindRepeating( + &MouseEnteredExitedDetector::CheckCursorPositionOverIcon, + weak_factory_.GetWeakPtr())); + } + + // Timer used to check if cursor is still over the icon. + base::MetronomeTimer mouse_exit_timer_; + + // Weak pointer to icon over which cursor is hovering. + base::WeakPtr current_mouse_entered_; + + base::WeakPtrFactory weak_factory_{this}; +}; + NotifyIconHost::NotifyIconHost() { // Register our window class WNDCLASSEX window_class; @@ -74,6 +175,9 @@ NotifyIconHost::NotifyIconHost() { instance_, 0); gfx::CheckWindowCreated(window_, ::GetLastError()); gfx::SetWindowUserData(window_, this); + + mouse_entered_exited_detector_ = + std::make_unique(); } NotifyIconHost::~NotifyIconHost() { @@ -116,6 +220,8 @@ void NotifyIconHost::Remove(NotifyIcon* icon) { return; } + mouse_entered_exited_detector_->IconRemoved(*i); + notify_icons_.erase(i); } @@ -208,6 +314,8 @@ LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, return TRUE; case WM_MOUSEMOVE: + mouse_entered_exited_detector_->MouseMoveEvent(win_icon); + content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&NotifyIcon::HandleMouseMoveEvent, win_icon_weak, GetKeyboardModifiers())); diff --git a/shell/browser/ui/win/notify_icon_host.h b/shell/browser/ui/win/notify_icon_host.h index 85a7537a3e..3b5ce54626 100644 --- a/shell/browser/ui/win/notify_icon_host.h +++ b/shell/browser/ui/win/notify_icon_host.h @@ -64,6 +64,9 @@ class NotifyIconHost { // The message ID of the "TaskbarCreated" message, sent to us when we need to // reset our status icons. UINT taskbar_created_message_ = 0; + + class MouseEnteredExitedDetector; + std::unique_ptr mouse_entered_exited_detector_; }; } // namespace electron