Repository: rime/weasel (language: cpp)

Issue:
`color_scheme_dark` 配置不能立即生效
**上报前请检查**
- [x] 我遇到的问题没有其他人在 issue 里提到过
- [x] 我的小狼毫版本于 rime/weasel 下载
- [x] 我在使用小狼毫的最新发布版本，或最新发布版本后的 CI 构建

**操作系统信息**
 - OS 详细版本: [ Windows 11 23H2 22631.2861]
 - 小狼毫版本: [ 最新的 CI 版本（[1e78320](https://github.com/rime/weasel/releases/tag/latest)） ]

**描述遇到的问题**
`color_scheme_dark` 配置不能立即生效

**复现步骤**
重现问题的步骤，如：
1. 现在在 Windows 设置里面是「浅色」，输入法是 `color_scheme` 的配色
2. 改变设置为「深色」，输入法依旧是 `color_scheme` 的配色，必须手动「重启算法服务」后，输入法才能变成 `color_scheme_dark` 的配色
3. 从「深色」切换成「浅色」，也需要手动「重启算法服务」

**预期行为**
Windows 颜色改变后，应该立即切换成配置文件对应的配色

**用户文件**
`weasel.custom.yaml` 的配置如下：
```
patch:
  "style/color_scheme": google
  "style/color_scheme_dark": lost_temple
```



Discussion / hints:

Fix patch:
diff --git a/RimeWithWeasel/RimeWithWeasel.cpp b/RimeWithWeasel/RimeWithWeasel.cpp
index 8b199b21a..54f3edd09 100644
--- a/RimeWithWeasel/RimeWithWeasel.cpp
+++ b/RimeWithWeasel/RimeWithWeasel.cpp
@@ -32,20 +32,13 @@ int expand_ibus_modifier(int m)
 	return (m & 0xff) | ((m & 0xff00) << 16);
 }
 
-#define SHOWN_ASCII (1<<0)
-#define SHOWN_SHAPE (1<<1)
-#define SHOWN_ASCII_PUNCT (1<<2)
-#define SHOWN_SIMPLIFICATION (1<<3)
-#define SHOWN_SCHEMA (1<<4)
-#define SHOWN_ALWAYS    (SHOWN_ASCII|SHOWN_SHAPE|SHOWN_ASCII_PUNCT|SHOWN_SCHEMA|SHOWN_SIMPLIFICATION)
-#define SHOWN_NEVER  0x00
-
 RimeWithWeaselHandler::RimeWithWeaselHandler(UI *ui)
 	: m_ui(ui)
 	, m_active_session(0)
 	, m_disabled(true)
 	, m_current_dark_mode(false)
-	, m_show_notifications_when(SHOWN_ALWAYS)
+	, m_global_ascii_mode(false)
+	, m_show_notifications_time(1200)
 	, _UpdateUICallback(NULL)
 {
 	_Setup();
@@ -53,9 +46,11 @@ RimeWithWeaselHandler::RimeWithWeaselHandler(UI *ui)
 
 RimeWithWeaselHandler::~RimeWithWeaselHandler()
 {
+	m_show_notifications.clear();
+	m_session_status_map.clear();
+	m_app_options.clear();
 }
 
-void _UpdateShowNotificationsWhen(RimeConfig* config, UINT* show_notifications_when);
 void _UpdateUIStyle(RimeConfig* config, UI* ui, bool initialize);
 bool _UpdateUIStyleColor(RimeConfig* config, UIStyle& style, std::string color = "");
 void _LoadAppOptions(RimeConfig* config, AppOptionsByAppName& app_options);
@@ -107,7 +102,7 @@ void RimeWithWeaselHandler::Initialize()
 		if (m_ui)
 		{
 			_UpdateUIStyle(&config, m_ui, true);
-			_UpdateShowNotificationsWhen(&config, &m_show_notifications_when);
+			_UpdateShowNotifications(&config, true);
 			m_current_dark_mode = IsUserDarkMode();
 			if(m_current_dark_mode) {
 				const int BUF_SIZE = 255;
@@ -119,6 +114,11 @@ void RimeWithWeaselHandler::Initialize()
 			}
 			m_base_style = m_ui->style();
 		}
+		Bool global_ascii = false;
+		if (RimeConfigGetBool(&config, "global_ascii", &global_ascii))
+			m_global_ascii_mode = !!global_ascii;
+		if (!RimeConfigGetInt(&config, "show_notifications_time", &m_show_notifications_time))
+			m_show_notifications_time = 1200;
 		_LoadAppOptions(&config, m_app_options);
 		RimeConfigClose(&config);
 	}
@@ -129,6 +129,7 @@ void RimeWithWeaselHandler::Finalize()
 {
 	m_active_session = 0;
 	m_disabled = true;
+	m_session_status_map.clear();
 	LOG(INFO) << "Finalizing la rime.";
 	RimeFinalize();
 }
@@ -149,28 +150,33 @@ UINT RimeWithWeaselHandler::AddSession(LPWSTR buffer, EatLine eat)
 		EndMaintenance();
 		if (m_disabled) return 0;
 	}
