Files
qingjian/apps/windows/server/src/dispatch/message.rs
T
yenharvey 61ca4b4150 feat: 符号面板加「翻译」组,删掉 Windows 的整句候选翻译
- 整句候选翻译译的是猜出来的候选,猜错就白发;改成用户在面板的框里自己打一句、停键后再译,整句只发确认过的内容
- 翻译组排在最前,每种目标语言算一个分类([ ] 换),打拼音时按 ↑ 直接落在这组;没在打字时 [shortcut] translate_panel(Ctrl+Alt+Y / ⌃⌥Y)打开
- Core panel/translate:原文带光标,停键 800 ms、两个字以上才发,内容没变不重发,没翻出来 2 秒后自动重试一次、再失败等回车;译文按原文 + 语言缓存在内存(换提供方清空),不落盘
- 译文放进应用的 preedit:Windows 译文靠轮询送到,DLL 记着写进文档的组句文字,变了就补一次编辑会话;行内不显示拼音的设置下也照样放
- 框的排版抽成 bamboo-slip 的 TextFlow,与设置程序的 TextField 共用;面板不是 Ui 搭的,布局后按各字位置给出 PanelHit::Caret
- [predict] sentence_translation 不再认(旧配置里写着不报错),两个设置程序的开关与文档一并去掉;生词本整句翻译保留
- Windows dispatch/composed/mod.rs 超了 800 行,按联想、导航、出帧拆开
- 初音主题当前分组胶囊改纯色
2026-11-04 16:18:39 +08:00

