#pragma once #include #include #include #include #include "ipc_protocol_limits.h" // Framed voice-composition snapshots on the worker pipe. // Shared by both endpoints; do not copy this implementation into a platform repo. // Each worker packet is still wchar_t[200]; a snapshot longer than one packet // is sent as ordered frames and assembled before any TSF SetText. namespace FanyImeVoiceCompositionPipe { constexpr wchar_t kFlagFirst = 0x1; constexpr wchar_t kFlagLast = 0x2; constexpr std::size_t kPacketChars = FanyImePipeLimits::CandidateTextCapacity; constexpr std::size_t kHeaderChars = 2; constexpr std::size_t kMaxChunkChars = kPacketChars - kHeaderChars - 1; constexpr std::size_t kMaxSnapshotChars = 2048; struct Frame { bool valid = false; bool first = false; bool last = false; wchar_t generation = 0; std::wstring chunk; }; inline Frame ParseFrame(const wchar_t *data, std::size_t n = kPacketChars) { Frame frame; if (data == nullptr || n < kHeaderChars + 1) { return frame; } // The terminator has to sit inside the chunk: a middle frame legally carries flags 0, so a scan starting at the // header would accept data[0] as the terminator and let the chunk read run past the packet. std::size_t terminatorIndex = n; for (std::size_t i = kHeaderChars; i < n; ++i) { if (data[i] == L'\0') { terminatorIndex = i; break; } } if (terminatorIndex == n) { return frame; } const wchar_t flags = data[0]; if ((flags & static_cast(~(kFlagFirst | kFlagLast))) != 0 || data[1] == 0) { return frame; } frame.valid = true; frame.first = (flags & kFlagFirst) != 0; frame.last = (flags & kFlagLast) != 0; frame.generation = data[1]; frame.chunk.assign(data + kHeaderChars, terminatorIndex - kHeaderChars); return frame; } inline std::vector EncodeSnapshot(std::wstring text, wchar_t generation) { std::vector frames; if (generation == 0) { return frames; } if (text.size() > kMaxSnapshotChars) { text.resize(kMaxSnapshotChars); } if (text.empty()) { std::wstring frame(kHeaderChars, L'\0'); frame[0] = static_cast(kFlagFirst | kFlagLast); frame[1] = generation; frames.push_back(std::move(frame)); return frames; } std::size_t offset = 0; while (offset < text.size()) { const std::size_t chunkLen = (std::min)(kMaxChunkChars, text.size() - offset); std::wstring frame(kHeaderChars + chunkLen, L'\0'); wchar_t flags = 0; if (offset == 0) { flags |= kFlagFirst; } if (offset + chunkLen >= text.size()) { flags |= kFlagLast; } frame[0] = flags; frame[1] = generation; std::copy(text.begin() + static_cast(offset), text.begin() + static_cast(offset + chunkLen), frame.begin() + kHeaderChars); frames.push_back(std::move(frame)); offset += chunkLen; } return frames; } inline std::wstring AssembleFrames(const std::vector &payloads) { std::wstring assembled; bool started = false; wchar_t generation = 0; for (const std::wstring &payload : payloads) { wchar_t data[kPacketChars] = {}; if (payload.size() >= kPacketChars) { return {}; } std::copy(payload.begin(), payload.end(), data); const Frame frame = ParseFrame(data); if (!frame.valid) { return {}; } if (frame.first) { assembled.clear(); generation = frame.generation; started = true; } else if (!started || frame.generation != generation) { return {}; } assembled += frame.chunk; if (frame.last) { return assembled; } } return {}; } } // namespace FanyImeVoiceCompositionPipe