Files
openless/openless-all/app/windows-ime/src/text_service.cpp
T

256 lines
6.3 KiB
C++

#include "text_service.h"
#include <new>
#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);
}
OpenLessTextService::~OpenLessTextService() {
Deactivate();
InterlockedDecrement(&g_object_count);
}
STDMETHODIMP OpenLessTextService::QueryInterface(REFIID iid, void** object) {
if (object == nullptr) {
return E_POINTER;
}
*object = nullptr;
if (iid == IID_IUnknown || iid == IID_ITfTextInputProcessor ||
iid == IID_ITfTextInputProcessorEx) {
*object = static_cast<ITfTextInputProcessorEx*>(this);
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) OpenLessTextService::AddRef() {
return static_cast<ULONG>(InterlockedIncrement(&ref_count_));
}
STDMETHODIMP_(ULONG) OpenLessTextService::Release() {
const ULONG count = static_cast<ULONG>(InterlockedDecrement(&ref_count_));
if (count == 0) {
delete this;
}
return count;
}
STDMETHODIMP OpenLessTextService::Activate(ITfThreadMgr* thread_mgr,
TfClientId client_id) {
return ActivateEx(thread_mgr, client_id, 0);
}
STDMETHODIMP OpenLessTextService::ActivateEx(ITfThreadMgr* thread_mgr,
TfClientId client_id,
DWORD flags) {
UNREFERENCED_PARAMETER(flags);
if (thread_mgr == nullptr) {
return E_INVALIDARG;
}
Deactivate();
owner_thread_id_ = GetCurrentThreadId();
thread_mgr_ = thread_mgr;
thread_mgr_->AddRef();
client_id_ = client_id;
HRESULT hr = EnsureMessageWindow();
if (FAILED(hr)) {
Deactivate();
return hr;
}
hr = StartIpcServer();
if (FAILED(hr)) {
Deactivate();
return hr;
}
return S_OK;
}
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;
}
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) {
return E_UNEXPECTED;
}
ITfDocumentMgr* document_mgr = nullptr;
HRESULT hr = thread_mgr_->GetFocus(&document_mgr);
if (FAILED(hr)) {
return hr;
}
if (document_mgr == nullptr) {
return E_FAIL;
}
ITfContext* context = nullptr;
hr = document_mgr->GetTop(&context);
document_mgr->Release();
document_mgr = nullptr;
if (FAILED(hr)) {
return hr;
}
if (context == nullptr) {
return E_FAIL;
}
auto* session = new (std::nothrow) OpenLessEditSession(context, text);
if (session == nullptr) {
context->Release();
return E_OUTOFMEMORY;
}
HRESULT edit_result = S_OK;
hr = context->RequestEditSession(client_id_, session,
TF_ES_SYNC | TF_ES_READWRITE, &edit_result);
session->Release();
context->Release();
if (FAILED(hr)) {
return hr;
}
return edit_result;
}
LRESULT CALLBACK OpenLessTextService::MessageWindowProc(HWND window,
UINT message,
WPARAM wparam,
LPARAM lparam) {
UNREFERENCED_PARAMETER(wparam);
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);
}