-	UINT session_id = RimeCreateSession();
+	UINT session_id = (UINT)RimeCreateSession();
 	DLOG(INFO) << "Add session: created session_id = " << session_id;
 	_ReadClientInfo(session_id, buffer);
 
+	m_session_status_map[session_id] = SesstionStatus();
+	m_session_status_map[session_id].style = m_base_style;
+
 	RIME_STRUCT(RimeStatus, status);
 	if (RimeGetStatus(session_id, &status))
 	{
 		std::string schema_id = status.schema_id;
 		m_last_schema_id = schema_id;
-		_LoadSchemaSpecificSettings(schema_id);
+		_LoadSchemaSpecificSettings(session_id, schema_id);
 		_LoadAppInlinePreeditSet(session_id, true);
 		_UpdateInlinePreeditStatus(session_id);
 		_RefreshTrayIcon(session_id, _UpdateUICallback);
+		m_session_status_map[session_id].status = status;
+		m_session_status_map[session_id].__synced = false;
 		RimeFreeStatus(&status);
 	}
+	m_ui->style() = m_session_status_map[session_id].style;
 	// show session's welcome message :-) if any
 	if (eat) {
 		_Respond(session_id, eat);
 	}
 	_UpdateUI(session_id);
 	m_active_session = session_id;
-	__synced[session_id] = false;
 	return session_id;
 }
 
@@ -181,7 +187,7 @@ UINT RimeWithWeaselHandler::RemoveSession(UINT session_id)
 	DLOG(INFO) << "Remove session: session_id = " << session_id;
 	// TODO: force committing? otherwise current composition would be lost
 	RimeDestroySession(session_id);
-	__synced.erase(session_id);
+	m_session_status_map.erase(session_id);
 	m_active_session = 0;
 	return 0;
 }
@@ -216,22 +222,22 @@ void RimeWithWeaselHandler::UpdateColorTheme(BOOL darkMode)
 			}
 			m_base_style = m_ui->style();
 		}
-		_LoadAppOptions(&config, m_app_options);
+		RimeConfigClose(&config);
+	}
+
+	for(auto& pair : m_session_status_map) {
 		RIME_STRUCT(RimeStatus, status);
-		if (RimeGetStatus(m_active_session, &status))
+		if (RimeGetStatus(pair.first, &status))
 		{
-			_LoadSchemaSpecificSettings(std::string(status.schema_id));
-			_LoadAppInlinePreeditSet(m_active_session, true);
-			_UpdateInlinePreeditStatus(m_active_session);
+			_LoadSchemaSpecificSettings(pair.first, std::string(status.schema_id));
+			_LoadAppInlinePreeditSet(pair.first, true);
+			_UpdateInlinePreeditStatus(pair.first);
+			pair.second.status = status;
+			pair.second.__synced = false;
 			RimeFreeStatus(&status);
 		}
-		RimeConfigClose(&config);
 	}
-
-	_LoadAppInlinePreeditSet(m_active_session);
-	_UpdateInlinePreeditStatus(m_active_session);
-	for(auto &pair : __synced)
-		pair.second = false;
+	m_ui->style() = m_session_status_map[m_active_session].style;
 }
 
 BOOL RimeWithWeaselHandler::ProcessKeyEvent(KeyEvent keyEvent, UINT session_id, EatLine eat)
@@ -313,6 +319,8 @@ void RimeWithWeaselHandler::UpdateInputPosition(RECT const& rc, UINT session_id)
 
 std::string RimeWithWeaselHandler::m_message_type;
 std::string RimeWithWeaselHandler::m_message_value;
