Skip to content

Commit 933dda7

Browse files
committed
Add WindowMessageDispatcher for tray icon events
1 parent d57bfcc commit 933dda7

File tree

5 files changed

+322
-227
lines changed

5 files changed

+322
-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::DispatchWindowProc;
147150
wc.hInstance = hInstance;
148151
wc.lpszClassName = class_name.c_str();
149152

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include "window_message_dispatcher.h"
2+
#include <algorithm>
3+
#include <vector>
4+
5+
namespace nativeapi {
6+
7+
WindowMessageDispatcher& WindowMessageDispatcher::GetInstance() {
8+
static WindowMessageDispatcher instance;
9+
return instance;
10+
}
11+
12+
WindowMessageDispatcher::~WindowMessageDispatcher() {
13+
std::lock_guard<std::mutex> lock(mutex_);
14+
15+
// Uninstall all hooks before destruction
16+
for (const auto& [hwnd, _] : original_procs_) {
17+
UninstallHook(hwnd);
18+
}
19+
original_procs_.clear();
20+
}
21+
22+
int WindowMessageDispatcher::RegisterHandler(WindowMessageHandler handler) {
23+
std::lock_guard<std::mutex> lock(mutex_);
24+
25+
int id = next_id_++;
26+
handlers_[id] = {std::move(handler), HWND(0)}; // HWND(0) for global handler
27+
return id;
28+
}
29+
30+
int WindowMessageDispatcher::RegisterHandler(HWND hwnd,
31+
WindowMessageHandler handler) {
32+
std::lock_guard<std::mutex> lock(mutex_);
33+
34+
int id = next_id_++;
35+
handlers_[id] = {std::move(handler), hwnd};
36+
37+
// Install hook for this window if not already installed
38+
if (original_procs_.find(hwnd) == original_procs_.end()) {
39+
InstallHook(hwnd);
40+
}
41+
42+
return id;
43+
}
44+
45+
bool WindowMessageDispatcher::UnregisterHandler(int id) {
46+
std::lock_guard<std::mutex> lock(mutex_);
47+
48+
auto it = handlers_.find(id);
49+
if (it == handlers_.end()) {
50+
return false;
51+
}
52+
53+
HWND target_hwnd = it->second.target_hwnd;
54+
handlers_.erase(it);
55+
56+
// Check if this was the last handler for this window
57+
if (target_hwnd != HWND(0)) {
58+
bool has_other_handlers = std::any_of(
59+
handlers_.begin(), handlers_.end(), [target_hwnd](const auto& pair) {
60+
return pair.second.target_hwnd == target_hwnd;
61+
});
62+
63+
if (!has_other_handlers) {
64+
UninstallHook(target_hwnd);
65+
}
66+
}
67+
68+
return true;
69+
}
70+
71+
LRESULT CALLBACK WindowMessageDispatcher::DispatchWindowProc(HWND hwnd,
72+
UINT msg,
73+
WPARAM wparam,
74+
LPARAM lparam) {
75+
auto& dispatcher = GetInstance();
76+
77+
// Get original window procedure and copy handlers while holding lock
78+
WNDPROC original_proc = nullptr;
79+
std::vector<std::pair<int, HandlerEntry>> handlers_vector;
80+
81+
{
82+
std::lock_guard<std::mutex> lock(dispatcher.mutex_);
83+
84+
// Get original window procedure
85+
auto proc_it = dispatcher.original_procs_.find(hwnd);
86+
if (proc_it == dispatcher.original_procs_.end()) {
87+
return DefWindowProc(hwnd, msg, wparam, lparam);
88+
}
89+
90+
original_proc = proc_it->second;
91+
92+
// Copy handlers while holding lock (to avoid deadlock when handlers call
93+
// back)
94+
handlers_vector.assign(dispatcher.handlers_.begin(),
95+
dispatcher.handlers_.end());
96+
}
97+
98+
// Try handlers in reverse order (most recently registered first)
99+
// Process handlers without holding the mutex to avoid deadlock
100+
for (auto it = handlers_vector.rbegin(); it != handlers_vector.rend(); ++it) {
101+
const auto& [id, entry] = *it;
102+
103+
// Check if this handler applies to this window
104+
if (entry.target_hwnd == HWND(0) || entry.target_hwnd == hwnd) {
105+
auto result = entry.handler(hwnd, msg, wparam, lparam);
106+
if (result.has_value()) {
107+
return result.value();
108+
}
109+
}
110+
}
111+
112+
// No handler consumed the message, call original procedure
113+
return CallWindowProc(original_proc, hwnd, msg, wparam, lparam);
114+
}
115+
116+
bool WindowMessageDispatcher::InstallHook(HWND hwnd) {
117+
if (!hwnd || !IsWindow(hwnd)) {
118+
return false;
119+
}
120+
121+
// Get current window procedure
122+
WNDPROC current_proc =
123+
reinterpret_cast<WNDPROC>(GetWindowLongPtr(hwnd, GWLP_WNDPROC));
124+
if (!current_proc) {
125+
return false;
126+
}
127+
128+
// Store original procedure
129+
original_procs_[hwnd] = current_proc;
130+
131+
// Install our dispatcher as the new window procedure
132+
SetWindowLongPtr(hwnd, GWLP_WNDPROC,
133+
reinterpret_cast<LONG_PTR>(DispatchWindowProc));
134+
135+
return true;
136+
}
137+
138+
void WindowMessageDispatcher::UninstallHook(HWND hwnd) {
139+
auto it = original_procs_.find(hwnd);
140+
if (it == original_procs_.end()) {
141+
return;
142+
}
143+
144+
WNDPROC original_proc = it->second;
145+
146+
// Restore original window procedure
147+
SetWindowLongPtr(hwnd, GWLP_WNDPROC,
148+
reinterpret_cast<LONG_PTR>(original_proc));
149+
150+
// Remove from our tracking
151+
original_procs_.erase(it);
152+
}
153+
154+
} // 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)