Skip to content

Commit

Permalink
minimal TSF impl that commits 哈 on key down (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj authored Jan 26, 2025
1 parent cfccfb0 commit 14a8694
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 0 deletions.
1 change: 1 addition & 0 deletions win32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20)

add_subdirectory(assets)
add_subdirectory(tsf)
add_subdirectory(dll)

enable_testing()
Expand Down
2 changes: 2 additions & 0 deletions win32/dll/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ add_library(fcitx5-x86_64 SHARED
)

target_compile_options(fcitx5-x86_64 PRIVATE "-Wno-dll-attribute-on-redeclaration")

target_link_libraries(fcitx5-x86_64 tsf)
75 changes: 75 additions & 0 deletions win32/dll/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,81 @@
#include "../tsf/tsf.h"
#include "register.h"

CRITICAL_SECTION CS;
LONG dllRefCount = 0;

void DllAddRef() { InterlockedIncrement(&dllRefCount); }

void DllRelease() { InterlockedDecrement(&dllRefCount); }

class ClassFactory : public IClassFactory {
public:
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override {
if (IsEqualIID(riid, IID_IClassFactory) ||
IsEqualIID(riid, IID_IUnknown)) {
*ppvObject = this;
DllAddRef();
return NOERROR;
}
*ppvObject = nullptr;
return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) AddRef() override {
DllAddRef();
return dllRefCount;
}

STDMETHODIMP_(ULONG) Release() override {
DllRelease();
return dllRefCount;
}

// IClassFactory methods
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid,
void **ppvObject) override {
fcitx::Tsf *tsf;
HRESULT hr;
if (ppvObject == nullptr)
return E_INVALIDARG;
*ppvObject = nullptr;
if (pUnkOuter != nullptr)
return CLASS_E_NOAGGREGATION;
if ((tsf = new fcitx::Tsf()) == nullptr)
return E_OUTOFMEMORY;
hr = tsf->QueryInterface(riid, ppvObject);
tsf->Release(); // caller still holds ref if hr == S_OK
return hr;
}

STDMETHODIMP LockServer(BOOL fLock) override {
fLock ? DllAddRef() : DllRelease();
return S_OK;
}
};

ClassFactory *factory = nullptr;

__declspec(dllexport) STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
void **ppvObject) {
if (factory == nullptr) {
EnterCriticalSection(&CS);
if (factory == nullptr) {
factory = new ClassFactory();
}
LeaveCriticalSection(&CS);
}
if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IUnknown)) {
*ppvObject = factory;
DllAddRef();
return NOERROR;
}
*ppvObject = nullptr;
return CLASS_E_CLASSNOTAVAILABLE;
}

__declspec(dllexport) STDAPI DllCanUnloadNow() { return dllRefCount == 0; }

__declspec(dllexport) STDAPI DllUnregisterServer() {
fcitx::UnregisterCategoriesAndProfiles();
Expand Down
9 changes: 9 additions & 0 deletions win32/tsf/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_library(tsf STATIC
tsf.cpp
TextInputProcessorEx.cpp
ThreadMgrEventSink.cpp
TextEditSink.cpp
KeyEventSink.cpp
EditSession.cpp
CompositionSink.cpp
)
8 changes: 8 additions & 0 deletions win32/tsf/CompositionSink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "tsf.h"

namespace fcitx {
STDMETHODIMP Tsf::OnCompositionTerminated(TfEditCookie ecWrite,
ITfComposition *pComposition) {
return S_OK;
}
} // namespace fcitx
33 changes: 33 additions & 0 deletions win32/tsf/EditSession.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include "tsf.h"

namespace fcitx {
STDMETHODIMP Tsf::DoEditSession(TfEditCookie ec) {
CComPtr<ITfInsertAtSelection> insertAtSelection;
if (textEditSinkContext_->QueryInterface(
IID_ITfInsertAtSelection, (LPVOID *)&insertAtSelection) != S_OK) {
return E_FAIL;
}
CComPtr<ITfRange> range;
if (insertAtSelection->InsertTextAtSelection(ec, TF_IAS_QUERYONLY, nullptr,
0, &range) != S_OK) {
return E_FAIL;
}
CComPtr<ITfContextComposition> contextComposition;
if (textEditSinkContext_->QueryInterface(
IID_ITfContextComposition, (void **)&contextComposition) != S_OK) {
return E_FAIL;
}
CComPtr<ITfComposition> composition;
if (contextComposition->StartComposition(ec, range, this, &composition) !=
S_OK ||
composition == nullptr) {
return E_FAIL;
}

range->SetText(ec, 0, L"", 1);

composition->EndComposition(ec);

return S_OK;
}
} // namespace fcitx
65 changes: 65 additions & 0 deletions win32/tsf/KeyEventSink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "tsf.h"

namespace fcitx {
bool Tsf::initKeyEventSink() {
CComPtr<ITfKeystrokeMgr> keystrokeMgr;
if (threadMgr_->QueryInterface(&keystrokeMgr) != S_OK) {
return false;
}
return keystrokeMgr->AdviseKeyEventSink(clientId_, (ITfKeyEventSink *)this,
TRUE) == S_OK;
}

void Tsf::uninitKeyEventSink() {
CComPtr<ITfKeystrokeMgr> keystrokeMgr;
if (threadMgr_->QueryInterface(&keystrokeMgr) != S_OK) {
return;
}
keystrokeMgr->UnadviseKeyEventSink(clientId_);
}

BOOL Tsf::processKey(WPARAM wParam, LPARAM lParam) {
HRESULT phrSession;
textEditSinkContext_->RequestEditSession(
clientId_, this, TF_ES_ASYNCDONTCARE | TF_ES_READWRITE, &phrSession);
return TRUE;
}

STDMETHODIMP Tsf::OnSetFocus(BOOL fForeground) { return S_OK; }

STDMETHODIMP Tsf::OnTestKeyDown(ITfContext *pContext, WPARAM wParam,
LPARAM lParam, BOOL *pfEaten) {
if (keyDownHandled_) {
*pfEaten = TRUE;
} else {
*pfEaten = keyDownHandled_ = processKey(wParam, lParam);
}
return S_OK;
}

STDMETHODIMP Tsf::OnKeyDown(ITfContext *pContext, WPARAM wParam, LPARAM lParam,
BOOL *pfEaten) {
if (keyDownHandled_) {
keyDownHandled_ = FALSE;
*pfEaten = TRUE;
} else {
*pfEaten = keyDownHandled_ = processKey(wParam, lParam);
}
return S_OK;
}

STDMETHODIMP Tsf::OnTestKeyUp(ITfContext *pContext, WPARAM wParam,
LPARAM lParam, BOOL *pfEaten) {
return S_OK;
}

STDMETHODIMP Tsf::OnKeyUp(ITfContext *pContext, WPARAM wParam, LPARAM lParam,
BOOL *pfEaten) {
return S_OK;
}

STDMETHODIMP Tsf::OnPreservedKey(ITfContext *pContext, REFGUID rguid,
BOOL *pfEaten) {
return S_OK;
}
} // namespace fcitx
46 changes: 46 additions & 0 deletions win32/tsf/TextEditSink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "tsf.h"

namespace fcitx {
bool Tsf::initTextEditSink(CComPtr<ITfDocumentMgr> documentMgr) {
CComPtr<ITfSource> source;
// clear out any previous sink first
if (textEditSinkCookie_ != TF_INVALID_COOKIE) {
if (SUCCEEDED(textEditSinkContext_->QueryInterface(IID_ITfSource,
(void **)&source))) {
source->UnadviseSink(textEditSinkCookie_);
}
textEditSinkContext_ = nullptr;
textEditSinkCookie_ = TF_INVALID_COOKIE;
}
if (documentMgr == nullptr) {
return true; // caller just wanted to clear the previous sink
}
if (FAILED(documentMgr->GetTop(&textEditSinkContext_))) {
return false;
}
if (textEditSinkContext_ == nullptr) {
return true; // empty document, no sink possible
}
source.Release();
bool ret = false;
if (SUCCEEDED(textEditSinkContext_->QueryInterface(IID_ITfSource,
(void **)&source))) {
if (SUCCEEDED(source->AdviseSink(IID_ITfTextEditSink,
(ITfTextEditSink *)this,
&textEditSinkCookie_))) {
ret = true;
} else {
textEditSinkCookie_ = TF_INVALID_COOKIE;
}
}
if (!ret) {
textEditSinkContext_ = nullptr;
}
return ret;
}

STDMETHODIMP Tsf::OnEndEdit(ITfContext *pic, TfEditCookie ecReadOnly,
ITfEditRecord *pEditRecord) {
return S_OK;
}
} // namespace fcitx
41 changes: 41 additions & 0 deletions win32/tsf/TextInputProcessorEx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "tsf.h"

namespace fcitx {
STDAPI Tsf::Activate(ITfThreadMgr *pThreadMgr, TfClientId tfClientId) {
return ActivateEx(pThreadMgr, tfClientId, 0U);
}

STDAPI Tsf::Deactivate() {
initTextEditSink(CComPtr<ITfDocumentMgr>());
uninitThreadMgrEventSink();
uninitKeyEventSink();
threadMgr_ = nullptr;
clientId_ = TF_CLIENTID_NULL;
return S_OK;
}

STDAPI Tsf::ActivateEx(ITfThreadMgr *pThreadMgr, TfClientId tfClientId,
DWORD dwFlags) {
CComPtr<ITfDocumentMgr> documentMgr;
threadMgr_ = pThreadMgr;
clientId_ = tfClientId;
if (!initThreadMgrEventSink()) {
goto ActivateExError;
}

if ((threadMgr_->GetFocus(&documentMgr) == S_OK) &&
(documentMgr != nullptr)) {
initTextEditSink(documentMgr);
}

if (!initKeyEventSink()) {
goto ActivateExError;
}

return S_OK;

ActivateExError:
Deactivate();
return E_FAIL;
}
} // namespace fcitx
42 changes: 42 additions & 0 deletions win32/tsf/ThreadMgrEventSink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "tsf.h"

namespace fcitx {
bool Tsf::initThreadMgrEventSink() {
CComPtr<ITfSource> source;
if (threadMgr_->QueryInterface(IID_ITfSource, (void **)&source) != S_OK) {
return false;
}
if (source->AdviseSink(IID_ITfThreadMgrEventSink,
(ITfThreadMgrEventSink *)this,
&threadMgrEventSinkCookie_) != S_OK) {
threadMgrEventSinkCookie_ = TF_INVALID_COOKIE;
}
return threadMgrEventSinkCookie_ != TF_INVALID_COOKIE;
}

void Tsf::uninitThreadMgrEventSink() {
CComPtr<ITfSource> source;
if (threadMgrEventSinkCookie_ == TF_INVALID_COOKIE) {
return;
}
if (SUCCEEDED(
threadMgr_->QueryInterface(IID_ITfSource, (void **)&source))) {
source->UnadviseSink(threadMgrEventSinkCookie_);
}
threadMgrEventSinkCookie_ = TF_INVALID_COOKIE;
}

STDMETHODIMP Tsf::OnInitDocumentMgr(ITfDocumentMgr *pDocMgr) { return S_OK; }

STDMETHODIMP Tsf::OnUninitDocumentMgr(ITfDocumentMgr *pDocMgr) { return S_OK; }

STDMETHODIMP Tsf::OnSetFocus(ITfDocumentMgr *pDocMgrFocus,
ITfDocumentMgr *pDocMgrPrevFocus) {
initTextEditSink(pDocMgrFocus);
return S_OK;
}

STDMETHODIMP Tsf::OnPushContext(ITfContext *pContext) { return S_OK; }

STDMETHODIMP Tsf::OnPopContext(ITfContext *pContext) { return S_OK; }
} // namespace fcitx
52 changes: 52 additions & 0 deletions win32/tsf/tsf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "tsf.h"
#include <cassert>

extern void DllAddRef();
extern void DllRelease();

namespace fcitx {
Tsf::Tsf() { DllAddRef(); }

Tsf::~Tsf() { DllRelease(); }

// Windows also queries ITfDisplayAttributeCollectionProvider
// {3977526D-1A0A-435A-8D06-ECC9516B484F} which is internal and we simply
// ignore.
STDAPI Tsf::QueryInterface(REFIID riid, void **ppvObject) {
if (ppvObject == nullptr) {
return E_INVALIDARG;
}
*ppvObject = nullptr;

if (IsEqualIID(riid, IID_IUnknown) ||
IsEqualIID(riid, IID_ITfTextInputProcessor))
*ppvObject = (ITfTextInputProcessor *)this;
else if (IsEqualIID(riid, IID_ITfTextInputProcessorEx))
*ppvObject = (ITfTextInputProcessorEx *)this;
else if (IsEqualIID(riid, IID_ITfThreadMgrEventSink))
*ppvObject = (ITfThreadMgrEventSink *)this;
else if (IsEqualIID(riid, IID_ITfTextEditSink))
*ppvObject = (ITfTextEditSink *)this;
else if (IsEqualIID(riid, IID_ITfKeyEventSink))
*ppvObject = (ITfKeyEventSink *)this;
else if (IsEqualIID(riid, IID_ITfEditSession))
*ppvObject = (ITfEditSession *)this;

if (*ppvObject) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}

STDAPI_(ULONG) Tsf::AddRef() { return ++refCount_; }

STDAPI_(ULONG) Tsf::Release() {
LONG ret = --refCount_;
assert(refCount_ >= 0);
if (refCount_ == 0) {
delete this;
}
return ret;
}
} // namespace fcitx
Loading

0 comments on commit 14a8694

Please sign in to comment.