Skip to content

Commit 2b0e7ce

Browse files
committed
Add WindowMessageDispatcher for tray icon events
Introduces WindowMessageDispatcher to manage global window message handlers, replacing WindowProcDelegateManager in tray_icon_windows.cpp. This refactor improves message routing and handler management for Windows tray icon functionality.
1 parent d57bfcc commit 2b0e7ce

File tree

3 files changed

+245
-7
lines changed

3 files changed

+245
-7
lines changed

src/platform/windows/tray_icon_windows.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include "../../tray_icon.h"
1616
#include "../../tray_icon_event.h"
1717
#include "string_utils_windows.h"
18-
#include "window_proc_delegate_manager.h"
18+
#include "window_message_dispatcher.h"
1919

2020
namespace nativeapi {
2121

@@ -57,15 +57,18 @@ class TrayIcon::Impl {
5757
nid_.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
5858
nid_.uCallbackMessage = WM_USER + 1; // Custom message for tray icon events
5959

60-
window_proc_id_ = WindowProcDelegateManager::GetInstance().RegisterDelegate(
60+
window_proc_handle_id_ = WindowMessageDispatcher::GetInstance().RegisterHandler(
6161
[this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
6262
return HandleWindowProc(hwnd, message, wparam, lparam);
6363
});
6464
}
6565

6666
~Impl() {
67-
WindowProcDelegateManager::GetInstance().UnregisterDelegate(
68-
window_proc_id_);
67+
// Unregister window procedure handler
68+
if (window_proc_handle_id_ != -1) {
69+
WindowMessageDispatcher::GetInstance().UnregisterHandler(window_proc_handle_id_);
70+
}
71+
6972
if (hwnd_) {
7073
Shell_NotifyIconW(NIM_DELETE, &nid_);
7174
// Destroy the window if we own it
@@ -112,7 +115,7 @@ class TrayIcon::Impl {
112115
return std::nullopt; // Let default window procedure handle it
113116
}
114117

115-
int window_proc_id_;
118+
int window_proc_handle_id_;
116119
HWND hwnd_;
117120
NOTIFYICONDATAW nid_;
118121
std::shared_ptr<Menu> context_menu_;
@@ -142,8 +145,8 @@ TrayIcon::TrayIcon(void* native_tray_icon) {
142145

143146
if (!class_registered) {
144147
WNDCLASSW wc = {};
145-
// Use WindowProcDelegateManager to route messages to registered delegates
146-
wc.lpfnWndProc = WindowProcDelegateManager::InternalWindowProc;
148+
// Use WindowMessageDispatcher to route messages to registered handlers
149+
wc.lpfnWndProc = WindowMessageDispatcher::WindowProc;
147150
wc.hInstance = hInstance;
148151
wc.lpszClassName = class_name.c_str();
149152

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#include "window_message_dispatcher.h"
2+
3+
#include <windows.h>
4+
#include <iostream>
5+
#include <vector>
6+
7+
namespace nativeapi {
8+
9+
WindowMessageDispatcher& WindowMessageDispatcher::GetInstance() {
10+
static WindowMessageDispatcher instance;
11+
return instance;
12+
}
13+
14+
WindowMessageDispatcher::~WindowMessageDispatcher() {
15+
RestoreOriginalWindowProc();
16+
}
17+
18+
LRESULT CALLBACK WindowMessageDispatcher::WindowProc(HWND hwnd,
19+
UINT message,
20+
WPARAM wparam,
21+
LPARAM lparam) {
22+
WindowMessageDispatcher& dispatcher = GetInstance();
23+
24+
// Copy handlers while holding the lock to avoid holding lock during handler
25+
// execution
26+
std::vector<WindowMessageHandler> handlers_copy;
27+
WNDPROC original_proc = nullptr;
28+
29+
{
30+
std::lock_guard<std::mutex> lock(dispatcher.mutex_);
31+
handlers_copy.reserve(dispatcher.handlers_.size());
32+
for (const auto& pair : dispatcher.handlers_) {
33+
if (pair.second) {
34+
handlers_copy.push_back(pair.second);
35+
}
36+
}
37+
original_proc = dispatcher.original_window_proc_;
38+
}
39+
40+
// Dispatch message to all registered handlers (without holding the lock)
41+
for (const auto& handler : handlers_copy) {
42+
if (handler) {
43+
auto result = handler(hwnd, message, wparam, lparam);
44+
if (result.has_value()) {
45+
return result.value();
46+
}
47+
}
48+
}
49+
50+
// If no handler handled the message, call the original WindowProc
51+
if (original_proc) {
52+
return CallWindowProc(original_proc, hwnd, message, wparam, lparam);
53+
}
54+
55+
// Fallback to DefWindowProc if no original proc is available
56+
return DefWindowProc(hwnd, message, wparam, lparam);
57+
}
58+
59+
HWND WindowMessageDispatcher::GetTopLevelWindow() {
60+
// Try to get the foreground window first
61+
HWND hwnd = GetForegroundWindow();
62+
if (hwnd && IsWindow(hwnd)) {
63+
return hwnd;
64+
}
65+
66+
// If no foreground window, try to get the active window
67+
hwnd = GetActiveWindow();
68+
if (hwnd && IsWindow(hwnd)) {
69+
return hwnd;
70+
}
71+
72+
// If still no window, try to find any top-level window
73+
hwnd = FindWindow(nullptr, nullptr);
74+
if (hwnd && IsWindow(hwnd)) {
75+
return hwnd;
76+
}
77+
78+
return nullptr;
79+
}
80+
81+
void WindowMessageDispatcher::SetupInternalWindowProc() {
82+
if (window_proc_hooked_) {
83+
return; // Already hooked
84+
}
85+
86+
top_level_window_ = GetTopLevelWindow();
87+
if (!top_level_window_) {
88+
std::cerr << "Failed to get top level window for WindowProc hooking"
89+
<< std::endl;
90+
return;
91+
}
92+
93+
// Store the original WindowProc
94+
original_window_proc_ = reinterpret_cast<WNDPROC>(
95+
GetWindowLongPtr(top_level_window_, GWLP_WNDPROC));
96+
if (!original_window_proc_) {
97+
std::cerr << "Failed to get original WindowProc" << std::endl;
98+
return;
99+
}
100+
101+
// Set our internal WindowProc
102+
if (SetWindowLongPtr(top_level_window_, GWLP_WNDPROC,
103+
reinterpret_cast<LONG_PTR>(WindowProc)) == 0) {
104+
DWORD error = GetLastError();
105+
std::cerr << "Failed to set internal WindowProc. Error: " << error
106+
<< std::endl;
107+
return;
108+
}
109+
110+
window_proc_hooked_ = true;
111+
}
112+
113+
void WindowMessageDispatcher::RestoreOriginalWindowProc() {
114+
if (!window_proc_hooked_ || !top_level_window_ || !original_window_proc_) {
115+
return;
116+
}
117+
118+
// Restore the original WindowProc
119+
if (SetWindowLongPtr(top_level_window_, GWLP_WNDPROC,
120+
reinterpret_cast<LONG_PTR>(original_window_proc_)) ==
121+
0) {
122+
DWORD error = GetLastError();
123+
std::cerr << "Failed to restore original WindowProc. Error: " << error
124+
<< std::endl;
125+
}
126+
127+
// Reset state
128+
window_proc_hooked_ = false;
129+
top_level_window_ = nullptr;
130+
original_window_proc_ = nullptr;
131+
}
132+
133+
int WindowMessageDispatcher::RegisterHandler(WindowMessageHandler handler) {
134+
if (!handler) {
135+
return -1;
136+
}
137+
138+
std::lock_guard<std::mutex> lock(mutex_);
139+
140+
int id = next_id_++;
141+
handlers_[id] = std::move(handler);
142+
143+
// If this is the first handler, setup the internal WindowProc
144+
if (handlers_.size() == 1) {
145+
SetupInternalWindowProc();
146+
}
147+
148+
return id;
149+
}
150+
151+
bool WindowMessageDispatcher::UnregisterHandler(int id) {
152+
if (id < 0) {
153+
return false;
154+
}
155+
156+
std::lock_guard<std::mutex> lock(mutex_);
157+
158+
auto it = handlers_.find(id);
159+
if (it != handlers_.end()) {
160+
handlers_.erase(it);
161+
162+
// If no handlers remain, restore the original WindowProc
163+
if (handlers_.empty()) {
164+
RestoreOriginalWindowProc();
165+
}
166+
167+
return true;
168+
}
169+
170+
return false;
171+
}
172+
173+
} // namespace nativeapi
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
3+
#include <windows.h>
4+
5+
#include <functional>
6+
#include <mutex>
7+
#include <optional>
8+
#include <unordered_map>
9+
10+
namespace nativeapi {
11+
12+
using WindowMessageHandler = std::function<std::optional<
13+
LRESULT>(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)>;
14+
15+
/// @brief Simplified window message dispatcher
16+
class WindowMessageDispatcher {
17+
public:
18+
// Singleton pattern
19+
static WindowMessageDispatcher& GetInstance();
20+
21+
// Prevent copying
22+
WindowMessageDispatcher(const WindowMessageDispatcher&) = delete;
23+
WindowMessageDispatcher& operator=(const WindowMessageDispatcher&) = delete;
24+
25+
/// @brief Register a handler
26+
/// @return Handler ID if successful, -1 on failure
27+
int RegisterHandler(WindowMessageHandler handler);
28+
29+
/// @brief Unregister a handler by ID
30+
/// @return true if handler was found and removed, false otherwise
31+
bool UnregisterHandler(int id);
32+
33+
/// @brief Window procedure hook - DO NOT CALL DIRECTLY
34+
static LRESULT CALLBACK WindowProc(HWND hwnd,
35+
UINT msg,
36+
WPARAM wparam,
37+
LPARAM lparam);
38+
39+
private:
40+
WindowMessageDispatcher() = default;
41+
~WindowMessageDispatcher();
42+
43+
// Get the top level window handle
44+
HWND GetTopLevelWindow();
45+
46+
// Setup internal WindowProc for the top level window
47+
void SetupInternalWindowProc();
48+
49+
// Restore original WindowProc when no handlers are registered
50+
void RestoreOriginalWindowProc();
51+
52+
mutable std::mutex mutex_;
53+
std::unordered_map<int, WindowMessageHandler> handlers_;
54+
int next_id_ = 1;
55+
56+
// Store original WindowProc and window handle
57+
HWND top_level_window_ = nullptr;
58+
WNDPROC original_window_proc_ = nullptr;
59+
bool window_proc_hooked_ = false;
60+
};
61+
62+
} // namespace nativeapi

0 commit comments

Comments
 (0)