mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-07 22:47:29 +00:00
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
369 lines
13 KiB
C++
369 lines
13 KiB
C++
/*
|
|
Name: kmkey
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 19 Jun 2007
|
|
|
|
Modified Date: 28 Mar 2016
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 19 Jun 2007 - mcdurdin - I890 - Deadkeys not working correctly in TSF
|
|
19 Jun 2007 - mcdurdin - I822 - TSF Addin not working
|
|
07 Sep 2009 - mcdurdin - I2095 - TSF addin is not threadsafe
|
|
07 Nov 2012 - mcdurdin - I3549 - V9.0 - x64 version of kmtip addin
|
|
17 Nov 2012 - mcdurdin - I3567 - V9.0 - KeymanProcessOutput and KeymanGetContext return S_OK but are declared as BOOL
|
|
17 Nov 2012 - mcdurdin - I3568 - V9.0 - Support legacy output by testing for transitory context
|
|
20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
|
|
20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
|
|
28 Nov 2012 - mcdurdin - I3588 - V9.0 - Use preserved keys in TSF to handle key combinations in Keyman
|
|
28 Nov 2012 - mcdurdin - I3589 - V9.0 - Handle key state for KeyUp events in TSF
|
|
28 Nov 2012 - mcdurdin - I3590 - V9.0 - Initialise keystroke sink after we know which keyboard to activate
|
|
01 Dec 2012 - mcdurdin - I3608 - V9.0 - Shortcut test for VK_PACKET in kmtip
|
|
01 May 2014 - mcdurdin - I4201 - V9.0 - Shift + Arrows do not select text (only move caret) in Win 8 when Keyman keyboard is active
|
|
10 Jun 2014 - mcdurdin - I4262 - V9.0 - TSF deadkeys do not function correctly
|
|
16 Jun 2014 - mcdurdin - I4274 - V9.0 - kmtip does not work if already active before KM starts
|
|
13 Aug 2014 - mcdurdin - I4375 - V9.0 - Add registry flag deep integration to allow us to disable TIP context
|
|
13 Aug 2014 - mcdurdin - I4370 - Deadkeys are still not working in Winword TIP mode
|
|
14 Aug 2014 - mcdurdin - I4378 - V9.0 - Rapid typing in legacy mode breaks Keyman input
|
|
28 Mar 2016 - mcdurdin - I4933 - Compat issue with Firefox 42 and IE and Keyman 9 TSF
|
|
*/
|
|
|
|
#include "pch.h"
|
|
#include "kmtip.h"
|
|
#include "editsess.h"
|
|
#include "../../../../common/windows/cpp/include/legacy_kmx_file.h"
|
|
#include "../../../../common/windows/cpp/include/registry.h"
|
|
#include "..\keymanmc\keymanmc.h"
|
|
|
|
struct deadkeyinfo
|
|
{
|
|
int pos, val;
|
|
};
|
|
|
|
class CKeymanEditSession : public CEditSessionBase
|
|
{
|
|
public:
|
|
CKeymanEditSession(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL fUpdate, BOOL fPreserved, DWORD dwDeepIntegration) : CEditSessionBase(pContext) // I3588 // I4375
|
|
{
|
|
_wParam = wParam;
|
|
_lParam = lParam; // I3589
|
|
_fUpdate = fUpdate;
|
|
_fPreserved = fPreserved; // I3588
|
|
_dwDeepIntegration = dwDeepIntegration; // I4375
|
|
_hr = S_FALSE;
|
|
_ec = 0;
|
|
}
|
|
|
|
// ITfEditSession
|
|
STDMETHODIMP DoEditSession(TfEditCookie ec);
|
|
|
|
HRESULT WINAPI KeymanProcessOutput(int n, PWSTR buf, int nbuf); // I3567
|
|
/**
|
|
* Retrieves the current context and checks if text is selected.
|
|
* @param[in] n The size of the buffer.
|
|
* @param[out] buf The buffer to store the context.
|
|
* @param[out] isTextSelected A flag indicating whether text is selected.
|
|
* @return BOOL if successful.
|
|
*/
|
|
HRESULT WINAPI KeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected); // I3567
|
|
HRESULT GetResult() { return _hr; }
|
|
|
|
private:
|
|
BOOL _fUpdate;
|
|
BOOL _fPreserved; // I3588
|
|
HRESULT _hr;
|
|
WPARAM _wParam;
|
|
LPARAM _lParam; // I3589
|
|
DWORD _dwDeepIntegration; // I4375
|
|
TfEditCookie _ec;
|
|
/**
|
|
* Checks if any text is selected in the given context.
|
|
*
|
|
* @param[in] hrGetSelection The result of a call to GetSelection selection.
|
|
* @param[in] cFetched The number of selections fetched.
|
|
* @param[in] tfSelection The selection details.
|
|
* @return S_OK if successful, otherwise an HRESULT error code.
|
|
*/
|
|
BOOL CKeymanEditSession::KeymanIsTextSelected(const HRESULT hrGetSelection, const ULONG cFetched, const TF_SELECTION tfSelection);
|
|
};
|
|
|
|
#define KEYEVENT_EXTRAINFO_KEYMAN 0xF00F0000 // I4370
|
|
|
|
BOOL CKMTipTextService::_KeymanProcessKeystroke(ITfContext *pContext, WPARAM wParam, LPARAM lParam, BOOL fUpdate, BOOL fPreserved) // I3588
|
|
{
|
|
CKeymanEditSession *pEditSession;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Don't process Unicode characters injected or ProcessKey events which are generated by Windows
|
|
if (wParam == VK_PACKET || wParam == VK_PROCESSKEY) {
|
|
SendDebugMessageFormat(L"wParam=%x early return FALSE", wParam);
|
|
return FALSE; // I3608 // I4201
|
|
}
|
|
|
|
SendDebugEntry();
|
|
SendDebugMessageFormat(L"%x %x %s %s ex=%x", wParam, lParam, fUpdate ? L"update" : L"", fPreserved ? L"preserved" : L"", GetMessageExtraInfo()); // I4378
|
|
|
|
// Don't process keystrokes generated by Keyman (scan code = 0xFF = SCAN_FLAG_KEYMAN_KEY_EVENT)
|
|
// But we need to pass Caps Lock through to the keyman engine (but not the core),
|
|
// even if we generated it, so we can track Caps Lock state.
|
|
if ((lParam & 0xFF0000) == 0xFF0000 &&
|
|
wParam != VK_CAPITAL) {
|
|
return_SendDebugExit(FALSE);
|
|
}
|
|
|
|
if (!Keyman32Interface::TIPIsKeymanRunning()) {
|
|
_TryAndStartKeyman();
|
|
return_SendDebugExit(FALSE);
|
|
}
|
|
|
|
// we'll insert a char ourselves in place of this keystroke
|
|
if ((pEditSession = new CKeymanEditSession(pContext, wParam, lParam, fUpdate, fPreserved, _dwDeepIntegration)) == NULL) { // I3588 // I3589 // I4375
|
|
SendDebugMessageFormat(L"new CKeymanEditSession failed, out of memory?");
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else {
|
|
HRESULT hrSession;
|
|
if ((hr = pContext->RequestEditSession(_tfClientId, pEditSession,
|
|
fUpdate ? TF_ES_SYNC | TF_ES_READWRITE : TF_ES_SYNC | TF_ES_READ, &hrSession)) != S_OK) {
|
|
SendDebugMessageFormat(L"RequestEditSession failed with %x (session hr=%x)", hr, hrSession);
|
|
hr = E_FAIL;
|
|
}
|
|
else {
|
|
if (hrSession == S_OK) {
|
|
hr = pEditSession->GetResult();
|
|
if (hr != S_OK) {
|
|
SendDebugMessageFormat(L"GetResult failed with %x", hr);
|
|
}
|
|
// GetResult will return E_FAIL when we don't process the keystroke
|
|
// but this actually isn't a failure (it should return S_FALSE, but
|
|
// that can be work for another day)
|
|
}
|
|
else {
|
|
SendDebugMessageFormat(L"RequestEditSession failed with session failure %x", hrSession);
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
pEditSession->Release();
|
|
}
|
|
|
|
SendDebugExit();
|
|
return hr == S_OK;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// CKeymanEditSession
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
CKeymanEditSession *ExtEditSession = NULL;
|
|
|
|
extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanProcessOutput(int n, WCHAR *buf, int nbuf) // I3567
|
|
{
|
|
SendDebugEntry();
|
|
HRESULT res;
|
|
if (ExtEditSession) res = ExtEditSession->KeymanProcessOutput(n, buf, nbuf);
|
|
else res = E_FAIL; // I3567
|
|
SendDebugExit();
|
|
return res;
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanGetContext(int n, PWSTR buf) // I3567
|
|
{
|
|
SendDebugEntry();
|
|
HRESULT res;
|
|
BOOL isTextSelectedDummy; // not used
|
|
if (ExtEditSession) res = ExtEditSession->KeymanGetContext(n, buf, &isTextSelectedDummy);
|
|
else res = E_FAIL; // I3567
|
|
SendDebugExit();
|
|
return res;
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) HRESULT WINAPI ExtKeymanGetContextIsSelected(int n, PWSTR buf, BOOL* isTextSelected) // I3567
|
|
{
|
|
SendDebugEntry();
|
|
HRESULT res;
|
|
if (ExtEditSession) res = ExtEditSession->KeymanGetContext(n, buf, isTextSelected);
|
|
else res = E_FAIL; // I3567
|
|
SendDebugExit();
|
|
return res;
|
|
}
|
|
|
|
STDAPI CKeymanEditSession::DoEditSession(TfEditCookie ec)
|
|
{
|
|
SendDebugEntry();
|
|
|
|
_ec = ec;
|
|
_hr = S_OK;
|
|
|
|
|
|
ExtEditSession = this;
|
|
|
|
if (!Keyman32Interface::TIPProcessKeyEx(_wParam, _lParam, ExtKeymanProcessOutput, ExtKeymanGetContextIsSelected, _fUpdate, _fPreserved)) {
|
|
SendDebugMessage(L"TIPProcessKeyEx did not handle the keystroke");
|
|
_hr = E_FAIL; // TODO: use S_FALSE -> this tells _KeymanProcessKeystroke to pass keystroke on
|
|
}
|
|
|
|
ExtEditSession = NULL;
|
|
|
|
// Call keyman32.processtipkey; this may call "KeymanGetContext(n, buf, isTextSelected)";
|
|
// keyman32.processtipkey this will call "KeymanProcessOutput"
|
|
// which will delete n chrs from left of cursor and output a text string
|
|
// beeps, deadkeys, etc. will be managed from within keyman32.dll itself
|
|
|
|
// At this point, the text will already have been output
|
|
// callback will have the information necessary
|
|
// to know what is happening -- make sure that pContext is available globally
|
|
|
|
// we need a lock to do our work
|
|
// nb: this method is one of the few places where it is legal to use
|
|
// the TF_ES_SYNC flag
|
|
SendDebugExit();
|
|
return S_OK;
|
|
}
|
|
|
|
char *debugstr(PWSTR buf);
|
|
|
|
HRESULT WINAPI CKeymanEditSession::KeymanProcessOutput(int n, WCHAR *buf, int nbuf) // I3567
|
|
{
|
|
SendDebugEntry();
|
|
|
|
if (ShouldDebug()) {
|
|
char *p = debugstr(buf);
|
|
SendDebugMessageFormat(L"fUpdate = %d, deleting %d, inserting %d '%hs'", _fUpdate, n, nbuf, p); // I4262
|
|
delete[] p;
|
|
}
|
|
|
|
if (_fUpdate) {
|
|
/* Delete characters left of selection */
|
|
DeleteLeftOfSelection(_ec, _pContext, n);
|
|
|
|
/* Add text to screen */
|
|
|
|
InsertTextAtSelection(_ec, _pContext, buf, nbuf); // I4262
|
|
}
|
|
SendDebugExit();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT WINAPI CKeymanEditSession::KeymanGetContext(int n, PWSTR buf, BOOL* isTextSelected) // I3567
|
|
{
|
|
SendDebugEntry();
|
|
|
|
HRESULT hr, hrGetSelection;
|
|
ULONG cFetched;
|
|
TF_SELECTION tfSelection = {0};
|
|
TF_STATUS tfStatus;
|
|
|
|
*isTextSelected = FALSE; // set to false before any early returns
|
|
|
|
if (_dwDeepIntegration == DEEPINTEGRATION_DISABLE) { // I4375
|
|
SendDebugMessage(L"Exit: deep integration disabled by registry (or default)");
|
|
return_SendDebugExit(S_FALSE);
|
|
}
|
|
|
|
if (!SUCCEEDED(hr = _pContext->GetStatus(&tfStatus))) // I3568
|
|
{
|
|
SendDebugMessageFormat(L"Exit -- Failed GetStatus = %x", hr);
|
|
return_SendDebugExit(hr);
|
|
}
|
|
|
|
if (tfStatus.dwStaticFlags & TF_SS_TRANSITORY) // I3568
|
|
{
|
|
SendDebugMessage(L"Exit: Context does not support manipulation. Using legacy interaction");
|
|
return_SendDebugExit(S_FALSE);
|
|
}
|
|
|
|
if (!GetLeftOfSelection(_ec, _pContext, buf, n, &hrGetSelection, &cFetched, &tfSelection)) { // I4933
|
|
return_SendDebugExit(S_FALSE); // I4933
|
|
}
|
|
|
|
// now check for selected text
|
|
*isTextSelected = KeymanIsTextSelected(hrGetSelection, cFetched, tfSelection);
|
|
|
|
if(tfSelection.range) {
|
|
tfSelection.range->Release();
|
|
}
|
|
|
|
return_SendDebugExit(S_OK);
|
|
}
|
|
|
|
BOOL
|
|
CKeymanEditSession::KeymanIsTextSelected(const HRESULT hrGetSelection, const ULONG cFetched, const TF_SELECTION tfSelection) {
|
|
if (hrGetSelection == TF_E_NOSELECTION || cFetched == 0 || !SUCCEEDED(hrGetSelection) || !tfSelection.range) {
|
|
// No selection is present, or an error occurred trying to get the selection
|
|
return FALSE;
|
|
}
|
|
|
|
// check if the range is empty
|
|
HRESULT hr;
|
|
BOOL isEmpty = TRUE;
|
|
|
|
// Compare the start and end of the range
|
|
if (!SUCCEEDED(hr = tfSelection.range->IsEmpty(_ec, &isEmpty))) {
|
|
SendDebugMessageFormat(L"KeymanIsTextSelected: Exit: Testing range->isEmpty failed with %x", hr);
|
|
return FALSE;
|
|
}
|
|
|
|
return !isEmpty;
|
|
}
|
|
|
|
BOOL CKMTipTextService::TIPNotifyActivate(GUID *guidProfile) // I3581 // I4274
|
|
{
|
|
SendDebugEntry();
|
|
|
|
//_UninitKeystrokeSink(); // I3590
|
|
if (!_InitKeystrokeSink()) {
|
|
SendDebugMessage(L"_InitKeystrokeSink failed");
|
|
return_SendDebugExit(FALSE); // I3590
|
|
}
|
|
|
|
if (!Keyman32Interface::TIPActivateKeyboard(guidProfile)) {
|
|
SendDebugMessage(L"TIPActivateKeyboard failed");
|
|
return_SendDebugExit(FALSE);
|
|
}
|
|
|
|
if (!_InitPreservedKeys()) {
|
|
SendDebugMessage(L"_InitPreservedKeys failed");
|
|
return_SendDebugExit(FALSE);
|
|
}
|
|
return_SendDebugExit(TRUE);
|
|
}
|
|
|
|
BOOL CKMTipTextService::_InitKeyman() // I3590 // I4274
|
|
{
|
|
SendDebugEntry();
|
|
|
|
if (!Keyman32Interface::TIPActivateEx(TRUE)) {
|
|
return_SendDebugExit(FALSE);
|
|
}
|
|
|
|
return_SendDebugExit(TRUE);
|
|
}
|
|
|
|
void CKMTipTextService::_UninitKeyman() {
|
|
SendDebugEntry();
|
|
Keyman32Interface::TIPActivateEx(FALSE);
|
|
SendDebugExit();
|
|
}
|
|
|
|
void CKMTipTextService::_TryAndStartKeyman() {
|
|
if (!_tryAndStartKeyman)
|
|
return;
|
|
|
|
HANDLE hEventSource = RegisterEventSource(NULL, "Keyman");
|
|
if (!hEventSource) {
|
|
// This is unlikely to occur, but if it does, there's probably
|
|
// not much we can do about it; it's not like we can report an event...
|
|
return;
|
|
}
|
|
ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, SIMPLE_CATEGORY, MSG_START_KEYMAN_ON_DEMAND, NULL, 0, 0, NULL, NULL);
|
|
DeregisterEventSource(hEventSource);
|
|
}
|