feat: add Windows IME IPC protocol
This commit is contained in:
1 parent
271a51ad79
commit
83426bc057
2 files changed
+113
No files matched your search
@@ -20,6 +20,7 @@ mod persistence;
|
||||
mod polish;
|
||||
mod recorder;
|
||||
mod types;
|
||||
mod windows_ime_protocol;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::sync::mpsc;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const OPENLESS_IME_PROTOCOL_VERSION: u32 = 1;
|
||||
pub const OPENLESS_IME_PIPE_NAME: &str = r"\\.\pipe\OpenLessImeSubmit";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(
|
||||
tag = "type",
|
||||
rename_all = "camelCase",
|
||||
rename_all_fields = "camelCase"
|
||||
)]
|
||||
pub enum ImePipeMessage {
|
||||
ClientReady {
|
||||
protocol_version: u32,
|
||||
client_id: String,
|
||||
process_id: u32,
|
||||
thread_id: u32,
|
||||
},
|
||||
SubmitText {
|
||||
protocol_version: u32,
|
||||
session_id: String,
|
||||
text: String,
|
||||
created_at: String,
|
||||
},
|
||||
SubmitResult {
|
||||
protocol_version: u32,
|
||||
session_id: String,
|
||||
status: ImeSubmitStatus,
|
||||
error_code: Option<String>,
|
||||
},
|
||||
CancelSession {
|
||||
protocol_version: u32,
|
||||
session_id: String,
|
||||
},
|
||||
Ping {
|
||||
protocol_version: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ImeSubmitStatus {
|
||||
Committed,
|
||||
Rejected,
|
||||
Failed,
|
||||
}
|
||||
|
||||
pub fn encode_message(message: &ImePipeMessage) -> Result<String, serde_json::Error> {
|
||||
let mut line = serde_json::to_string(message)?;
|
||||
line.push('\n');
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
pub fn decode_message(line: &str) -> Result<ImePipeMessage, serde_json::Error> {
|
||||
serde_json::from_str(line)
|
||||
}
|
||||
|
||||
pub fn is_result_for_pending_session(
|
||||
message: &ImePipeMessage,
|
||||
pending_session_id: &str,
|
||||
) -> Result<(), &'static str> {
|
||||
match message {
|
||||
ImePipeMessage::SubmitResult { session_id, .. } if session_id == pending_session_id => {
|
||||
Ok(())
|
||||
}
|
||||
ImePipeMessage::SubmitResult { .. } => Err("submit result belongs to a different session"),
|
||||
_ => Err("message is not a submit result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn submit_text_roundtrips_as_camel_case_json() {
|
||||
let message = ImePipeMessage::SubmitText {
|
||||
protocol_version: OPENLESS_IME_PROTOCOL_VERSION,
|
||||
session_id: "session-1".to_string(),
|
||||
text: "\u{4f60}\u{597d} OpenLess".to_string(),
|
||||
created_at: "2026-05-01T12:00:00Z".to_string(),
|
||||
};
|
||||
|
||||
let json = encode_message(&message).expect("encode");
|
||||
assert!(json.contains("\"submitText\""));
|
||||
assert!(json.contains("\"sessionId\""));
|
||||
assert!(json.contains("\"createdAt\""));
|
||||
assert!(!json.contains("\"session_id\""));
|
||||
assert!(!json.contains("\"created_at\""));
|
||||
assert!(json.ends_with('\n'));
|
||||
|
||||
let decoded = decode_message(json.trim_end()).expect("decode");
|
||||
assert_eq!(decoded, message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_submit_result_is_rejected() {
|
||||
let result = ImePipeMessage::SubmitResult {
|
||||
protocol_version: OPENLESS_IME_PROTOCOL_VERSION,
|
||||
session_id: "old-session".to_string(),
|
||||
status: ImeSubmitStatus::Committed,
|
||||
error_code: Some("ime-busy".to_string()),
|
||||
};
|
||||
|
||||
let json = encode_message(&result).expect("encode");
|
||||
assert!(json.contains("\"errorCode\""));
|
||||
assert!(!json.contains("\"error_code\""));
|
||||
|
||||
assert!(is_result_for_pending_session(&result, "current-session").is_err());
|
||||
assert!(is_result_for_pending_session(&result, "old-session").is_ok());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user