diff --git a/RimeWithWeasel/RimeWithWeasel.cpp b/RimeWithWeasel/RimeWithWeasel.cpp index c4d3a5e22c..709cd7ee86 100644 --- a/RimeWithWeasel/RimeWithWeasel.cpp +++ b/RimeWithWeasel/RimeWithWeasel.cpp @@ -500,6 +500,9 @@ void RimeWithWeaselHandler::SetOption(WeaselSessionId ipc_id, } else { rime_api->set_option(to_session_id(ipc_id), opt.c_str(), val); } + // refresh UI (and tray icon) so the option change takes effect immediately, + // e.g. when toggling ascii_mode from the TSF language bar + _UpdateUI(ipc_id ? ipc_id : m_active_session); } void RimeWithWeaselHandler::OnUpdateUI(std::function const& cb) { diff --git a/WeaselIPCServer/WeaselServerImpl.cpp b/WeaselIPCServer/WeaselServerImpl.cpp index 1131e41e7e..0c276442ff 100644 --- a/WeaselIPCServer/WeaselServerImpl.cpp +++ b/WeaselIPCServer/WeaselServerImpl.cpp @@ -131,6 +131,19 @@ LRESULT ServerImpl::OnCommand(UINT uMsg, return 0; } +LRESULT ServerImpl::OnServiceNotifyMessage(UINT uMsg, + WPARAM wParam, + LPARAM lParam, + BOOL& bHandled) { + // Runs on the server message thread, NOT on a pipe worker thread and + // without holding g_api_mutex, so that Shell_NotifyIcon inside the tray + // update can never deadlock against the taskbar UI thread. + if (m_trayRefreshCallback) { + m_trayRefreshCallback(); + } + return 0; +} + DWORD ServerImpl::OnCommand(WEASEL_IPC_COMMAND uMsg, DWORD wParam, DWORD lParam) { @@ -466,6 +479,10 @@ void Server::AddMenuHandler(UINT uID, CommandHandler handler) { m_pImpl->AddMenuHandler(uID, handler); } +void Server::SetTrayRefreshCallback(std::function callback) { + m_pImpl->SetTrayRefreshCallback(callback); +} + HWND Server::GetHWnd() { return m_pImpl->m_hWnd; } diff --git a/WeaselIPCServer/WeaselServerImpl.h b/WeaselIPCServer/WeaselServerImpl.h index 5a2e3ab078..e826d066dc 100644 --- a/WeaselIPCServer/WeaselServerImpl.h +++ b/WeaselIPCServer/WeaselServerImpl.h @@ -28,6 +28,7 @@ class ServerImpl : public CWindowImpl MESSAGE_HANDLER(WM_DWMCOLORIZATIONCOLORCHANGED, OnColorChange) MESSAGE_HANDLER(WM_SETTINGCHANGE, OnColorChange) MESSAGE_HANDLER(WM_COMMAND, OnCommand) + MESSAGE_HANDLER(WM_WEASEL_SERVICE_NOTIFY, OnServiceNotifyMessage) END_MSG_MAP() LRESULT OnColorChange(UINT uMsg, @@ -46,6 +47,10 @@ class ServerImpl : public CWindowImpl LPARAM lParam, BOOL& bHandled); LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); + LRESULT OnServiceNotifyMessage(UINT uMsg, + WPARAM wParam, + LPARAM lParam, + BOOL& bHandled); DWORD OnCommand(WEASEL_IPC_COMMAND uMsg, DWORD wParam, DWORD lParam); DWORD OnEcho(WEASEL_IPC_COMMAND uMsg, DWORD wParam, DWORD lParam); DWORD OnStartSession(WEASEL_IPC_COMMAND uMsg, DWORD wParam, DWORD lParam); @@ -85,6 +90,9 @@ class ServerImpl : public CWindowImpl void AddMenuHandler(UINT uID, CommandHandler& handler) { m_MenuHandlers[uID] = handler; } + void SetTrayRefreshCallback(std::function callback) { + m_trayRefreshCallback = callback; + } private: void _Finailize(); @@ -95,6 +103,7 @@ class ServerImpl : public CWindowImpl std::unique_ptr pipeThread; RequestHandler* m_pRequestHandler; // reference std::map m_MenuHandlers; + std::function m_trayRefreshCallback; HMODULE m_hUser32Module; SecurityAttribute sa; BOOL m_darkMode; diff --git a/WeaselServer/WeaselServerApp.cpp b/WeaselServer/WeaselServerApp.cpp index e09c4c5ed2..6696b98e02 100644 --- a/WeaselServer/WeaselServerApp.cpp +++ b/WeaselServer/WeaselServerApp.cpp @@ -30,13 +30,15 @@ int WeaselServerApp::Run() { m_ui.Create(m_server.GetHWnd()); m_handler->Initialize(); - m_handler->OnUpdateUI([this]() { tray_icon.Refresh(); }); + m_handler->OnUpdateUI([this]() { tray_icon.RequestRefresh(); }); tray_icon.Create(m_server.GetHWnd()); - tray_icon.Refresh(); + m_server.SetTrayRefreshCallback([this]() { tray_icon.ApplyRefresh(); }); + tray_icon.RequestRefresh(); int ret = m_server.Run(); + tray_icon.DisableRefresh(); m_handler->Finalize(); m_ui.Destroy(); tray_icon.RemoveIcon(); diff --git a/WeaselServer/WeaselTrayIcon.cpp b/WeaselServer/WeaselTrayIcon.cpp index 119111affd..3eaff9b30a 100644 --- a/WeaselServer/WeaselTrayIcon.cpp +++ b/WeaselServer/WeaselTrayIcon.cpp @@ -37,9 +37,50 @@ BOOL WeaselTrayIcon::Create(HWND hTargetWnd) { return bRet; } -void WeaselTrayIcon::Refresh() { - if (!m_style.display_tray_icon && - !m_status.disabled) // display notification when deploying +void WeaselTrayIcon::RequestRefresh() { + std::lock_guard lock(m_state_mutex); + if (!m_refresh_enabled) { + return; + } + m_pending_state = WeaselTrayIconState::From(m_style, m_status); + if (m_refresh_pending) { + return; + } + m_refresh_pending = true; + if (!::PostMessage(GetTargetWnd(), WM_WEASEL_SERVICE_NOTIFY, 0, 0)) { + m_refresh_pending = false; + } +} + +void WeaselTrayIcon::ApplyRefresh() { + WeaselTrayIconState state; + { + std::lock_guard lock(m_state_mutex); + if (!m_refresh_pending || !m_refresh_enabled) { + return; + } + state = m_pending_state; + m_refresh_pending = false; + m_refresh_in_progress = true; + } + Refresh(state); + { + std::lock_guard lock(m_state_mutex); + m_refresh_in_progress = false; + } + m_state_cv.notify_all(); +} + +void WeaselTrayIcon::DisableRefresh() { + std::unique_lock lock(m_state_mutex); + m_refresh_enabled = false; + m_refresh_pending = false; + m_state_cv.wait(lock, [this] { return !m_refresh_in_progress; }); +} + +void WeaselTrayIcon::Refresh(const WeaselTrayIconState& state) { + if (!state.display_tray_icon && + !state.disabled) // display notification when deploying { if (m_mode != INITIAL) { RemoveIcon(); @@ -48,24 +89,24 @@ void WeaselTrayIcon::Refresh() { m_disabled = false; return; } - WeaselTrayMode mode = m_status.disabled ? DISABLED - : m_status.ascii_mode ? ASCII - : ZHUNG; + WeaselTrayMode mode = state.disabled ? DISABLED + : state.ascii_mode ? ASCII + : ZHUNG; /* change icon, when 1,mode changed 2,icon changed - 3,both m_schema_zhung_icon and m_style.current_zhung_icon empty(for - initialize) 4,both m_schema_ascii_icon and m_style.current_ascii_icon + 3,both m_schema_zhung_icon and state.current_zhung_icon empty(for + initialize) 4,both m_schema_ascii_icon and state.current_ascii_icon empty(for initialize) */ - if (mode != m_mode || m_schema_zhung_icon != m_style.current_zhung_icon || - (m_schema_zhung_icon.empty() && m_style.current_zhung_icon.empty()) || - m_schema_ascii_icon != m_style.current_ascii_icon || - (m_schema_ascii_icon.empty() && m_style.current_ascii_icon.empty())) { + if (mode != m_mode || m_schema_zhung_icon != state.current_zhung_icon || + (m_schema_zhung_icon.empty() && state.current_zhung_icon.empty()) || + m_schema_ascii_icon != state.current_ascii_icon || + (m_schema_ascii_icon.empty() && state.current_ascii_icon.empty())) { ShowIcon(); m_mode = mode; - m_schema_zhung_icon = m_style.current_zhung_icon; - m_schema_ascii_icon = m_style.current_ascii_icon; + m_schema_zhung_icon = state.current_zhung_icon; + m_schema_ascii_icon = state.current_ascii_icon; if (mode == ASCII) { if (m_schema_ascii_icon.empty()) SetIcon(mode_icon[mode]); diff --git a/WeaselServer/WeaselTrayIcon.h b/WeaselServer/WeaselTrayIcon.h index 42d407aaa5..5afa715831 100644 --- a/WeaselServer/WeaselTrayIcon.h +++ b/WeaselServer/WeaselTrayIcon.h @@ -3,8 +3,54 @@ #include #include "SystemTraySDK.h" +#include +#include + #define WM_WEASEL_TRAY_NOTIFY (WEASEL_IPC_LAST_COMMAND + 100) +// Snapshot of the tray-relevant UI state, computed on the pipe worker thread +// and applied on the server message thread. Keeps Shell_NotifyIcon off the +// pipe worker threads (and away from g_api_mutex), avoiding the deadlock loop +// where the taskbar UI thread waits on the pipe while the server waits for the +// taskbar UI thread inside Shell_NotifyIcon. +struct WeaselTrayIconState { + WeaselTrayIconState() + : valid(false), + display_tray_icon(false), + disabled(false), + ascii_mode(false) {} + + static WeaselTrayIconState From(const weasel::UIStyle& style, + const weasel::Status& status) { + WeaselTrayIconState state; + state.valid = true; + state.display_tray_icon = style.display_tray_icon; + state.disabled = status.disabled; + state.ascii_mode = status.ascii_mode; + state.current_zhung_icon = style.current_zhung_icon; + state.current_ascii_icon = style.current_ascii_icon; + return state; + } + + bool operator==(const WeaselTrayIconState& rhs) const { + return valid == rhs.valid && display_tray_icon == rhs.display_tray_icon && + disabled == rhs.disabled && ascii_mode == rhs.ascii_mode && + current_zhung_icon == rhs.current_zhung_icon && + current_ascii_icon == rhs.current_ascii_icon; + } + + bool operator!=(const WeaselTrayIconState& rhs) const { + return !(*this == rhs); + } + + bool valid; + bool display_tray_icon; + bool disabled; + bool ascii_mode; + std::wstring current_zhung_icon; + std::wstring current_ascii_icon; +}; + class WeaselTrayIcon : public CSystemTray { public: enum WeaselTrayMode { @@ -17,15 +63,32 @@ class WeaselTrayIcon : public CSystemTray { WeaselTrayIcon(weasel::UI& ui); BOOL Create(HWND hTargetWnd); - void Refresh(); + + // Captures the tray-relevant state and posts a refresh request to the server + // message thread. Never calls Shell_NotifyIcon itself. + void RequestRefresh(); + void DisableRefresh(); + + // Runs on the server message thread (no g_api_mutex held). + void ApplyRefresh(); protected: virtual void CustomizeMenu(HMENU hMenu); + void Refresh(const WeaselTrayIconState& state); + weasel::UIStyle& m_style; weasel::Status& m_status; WeaselTrayMode m_mode; std::wstring m_schema_zhung_icon; std::wstring m_schema_ascii_icon; bool m_disabled; + + // Guarded by m_state_mutex. + bool m_refresh_enabled = true; + bool m_refresh_pending = false; + bool m_refresh_in_progress = false; + WeaselTrayIconState m_pending_state; + std::mutex m_state_mutex; + std::condition_variable m_state_cv; }; diff --git a/include/WeaselIPC.h b/include/WeaselIPC.h index d260ac2b7d..27bfd63687 100644 --- a/include/WeaselIPC.h +++ b/include/WeaselIPC.h @@ -35,6 +35,10 @@ enum WEASEL_IPC_COMMAND { WEASEL_IPC_LAST_COMMAND }; +// Posted by WeaselTrayIcon to the server window so that Shell_NotifyIcon runs +// on the server message thread instead of a pipe worker thread. +#define WM_WEASEL_SERVICE_NOTIFY (WEASEL_IPC_LAST_COMMAND + 200) + namespace weasel { struct PipeMessage { WEASEL_IPC_COMMAND Msg; @@ -163,6 +167,10 @@ class Server { void AddMenuHandler(UINT uID, CommandHandler handler); HWND GetHWnd(); + // Callback invoked on the server message thread when a tray icon refresh is + // requested from a pipe worker thread. + void SetTrayRefreshCallback(std::function callback); + private: ServerImpl* m_pImpl; };