234 lines
9.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 按消息类型分派:会话开关、按键、轮询、失焦上屏、选区 / 光标矩形 / 中英模式的通知。
use qingjian_platform::protocol::{
ClientMessage, Frame, KeyEvent, KeyOutcome, PROTOCOL_VERSION, SESSION_OPENED_SINCE,
ServerMessage, SessionId,
};
use std::time::Instant;
use super::Router;
use super::key::Effect;
use super::session::SessionInfo;
impl Router {
pub(super) fn dispatch(&mut self, message: ClientMessage) -> Option<ServerMessage> {
match message {
ClientMessage::OpenSession {
session,
app,
protocol,
} => {
tracing::debug!(?session, app, protocol, "会话打开");
if protocol != PROTOCOL_VERSION {
tracing::warn!(
?session,
app,
dll = protocol,
server = PROTOCOL_VERSION,
"DLL 与 Server 的协议版本不同(应用还没重启、用着旧 DLL?),照常服务"
);
}
// 同一会话重开(DLL 断线重连):从干净状态起。
if self.focused == Some(session) {
self.reset_composition();
self.focused = None;
}
self.sessions.insert(
session,
SessionInfo {
app,
private: false,
english: None,
vietnamese: None,
protocol,
},
);
// 按键行为设置回一次,让 DLL 不必自己读配置文件。**只回给会读这条回包的 DLL**:
// 更老的 DLL 的 `open` 是只写不读,多回一条会被它当成下一次 `Poll` 的应答而报错,
// 那条连接就废了(老 DLL 在没重启的应用里还会活很久)。它们从 `ModeSync` 那一拍
// 也能拿到同一份(新字段它直接忽略),只是慢一拍。
(protocol >= SESSION_OPENED_SINCE).then(|| ServerMessage::SessionOpened {
session,
input: self.input_settings(session),
})
}
ClientMessage::Key { session, event } => Some(self.handle_key(session, event)),
ClientMessage::Poll { session } => Some(self.handle_poll(session)),
ClientMessage::Commit { session } => {
let text = self.commit_raw_for(session);
tracing::debug!(?session, ?text, "焦点离开,结束组句");
Some(ServerMessage::Committed { session, text })
}
ClientMessage::Surrounding { session, text } => {
tracing::trace!(?session, chars = text.chars().count(), "收到光标前文");
self.set_surrounding(session, text);
None
}
ClientMessage::Privacy { session, private } => {
tracing::debug!(?session, private, "输入框私密状态");
self.set_privacy(session, private);
None
}
ClientMessage::Selection {
session,
request,
text,
rect,
} => Some(self.handle_selection(session, request, text, rect)),
ClientMessage::PositionCandidates { session, rect } => {
self.position_candidates(session, rect);
None
}
ClientMessage::HideCandidates { session } => {
// 组句在 DLL 侧结束(应用终止组句 / 翻译评审失焦):只收窗口;缓冲留给下一键的 Commit 清。
if self.focused == Some(session) {
self.end_translation();
self.cancel_prediction();
self.hide_candidate_window();
}
None
}
ClientMessage::ModeChanged {
session,
english,
vietnamese,
} => {
tracing::debug!(?session, english, vietnamese, "输入模式");
self.handle_mode_changed(session, english, vietnamese);
None
}
ClientMessage::SyncMode { session } => {
self.activate_mode(session);
self.handle_ime_active();
Some(ServerMessage::ModeSync {
session,
english: Some(self.english),
vietnamese: Some(self.vietnamese_mode),
input: self.input_settings(session),
indicator: self.indicator_state(),
})
}
ClientMessage::ImeSwitched { session } => {
tracing::debug!(?session, "切成了别的输入法");
self.handle_ime_switched();
None
}
ClientMessage::Indicator { session, command } => {
tracing::debug!(?session, ?command, "任务栏图标菜单");
self.activate_mode(session);
self.handle_indicator(command);
None
}
ClientMessage::CloseSession { session } => {
self.sessions.remove(&session);
if self.mode_session == Some(session) {
self.mode_session = None;
}
if self.focused == Some(session) {
self.reset_composition();
self.focused = None;
}
self.flush_learning();
tracing::debug!(?session, "会话关闭");
None
}
}
}
fn handle_key(&mut self, session: SessionId, event: KeyEvent) -> ServerMessage {
self.ensure_focus(session);
self.notice = None;
self.candidates.close_menu();
if self.translation.is_some() {
return self.handle_translation_review(session, &event);
}
if self.engine.composition().is_empty()
&& !self.panel_open()
&& self.matches_translate_panel_combo(&event)
&& self.open_translate_panel()
{
tracing::debug!(?session, "translate panel opened by shortcut");
let shown = self.self_drawn_frame();
self.reconcile_candidates(&shown);
return ServerMessage::KeyResult {
session,
outcome: KeyOutcome::Consumed,
commit: None,
frame: self.current_frame(),
english: None,
};
}
if self.config.cloud_enabled
&& self.engine.composition().is_empty()
&& !self.panel_open()
&& self.engine.prediction_enabled()
&& self.matches_translate_combo(&event)
{
self.selection_seq += 1;
self.pending_selection = Some(self.selection_seq);
tracing::debug!(
?session,
request = self.selection_seq,
"翻译选中文字:请 DLL 读选区"
);
return ServerMessage::RequestSelection {
session,
request: self.selection_seq,
};
}
let (commit, outcome) = match self.apply_key(&event) {
Effect::Changed(commit) => {
if !event.modifiers.vietnamese_mode {
self.recompose();
}
(commit, KeyOutcome::Consumed)
}
Effect::Navigated => (None, KeyOutcome::Consumed),
Effect::Passthrough => (None, KeyOutcome::Passthrough),
};
// 点击上屏的字还没等到轮询就来了键:接在这次结果前面,免得丢
let commit = match (self.take_clicked(session), commit) {
(Some(clicked), commit) => Some(clicked + commit.as_deref().unwrap_or_default()),
(None, commit) => commit,
};
// 选词没选完一段拼音时 Core 把词留在组句里、上屏文本是空串:不让 DLL 结束组句
let commit = commit.filter(|text| !text.is_empty());
self.poll_prediction();
self.engine.tick_panel_translation(Instant::now());
// 自绘窗吃未降级的帧;发给 DLL 的那份按老协议降级(见 composed 的 current_frame)
let shown = self.self_drawn_frame();
self.reconcile_candidates(&shown);
ServerMessage::KeyResult {
session,
outcome,
commit,
frame: self.current_frame(),
english: None,
}
}
/// 云联想轮询:聚焦会话拉一次异步结果回最新一帧,否则回空帧。释义兜底与本地整句模型也借这个节拍收。
fn handle_poll(&mut self, session: SessionId) -> ServerMessage {
self.tick();
let learned = self.engine.poll_glosses();
if learned > 0 {
tracing::info!(learned, "释义兜底写入个人释义表");
}
let frame = if self.focused == Some(session) {
self.poll_prediction();
self.engine.tick_panel_translation(Instant::now());
let shown = self.self_drawn_frame();
self.reconcile_candidates(&shown);
self.current_frame()
} else {
Frame::default()
};
ServerMessage::Update {
session,
frame,
commit: self.take_clicked(session),
}
}
}