+std::string RimeWithWeaselHandler::m_message_label;
+std::string RimeWithWeaselHandler::m_option_name;
 
 void RimeWithWeaselHandler::OnNotify(void* context_object,
 	                                 uintptr_t session_id,
@@ -324,6 +332,18 @@ void RimeWithWeaselHandler::OnNotify(void* context_object,
 	if (!self || !message_type || !message_value) return;
 	m_message_type = message_type;
 	m_message_value = message_value;
+	RimeApi* rime = rime_get_api();
+	if (RIME_API_AVAILABLE(rime, get_state_label) &&
+		!strcmp(message_type, "option")) {
+		Bool state = message_value[0] != '!';
+		const char* option_name = message_value + !state;
+		m_option_name = option_name;
+		const char* state_label =
+			rime->get_state_label(session_id, option_name, state);
+		if (state_label) {
+			m_message_label = std::string(state_label);
+		}
+	}
 }
 
 void RimeWithWeaselHandler::_ReadClientInfo(UINT session_id, LPWSTR buffer)
@@ -373,7 +393,7 @@ void RimeWithWeaselHandler::_ReadClientInfo(UINT session_id, LPWSTR buffer)
 	// ime | tsf
 	RimeSetProperty(session_id, "client_type", client_type.c_str());
 	// inline preedit
-	bool inline_preedit = m_ui->style().inline_preedit && (client_type == "tsf");	
+	bool inline_preedit = m_session_status_map[session_id].style.inline_preedit && (client_type == "tsf");
 	RimeSetOption(session_id, "inline_preedit", Bool(inline_preedit));
 	// show soft cursor on weasel panel but not inline
 	RimeSetOption(session_id, "soft_cursor", Bool(!inline_preedit));
@@ -411,6 +431,7 @@ void RimeWithWeaselHandler::_GetCandidateInfo(CandidateInfo & cinfo, RimeContext
 
 void RimeWithWeaselHandler::StartMaintenance()
 {
+	m_session_status_map.clear();
 	Finalize();
 	_UpdateUI(0);
 }
@@ -422,6 +443,7 @@ void RimeWithWeaselHandler::EndMaintenance()
 		Initialize();
 		_UpdateUI(0);
 	}
+	m_session_status_map.clear();
 }
 
 void RimeWithWeaselHandler::SetOption(UINT session_id, const std::string & opt, bool val)
@@ -464,9 +486,9 @@ void RimeWithWeaselHandler::_UpdateUI(UINT session_id)
 	if (!m_ui) return;
 
 	if (RimeGetOption(session_id, "inline_preedit"))
-		m_ui->style().client_caps |= INLINE_PREEDIT_CAPABLE;
+		m_session_status_map[session_id].style.client_caps |= INLINE_PREEDIT_CAPABLE;
 	else
