Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions openless-all/app/windows-ime/src/ipc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,15 @@ std::wstring EscapeJsonString(const std::wstring& value) {

} // namespace

OpenLessPipeServer::OpenLessPipeServer() = default;
OpenLessPipeServer::OpenLessPipeServer()
: shutdown_event_(CreateEventW(nullptr, TRUE, FALSE, nullptr)) {}

OpenLessPipeServer::~OpenLessPipeServer() {
Stop();
if (shutdown_event_ != nullptr) {
CloseHandle(shutdown_event_);
shutdown_event_ = nullptr;
}
}

void OpenLessPipeServer::Start(OpenLessTextService* service) {
Expand All @@ -348,6 +353,11 @@ void OpenLessPipeServer::Start(OpenLessTextService* service) {
}

stop_requested_.store(false);

if (shutdown_event_ != nullptr) {
ResetEvent(shutdown_event_);
}

pipe_name_ = PipeNameForCurrentThread();
service_ = service;
service_->AddRef();
Expand All @@ -356,12 +366,21 @@ void OpenLessPipeServer::Start(OpenLessTextService* service) {

void OpenLessPipeServer::Stop() {
stop_requested_.store(true);

if (shutdown_event_ != nullptr) {
SetEvent(shutdown_event_);
}

if (thread_.joinable()) {
CancelSynchronousIo(thread_.native_handle());
WakePipe();
thread_.join();
}

if (shutdown_event_ != nullptr) {
ResetEvent(shutdown_event_);
}

if (service_ != nullptr) {
service_->Release();
service_ = nullptr;
Expand Down Expand Up @@ -458,7 +477,8 @@ void OpenLessPipeServer::HandleSubmitLine(HANDLE pipe, const std::string& line)
}

const HRESULT hr =
service_->SubmitTextFromPipe(message.session_id, message.text);
service_->SubmitTextFromPipe(message.session_id, message.text,
shutdown_event_);
if (SUCCEEDED(hr)) {
WriteResult(pipe, message.session_id, L"committed", nullptr);
} else {
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/windows-ime/src/ipc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class OpenLessPipeServer {
std::thread thread_;
std::mutex pipe_mutex_;
HANDLE pipe_handle_ = INVALID_HANDLE_VALUE;
HANDLE shutdown_event_ = nullptr;
std::wstring pipe_name_;
OpenLessTextService* service_ = nullptr;
};
109 changes: 90 additions & 19 deletions openless-all/app/windows-ime/src/text_service.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "text_service.h"

#include <atomic>
#include <memory>
#include <new>

Expand All @@ -15,11 +16,25 @@ constexpr UINT kSubmitTextMessage = WM_APP + 1;
constexpr UINT kSubmitTextTimeoutMs = 2000;

struct SubmitTextRequest {
const std::wstring* session_id = nullptr;
const std::wstring* text = nullptr;
// Owned copies — not pointers into another thread's stack.
std::wstring session_id;
std::wstring text;
std::shared_ptr<OpenLessAsyncEditState> async_completion;
bool wait_for_async_completion = false;
HRESULT result = E_UNEXPECTED;
HANDLE completion_event = nullptr;
// Two owners at creation: pipe thread (waiting on completion_event) and
// the queued window message. Each owner calls Release() when done; the
// last one to Release() closes the event and deletes the request.
std::atomic<int> ref_count{2};
void Release() {
if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
if (completion_event != nullptr) {
CloseHandle(completion_event);
}
delete this;
}
}
};

HRESULT WaitForAsyncEditCompletion(
Expand Down Expand Up @@ -133,7 +148,8 @@ STDMETHODIMP OpenLessTextService::Deactivate() {

HRESULT OpenLessTextService::SubmitTextFromPipe(
const std::wstring& session_id,
const std::wstring& text) {
const std::wstring& text,
HANDLE shutdown_event) {
if (GetCurrentThreadId() == owner_thread_id_) {
return CommitTextOnOwnerThread(session_id, text, nullptr, nullptr);
}
Expand All @@ -142,24 +158,60 @@ HRESULT OpenLessTextService::SubmitTextFromPipe(
return E_UNEXPECTED;
}

SubmitTextRequest request;
request.session_id = &session_id;
request.text = &text;
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) {
auto* request = new (std::nothrow) SubmitTextRequest;
if (request == nullptr) {
return E_OUTOFMEMORY;
}

request->session_id = session_id;
request->text = text;
request->completion_event =
CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (request->completion_event == nullptr) {
delete request;
return HRESULT_FROM_WIN32(GetLastError());
}

if (!PostMessageW(message_window_, kSubmitTextMessage, 0,
reinterpret_cast<LPARAM>(request))) {
const DWORD error = GetLastError();
return HRESULT_FROM_WIN32(error != ERROR_SUCCESS ? error : ERROR_TIMEOUT);
CloseHandle(request->completion_event);
delete request;
return HRESULT_FROM_WIN32(error);
}

if (request.wait_for_async_completion) {
return WaitForAsyncEditCompletion(request.async_completion);
HANDLE wait_handles[2] = {};
DWORD wait_count = 0;

wait_handles[wait_count] = request->completion_event;
++wait_count;

if (shutdown_event != nullptr) {
wait_handles[wait_count] = shutdown_event;
++wait_count;
}

return request.result;
const DWORD wait_result = WaitForMultipleObjects(
wait_count, wait_handles, FALSE, kSubmitTextTimeoutMs);

HRESULT result;
if (wait_result == WAIT_OBJECT_0) {
result = request->result;
if (request->wait_for_async_completion) {
result = WaitForAsyncEditCompletion(request->async_completion);
}
} else if (wait_count > 1 && wait_result == WAIT_OBJECT_0 + 1) {
result = HRESULT_FROM_WIN32(ERROR_CANCELLED);
} else if (wait_result == WAIT_TIMEOUT) {
result = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
} else {
result = HRESULT_FROM_WIN32(GetLastError());
}

// Release the pipe-thread reference. The message-thread reference, if
// still outstanding (timeout / shutdown paths), will clean up later.
request->Release();
return result;
}

HRESULT OpenLessTextService::StartIpcServer() {
Expand Down Expand Up @@ -200,6 +252,18 @@ HRESULT OpenLessTextService::EnsureMessageWindow() {

void OpenLessTextService::DestroyMessageWindow() {
if (message_window_ != nullptr) {
// Drain any kSubmitTextMessage still in the queue so the associated
// SubmitTextRequest references are released before the window is gone.
// These are left over when SubmitTextFromPipe exited via timeout or
// shutdown before the owner thread could dispatch the message.
MSG msg;
while (PeekMessageW(&msg, message_window_, kSubmitTextMessage,
kSubmitTextMessage, PM_REMOVE)) {
auto* request = reinterpret_cast<SubmitTextRequest*>(msg.lParam);
if (request != nullptr) {
request->Release();
}
}
DestroyWindow(message_window_);
message_window_ = nullptr;
}
Expand Down Expand Up @@ -317,14 +381,21 @@ LRESULT CALLBACK OpenLessTextService::MessageWindowProc(HWND window,
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) {
if (request == nullptr || request->session_id.empty() ||
request->text.empty()) {
if (request != nullptr) {
request->Release();
}
return 0;
}

request->result = service->CommitTextOnOwnerThread(
*request->session_id, *request->text, &request->async_completion,
request->session_id, request->text, &request->async_completion,
&request->wait_for_async_completion);
if (request->completion_event != nullptr) {
SetEvent(request->completion_event);
}
request->Release();
return 1;
}

Expand Down
3 changes: 2 additions & 1 deletion openless-all/app/windows-ime/src/text_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class OpenLessTextService final : public ITfTextInputProcessorEx {
DWORD flags) override;

HRESULT SubmitTextFromPipe(const std::wstring& session_id,
const std::wstring& text);
const std::wstring& text,
HANDLE shutdown_event = nullptr);

private:
HRESULT StartIpcServer();
Expand Down