feat: receive OpenLess IME submissions over a named pipe
This commit is contained in:
1 parent
e714fd2c7d
commit
52f7de5d85
5 files changed
+660
-5
No files matched your search
@@ -77,6 +77,7 @@
|
||||
<ClCompile Include="src\class_factory.cpp" />
|
||||
<ClCompile Include="src\dllmain.cpp" />
|
||||
<ClCompile Include="src\edit_session.cpp" />
|
||||
<ClCompile Include="src\ipc_client.cpp" />
|
||||
<ClCompile Include="src\registry.cpp" />
|
||||
<ClCompile Include="src\text_service.cpp" />
|
||||
</ItemGroup>
|
||||
@@ -84,6 +85,7 @@
|
||||
<ClInclude Include="src\class_factory.h" />
|
||||
<ClInclude Include="src\edit_session.h" />
|
||||
<ClInclude Include="src\guids.h" />
|
||||
<ClInclude Include="src\ipc_client.h" />
|
||||
<ClInclude Include="src\registry.h" />
|
||||
<ClInclude Include="src\text_service.h" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
#include "ipc_client.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "text_service.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kPipeName[] = L"\\\\.\\pipe\\OpenLessImeSubmit";
|
||||
constexpr DWORD kPipeBufferSize = 4096;
|
||||
constexpr size_t kMaxJsonLineBytes = 64 * 1024;
|
||||
|
||||
struct SubmitMessage {
|
||||
std::wstring type;
|
||||
std::wstring session_id;
|
||||
std::wstring text;
|
||||
int protocol_version = 0;
|
||||
bool has_type = false;
|
||||
bool has_session_id = false;
|
||||
bool has_text = false;
|
||||
bool has_protocol_version = false;
|
||||
};
|
||||
|
||||
bool AppendUtf8AsWide(const char* data,
|
||||
int length,
|
||||
std::wstring* output) {
|
||||
if (length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int required = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, data,
|
||||
length, nullptr, 0);
|
||||
if (required <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t old_size = output->size();
|
||||
output->resize(old_size + static_cast<size_t>(required));
|
||||
return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, data, length,
|
||||
output->data() + old_size, required) == required;
|
||||
}
|
||||
|
||||
bool WideToUtf8(const std::wstring& value, std::string* output) {
|
||||
output->clear();
|
||||
if (value.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const int required = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||
value.c_str(),
|
||||
static_cast<int>(value.size()),
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
if (required <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
output->resize(static_cast<size_t>(required));
|
||||
return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value.c_str(),
|
||||
static_cast<int>(value.size()), output->data(),
|
||||
required, nullptr, nullptr) == required;
|
||||
}
|
||||
|
||||
void SkipWhitespace(const std::string& json, size_t* pos) {
|
||||
while (*pos < json.size()) {
|
||||
const char c = json[*pos];
|
||||
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
|
||||
return;
|
||||
}
|
||||
++(*pos);
|
||||
}
|
||||
}
|
||||
|
||||
int HexDigit(char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ParseJsonString(const std::string& json,
|
||||
size_t* pos,
|
||||
std::wstring* value) {
|
||||
value->clear();
|
||||
if (*pos >= json.size() || json[*pos] != '"') {
|
||||
return false;
|
||||
}
|
||||
++(*pos);
|
||||
|
||||
size_t segment_start = *pos;
|
||||
while (*pos < json.size()) {
|
||||
const char c = json[*pos];
|
||||
if (static_cast<unsigned char>(c) < 0x20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
if (!AppendUtf8AsWide(json.data() + segment_start,
|
||||
static_cast<int>(*pos - segment_start), value)) {
|
||||
return false;
|
||||
}
|
||||
++(*pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c != '\\') {
|
||||
++(*pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!AppendUtf8AsWide(json.data() + segment_start,
|
||||
static_cast<int>(*pos - segment_start), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++(*pos);
|
||||
if (*pos >= json.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char escaped = json[*pos];
|
||||
switch (escaped) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
value->push_back(static_cast<wchar_t>(escaped));
|
||||
++(*pos);
|
||||
break;
|
||||
case 'b':
|
||||
value->push_back(L'\b');
|
||||
++(*pos);
|
||||
break;
|
||||
case 'f':
|
||||
value->push_back(L'\f');
|
||||
++(*pos);
|
||||
break;
|
||||
case 'n':
|
||||
value->push_back(L'\n');
|
||||
++(*pos);
|
||||
break;
|
||||
case 'r':
|
||||
value->push_back(L'\r');
|
||||
++(*pos);
|
||||
break;
|
||||
case 't':
|
||||
value->push_back(L'\t');
|
||||
++(*pos);
|
||||
break;
|
||||
case 'u': {
|
||||
if (*pos + 4 >= json.size()) {
|
||||
return false;
|
||||
}
|
||||
uint32_t code_unit = 0;
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
const int digit = HexDigit(json[*pos + static_cast<size_t>(i)]);
|
||||
if (digit < 0) {
|
||||
return false;
|
||||
}
|
||||
code_unit = (code_unit << 4) | static_cast<uint32_t>(digit);
|
||||
}
|
||||
value->push_back(static_cast<wchar_t>(code_unit));
|
||||
*pos += 5;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
segment_start = *pos;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseJsonInteger(const std::string& json, size_t* pos, int* value) {
|
||||
if (*pos >= json.size() || json[*pos] < '0' || json[*pos] > '9') {
|
||||
return false;
|
||||
}
|
||||
|
||||
int parsed = 0;
|
||||
while (*pos < json.size() && json[*pos] >= '0' && json[*pos] <= '9') {
|
||||
parsed = parsed * 10 + (json[*pos] - '0');
|
||||
++(*pos);
|
||||
}
|
||||
|
||||
*value = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipJsonValue(const std::string& json, size_t* pos) {
|
||||
std::wstring ignored;
|
||||
if (*pos >= json.size()) {
|
||||
return false;
|
||||
}
|
||||
if (json[*pos] == '"') {
|
||||
return ParseJsonString(json, pos, &ignored);
|
||||
}
|
||||
while (*pos < json.size() && json[*pos] != ',' && json[*pos] != '}') {
|
||||
++(*pos);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseSubmitMessage(const std::string& json, SubmitMessage* message) {
|
||||
size_t pos = 0;
|
||||
SkipWhitespace(json, &pos);
|
||||
if (pos >= json.size() || json[pos] != '{') {
|
||||
return false;
|
||||
}
|
||||
++pos;
|
||||
|
||||
while (true) {
|
||||
SkipWhitespace(json, &pos);
|
||||
if (pos < json.size() && json[pos] == '}') {
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
|
||||
std::wstring key;
|
||||
if (!ParseJsonString(json, &pos, &key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkipWhitespace(json, &pos);
|
||||
if (pos >= json.size() || json[pos] != ':') {
|
||||
return false;
|
||||
}
|
||||
++pos;
|
||||
SkipWhitespace(json, &pos);
|
||||
|
||||
if (key == L"type") {
|
||||
message->has_type = ParseJsonString(json, &pos, &message->type);
|
||||
if (!message->has_type) {
|
||||
return false;
|
||||
}
|
||||
} else if (key == L"sessionId") {
|
||||
message->has_session_id =
|
||||
ParseJsonString(json, &pos, &message->session_id);
|
||||
if (!message->has_session_id) {
|
||||
return false;
|
||||
}
|
||||
} else if (key == L"text") {
|
||||
message->has_text = ParseJsonString(json, &pos, &message->text);
|
||||
if (!message->has_text) {
|
||||
return false;
|
||||
}
|
||||
} else if (key == L"protocolVersion") {
|
||||
message->has_protocol_version =
|
||||
ParseJsonInteger(json, &pos, &message->protocol_version);
|
||||
if (!message->has_protocol_version) {
|
||||
return false;
|
||||
}
|
||||
} else if (!SkipJsonValue(json, &pos)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkipWhitespace(json, &pos);
|
||||
if (pos < json.size() && json[pos] == ',') {
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
if (pos < json.size() && json[pos] == '}') {
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SkipWhitespace(json, &pos);
|
||||
return pos == json.size();
|
||||
}
|
||||
|
||||
std::wstring EscapeJsonString(const std::wstring& value) {
|
||||
std::wstring escaped;
|
||||
for (wchar_t ch : value) {
|
||||
switch (ch) {
|
||||
case L'"':
|
||||
escaped += L"\\\"";
|
||||
break;
|
||||
case L'\\':
|
||||
escaped += L"\\\\";
|
||||
break;
|
||||
case L'\b':
|
||||
escaped += L"\\b";
|
||||
break;
|
||||
case L'\f':
|
||||
escaped += L"\\f";
|
||||
break;
|
||||
case L'\n':
|
||||
escaped += L"\\n";
|
||||
break;
|
||||
case L'\r':
|
||||
escaped += L"\\r";
|
||||
break;
|
||||
case L'\t':
|
||||
escaped += L"\\t";
|
||||
break;
|
||||
default:
|
||||
if (ch < 0x20) {
|
||||
wchar_t buffer[7] = {};
|
||||
swprintf_s(buffer, L"\\u%04X", static_cast<unsigned int>(ch));
|
||||
escaped += buffer;
|
||||
} else {
|
||||
escaped.push_back(ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OpenLessPipeServer::OpenLessPipeServer() = default;
|
||||
|
||||
OpenLessPipeServer::~OpenLessPipeServer() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void OpenLessPipeServer::Start(OpenLessTextService* service) {
|
||||
if (service == nullptr || thread_.joinable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
stop_requested_.store(false);
|
||||
service_ = service;
|
||||
service_->AddRef();
|
||||
thread_ = std::thread(&OpenLessPipeServer::Run, this);
|
||||
}
|
||||
|
||||
void OpenLessPipeServer::Stop() {
|
||||
stop_requested_.store(true);
|
||||
if (thread_.joinable()) {
|
||||
CancelSynchronousIo(thread_.native_handle());
|
||||
WakePipe();
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
if (service_ != nullptr) {
|
||||
service_->Release();
|
||||
service_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenLessPipeServer::Run() {
|
||||
while (!stop_requested_.load()) {
|
||||
HANDLE pipe = CreateNamedPipeW(
|
||||
kPipeName, PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT, 1,
|
||||
kPipeBufferSize, kPipeBufferSize, 0, nullptr);
|
||||
if (pipe == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pipe_mutex_);
|
||||
pipe_handle_ = pipe;
|
||||
}
|
||||
|
||||
const BOOL connected =
|
||||
ConnectNamedPipe(pipe, nullptr)
|
||||
? TRUE
|
||||
: (GetLastError() == ERROR_PIPE_CONNECTED ? TRUE : FALSE);
|
||||
|
||||
if (connected && !stop_requested_.load()) {
|
||||
std::string line;
|
||||
if (ReadJsonLine(pipe, &line)) {
|
||||
HandleSubmitLine(pipe, line);
|
||||
}
|
||||
}
|
||||
|
||||
FlushFileBuffers(pipe);
|
||||
DisconnectNamedPipe(pipe);
|
||||
CloseHandle(pipe);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pipe_mutex_);
|
||||
if (pipe_handle_ == pipe) {
|
||||
pipe_handle_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenLessPipeServer::ReadJsonLine(HANDLE pipe, std::string* line) {
|
||||
line->clear();
|
||||
char buffer[1024] = {};
|
||||
|
||||
while (!stop_requested_.load() && line->size() < kMaxJsonLineBytes) {
|
||||
DWORD bytes_read = 0;
|
||||
if (!ReadFile(pipe, buffer, sizeof(buffer), &bytes_read, nullptr)) {
|
||||
const DWORD error = GetLastError();
|
||||
if (error == ERROR_MORE_DATA && bytes_read > 0) {
|
||||
line->append(buffer, buffer + bytes_read);
|
||||
continue;
|
||||
}
|
||||
return !line->empty();
|
||||
}
|
||||
|
||||
if (bytes_read == 0) {
|
||||
return !line->empty();
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < bytes_read; ++i) {
|
||||
if (buffer[i] == '\n') {
|
||||
return true;
|
||||
}
|
||||
line->push_back(buffer[i]);
|
||||
if (line->size() >= kMaxJsonLineBytes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !line->empty();
|
||||
}
|
||||
|
||||
void OpenLessPipeServer::HandleSubmitLine(HANDLE pipe, const std::string& line) {
|
||||
SubmitMessage message;
|
||||
if (!ParseSubmitMessage(line, &message) || !message.has_type ||
|
||||
!message.has_protocol_version || !message.has_session_id ||
|
||||
!message.has_text || message.protocol_version != 1 ||
|
||||
message.type != L"submitText") {
|
||||
WriteResult(pipe, message.session_id, L"failed", L"protocolError");
|
||||
return;
|
||||
}
|
||||
|
||||
if (service_ == nullptr) {
|
||||
WriteResult(pipe, message.session_id, L"failed", L"serviceUnavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT hr =
|
||||
service_->SubmitTextFromPipe(message.session_id, message.text);
|
||||
if (SUCCEEDED(hr)) {
|
||||
WriteResult(pipe, message.session_id, L"committed", nullptr);
|
||||
} else {
|
||||
WriteResult(pipe, message.session_id, L"rejected", L"commitRejected");
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenLessPipeServer::WriteResult(HANDLE pipe,
|
||||
const std::wstring& session_id,
|
||||
const wchar_t* status,
|
||||
const wchar_t* error_code) {
|
||||
std::wstring response = L"{\"type\":\"submitResult\",\"protocolVersion\":1,";
|
||||
response += L"\"sessionId\":\"";
|
||||
response += EscapeJsonString(session_id);
|
||||
response += L"\",\"status\":\"";
|
||||
response += status;
|
||||
response += L"\",\"errorCode\":";
|
||||
if (error_code == nullptr) {
|
||||
response += L"null";
|
||||
} else {
|
||||
response += L"\"";
|
||||
response += error_code;
|
||||
response += L"\"";
|
||||
}
|
||||
response += L"}\n";
|
||||
|
||||
std::string utf8_response;
|
||||
if (!WideToUtf8(response, &utf8_response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD bytes_written = 0;
|
||||
return WriteFile(pipe, utf8_response.data(),
|
||||
static_cast<DWORD>(utf8_response.size()), &bytes_written,
|
||||
nullptr) &&
|
||||
bytes_written == utf8_response.size();
|
||||
}
|
||||
|
||||
void OpenLessPipeServer::WakePipe() {
|
||||
HANDLE pipe =
|
||||
CreateFileW(kPipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (pipe != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(pipe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
class OpenLessTextService;
|
||||
|
||||
class OpenLessPipeServer {
|
||||
public:
|
||||
OpenLessPipeServer();
|
||||
OpenLessPipeServer(const OpenLessPipeServer&) = delete;
|
||||
OpenLessPipeServer& operator=(const OpenLessPipeServer&) = delete;
|
||||
~OpenLessPipeServer();
|
||||
|
||||
void Start(OpenLessTextService* service);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void Run();
|
||||
bool ReadJsonLine(HANDLE pipe, std::string* line);
|
||||
void HandleSubmitLine(HANDLE pipe, const std::string& line);
|
||||
bool WriteResult(HANDLE pipe,
|
||||
const std::wstring& session_id,
|
||||
const wchar_t* status,
|
||||
const wchar_t* error_code);
|
||||
void WakePipe();
|
||||
|
||||
std::atomic<bool> stop_requested_{false};
|
||||
std::thread thread_;
|
||||
std::mutex pipe_mutex_;
|
||||
HANDLE pipe_handle_ = INVALID_HANDLE_VALUE;
|
||||
OpenLessTextService* service_ = nullptr;
|
||||
};
|
||||
@@ -5,6 +5,21 @@
|
||||
#include "edit_session.h"
|
||||
|
||||
extern LONG g_object_count;
|
||||
extern HINSTANCE g_module;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wchar_t kMessageWindowClassName[] = L"OpenLessImeMessageWindow";
|
||||
constexpr UINT kSubmitTextMessage = WM_APP + 1;
|
||||
constexpr UINT kSubmitTextTimeoutMs = 3000;
|
||||
|
||||
struct SubmitTextRequest {
|
||||
const std::wstring* session_id = nullptr;
|
||||
const std::wstring* text = nullptr;
|
||||
HRESULT result = E_UNEXPECTED;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
OpenLessTextService::OpenLessTextService() {
|
||||
InterlockedIncrement(&g_object_count);
|
||||
@@ -59,11 +74,19 @@ STDMETHODIMP OpenLessTextService::ActivateEx(ITfThreadMgr* thread_mgr,
|
||||
|
||||
Deactivate();
|
||||
|
||||
owner_thread_id_ = GetCurrentThreadId();
|
||||
|
||||
thread_mgr_ = thread_mgr;
|
||||
thread_mgr_->AddRef();
|
||||
client_id_ = client_id;
|
||||
|
||||
const HRESULT hr = StartIpcServer();
|
||||
HRESULT hr = EnsureMessageWindow();
|
||||
if (FAILED(hr)) {
|
||||
Deactivate();
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = StartIpcServer();
|
||||
if (FAILED(hr)) {
|
||||
Deactivate();
|
||||
return hr;
|
||||
@@ -74,12 +97,14 @@ STDMETHODIMP OpenLessTextService::ActivateEx(ITfThreadMgr* thread_mgr,
|
||||
|
||||
STDMETHODIMP OpenLessTextService::Deactivate() {
|
||||
StopIpcServer();
|
||||
DestroyMessageWindow();
|
||||
|
||||
if (thread_mgr_ != nullptr) {
|
||||
thread_mgr_->Release();
|
||||
thread_mgr_ = nullptr;
|
||||
}
|
||||
client_id_ = TF_CLIENTID_NULL;
|
||||
owner_thread_id_ = 0;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -87,6 +112,74 @@ STDMETHODIMP OpenLessTextService::Deactivate() {
|
||||
HRESULT OpenLessTextService::SubmitTextFromPipe(
|
||||
const std::wstring& session_id,
|
||||
const std::wstring& text) {
|
||||
if (GetCurrentThreadId() == owner_thread_id_) {
|
||||
return CommitTextOnOwnerThread(session_id, text);
|
||||
}
|
||||
|
||||
if (message_window_ == nullptr) {
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
|
||||
SubmitTextRequest request{&session_id, &text, E_UNEXPECTED};
|
||||
DWORD_PTR message_result = 0;
|
||||
const LRESULT sent = SendMessageTimeoutW(
|
||||
message_window_, kSubmitTextMessage, 0,
|
||||
reinterpret_cast<LPARAM>(&request), SMTO_ABORTIFHUNG,
|
||||
kSubmitTextTimeoutMs, &message_result);
|
||||
if (sent == 0) {
|
||||
const DWORD error = GetLastError();
|
||||
return HRESULT_FROM_WIN32(error != ERROR_SUCCESS ? error : ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
return request.result;
|
||||
}
|
||||
|
||||
HRESULT OpenLessTextService::StartIpcServer() {
|
||||
pipe_server_.Start(this);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void OpenLessTextService::StopIpcServer() {
|
||||
pipe_server_.Stop();
|
||||
}
|
||||
|
||||
HRESULT OpenLessTextService::EnsureMessageWindow() {
|
||||
if (message_window_ != nullptr) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
WNDCLASSW window_class = {};
|
||||
window_class.lpfnWndProc = OpenLessTextService::MessageWindowProc;
|
||||
window_class.hInstance = g_module;
|
||||
window_class.lpszClassName = kMessageWindowClassName;
|
||||
|
||||
if (!RegisterClassW(&window_class)) {
|
||||
const DWORD error = GetLastError();
|
||||
if (error != ERROR_CLASS_ALREADY_EXISTS) {
|
||||
return HRESULT_FROM_WIN32(error);
|
||||
}
|
||||
}
|
||||
|
||||
message_window_ =
|
||||
CreateWindowExW(0, kMessageWindowClassName, L"", 0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, nullptr, g_module, this);
|
||||
if (message_window_ == nullptr) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void OpenLessTextService::DestroyMessageWindow() {
|
||||
if (message_window_ != nullptr) {
|
||||
DestroyWindow(message_window_);
|
||||
message_window_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT OpenLessTextService::CommitTextOnOwnerThread(
|
||||
const std::wstring& session_id,
|
||||
const std::wstring& text) {
|
||||
UNREFERENCED_PARAMETER(session_id);
|
||||
|
||||
if (thread_mgr_ == nullptr || client_id_ == TF_CLIENTID_NULL) {
|
||||
@@ -131,8 +224,32 @@ HRESULT OpenLessTextService::SubmitTextFromPipe(
|
||||
return edit_result;
|
||||
}
|
||||
|
||||
HRESULT OpenLessTextService::StartIpcServer() {
|
||||
return S_OK;
|
||||
}
|
||||
LRESULT CALLBACK OpenLessTextService::MessageWindowProc(HWND window,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
UNREFERENCED_PARAMETER(wparam);
|
||||
|
||||
void OpenLessTextService::StopIpcServer() {}
|
||||
if (message == WM_NCCREATE) {
|
||||
const auto* create = reinterpret_cast<CREATESTRUCTW*>(lparam);
|
||||
SetWindowLongPtrW(window, GWLP_USERDATA,
|
||||
reinterpret_cast<LONG_PTR>(create->lpCreateParams));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
auto* service = reinterpret_cast<OpenLessTextService*>(
|
||||
GetWindowLongPtrW(window, GWLP_USERDATA));
|
||||
if (message == kSubmitTextMessage && service != nullptr) {
|
||||
auto* request = reinterpret_cast<SubmitTextRequest*>(lparam);
|
||||
if (request == nullptr || request->session_id == nullptr ||
|
||||
request->text == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
request->result =
|
||||
service->CommitTextOnOwnerThread(*request->session_id, *request->text);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
#include "ipc_client.h"
|
||||
|
||||
class OpenLessTextService final : public ITfTextInputProcessorEx {
|
||||
public:
|
||||
OpenLessTextService();
|
||||
@@ -27,8 +29,20 @@ class OpenLessTextService final : public ITfTextInputProcessorEx {
|
||||
private:
|
||||
HRESULT StartIpcServer();
|
||||
void StopIpcServer();
|
||||
HRESULT EnsureMessageWindow();
|
||||
void DestroyMessageWindow();
|
||||
HRESULT CommitTextOnOwnerThread(const std::wstring& session_id,
|
||||
const std::wstring& text);
|
||||
|
||||
static LRESULT CALLBACK MessageWindowProc(HWND window,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam);
|
||||
|
||||
LONG ref_count_ = 1;
|
||||
ITfThreadMgr* thread_mgr_ = nullptr;
|
||||
TfClientId client_id_ = TF_CLIENTID_NULL;
|
||||
DWORD owner_thread_id_ = 0;
|
||||
HWND message_window_ = nullptr;
|
||||
OpenLessPipeServer pipe_server_;
|
||||
};
|
||||
Reference in new issue
Block a user