-		m_ui->style().client_caps &= ~INLINE_PREEDIT_CAPABLE;
+		m_session_status_map[session_id].style.client_caps &= ~INLINE_PREEDIT_CAPABLE;
 
 	if (weasel_status.composing)
 	{
@@ -483,6 +505,8 @@ void RimeWithWeaselHandler::_UpdateUI(UINT session_id)
 
 	m_message_type.clear();
 	m_message_value.clear();
+	m_message_label.clear();
+	m_option_name.clear();
 }
 
 void _LoadIconSettingFromSchema(RimeConfig& config, char *buffer, const int& BUF_SIZE, 
@@ -504,16 +528,18 @@ void _LoadIconSettingFromSchema(RimeConfig& config, char *buffer, const int& BUF
 	else value = L"";
 }
 
-void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(const std::string& schema_id)
+void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(UINT session_id, const std::string& schema_id)
 {
 	if (!m_ui) return;
 	const int BUF_SIZE = 255;
 	char buffer[BUF_SIZE + 1];
 	RimeConfig config;
 	if (!RimeSchemaOpen(schema_id.c_str(), &config)) return;
+	_UpdateShowNotifications(&config);
 	m_ui->style() = m_base_style;
 	_UpdateUIStyle(&config, m_ui, false);
-
+	SesstionStatus& session_status = m_session_status_map[session_id];
+	session_status.style = m_ui->style();
 	// load schema color style config
 	memset(buffer, '\0', sizeof(buffer));
 	if (!m_current_dark_mode && RimeConfigGetString(&config, "style/color_scheme", buffer, BUF_SIZE))
@@ -522,14 +548,14 @@ void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(const std::string& schem
 		RimeConfigIterator preset = {0};
 		if(RimeConfigBeginMap(&preset, &config, ("preset_color_schemes/" + color_name).c_str()))
 		{
-			_UpdateUIStyleColor(&config, m_ui->style(), color_name);
+			_UpdateUIStyleColor(&config, session_status.style, color_name);
 		}
 		else
 		{
 			RimeConfig weaselconfig;
 			if (RimeConfigOpen("weasel", &weaselconfig))
 			{
-				_UpdateUIStyleColor(&weaselconfig, m_ui->style(), std::string(buffer));
+				_UpdateUIStyleColor(&weaselconfig, session_status.style, std::string(buffer));
 				RimeConfigClose(&weaselconfig);
 			}
 		}
@@ -540,14 +566,14 @@ void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(const std::string& schem
 		RimeConfigIterator preset = {0};
 		if(RimeConfigBeginMap(&preset, &config, ("preset_color_schemes/" + color_name).c_str()))
 		{
-			_UpdateUIStyleColor(&config, m_ui->style(), color_name);
+			_UpdateUIStyleColor(&config, session_status.style, color_name);
 		}
 		else
 		{
 			RimeConfig weaselconfig;
 			if (RimeConfigOpen("weasel", &weaselconfig))
 			{
-				_UpdateUIStyleColor(&weaselconfig, m_ui->style(), std::string(buffer));
+				_UpdateUIStyleColor(&weaselconfig, session_status.style, std::string(buffer));
 				RimeConfigClose(&weaselconfig);
 			}
 		}
@@ -556,10 +582,10 @@ void RimeWithWeaselHandler::_LoadSchemaSpecificSettings(const std::string& schem
 	{
 		std::wstring user_dir = string_to_wstring(weasel_user_data_dir());
 		std::wstring shared_dir = string_to_wstring(weasel_shared_data_dir());
-		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/icon", "schema/zhung_icon", user_dir, shared_dir, m_ui->style().current_zhung_icon);
-		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/ascii_icon", NULL, user_dir, shared_dir, m_ui->style().current_ascii_icon);
-		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/full_icon", NULL, user_dir, shared_dir, m_ui->style().current_full_icon);
-		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/half_icon", NULL, user_dir, shared_dir, m_ui->style().current_half_icon);
+		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/icon", "schema/zhung_icon", user_dir, shared_dir, session_status.style.current_zhung_icon);
+		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/ascii_icon", NULL, user_dir, shared_dir, session_status.style.current_ascii_icon);
+		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/full_icon", NULL, user_dir, shared_dir, session_status.style.current_full_icon);
+		_LoadIconSettingFromSchema(config, buffer, BUF_SIZE, "schema/half_icon", NULL, user_dir, shared_dir, session_status.style.current_half_icon);
 	}
 	// load schema icon end
 	RimeConfigClose(&config);
@@ -573,7 +599,8 @@ void RimeWithWeaselHandler::_LoadAppInlinePreeditSet(UINT session_id, bool ignor
 	if(!ignore_app_name && m_last_app_name == app_name)
 		return;
 	m_last_app_name = app_name;
-	bool inline_preedit = m_ui->style().inline_preedit;
+	SesstionStatus& session_status = m_session_status_map[session_id];
+	bool inline_preedit = session_status.style.inline_preedit;
 	if (!app_name.empty())
 	{
 		auto it = m_app_options.find(app_name);
@@ -587,8 +614,8 @@ void RimeWithWeaselHandler::_LoadAppInlinePreeditSet(UINT session_id, bool ignor
 			{
 				*pfind = true;
 				RimeSetOption(session_id, pair.first.c_str(), Bool(pair.second));
-				m_ui->style().inline_preedit = Bool(pair.second);
-				if(m_ui->style().inline_preedit != inline_preedit)
+				m_session_status_map[session_id].style.inline_preedit = Bool(pair.second);
+				if(m_session_status_map[session_id].style.inline_preedit != inline_preedit)
 					_UpdateInlinePreeditStatus(session_id);
 			}
 			});
@@ -600,19 +627,19 @@ void RimeWithWeaselHandler::_LoadAppInlinePreeditSet(UINT session_id, bool ignor
 		else
 		{
 load_schema_inline:
-			m_ui->style().inline_preedit = m_base_style.inline_preedit;
+			session_status.style.inline_preedit = m_base_style.inline_preedit;
 			RIME_STRUCT(RimeStatus, status);
 			if (RimeGetStatus(session_id, &status))
 			{
 				std::string schema_id = status.schema_id;
 				RimeConfig config;
 				if (!RimeSchemaOpen(schema_id.c_str(), &config)) return;
-				Bool inline_preedit = m_ui->style().inline_preedit;
+				Bool inline_preedit = session_status.style.inline_preedit;
 				if (RimeConfigGetBool(&config, "style/inline_preedit", &inline_preedit))
-					m_ui->style().inline_preedit = !!inline_preedit;
+					session_status.style.inline_preedit = !!inline_preedit;
 				RimeConfigClose(&config);
 				RimeFreeStatus(&status);
-				if(m_ui->style().inline_preedit != inline_preedit)
+				if(session_status.style.inline_preedit != (!!inline_preedit))
 					_UpdateInlinePreeditStatus(session_id);
 			}
 		}
@@ -642,11 +669,11 @@ bool RimeWithWeaselHandler::_ShowMessage(Context& ctx, Status& status) {
 	else if (m_message_type == "option") {
 		if (m_message_value == "!ascii_mode")
 		{
-			show_icon = true;  
+			show_icon = true;
 		}
 		else if (m_message_value == "ascii_mode")
 		{
-			show_icon = true;  
+			show_icon = true;
 		}
 		else if (m_message_value == "!full_shape")
 			tips = L"半角";
@@ -660,16 +687,19 @@ bool RimeWithWeaselHandler::_ShowMessage(Context& ctx, Status& status) {
 			tips = L"漢字";
 		else if (m_message_value == "simplification")
 			tips = L"汉字";
+		else
+			tips = string_to_wstring(m_message_label, CP_UTF8);
 	}
 	if (tips.empty() && !show_icon)
 		return m_ui->IsCountingDown();
-	if( ((m_show_notifications_when & SHOWN_ASCII) && (m_message_value == "ascii_mode" || m_message_value == "!ascii_mode"))
-			|| ((m_show_notifications_when & SHOWN_SHAPE) && (m_message_value == "full_shape" || m_message_value == "!full_shape"))
-			|| ((m_show_notifications_when & SHOWN_ASCII_PUNCT) && (m_message_value == "ascii_punct" || m_message_value == "!ascii_punct"))
-			|| ((m_show_notifications_when & SHOWN_SIMPLIFICATION) && (m_message_value == "simplification" || m_message_value == "!simplification"))
-			|| (m_message_type == "schema" && (m_show_notifications_when && SHOWN_SCHEMA))
-			|| m_message_type == "deploy") { m_ui->Update(ctx, status);
-		m_ui->ShowWithTimeout(1200 + 200 * tips.length());
+	auto foption = m_show_notifications.find(m_option_name);
+	auto falways = m_show_notifications.find("always");
+	if (foption != m_show_notifications.end() ||
+		falways != m_show_notifications.end() ||
+		m_message_type == "deploy") {
+		m_ui->Update(ctx, status);
+		if (m_show_notifications_time)
+			m_ui->ShowWithTimeout(m_show_notifications_time);
 		return true;
 	} else {
 		return m_ui->IsCountingDown();
@@ -687,10 +717,7 @@ bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
 	std::set<std::string> actions;
 	std::list<std::string> messages;
 
-	// load App inline_preedit setting if app_name changed
-	_LoadAppInlinePreeditSet(session_id);
-	// extract information
-
+	SesstionStatus& session_status = m_session_status_map[session_id];
 	RIME_STRUCT(RimeCommit, commit);
 	if (RimeGetCommit(session_id, &commit))
 	{
@@ -710,6 +737,13 @@ bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
 		messages.push_back(std::string("status.disabled=") + std::to_string(status.is_disabled) + '\n');
 		messages.push_back(std::string("status.full_shape=") + std::to_string(status.is_full_shape) + '\n');
 		messages.push_back(std::string("status.schema_id=") + std::string(status.schema_id) + '\n');
+		if (m_global_ascii_mode && (session_status.status.is_ascii_mode != status.is_ascii_mode)) {
+			for (auto& pair : m_session_status_map) {
+				if(pair.first != session_id)
+					RimeSetOption(pair.first, "ascii_mode", !!status.is_ascii_mode);
+			}
+		}
+		session_status.status = status;
 		RimeFreeStatus(&status);
 	}
 	
@@ -719,7 +753,7 @@ bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
 		if (is_composing)
 		{
 			actions.insert("ctx");
-			switch (m_ui->style().preedit_type)
+			switch (session_status.style.preedit_type)
 			{
 			case UIStyle::PREVIEW:
 				if (ctx.commit_text_preview != NULL)
@@ -749,9 +783,9 @@ bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
 				std::string topush = std::string("ctx.preedit=") + ctx.composition.preedit + "  [";
 				for (auto i = 0; i < ctx.menu.num_candidates; i++)
 				{
-					std::string label = m_ui->style().label_font_point > 0 ? _GetLabelText(cinfo.labels, i, m_ui->style().label_text_format.c_str()) : "";
-					std::string comment = m_ui->style().comment_font_point > 0 ? wstring_to_string(cinfo.comments.at(i).str, CP_UTF8) : "";
-					std::string mark_text = m_ui->style().mark_text.empty() ? "*" : wstring_to_string(m_ui->style().mark_text, CP_UTF8);
+					std::string label = session_status.style.label_font_point > 0 ? _GetLabelText(cinfo.labels, i, session_status.style.label_text_format.c_str()) : "";
+					std::string comment = session_status.style.comment_font_point > 0 ? wstring_to_string(cinfo.comments.at(i).str, CP_UTF8) : "";
+					std::string mark_text = session_status.style.mark_text.empty() ? "*" : wstring_to_string(session_status.style.mark_text, CP_UTF8);
 					std::string prefix = (i != ctx.menu.highlighted_candidate_index) ? "" : mark_text;
 					topush += " " + prefix + label + std::string(ctx.menu.candidates[i].text) + " " + comment;
 				}
@@ -783,17 +817,17 @@ bool RimeWithWeaselHandler::_Respond(UINT session_id, EatLine eat)
 
 	// configuration information
 	actions.insert("config");
-	messages.push_back(std::string("config.inline_preedit=") + std::to_string((int)m_ui->style().inline_preedit) + '\n');
+	messages.push_back(std::string("config.inline_preedit=") + std::to_string((int)session_status.style.inline_preedit) + '\n');
 
 	// style
-	if (!__synced[session_id]) { 
+	if (!session_status.__synced) {
 		std::wstringstream ss;
 		boost::archive::text_woarchive oa(ss);
-		oa << m_ui->style();
+		oa << session_status.style;
 
 		actions.insert("style");
 		messages.push_back(std::string("style=") + wstring_to_string(ss.str().c_str(), CP_UTF8) + '\n');
-		__synced[session_id] = TRUE;
+		session_status.__synced = true;
 	}
 
 	// summarize
@@ -956,34 +990,38 @@ static void _RimeGetStringWithFunc(RimeConfig* config, const char* key, std::wst
 		value = *fallback;
 }
 
-void _UpdateShowNotificationsWhen(RimeConfig* config, UINT* show_notifications_when)
+void RimeWithWeaselHandler::_UpdateShowNotifications(RimeConfig *config, bool initialize)
 {
-	char buffer[256] = { 0 };
-	// if not set, shown always as default
-	if (!RimeConfigGetString(config, "show_notifications_when", buffer, 256))
-		*show_notifications_when = SHOWN_ALWAYS;
-	else
-	{
-		std::string noti_str(buffer);
-		if (std::regex_match(noti_str, std::regex(".*always.*")) || noti_str.empty())
-			*show_notifications_when = SHOWN_ALWAYS;
-		else if (noti_str == "never")
-			*show_notifications_when = SHOWN_NEVER;
-		else {
-			*show_notifications_when = SHOWN_NEVER;
-			if (std::regex_match(noti_str, std::regex(".*ascii_punct.*")))
-				*show_notifications_when |= SHOWN_ASCII_PUNCT;
-			if (std::regex_match(noti_str, std::regex(".*ascii_mode.*")))
-				*show_notifications_when |= SHOWN_ASCII;
-			if (std::regex_match(noti_str, std::regex(".*shape.*")))
-				*show_notifications_when |= SHOWN_SHAPE;
-			if (std::regex_match(noti_str, std::regex(".*schema.*")))
-				*show_notifications_when |= SHOWN_SCHEMA;
-			if (std::regex_match(noti_str, std::regex(".*simplification.*")))
-				*show_notifications_when |= SHOWN_SIMPLIFICATION;
+	Bool show_notifications = true;
+	RimeConfigIterator iter;
+	if (initialize)
+		m_show_notifications_base.clear();
+	m_show_notifications.clear();
+
+	if(RimeConfigGetBool(config, "show_notifications", &show_notifications)) {
+		// config read as bool, for gloal all on or off
+		if(show_notifications) 
+			m_show_notifications["always"] = true;
+		if (initialize)
+			m_show_notifications_base = m_show_notifications;
+	} else if (RimeConfigBeginList(&iter, config, "show_notifications")) {
+		// config read as list, list item should be option name in schema
+		// or key word 'schema' for schema switching tip
+		while(RimeConfigNext(&iter)) {
+			char buffer[256] = {0};
+			if(RimeConfigGetString(config, iter.path, buffer, 256))
+				m_show_notifications[std::string(buffer)] = true;
 		}
+		if (initialize)
+			m_show_notifications_base = m_show_notifications;
+	} else {
+		// not configured, or incorrect type
+		if (initialize)
+			m_show_notifications_base["always"] = true;
+		m_show_notifications = m_show_notifications_base;
 	}
 }
+
 // update ui's style parameters, ui has been check before referenced 
 static void _UpdateUIStyle(RimeConfig* config, UI* ui, bool initialize)
 {
@@ -1208,24 +1246,23 @@ void RimeWithWeaselHandler::_GetStatus(Status & stat, UINT session_id, Context&
 		stat.full_shape = !!status.is_full_shape;
 		if (schema_id != m_last_schema_id)
 		{
+			m_session_status_map[session_id].__synced = false;
 			m_last_schema_id = schema_id;
 			if(schema_id != ".default") {						// don't load for schema select menu
-				__synced[session_id] = false;
-				bool inline_preedit = m_ui->style().inline_preedit;
-				_LoadSchemaSpecificSettings(schema_id);
+				bool inline_preedit = m_session_status_map[session_id].style.inline_preedit;
+				_LoadSchemaSpecificSettings(session_id, schema_id);
 				_LoadAppInlinePreeditSet(session_id, true);
-				if(m_ui->style().inline_preedit != inline_preedit)
+				if(m_session_status_map[session_id].style.inline_preedit != inline_preedit)
 					_UpdateInlinePreeditStatus(session_id);			// in case of inline_preedit set in schema
 				_RefreshTrayIcon(session_id, _UpdateUICallback);	// refresh icon after schema changed
-				if (m_show_notifications_when & SHOWN_SCHEMA) {
+				m_ui->style() = m_session_status_map[session_id].style;
+				if (m_show_notifications.find("schema") != m_show_notifications.end()) {
 					ctx.aux.str = stat.schema_name;
 					m_ui->Update(ctx, stat);
 					m_ui->ShowWithTimeout(1200);
 				}
 			}
 		}
-		else
-			_LoadAppInlinePreeditSet(session_id);
 		RimeFreeStatus(&status);
 	}
 }
@@ -1268,7 +1305,7 @@ void RimeWithWeaselHandler::_UpdateInlinePreeditStatus(UINT session_id)
 {
 	if (!m_ui)	return;
 	// set inline_preedit option
-	bool inline_preedit = m_ui->style().inline_preedit && _IsSessionTSF(session_id);
+	bool inline_preedit = m_session_status_map[session_id].style.inline_preedit && _IsSessionTSF(session_id);
 	RimeSetOption(session_id, "inline_preedit", Bool(inline_preedit));
 	// show soft cursor on weasel panel but not inline
 	RimeSetOption(session_id, "soft_cursor", Bool(!inline_preedit));
diff --git a/WeaselIPCServer/WeaselServerImpl.cpp b/WeaselIPCServer/WeaselServerImpl.cpp
index de950f460..e9688cf2a 100644
--- a/WeaselIPCServer/WeaselServerImpl.cpp
+++ b/WeaselIPCServer/WeaselServerImpl.cpp
@@ -60,9 +60,11 @@ void ServerImpl::_Finailize()
 
 LRESULT ServerImpl::OnColorChange(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
 {
-	if(IsUserDarkMode() != m_darkMode) {
-		m_darkMode = IsUserDarkMode();
-		m_pRequestHandler->UpdateColorTheme(m_darkMode);
+	if (uMsg == WM_SETTINGCHANGE && (std::wstring((wchar_t*)lParam) == L"ImmersiveColorSet") || uMsg == WM_DWMCOLORIZATIONCOLORCHANGED) {
+		if (IsUserDarkMode() != m_darkMode) {
+			m_darkMode = IsUserDarkMode();
+			m_pRequestHandler->UpdateColorTheme(m_darkMode);
+		}
 	}
 	return 0;
 }
diff --git a/WeaselIPCServer/WeaselServerImpl.h b/WeaselIPCServer/WeaselServerImpl.h
index 930780d4c..b07bccd0f 100644
--- a/WeaselIPCServer/WeaselServerImpl.h
+++ b/WeaselIPCServer/WeaselServerImpl.h
@@ -28,6 +28,7 @@ namespace weasel
 			MESSAGE_HANDLER(WM_QUERYENDSESSION, OnQueryEndSystemSession)
 			MESSAGE_HANDLER(WM_ENDSESSION, OnEndSystemSession)
 			MESSAGE_HANDLER(WM_DWMCOLORIZATIONCOLORCHANGED, OnColorChange)
+			MESSAGE_HANDLER(WM_SETTINGCHANGE, OnColorChange)
 			MESSAGE_HANDLER(WM_COMMAND, OnCommand)
 		END_MSG_MAP()
 
diff --git a/WeaselUI/WeaselUI.cpp b/WeaselUI/WeaselUI.cpp
index 460b13de7..d1045a423 100644
--- a/WeaselUI/WeaselUI.cpp
+++ b/WeaselUI/WeaselUI.cpp
@@ -23,7 +23,7 @@ class weasel::UIImpl {
 	}
 	void Show();
 	void Hide();
-	void ShowWithTimeout(DWORD millisec);
+	void ShowWithTimeout(size_t millisec);
 	bool IsShown() const { return shown; }
 
 	static VOID CALLBACK OnTimer(
@@ -63,7 +63,7 @@ void UIImpl::Hide()
 	}
 }
 
-void UIImpl::ShowWithTimeout(DWORD millisec)
+void UIImpl::ShowWithTimeout(size_t millisec)
 {
 	if (!panel.IsWindow()) return;
 	DLOG(INFO) << "ShowWithTimeout: " << millisec;
@@ -148,7 +148,7 @@ void UI::Hide()
 	}
 }
 
-void UI::ShowWithTimeout(DWORD millisec)
+void UI::ShowWithTimeout(size_t millisec)
 {
 	if (pimpl_)
 	{
diff --git a/include/RimeWithWeasel.h b/include/RimeWithWeasel.h
index 784ae8af7..f816034b8 100644
--- a/include/RimeWithWeasel.h
+++ b/include/RimeWithWeasel.h
@@ -19,6 +19,14 @@ struct CaseInsensitiveCompare {
 
 typedef std::map<std::string, bool> AppOptions;
 typedef std::map<std::string, AppOptions, CaseInsensitiveCompare> AppOptionsByAppName;
+struct SesstionStatus
+{
+	SesstionStatus() : style(weasel::UIStyle()), __synced(false) { RIME_STRUCT(RimeStatus, status); }
+	weasel::UIStyle style;
+	RimeStatus status;
+	bool __synced;
+};
+typedef std::map<UINT, SesstionStatus> SesstionStatusMap;
 class RimeWithWeaselHandler :
 	public weasel::RequestHandler
 {
@@ -48,7 +56,7 @@ class RimeWithWeaselHandler :
 	void _Setup();
 	bool _IsDeployerRunning();
 	void _UpdateUI(UINT session_id);
-	void _LoadSchemaSpecificSettings(const std::string& schema_id);
+	void _LoadSchemaSpecificSettings(UINT session_id, const std::string& schema_id);
 	void _LoadAppInlinePreeditSet(UINT session_id, bool ignore_app_name = false);
 	bool _ShowMessage(weasel::Context& ctx, weasel::Status& status);
 	bool _Respond(UINT session_id, EatLine eat);
@@ -56,6 +64,7 @@ class RimeWithWeaselHandler :
 	void _GetCandidateInfo(weasel::CandidateInfo &cinfo, RimeContext &ctx);
 	void _GetStatus(weasel::Status &stat, UINT session_id, weasel::Context& ctx);
 	void _GetContext(weasel::Context &ctx, UINT session_id);
+	void _UpdateShowNotifications(RimeConfig* config, bool initialize = false);
 
 	bool _IsSessionTSF(UINT session_id);
 	void _UpdateInlinePreeditStatus(UINT session_id);
@@ -67,8 +76,8 @@ class RimeWithWeaselHandler :
 	std::string m_last_schema_id;
 	std::string m_last_app_name;
 	weasel::UIStyle m_base_style;
-	UINT m_show_notifications_when;
-
+	std::map<std::string, bool> m_show_notifications;
+	std::map<std::string, bool> m_show_notifications_base;
 	std::function<void()> _UpdateUICallback;
 
 	static void OnNotify(void* context_object,
@@ -77,6 +86,10 @@ class RimeWithWeaselHandler :
                          const char* message_value);
 	static std::string m_message_type;
 	static std::string m_message_value;
-	std::map<UINT, BOOL> __synced;
+	static std::string m_message_label;
+	static std::string m_option_name;
+	SesstionStatusMap m_session_status_map;
 	bool m_current_dark_mode;
+	bool m_global_ascii_mode;
+	int m_show_notifications_time;
 };
diff --git a/include/WeaselUI.h b/include/WeaselUI.h
index f1399af56..43e924e23 100644
--- a/include/WeaselUI.h
+++ b/include/WeaselUI.h
@@ -61,7 +61,7 @@ namespace weasel
 		// 界面显隐
 		void Show();
 		void Hide();
-		void ShowWithTimeout(DWORD millisec);
+		void ShowWithTimeout(size_t millisec);
 		bool IsCountingDown() const;
 		bool IsShown() const;
 		
diff --git a/output/data/weasel.yaml b/output/data/weasel.yaml
index fe599dcc5..121c691ca 100644
--- a/output/data/weasel.yaml
+++ b/output/data/weasel.yaml
@@ -9,10 +9,11 @@ app_options:
   conhost.exe:
     ascii_mode: true
 
-# configuration: when to show notifications
-# always, never, or combination of: ascii_mode, ascii_punct, shape, simplification, schema
-# e.g., ascii_mode+shape+schema
-show_notifications_when: always
+show_notifications: true
+# how long a period notifications shows
+show_notifications_time: 1200
+# use global ascii status
+global_ascii: false
 
 style:
   antialias_mode: default


Test patch: