mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-19 12:27:47 +00:00
This is the first half of a two-part update to Keyman for Windows. There are two significant changes here. These changes apply to keyman32/keyman64, as well as kmtip/kmtip64, but will refer to the 32-bit equivalents for simplicity: 1. Instead of using `GetProcAddress` to connect to keyman32.dll, we now have an import library connection. This removes one particularly suspect piece of architecture for Keyman, as using `GetProcAddress` could lead to a race condition, where keyman32.dll is unloaded after that function is called but before we use the handle. This makes the kmtip.dll build dependent on keyman32.dll; loading kmtip.dll (by selecting a Keyman input method) also loads keyman32.dll. 2. It's not enough (yet) to load kmtip.dll. We also need to be running keyman.exe itself, due to global hooks that we still require (it may be possible, one day, to avoid these, but it doesn't look very hopeful). To that end, kmtip calls new function `TIPIsKeymanRunning` to check whether keyman32.dll is initialised (and hence keyman.exe has initialised it). Then, if keyman.exe is not running, it issues an event to the Windows Event Log, which we will pick up with a scheduled task in the next PR. The first part of this is an architectural change which may lead to some change in behaviours, as keyman32.dll now may not be unloaded from a process when keyman.exe exits, due to the lock that kmtip.dll now has on it. It will be important to test this (@MakaraSok) by running through various startup and shutdown scenarios. In my initial testing, I found that there were some situations after restarting keyman.exe where I needed to switch in and out of the Keyman TIP, but those were not all critical. This change also lays groundwork for an eventual ability to run the Keyman input method without keyman.exe running, which will be helpful e.g. on login screen, even if missing certain additional functionality such as the serialised input queue.
290 lines
7.1 KiB
C++
290 lines
7.1 KiB
C++
/*
|
|
Name: kmtip
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 7 Sep 2009
|
|
|
|
Modified Date: 28 Mar 2016
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 07 Sep 2009 - mcdurdin - I2095 - TSF addin is not threadsafe
|
|
20 Nov 2012 - mcdurdin - I3582 - V9.0 - Remove language bar integration
|
|
20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
|
|
28 Nov 2012 - mcdurdin - I3590 - V9.0 - Initialise keystroke sink after we know which keyboard to activate
|
|
28 Nov 2012 - mcdurdin - I3588 - V9.0 - Use preserved keys in TSF to handle key combinations in Keyman
|
|
01 Jan 2013 - mcdurdin - I3714 - V9.0 - Applications hang when switching kmtip off if Keyman not running
|
|
01 May 2014 - mcdurdin - I4216 - V9.0 - Keyman TIP should use ITfTextInputProcessorEx
|
|
28 May 2014 - mcdurdin - I3706 - V9.0 - If kmtip CKMTipTextService::Activate fails, should we release pThreadMgr?
|
|
16 Jun 2014 - mcdurdin - I4274 - V9.0 - kmtip does not work if already active before KM starts
|
|
*/
|
|
//
|
|
// kmtip.cpp
|
|
//
|
|
// IUnknown, ITfTextInputProcessor implementation.
|
|
//
|
|
|
|
#include "pch.h"
|
|
#include "kmtip.h"
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CreateInstance
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
/* static */
|
|
HRESULT CKMTipTextService::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)
|
|
{
|
|
CKMTipTextService *pKMTip;
|
|
HRESULT hr;
|
|
|
|
if (ppvObj == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppvObj = NULL;
|
|
|
|
if (NULL != pUnkOuter)
|
|
return CLASS_E_NOAGGREGATION;
|
|
|
|
if ((pKMTip = new CKMTipTextService) == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
hr = pKMTip->QueryInterface(riid, ppvObj);
|
|
|
|
pKMTip->Release(); // caller still holds ref if hr == S_OK
|
|
|
|
return hr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// ctor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
__declspec(thread) CKMTipTextService *CKMTipTextService::ThreadThis;
|
|
|
|
CKMTipTextService::CKMTipTextService()
|
|
{
|
|
DllAddRef();
|
|
|
|
_pThreadMgr = NULL;
|
|
_tfClientId = TF_CLIENTID_NULL;
|
|
// I3582
|
|
_dwThreadMgrEventSinkCookie = TF_INVALID_COOKIE;
|
|
|
|
memset(&guidActiveProfile, 0, sizeof(GUID));
|
|
_keystrokeSinkInitialized = FALSE;
|
|
_dwActiveLanguageProfileNotifySinkCookie = 0;
|
|
_PreservedKeys = NULL;
|
|
_cPreservedKeyCount = 0;
|
|
_dwDeepIntegration = 0;
|
|
|
|
_cRef = 1;
|
|
ThreadThis = this;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// dtor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CKMTipTextService::~CKMTipTextService()
|
|
{
|
|
ThreadThis = NULL;
|
|
DllRelease();
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// QueryInterface
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CKMTipTextService::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
if (ppvObj == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
*ppvObj = NULL;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown) ||
|
|
IsEqualIID(riid, IID_ITfTextInputProcessor))
|
|
{
|
|
*ppvObj = (ITfTextInputProcessor *)this;
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfThreadMgrEventSink))
|
|
{
|
|
*ppvObj = (ITfThreadMgrEventSink *)this;
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfActiveLanguageProfileNotifySink)) // I3581
|
|
{
|
|
*ppvObj = (ITfActiveLanguageProfileNotifySink *)this;
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfThreadFocusSink))
|
|
{
|
|
*ppvObj = (ITfThreadFocusSink *)this;
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfTextEditSink))
|
|
{
|
|
*ppvObj = (ITfTextEditSink *)this;
|
|
}
|
|
else if (IsEqualIID(riid, IID_ITfKeyEventSink))
|
|
{
|
|
*ppvObj = (ITfKeyEventSink *)this;
|
|
}
|
|
|
|
if (*ppvObj)
|
|
{
|
|
AddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// AddRef
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI_(ULONG) CKMTipTextService::AddRef()
|
|
{
|
|
return ++_cRef;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Release
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI_(ULONG) CKMTipTextService::Release()
|
|
{
|
|
LONG cr = --_cRef;
|
|
|
|
assert(_cRef >= 0);
|
|
|
|
if (_cRef == 0)
|
|
{
|
|
Log(L"CKMTipTextService::Release --> deleting");
|
|
delete this;
|
|
}
|
|
|
|
return cr;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Activate
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CKMTipTextService::ActivateEx(ITfThreadMgr *ptim, TfClientId tid, DWORD dwFlags) { // I4216
|
|
LogEnter();
|
|
ITfDocumentMgr *pFocusDoc;
|
|
|
|
_cPreservedKeyCount = 0; // I3588 // I3714
|
|
_PreservedKeys = NULL; // I3588 // I3714
|
|
|
|
_pThreadMgr = ptim;
|
|
_pThreadMgr->AddRef();
|
|
|
|
_tfClientId = tid;
|
|
|
|
if(!_InitThreadMgrSink()) {
|
|
Log(L"Could not initialise thread manager sink");
|
|
goto ExitError;
|
|
}
|
|
|
|
if(!_InitKeyman()) {
|
|
Log(L"Could not initialise Keyman");
|
|
goto ExitError;
|
|
}
|
|
|
|
// start tracking the focus doc
|
|
if(_pThreadMgr->GetFocus(&pFocusDoc) == S_OK) {
|
|
// The system will call OnSetFocus only for focus events after Activate
|
|
// is called.
|
|
OnSetFocus(pFocusDoc, NULL);
|
|
pFocusDoc->Release();
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
ExitError:
|
|
Deactivate(); // cleanup any half-finished init // I3706
|
|
return E_FAIL;
|
|
}
|
|
|
|
STDAPI CKMTipTextService::Activate(ITfThreadMgr *pThreadMgr, TfClientId tfClientId)
|
|
{
|
|
LogEnter();
|
|
ITfDocumentMgr *pFocusDoc;
|
|
|
|
_cPreservedKeyCount = 0; // I3588 // I3714
|
|
_PreservedKeys = NULL; // I3588 // I3714
|
|
|
|
_pThreadMgr = pThreadMgr;
|
|
_pThreadMgr->AddRef();
|
|
|
|
_tfClientId = tfClientId;
|
|
|
|
if (!_InitThreadMgrSink())
|
|
{
|
|
Log(L"Could not initialise thread manager sink");
|
|
goto ExitError;
|
|
}
|
|
|
|
if(!_InitKeyman()) {
|
|
_TryAndStartKeyman();
|
|
Log(L"Could not initialise Keyman");
|
|
//goto ExitError;
|
|
}
|
|
|
|
// start tracking the focus doc
|
|
if (_pThreadMgr->GetFocus(&pFocusDoc) == S_OK)
|
|
{
|
|
// The system will call OnSetFocus only for focus events after Activate
|
|
// is called.
|
|
OnSetFocus(pFocusDoc, NULL);
|
|
pFocusDoc->Release();
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
ExitError:
|
|
Deactivate(); // cleanup any half-finished init // I3706
|
|
return E_FAIL;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Deactivate
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
STDAPI CKMTipTextService::Deactivate()
|
|
{
|
|
LogEnter();
|
|
|
|
_UninitThreadMgrSink();
|
|
_UninitKeystrokeSink();
|
|
_UninitKeyman();
|
|
|
|
// we MUST release all refs to _pThreadMgr in Deactivate
|
|
SafeReleaseClear(_pThreadMgr);
|
|
|
|
_tfClientId = TF_CLIENTID_NULL;
|
|
|
|
return S_OK;
|
|
}
|