Skip to content

Commit 3122b02

Browse files
committed
Add WindowMessageDispatcher for tray icon events
1 parent d57bfcc commit 3122b02

File tree

5 files changed

+307
-227
lines changed

5 files changed

+307
-227
lines changed

src/platform/windows/tray_icon_windows.cpp

Lines changed: 11 additions & 8 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(
61-
[this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
60+
window_proc_handle_id_ = WindowMessageDispatcher::GetInstance().RegisterHandler(
61+
hwnd, [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: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#include "window_message_dispatcher.h"
2+
#include <algorithm>
3+
4+
namespace nativeapi {
5+
6+
WindowMessageDispatcher& WindowMessageDispatcher::GetInstance() {
7+
static WindowMessageDispatcher instance;
8+
return instance;
9+
}
10+
11+
WindowMessageDispatcher::~WindowMessageDispatcher() {
12+
std::lock_guard<std::mutex> lock(mutex_);
13+
14+
// Uninstall all hooks before destruction
15+
for (const auto& [hwnd, _] : original_procs_) {
16+
UninstallHook(hwnd);
17+
}
18+
original_procs_.clear();
19+
}
20+
21+
int WindowMessageDispatcher::RegisterHandler(WindowMessageHandler handler) {
22+
std::lock_guard<std::mutex> lock(mutex_);
23+
24+
int id = next_id_++;
25+
handlers_[id] = {std::move(handler), HWND(0)}; // HWND(0) for global handler
26+
return id;
27+
}
28+
29+
int WindowMessageDispatcher::RegisterHandler(HWND hwnd, WindowMessageHandler handler) {
30+
std::lock_guard<std::mutex> lock(mutex_);
31+
32+
int id = next_id_++;
33+
handlers_[id] = {std::move(handler), hwnd};
34+
35+
// Install hook for this window if not already installed
36+
if (original_procs_.find(hwnd) == original_procs_.end()) {
37+
InstallHook(hwnd);
38+
}
39+
40+
return id;
41+
}
42+
43+
bool WindowMessageDispatcher::UnregisterHandler(int id) {
44+
std::lock_guard<std::mutex> lock(mutex_);
45+
46+
auto it = handlers_.find(id);
47+
if (it == handlers_.end()) {
48+
return false;
49+
}
50+
51+
HWND target_hwnd = it->second.target_hwnd;
52+
handlers_.erase(it);
53+
54+
// Check if this was the last handler for this window
55+
if (target_hwnd != HWND(0)) {
56+
bool has_other_handlers = std::any_of(
57+
handlers_.begin(), handlers_.end(),
58+
[target_hwnd](const auto& pair) {
59+
return pair.second.target_hwnd == target_hwnd;
60+
});
61+
62+
if (!has_other_handlers) {
63+
UninstallHook(target_hwnd);
64+
}
65+
}
66+
67+
return true;
68+
}
69+
70+
LRESULT CALLBACK WindowMessageDispatcher::DispatchWindowProc(HWND hwnd,
71+
UINT msg,
72+
WPARAM wparam,
73+
LPARAM lparam) {
74+
auto& dispatcher = GetInstance();
75+
std::lock_guard<std::mutex> lock(dispatcher.mutex_);
76+
77+
// Get original window procedure
78+
auto proc_it = dispatcher.original_procs_.find(hwnd);
79+
if (proc_it == dispatcher.original_procs_.end()) {
80+
return DefWindowProc(hwnd, msg, wparam, lparam);
81+
}
82+
83+
WNDPROC original_proc = proc_it->second;
84+
85+
// Try handlers in reverse order (most recently registered first)
86+
for (auto it = dispatcher.handlers_.rbegin(); it != dispatcher.handlers_.rend(); ++it) {
87+
const auto& [id, entry] = *it;
88+
89+
// Check if this handler applies to this window
90+
if (entry.target_hwnd == HWND(0) || entry.target_hwnd == hwnd) {
91+
auto result = entry.handler(hwnd, msg, wparam, lparam);
92+
if (result.has_value()) {
93+
return result.value();
94+
}
95+
}
96+
}
97+
98+
// No handler consumed the message, call original procedure
99+
return CallWindowProc(original_proc, hwnd, msg, wparam, lparam);
100+
}
101+
102+
bool WindowMessageDispatcher::InstallHook(HWND hwnd) {
103+
if (!hwnd || !IsWindow(hwnd)) {
104+
return false;
105+
}
106+
107+
// Get current window procedure
108+
WNDPROC current_proc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hwnd, GWLP_WNDPROC));
109+
if (!current_proc) {
110+
return false;
111+
}
112+
113+
// Store original procedure
114+
original_procs_[hwnd] = current_proc;
115+
116+
// Install our dispatcher as the new window procedure
117+
SetWindowLongPtr(hwnd, GWLP_WNDPROC,
118+
reinterpret_cast<LONG_PTR>(DispatchWindowProc));
119+
120+
return true;
121+
}
122+
123+
void WindowMessageDispatcher::UninstallHook(HWND hwnd) {
124+
auto it = original_procs_.find(hwnd);
125+
if (it == original_procs_.end()) {
126+
return;
127+
}
128+
129+
WNDPROC original_proc = it->second;
130+
131+
// Restore original window procedure
132+
SetWindowLongPtr(hwnd, GWLP_WNDPROC,
133+
reinterpret_cast<LONG_PTR>(original_proc));
134+
135+
// Remove from our tracking
136+
original_procs_.erase(it);
137+
}
138+
139+
} // namespace nativeapi
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#pragma once
2+
3+
#include <windows.h>
4+
#include <functional>
5+
#include <mutex>
6+
#include <optional>
7+
#include <unordered_map>
8+
9+
namespace nativeapi {
10+
11+
/**
12+
* @brief Function type for handling Windows messages.
13+
*
14+
* @param hwnd Window handle receiving the message
15+
* @param msg Message identifier (WM_* constants)
16+
* @param wparam Message-specific parameter
17+
* @param lparam Message-specific parameter
18+
* @return std::optional<LRESULT> If a value is returned, the message is
19+
* considered handled. If std::nullopt is returned, the message continues to
20+
* other handlers.
21+
*/
22+
using WindowMessageHandler = std::function<
23+
std::optional<LRESULT>(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)>;
24+
25+
/**
26+
* @brief Singleton dispatcher for Windows message handling across multiple
27+
* windows.
28+
*
29+
* This class provides a centralized way to register message handlers for
30+
* Windows messages. It supports both global handlers (applied to all windows)
31+
* and window-specific handlers. The dispatcher automatically hooks into window
32+
* procedures using SetWindowLongPtr and restores original procedures when
33+
* handlers are unregistered.
34+
*
35+
* Thread Safety: All public methods are thread-safe.
36+
*
37+
* Usage Example:
38+
* ```cpp
39+
* auto& dispatcher = WindowMessageDispatcher::GetInstance();
40+
*
41+
* // Register global handler for all windows
42+
* int global_id = dispatcher.RegisterHandler([](HWND hwnd, UINT msg, WPARAM wp,
43+
* LPARAM lp) { if (msg == WM_SIZE) {
44+
* // Handle window resize
45+
* return std::make_optional<LRESULT>(0);
46+
* }
47+
* return std::nullopt; // Let other handlers process
48+
* });
49+
*
50+
* // Register window-specific handler
51+
* int window_id = dispatcher.RegisterHandler(specific_hwnd, [](HWND hwnd, UINT
52+
* msg, WPARAM wp, LPARAM lp) { if (msg == WM_CLOSE) {
53+
* // Prevent window from closing
54+
* return std::make_optional<LRESULT>(0);
55+
* }
56+
* return std::nullopt;
57+
* });
58+
*
59+
* // Unregister when done
60+
* dispatcher.UnregisterHandler(global_id);
61+
* dispatcher.UnregisterHandler(window_id);
62+
* ```
63+
*/
64+
class WindowMessageDispatcher {
65+
public:
66+
/**
67+
* @brief Get the singleton instance of the dispatcher.
68+
* @return Reference to the singleton WindowMessageDispatcher instance.
69+
*/
70+
static WindowMessageDispatcher& GetInstance();
71+
72+
/**
73+
* @brief Register a global message handler that applies to all windows.
74+
*
75+
* @param handler Function to call for Windows messages
76+
* @return int Handler ID for unregistration
77+
*/
78+
int RegisterHandler(WindowMessageHandler handler);
79+
80+
/**
81+
* @brief Register a message handler for a specific window.
82+
*
83+
* This method automatically installs a hook into the window's procedure
84+
* if not already installed. The hook is removed when the last handler
85+
* for the window is unregistered.
86+
*
87+
* @param hwnd Target window handle
88+
* @param handler Function to call for Windows messages
89+
* @return int Handler ID for unregistration
90+
*/
91+
int RegisterHandler(HWND hwnd, WindowMessageHandler handler);
92+
93+
/**
94+
* @brief Unregister a message handler by ID.
95+
*
96+
* @param id Handler ID returned from RegisterHandler
97+
* @return bool true if handler was found and removed, false otherwise
98+
*/
99+
bool UnregisterHandler(int id);
100+
101+
/**
102+
* @brief Internal window procedure function used for message dispatching.
103+
*
104+
* This function is installed as the window procedure for windows that have
105+
* registered handlers. It processes messages through registered handlers
106+
* and falls back to the original window procedure if no handler consumes
107+
* the message.
108+
*
109+
* @param hwnd Window handle
110+
* @param msg Message identifier
111+
* @param wparam Message parameter
112+
* @param lparam Message parameter
113+
* @return LRESULT Message processing result
114+
*/
115+
static LRESULT CALLBACK DispatchWindowProc(HWND hwnd,
116+
UINT msg,
117+
WPARAM wparam,
118+
LPARAM lparam);
119+
120+
private:
121+
WindowMessageDispatcher() = default;
122+
~WindowMessageDispatcher();
123+
124+
/**
125+
* @brief Entry for a registered message handler.
126+
*/
127+
struct HandlerEntry {
128+
WindowMessageHandler handler; ///< The handler function
129+
HWND target_hwnd; ///< Target window (HWND(0) for global handlers)
130+
};
131+
132+
/**
133+
* @brief Install message hook for a window.
134+
*
135+
* @param hwnd Window handle to hook
136+
* @return bool true if hook was installed successfully
137+
*/
138+
bool InstallHook(HWND hwnd);
139+
140+
/**
141+
* @brief Uninstall message hook for a window.
142+
*
143+
* @param hwnd Window handle to unhook
144+
*/
145+
void UninstallHook(HWND hwnd);
146+
147+
///< Mutex for thread safety
148+
mutable std::mutex mutex_;
149+
///< Registered handlers by ID
150+
std::unordered_map<int, HandlerEntry> handlers_;
151+
///< Original window procedures
152+
std::unordered_map<HWND, WNDPROC> original_procs_;
153+
///< Next available handler ID
154+
int next_id_ = 1;
155+
};
156+
157+
} // namespace nativeapi

0 commit comments

Comments
 (0)