You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2188 lines
68 KiB
2188 lines
68 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: kbdlyout.c
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* Keyboard Layout API
|
|
*
|
|
* History:
|
|
* 04-14-92 IanJa Created
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/*
|
|
* Workers (forward declarations)
|
|
*/
|
|
BOOL xxxInternalUnloadKeyboardLayout(PWINDOWSTATION, PKL, UINT);
|
|
VOID ReorderKeyboardLayouts(PWINDOWSTATION, PKL);
|
|
|
|
/****************************************************************************\
|
|
* HKLtoPKL
|
|
*
|
|
* pti - thread to look in
|
|
* hkl - HKL_NEXT or HKL_PREV
|
|
* Finds the the next/prev LOADED layout, NULL if none.
|
|
* (Starts from the pti's active layout, may return pklActive itself)
|
|
* - a real HKL (Keyboard Layout handle):
|
|
* Finds the kbd layout struct (loaded or not), NULL if no match found.
|
|
*
|
|
* History:
|
|
* 1997-02-05 IanJa added pti parameter
|
|
\****************************************************************************/
|
|
PKL HKLtoPKL(
|
|
PTHREADINFO pti,
|
|
HKL hkl)
|
|
{
|
|
PKL pklActive;
|
|
PKL pkl;
|
|
|
|
UserAssert(pti != NULL);
|
|
if ((pklActive = pti->spklActive) == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pkl = pklActive;
|
|
|
|
if (hkl == (HKL)HKL_PREV) {
|
|
do {
|
|
pkl = pkl->pklPrev;
|
|
if (!(pkl->dwKL_Flags & KL_UNLOADED)) {
|
|
return pkl;
|
|
}
|
|
} while (pkl != pklActive);
|
|
return NULL;
|
|
} else if (hkl == (HKL)HKL_NEXT) {
|
|
do {
|
|
pkl = pkl->pklNext;
|
|
if (!(pkl->dwKL_Flags & KL_UNLOADED)) {
|
|
return pkl;
|
|
}
|
|
} while (pkl != pklActive);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find the pkl for this hkl.
|
|
* If the kbd layout isn't specified (in the HIWORD), ignore it and look
|
|
* for a Locale match only. (Mohamed Hamid's fix for Word bug)
|
|
*/
|
|
if (HandleToUlong(hkl) & 0xffff0000) {
|
|
do {
|
|
if (pkl->hkl == hkl) {
|
|
return pkl;
|
|
}
|
|
pkl = pkl->pklNext;
|
|
} while (pkl != pklActive);
|
|
} else {
|
|
do {
|
|
if (LOWORD(HandleToUlong(pkl->hkl)) == LOWORD(HandleToUlong(hkl))) {
|
|
return pkl;
|
|
}
|
|
pkl = pkl->pklNext;
|
|
} while (pkl != pklActive);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* ReadLayoutFile
|
|
*
|
|
* Maps layout file into memory and initializes layout table.
|
|
*
|
|
* History:
|
|
* 01-10-95 JimA Created.
|
|
\***************************************************************************/
|
|
|
|
#define GET_HEADER_FIELD(x) \
|
|
((fWin64Header) ? (NtHeader64->x) : (NtHeader32->x))
|
|
|
|
/*
|
|
* Note that this only works for sections < 64K
|
|
* Implicitly assumes pBaseVirt, pBaseDst and dwDataSize.
|
|
*/
|
|
|
|
#if DBG
|
|
BOOL gfEnableChecking = TRUE;
|
|
#else
|
|
BOOL gfEnableChecking = FALSE;
|
|
#endif
|
|
|
|
#define EXIT_READ(p) \
|
|
RIPMSGF1(RIP_WARNING, #p " is @ invalid address %p", p); \
|
|
if (gfEnableChecking) { \
|
|
goto exitread; \
|
|
}
|
|
|
|
#define VALIDATE_PTR(p) \
|
|
if ((PBYTE)(p) < (PBYTE)pBaseDst || (PBYTE)(p) + sizeof *(p) > (PBYTE)pBaseDst + dwDataSize) { \
|
|
EXIT_READ(p); \
|
|
}
|
|
|
|
#define FIXUP_PTR(p) \
|
|
if (p) { \
|
|
p = (PVOID)((PBYTE)pBaseVirt + (WORD)(ULONG_PTR)(p)); \
|
|
VALIDATE_PTR(p); \
|
|
} \
|
|
TAGMSGF1(DBGTAG_KBD, #p " validation finished %p", p);
|
|
|
|
PKBDTABLES ReadLayoutFile(
|
|
PKBDFILE pkf,
|
|
HANDLE hFile,
|
|
UINT offTable,
|
|
UINT offNlsTable
|
|
)
|
|
{
|
|
HANDLE hmap = NULL;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
SIZE_T ulViewSize = 0;
|
|
NTSTATUS Status;
|
|
PIMAGE_DOS_HEADER DosHdr = NULL;
|
|
BOOLEAN fWin64Header;
|
|
PIMAGE_NT_HEADERS32 NtHeader32;
|
|
PIMAGE_NT_HEADERS64 NtHeader64;
|
|
PIMAGE_SECTION_HEADER SectionTableEntry;
|
|
ULONG NumberOfSubsections;
|
|
ULONG OffsetToSectionTable;
|
|
PBYTE pBaseDst = NULL, pBaseVirt = NULL;
|
|
PKBDTABLES pktNew = NULL;
|
|
DWORD dwDataSize;
|
|
PKBDNLSTABLES pknlstNew = NULL;
|
|
BOOL fSucceeded = FALSE;
|
|
|
|
TAGMSGF1(DBGTAG_KBD, "entering for '%ls'", pkf->awchDllName);
|
|
|
|
/*
|
|
* Mask off hiword.
|
|
*/
|
|
UserAssert((offTable & ~0xffff) == 0);
|
|
UserAssert((offNlsTable & ~0xffff) == 0);
|
|
|
|
/*
|
|
* Initialize KbdNlsTables with NULL.
|
|
*/
|
|
pkf->pKbdNlsTbl = NULL;
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
NULL,
|
|
OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* Map the layout file into memory
|
|
*/
|
|
Status = ZwCreateSection(&hmap, SECTION_MAP_READ, &ObjectAttributes,
|
|
NULL, PAGE_READONLY, SEC_COMMIT, hFile);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSGF3(RIP_WARNING, "failed to create a section for %ls, hFile=%p, stat=%08x", pkf->awchDllName, hFile, Status);
|
|
goto exitread;
|
|
}
|
|
|
|
Status = ZwMapViewOfSection(hmap, NtCurrentProcess(), &DosHdr, 0, 0, NULL,
|
|
&ulViewSize, ViewUnmap, 0, PAGE_READONLY);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSGF1(RIP_WARNING, "failed to map the view for %ls", pkf->awchDllName);
|
|
goto exitread;
|
|
}
|
|
|
|
if (ulViewSize < sizeof *DosHdr || ulViewSize > (128 * 1024)) {
|
|
RIPMSGF1(RIP_WARNING, "ViewSize is too small or large %08x", ulViewSize);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* We find the .data section in the file header and by
|
|
* subtracting the virtual address from offTable find
|
|
* the offset in the section of the layout table.
|
|
*/
|
|
UserAssert(sizeof *NtHeader64 >= sizeof *NtHeader32);
|
|
|
|
try {
|
|
NtHeader64 = (PIMAGE_NT_HEADERS64)((PBYTE)DosHdr + (ULONG)DosHdr->e_lfanew);
|
|
NtHeader32 = (PIMAGE_NT_HEADERS32)NtHeader64;
|
|
|
|
|
|
#if defined(_WIN64)
|
|
if ((PBYTE)NtHeader64 < (PBYTE)DosHdr || // signed Overflow
|
|
(PBYTE)NtHeader64 + sizeof *NtHeader64 >= (PBYTE)DosHdr + ulViewSize) {
|
|
RIPMSGF1(RIP_WARNING, "Header is out of Range %p", NtHeader64);
|
|
goto exitread;
|
|
}
|
|
|
|
fWin64Header = (NtHeader64->FileHeader.Machine == IMAGE_FILE_MACHINE_IA64) ||
|
|
(NtHeader64->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64);
|
|
#else
|
|
if ((PBYTE)NtHeader32 < (PBYTE)DosHdr || // signed Overflow
|
|
(PBYTE)NtHeader32 + sizeof *NtHeader32 >= (PBYTE)DosHdr + ulViewSize) {
|
|
RIPMSGF1(RIP_WARNING, "Header is out of Range %p", NtHeader32);
|
|
goto exitread;
|
|
}
|
|
|
|
fWin64Header = FALSE;
|
|
#endif
|
|
|
|
TAGMSGF2(DBGTAG_KBD, "DLL='%ls', Is64=%d", pkf->awchDllName, fWin64Header);
|
|
|
|
/*
|
|
* At this point the object table is read in (if it was not
|
|
* already read in) and may displace the image header.
|
|
*/
|
|
NumberOfSubsections = GET_HEADER_FIELD(FileHeader.NumberOfSections);
|
|
|
|
OffsetToSectionTable = sizeof(ULONG) + // Signature
|
|
sizeof(IMAGE_FILE_HEADER) + // FileHeader
|
|
GET_HEADER_FIELD(FileHeader.SizeOfOptionalHeader);
|
|
|
|
SectionTableEntry = (PIMAGE_SECTION_HEADER)((PBYTE)NtHeader32 +
|
|
OffsetToSectionTable);
|
|
|
|
|
|
while (NumberOfSubsections > 0) {
|
|
/*
|
|
* Validate the SectionTableEntry.
|
|
*/
|
|
if ((PBYTE)SectionTableEntry < (PBYTE)DosHdr ||
|
|
(PBYTE)SectionTableEntry + sizeof *SectionTableEntry >= (PBYTE)DosHdr + ulViewSize) {
|
|
RIPMSGF1(RIP_WARNING, "SectionTableEntry @ %p is not within the view section.",
|
|
SectionTableEntry);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* Is this the .data section that we are looking for?
|
|
*/
|
|
if (strcmp(SectionTableEntry->Name, ".data") == 0) {
|
|
break;
|
|
}
|
|
|
|
SectionTableEntry++;
|
|
NumberOfSubsections--;
|
|
}
|
|
|
|
if (NumberOfSubsections == 0) {
|
|
RIPMSGF1(RIP_WARNING, "number of sections is 0 for %ls.", pkf->awchDllName);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* We found the section, now compute starting offset and the table size.
|
|
*/
|
|
offTable -= SectionTableEntry->VirtualAddress;
|
|
dwDataSize = SectionTableEntry->Misc.VirtualSize;
|
|
|
|
/*
|
|
* Validate the offTable to see if it fits in the section.
|
|
*/
|
|
if (offTable >= dwDataSize) {
|
|
RIPMSGF3(RIP_WARNING, "illegal offTable=0x%x or offNlsTable=0x%x, dwDataSize=0x%x",
|
|
offTable, offNlsTable, dwDataSize);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* Validate the .data size not exceeding our assumption (<64KB).
|
|
*/
|
|
if (dwDataSize >= 0xffff) {
|
|
RIPMSGF1(RIP_WARNING, "unexpected size in .data: 0x%x", dwDataSize);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* Validate we are not over-shooting our view
|
|
*/
|
|
if ((PBYTE)DosHdr + SectionTableEntry->PointerToRawData + dwDataSize >=
|
|
(PBYTE)DosHdr + ulViewSize) {
|
|
RIPMSGF1(RIP_WARNING, "Layout Table @ %p is not within the view section.",
|
|
(PBYTE)DosHdr + SectionTableEntry->PointerToRawData);
|
|
goto exitread;
|
|
}
|
|
|
|
/*
|
|
* Allocate layout table and copy from file.
|
|
*/
|
|
TAGMSGF2(DBGTAG_KBD, "data size for '%S' = %x", pkf->awchDllName, dwDataSize);
|
|
pBaseDst = UserAllocPool(dwDataSize, TAG_KBDTABLE);
|
|
|
|
#if DBG
|
|
if (pBaseDst == NULL) {
|
|
RIPMSGF2(RIP_WARNING, "failed to allocate 0x%x bytes of memory for %ls", dwDataSize, pkf->awchDllName);
|
|
}
|
|
#endif
|
|
|
|
if (pBaseDst != NULL) {
|
|
VK_TO_WCHAR_TABLE *pVkToWcharTable;
|
|
VSC_LPWSTR *pKeyName;
|
|
|
|
pkf->hBase = (HANDLE)pBaseDst;
|
|
|
|
RtlMoveMemory(pBaseDst,
|
|
(PBYTE)DosHdr + SectionTableEntry->PointerToRawData,
|
|
dwDataSize);
|
|
|
|
if (ISTS()) {
|
|
pkf->Size = dwDataSize; // For shadow hotkey processing
|
|
}
|
|
|
|
/*
|
|
* Compute table address and fixup pointers in table.
|
|
*/
|
|
pktNew = (PKBDTABLES)(pBaseDst + offTable);
|
|
|
|
/*
|
|
* The address in the data section has the virtual address
|
|
* added in, so we need to adjust the fixup pointer to
|
|
* compensate.
|
|
*/
|
|
pBaseVirt = pBaseDst - SectionTableEntry->VirtualAddress;
|
|
|
|
FIXUP_PTR(pktNew->pCharModifiers);
|
|
FIXUP_PTR(pktNew->pCharModifiers->pVkToBit);
|
|
/*
|
|
* Validate pVkToBit table.
|
|
*/
|
|
{
|
|
PVK_TO_BIT pVkToBit;
|
|
|
|
for (pVkToBit = pktNew->pCharModifiers->pVkToBit; ; pVkToBit++) {
|
|
VALIDATE_PTR(pVkToBit);
|
|
if (pVkToBit->Vk == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pVkToWcharTable);
|
|
#if DBG
|
|
if (pktNew->pVkToWcharTable == NULL) {
|
|
RIPMSGF1(RIP_WARNING, "KL %ls does not have pVkToWcharTable???", pkf->awchDllName);
|
|
}
|
|
#endif
|
|
if (pktNew->pVkToWcharTable) {
|
|
/*
|
|
* Fix up and validate VkToWchar table.
|
|
*/
|
|
for (pVkToWcharTable = pktNew->pVkToWcharTable; ; pVkToWcharTable++) {
|
|
VALIDATE_PTR(pVkToWcharTable);
|
|
if (pVkToWcharTable->pVkToWchars == NULL) {
|
|
break;
|
|
}
|
|
FIXUP_PTR(pVkToWcharTable->pVkToWchars);
|
|
}
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pDeadKey);
|
|
/*
|
|
* Validate pDeadKey array.
|
|
*/
|
|
{
|
|
PDEADKEY pDeadKey = pktNew->pDeadKey;
|
|
while (pDeadKey) {
|
|
VALIDATE_PTR(pDeadKey);
|
|
if (pDeadKey->dwBoth == 0) {
|
|
break;
|
|
}
|
|
pDeadKey++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Version 1 layouts support ligatures.
|
|
*/
|
|
if (GET_KBD_VERSION(pktNew)) {
|
|
FIXUP_PTR(pktNew->pLigature);
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pKeyNames);
|
|
if (pktNew->pKeyNames == NULL) {
|
|
RIPMSGF1(RIP_WARNING, "KL %ls does not have pKeyNames???", pkf->awchDllName);
|
|
}
|
|
|
|
if (pktNew->pKeyNames) {
|
|
for (pKeyName = pktNew->pKeyNames; ; pKeyName++) {
|
|
VALIDATE_PTR(pKeyName);
|
|
if (pKeyName->vsc == 0) {
|
|
break;
|
|
}
|
|
FIXUP_PTR(pKeyName->pwsz);
|
|
}
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pKeyNamesExt);
|
|
if (pktNew->pKeyNamesExt) {
|
|
for (pKeyName = pktNew->pKeyNamesExt; ; pKeyName++) {
|
|
VALIDATE_PTR(pKeyName);
|
|
if (pKeyName->vsc == 0) {
|
|
break;
|
|
}
|
|
FIXUP_PTR(pKeyName->pwsz);
|
|
}
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pKeyNamesDead);
|
|
if (pktNew->pKeyNamesDead) {
|
|
LPWSTR *lpDeadKey;
|
|
for (lpDeadKey = pktNew->pKeyNamesDead; ; lpDeadKey++) {
|
|
LPCWSTR lpwstr;
|
|
|
|
VALIDATE_PTR(lpDeadKey);
|
|
if (*lpDeadKey == NULL) {
|
|
break;
|
|
}
|
|
FIXUP_PTR(*lpDeadKey);
|
|
UserAssert(*lpDeadKey);
|
|
for (lpwstr = *lpDeadKey; ; lpwstr++) {
|
|
VALIDATE_PTR(lpwstr);
|
|
if (*lpwstr == L'\0') {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Fix up and validate Virtual Scan Code to VK table.
|
|
*/
|
|
if (pktNew->pusVSCtoVK == NULL) {
|
|
RIPMSGF1(RIP_WARNING, "KL %ls does not have the basic VSC to VK table", pkf->awchDllName);
|
|
goto exitread;
|
|
}
|
|
FIXUP_PTR(pktNew->pusVSCtoVK);
|
|
VALIDATE_PTR(pktNew->pusVSCtoVK + pktNew->bMaxVSCtoVK);
|
|
|
|
FIXUP_PTR(pktNew->pVSCtoVK_E0);
|
|
if (pktNew->pVSCtoVK_E0) {
|
|
PVSC_VK pVscVk;
|
|
for (pVscVk = pktNew->pVSCtoVK_E0; pVscVk->Vk; pVscVk++) {
|
|
VALIDATE_PTR(pVscVk);
|
|
}
|
|
}
|
|
|
|
FIXUP_PTR(pktNew->pVSCtoVK_E1);
|
|
if (pktNew->pVSCtoVK_E1) {
|
|
PVSC_VK pVscVk;
|
|
for (pVscVk = pktNew->pVSCtoVK_E1; ; pVscVk++) {
|
|
VALIDATE_PTR(pVscVk);
|
|
if (pVscVk->Vk == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (offNlsTable) {
|
|
/*
|
|
* Compute table address and fixup pointers in table.
|
|
*/
|
|
offNlsTable -= SectionTableEntry->VirtualAddress;
|
|
pknlstNew = (PKBDNLSTABLES)(pBaseDst + offNlsTable);
|
|
|
|
VALIDATE_PTR(pknlstNew);
|
|
|
|
/*
|
|
* Fixup and validate the address.
|
|
*/
|
|
FIXUP_PTR(pknlstNew->pVkToF);
|
|
if (pknlstNew->pVkToF) {
|
|
VALIDATE_PTR(&pknlstNew->pVkToF[pknlstNew->NumOfVkToF - 1]);
|
|
}
|
|
|
|
|
|
FIXUP_PTR(pknlstNew->pusMouseVKey);
|
|
if (pknlstNew->pusMouseVKey) {
|
|
VALIDATE_PTR(&pknlstNew->pusMouseVKey[pknlstNew->NumOfMouseVKey - 1]);
|
|
}
|
|
|
|
/*
|
|
* Save the pointer.
|
|
*/
|
|
pkf->pKbdNlsTbl = pknlstNew;
|
|
|
|
#if DBG_FE
|
|
{
|
|
UINT NumOfVkToF = pknlstNew->NumOfVkToF;
|
|
|
|
DbgPrint("NumOfVkToF - %d\n",NumOfVkToF);
|
|
|
|
while(NumOfVkToF) {
|
|
DbgPrint("VK = %x\n",pknlstNew->pVkToF[NumOfVkToF-1].Vk);
|
|
NumOfVkToF--;
|
|
}
|
|
}
|
|
#endif // DBG_FE
|
|
}
|
|
}
|
|
|
|
} except(W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
RIPMSGF1(RIP_WARNING, "took exception reading from %ls", pkf->awchDllName);
|
|
goto exitread;
|
|
}
|
|
|
|
fSucceeded = TRUE;
|
|
exitread:
|
|
|
|
if (!fSucceeded && pBaseDst) {
|
|
UserFreePool(pBaseDst);
|
|
}
|
|
|
|
/*
|
|
* Unmap and release the mapped section.
|
|
*/
|
|
if (DosHdr) {
|
|
ZwUnmapViewOfSection(NtCurrentProcess(), DosHdr);
|
|
}
|
|
|
|
if (hmap != NULL) {
|
|
ZwClose(hmap);
|
|
}
|
|
|
|
TAGMSGF1(DBGTAG_KBD, "returning pkl = %p", pktNew);
|
|
|
|
if (!fSucceeded) {
|
|
return NULL;
|
|
}
|
|
|
|
return pktNew;
|
|
}
|
|
|
|
PKBDTABLES PrepareFallbackKeyboardFile(PKBDFILE pkf)
|
|
{
|
|
PBYTE pBaseDst;
|
|
|
|
pBaseDst = UserAllocPool(sizeof(KBDTABLES), TAG_KBDTABLE);
|
|
if (pBaseDst != NULL) {
|
|
RtlCopyMemory(pBaseDst, &KbdTablesFallback, sizeof KbdTablesFallback);
|
|
// Note: Unlike ReadLayoutFile(),
|
|
// we don't need to fix up pointers in struct KBDFILE.
|
|
}
|
|
pkf->hBase = (HANDLE)pBaseDst;
|
|
pkf->pKbdNlsTbl = NULL;
|
|
return (PKBDTABLES)pBaseDst;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LoadKeyboardLayoutFile
|
|
*
|
|
* History:
|
|
* 10-29-95 GregoryW Created.
|
|
\***************************************************************************/
|
|
|
|
PKBDFILE LoadKeyboardLayoutFile(
|
|
HANDLE hFile,
|
|
UINT offTable,
|
|
UINT offNlsTable,
|
|
LPCWSTR pwszKLID,
|
|
LPWSTR pwszDllName,
|
|
DWORD dwType,
|
|
DWORD dwSubType)
|
|
{
|
|
PKBDFILE pkf = gpkfList;
|
|
|
|
TAGMSG4(DBGTAG_KBD | RIP_THERESMORE, "LoadKeyboardLayoutFile: new KL=%S, dllName='%S', %d:%d",
|
|
pwszKLID, pwszDllName ? pwszDllName : L"",
|
|
dwType, dwSubType);
|
|
UNREFERENCED_PARAMETER(pwszKLID);
|
|
|
|
/*
|
|
* Search for the existing layout file.
|
|
*/
|
|
if (pkf) {
|
|
do {
|
|
TAGMSG3(DBGTAG_KBD | RIP_THERESMORE, "LoadKeyboardLayoutFile: looking at dll=%S, %d:%d",
|
|
pkf->awchDllName,
|
|
pkf->pKbdTbl->dwType, pkf->pKbdTbl->dwSubType);
|
|
if (pwszDllName && _wcsicmp(pkf->awchDllName, pwszDllName) == 0) {
|
|
/*
|
|
* The layout is already loaded.
|
|
*/
|
|
TAGMSG1(DBGTAG_KBD, "LoadKeyboardLayoutFile: duplicated KBDFILE found(#1). pwszDllName='%ls'\n", pwszDllName);
|
|
return pkf;
|
|
}
|
|
pkf = pkf->pkfNext;
|
|
} while (pkf);
|
|
}
|
|
TAGMSG1(DBGTAG_KBD, "LoadKeyboardLayoutFile: layout %S is not yet loaded.", pwszDllName);
|
|
|
|
/*
|
|
* Allocate a new Keyboard File structure.
|
|
*/
|
|
pkf = (PKBDFILE)HMAllocObject(NULL, NULL, TYPE_KBDFILE, sizeof(KBDFILE));
|
|
if (!pkf) {
|
|
RIPMSG0(RIP_WARNING, "Keyboard Layout File: out of memory");
|
|
return (PKBDFILE)NULL;
|
|
}
|
|
|
|
/*
|
|
* Load layout table.
|
|
*/
|
|
if (hFile != NULL) {
|
|
/*
|
|
* Load NLS layout table also...
|
|
*/
|
|
wcsncpycch(pkf->awchDllName, pwszDllName, ARRAY_SIZE(pkf->awchDllName));
|
|
pkf->awchDllName[ARRAY_SIZE(pkf->awchDllName) - 1] = 0;
|
|
pkf->pKbdTbl = ReadLayoutFile(pkf, hFile, offTable, offNlsTable);
|
|
if (dwType || dwSubType) {
|
|
pkf->pKbdTbl->dwType = dwType;
|
|
pkf->pKbdTbl->dwSubType = dwSubType;
|
|
}
|
|
} else {
|
|
/*
|
|
* We failed to open the keyboard layout file in client side
|
|
* because the dll was missing.
|
|
* If this ever happens, we used to fail creating
|
|
* a window station, but we should allow a user
|
|
* at least to boot the system.
|
|
*/
|
|
TAGMSG1(DBGTAG_KBD, "LoadKeyboardLayoutFile: hFile is NULL for %ls, preparing the fallback.", pwszDllName);
|
|
pkf->pKbdTbl = PrepareFallbackKeyboardFile(pkf);
|
|
// Note: pkf->pKbdNlsTbl has been NULL'ed in PrepareFallbackKeyboardFile()
|
|
}
|
|
|
|
if (pkf->pKbdTbl == NULL) {
|
|
RIPMSG0(RIP_WARNING, "LoadKeyboardLayoutFile: pkf->pKbdTbl is NULL.");
|
|
HMFreeObject(pkf);
|
|
return (PKBDFILE)NULL;
|
|
}
|
|
|
|
/*
|
|
* Put keyboard layout file at front of list.
|
|
*/
|
|
pkf->pkfNext = gpkfList;
|
|
gpkfList = pkf;
|
|
|
|
return pkf;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RemoveKeyboardLayoutFile
|
|
*
|
|
* History:
|
|
* 10-29-95 GregoryW Created.
|
|
\***************************************************************************/
|
|
VOID RemoveKeyboardLayoutFile(
|
|
PKBDFILE pkf)
|
|
{
|
|
PKBDFILE pkfPrev, pkfCur;
|
|
|
|
// FE: NT4 SP4 #107809
|
|
if (gpKbdTbl == pkf->pKbdTbl) {
|
|
gpKbdTbl = &KbdTablesFallback;
|
|
}
|
|
if (gpKbdNlsTbl == pkf->pKbdNlsTbl) {
|
|
gpKbdNlsTbl = NULL;
|
|
}
|
|
|
|
/*
|
|
* Good old linked list management 101
|
|
*/
|
|
if (pkf == gpkfList) {
|
|
/*
|
|
* Head of the list.
|
|
*/
|
|
gpkfList = pkf->pkfNext;
|
|
return;
|
|
}
|
|
pkfPrev = gpkfList;
|
|
pkfCur = gpkfList->pkfNext;
|
|
while (pkf != pkfCur) {
|
|
pkfPrev = pkfCur;
|
|
pkfCur = pkfCur->pkfNext;
|
|
}
|
|
/*
|
|
* Found it!
|
|
*/
|
|
pkfPrev->pkfNext = pkfCur->pkfNext;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* DestroyKF
|
|
*
|
|
* Called when a keyboard layout file is destroyed due to an unlock.
|
|
*
|
|
* History:
|
|
* 24-Feb-1997 adams Created.
|
|
\***************************************************************************/
|
|
|
|
void
|
|
DestroyKF(PKBDFILE pkf)
|
|
{
|
|
if (!HMMarkObjectDestroy(pkf))
|
|
return;
|
|
|
|
RemoveKeyboardLayoutFile(pkf);
|
|
UserFreePool(pkf->hBase);
|
|
HMFreeObject(pkf);
|
|
}
|
|
|
|
INT GetThreadsWithPKL(
|
|
PTHREADINFO **ppptiList,
|
|
PKL pkl)
|
|
{
|
|
PTHREADINFO ptiT, *pptiT, *pptiListAllocated;
|
|
INT cThreads, cThreadsAllocated;
|
|
PWINDOWSTATION pwinsta;
|
|
PDESKTOP pdesk;
|
|
PLIST_ENTRY pHead, pEntry;
|
|
|
|
if (ppptiList != NULL)
|
|
*ppptiList = NULL;
|
|
|
|
cThreads = 0;
|
|
|
|
/*
|
|
* allocate a first list for 128 entries
|
|
*/
|
|
cThreadsAllocated = 128;
|
|
pptiListAllocated = UserAllocPool(cThreadsAllocated * sizeof(PTHREADINFO),
|
|
TAG_SYSTEM);
|
|
|
|
if (pptiListAllocated == NULL) {
|
|
RIPMSG0(RIP_WARNING, "GetPKLinThreads: out of memory");
|
|
return 0;
|
|
}
|
|
|
|
// for all the winstations
|
|
for (pwinsta = grpWinStaList; pwinsta != NULL ; pwinsta = pwinsta->rpwinstaNext) {
|
|
|
|
// for all the desktops in that winstation
|
|
for (pdesk = pwinsta->rpdeskList; pdesk != NULL ; pdesk = pdesk->rpdeskNext) {
|
|
|
|
pHead = &pdesk->PtiList;
|
|
|
|
// for all the threads in that desktop
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
|
|
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
|
|
|
|
if (ptiT == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (pkl && (pkl != ptiT->spklActive)) { // #99321 cmp pkls, not hkls?
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* WindowsBug 349045
|
|
* Unload IMEs only for the normal apps.... leave them as is if they are
|
|
* loaded for services.
|
|
* Note, this is not really a clean fix, but some customers demand it.
|
|
*/
|
|
UserAssert(PsGetCurrentProcessId() == gpidLogon);
|
|
if (ptiT->ppi->Process != gpepCSRSS && ptiT->ppi->Process != PsGetCurrentProcess()) {
|
|
/*
|
|
* By the time this routine is called (solely by WinLogon), all the other
|
|
* applications should be gone or terminated. So skipping like above
|
|
* leaves IMEs loaded in the services.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (cThreads == cThreadsAllocated) {
|
|
|
|
cThreadsAllocated += 128;
|
|
|
|
pptiT = UserReAllocPool(pptiListAllocated,
|
|
cThreads * sizeof(PTHREADINFO),
|
|
cThreadsAllocated * sizeof(PTHREADINFO),
|
|
TAG_SYSTEM);
|
|
|
|
if (pptiT == NULL) {
|
|
RIPMSG0(RIP_ERROR, "GetPKLinThreads: Out of memory");
|
|
UserFreePool(pptiListAllocated);
|
|
return 0;
|
|
}
|
|
|
|
pptiListAllocated = pptiT;
|
|
|
|
}
|
|
|
|
pptiListAllocated[cThreads++] = ptiT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* add CSRSS threads
|
|
*/
|
|
for (ptiT = PpiFromProcess(gpepCSRSS)->ptiList; ptiT != NULL; ptiT = ptiT->ptiSibling) {
|
|
|
|
if (pkl && (pkl != ptiT->spklActive)) { // #99321 cmp pkls, not hkls?
|
|
continue;
|
|
}
|
|
|
|
if (cThreads == cThreadsAllocated) {
|
|
|
|
cThreadsAllocated += 128;
|
|
|
|
pptiT = UserReAllocPool(pptiListAllocated,
|
|
cThreads * sizeof(PTHREADINFO),
|
|
cThreadsAllocated * sizeof(PTHREADINFO),
|
|
TAG_SYSTEM);
|
|
|
|
if (pptiT == NULL) {
|
|
RIPMSG0(RIP_ERROR, "GetPKLinThreads: Out of memory");
|
|
UserFreePool(pptiListAllocated);
|
|
return 0;
|
|
}
|
|
|
|
pptiListAllocated = pptiT;
|
|
|
|
}
|
|
|
|
pptiListAllocated[cThreads++] = ptiT;
|
|
}
|
|
|
|
if (cThreads == 0) {
|
|
UserFreePool(pptiListAllocated);
|
|
} else if (ppptiList != NULL) {
|
|
*ppptiList = pptiListAllocated;
|
|
} else {
|
|
UserFreePool(pptiListAllocated);
|
|
}
|
|
|
|
return cThreads;
|
|
}
|
|
|
|
|
|
VOID xxxSetPKLinThreads(
|
|
PKL pklNew,
|
|
PKL pklToBeReplaced)
|
|
{
|
|
PTHREADINFO *pptiList;
|
|
INT cThreads, i;
|
|
|
|
UserAssert(pklNew != pklToBeReplaced);
|
|
|
|
CheckLock(pklNew);
|
|
CheckLock(pklToBeReplaced);
|
|
|
|
cThreads = GetThreadsWithPKL(&pptiList, pklToBeReplaced);
|
|
|
|
/*
|
|
* Will the foreground thread's keyboard layout change?
|
|
*/
|
|
if (pklNew && gptiForeground && gptiForeground->spklActive == pklToBeReplaced) {
|
|
ChangeForegroundKeyboardTable(pklToBeReplaced, pklNew);
|
|
}
|
|
|
|
if (pptiList != NULL) {
|
|
if (pklToBeReplaced == NULL) {
|
|
for (i = 0; i < cThreads; i++) {
|
|
Lock(&pptiList[i]->spklActive, pklNew);
|
|
}
|
|
} else {
|
|
/*
|
|
* This is a replace. First, deactivate the *replaced* IME by
|
|
* activating the pklNew. Second, unload the *replaced* IME.
|
|
*/
|
|
xxxImmActivateAndUnloadThreadsLayout(pptiList, cThreads, NULL,
|
|
pklNew, HandleToUlong(pklToBeReplaced->hkl));
|
|
}
|
|
UserFreePool(pptiList);
|
|
}
|
|
|
|
/*
|
|
* If this is a replace, link the new layout immediately after the
|
|
* layout being replaced. This maintains ordering of layouts when
|
|
* the *replaced* layout is unloaded. The input locale panel in the
|
|
* regional settings applet depends on this.
|
|
*/
|
|
if (pklToBeReplaced) {
|
|
if (pklToBeReplaced->pklNext == pklNew) {
|
|
/*
|
|
* Ordering already correct. Nothing to do.
|
|
*/
|
|
return;
|
|
}
|
|
/*
|
|
* Move new layout immediately after layout being replaced.
|
|
* 1. Remove new layout from current position.
|
|
* 2. Update links in new layout.
|
|
* 3. Link new layout into desired position.
|
|
*/
|
|
pklNew->pklPrev->pklNext = pklNew->pklNext;
|
|
pklNew->pklNext->pklPrev = pklNew->pklPrev;
|
|
|
|
pklNew->pklNext = pklToBeReplaced->pklNext;
|
|
pklNew->pklPrev = pklToBeReplaced;
|
|
|
|
pklToBeReplaced->pklNext->pklPrev = pklNew;
|
|
pklToBeReplaced->pklNext = pklNew;
|
|
}
|
|
}
|
|
|
|
VOID xxxFreeImeKeyboardLayouts(
|
|
PWINDOWSTATION pwinsta)
|
|
{
|
|
PTHREADINFO *pptiList;
|
|
INT cThreads;
|
|
|
|
if (pwinsta->dwWSF_Flags & WSF_NOIO)
|
|
return;
|
|
|
|
/*
|
|
* should make GetThreadsWithPKL aware of pwinsta?
|
|
*/
|
|
cThreads = GetThreadsWithPKL(&pptiList, NULL);
|
|
if (pptiList != NULL) {
|
|
xxxImmUnloadThreadsLayout(pptiList, cThreads, NULL, IFL_UNLOADIME);
|
|
UserFreePool(pptiList);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxLoadKeyboardLayoutEx
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
HKL xxxLoadKeyboardLayoutEx(
|
|
PWINDOWSTATION pwinsta,
|
|
HANDLE hFile,
|
|
HKL hklToBeReplaced,
|
|
UINT offTable,
|
|
PKBDTABLE_MULTI_INTERNAL pKbdTableMulti,
|
|
LPCWSTR pwszKLID,
|
|
UINT KbdInputLocale,
|
|
UINT Flags)
|
|
{
|
|
PKL pkl, pklFirst, pklToBeReplaced;
|
|
PKBDFILE pkf;
|
|
CHARSETINFO cs;
|
|
TL tlpkl;
|
|
PTHREADINFO ptiCurrent;
|
|
UNICODE_STRING strLcidKF;
|
|
UNICODE_STRING strKLID;
|
|
LCID lcidKF;
|
|
BOOL bCharSet;
|
|
PIMEINFOEX piiex;
|
|
|
|
|
|
TAGMSG1(DBGTAG_KBD, "xxxLoadKeyboardLayoutEx: new KL: pwszKLID=\"%ls\"", pwszKLID);
|
|
|
|
/*
|
|
* If the windowstation does not do I/O, don't load the
|
|
* layout. Also check KdbInputLocale for #307132
|
|
*/
|
|
if ((KbdInputLocale == 0) || (pwinsta->dwWSF_Flags & WSF_NOIO)) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If hklToBeReplaced is non-NULL make sure it's valid.
|
|
* NOTE: may want to verify they're not passing HKL_NEXT or HKL_PREV.
|
|
*/
|
|
ptiCurrent = PtiCurrent();
|
|
if (hklToBeReplaced && !(pklToBeReplaced = HKLtoPKL(ptiCurrent, hklToBeReplaced))) {
|
|
return NULL;
|
|
}
|
|
if (KbdInputLocale == HandleToUlong(hklToBeReplaced)) {
|
|
/*
|
|
* Replacing a layout/lang pair with itself. Nothing to do.
|
|
*/
|
|
return pklToBeReplaced->hkl;
|
|
}
|
|
|
|
if (Flags & KLF_RESET) {
|
|
/*
|
|
* Only WinLogon can use this flag
|
|
*/
|
|
if (PsGetThreadProcessId(ptiCurrent->pEThread) != gpidLogon) {
|
|
RIPERR0(ERROR_INVALID_FLAGS, RIP_WARNING,
|
|
"Invalid flag passed to LoadKeyboardLayout" );
|
|
return NULL;
|
|
}
|
|
xxxFreeImeKeyboardLayouts(pwinsta);
|
|
/*
|
|
* Make sure we don't lose track of the left-over layouts
|
|
* They have been unloaded, but are still in use by some threads).
|
|
* The FALSE will prevent xxxFreeKeyboardLayouts from unlocking the
|
|
* unloaded layouts.
|
|
*/
|
|
xxxFreeKeyboardLayouts(pwinsta, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Does this hkl already exist?
|
|
*/
|
|
pkl = pklFirst = pwinsta->spklList;
|
|
|
|
if (pkl) {
|
|
do {
|
|
if (pkl->hkl == (HKL)IntToPtr( KbdInputLocale )) {
|
|
/*
|
|
* The hkl already exists.
|
|
*/
|
|
|
|
/*
|
|
* If it is unloaded (but not yet destroyed because it is
|
|
* still is use), recover it.
|
|
*/
|
|
if (pkl->dwKL_Flags & KL_UNLOADED) {
|
|
// stop it from being destroyed if not is use.
|
|
PHE phe = HMPheFromObject(pkl);
|
|
// An unloaded layout must be marked for destroy.
|
|
UserAssert(phe->bFlags & HANDLEF_DESTROY);
|
|
phe->bFlags &= ~HANDLEF_DESTROY;
|
|
#if DBG
|
|
phe->bFlags &= ~HANDLEF_MARKED_OK;
|
|
#endif
|
|
pkl->dwKL_Flags &= ~KL_UNLOADED;
|
|
} else if (!(Flags & KLF_RESET)) {
|
|
/*
|
|
* If it was already loaded and we didn't change all layouts
|
|
* with KLF_RESET, there is nothing to tell the shell about
|
|
*/
|
|
Flags &= ~KLF_NOTELLSHELL;
|
|
}
|
|
|
|
goto AllPresentAndCorrectSir;
|
|
}
|
|
pkl = pkl->pklNext;
|
|
} while (pkl != pklFirst);
|
|
}
|
|
|
|
if (IS_IME_KBDLAYOUT((HKL)IntToPtr( KbdInputLocale ))
|
|
#ifdef CUAS_ENABLE
|
|
||
|
|
IS_CICERO_ENABLED_AND_NOT16BIT()
|
|
#endif // CUAS_ENABLE
|
|
) {
|
|
/*
|
|
* This is an IME keyboard layout, do a callback
|
|
* to read the extended IME information structure.
|
|
* Note: We can't fail the call so easily if
|
|
* KLF_RESET is specified.
|
|
*/
|
|
piiex = xxxImmLoadLayout((HKL)IntToPtr( KbdInputLocale ));
|
|
if (piiex == NULL && (Flags & (KLF_RESET | KLF_INITTIME)) == 0) {
|
|
/*
|
|
* Not Resetting, not creating a window station
|
|
*/
|
|
RIPMSG1(RIP_WARNING,
|
|
"Keyboard Layout: xxxImmLoadLayout(%lx) failed", KbdInputLocale);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
piiex = NULL;
|
|
}
|
|
|
|
/*
|
|
* Get the system font's font signature. These are 64-bit FS_xxx values,
|
|
* but we are only asking for an ANSI ones, so gSystemFS is just a DWORD.
|
|
* gSystemFS is consulted when posting WM_INPUTLANGCHANGEREQUEST (input.c)
|
|
*/
|
|
if (gSystemFS == 0) {
|
|
LCID lcid;
|
|
|
|
ZwQueryDefaultLocale(FALSE, &lcid);
|
|
if (xxxClientGetCharsetInfo(lcid, &cs)) {
|
|
gSystemFS = cs.fs.fsCsb[0];
|
|
gSystemCPCharSet = (BYTE)cs.ciCharset;
|
|
} else {
|
|
gSystemFS = 0xFFFF;
|
|
gSystemCPCharSet = ANSI_CHARSET;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Use the Keyboard Layout's LCID to calculate the charset, codepage etc,
|
|
* so that characters from that layout don't just becomes ?s if the input
|
|
* locale doesn't match. This allows "dumb" applications to display the
|
|
* text if the user chooses the right font.
|
|
* We can't just use the HIWORD of KbdInputLocale because if a variant
|
|
* keyboard layout was chosen, this will be something like F008 - have to
|
|
* look inside the KF to get the real LCID of the kbdfile: this will be
|
|
* something like L"00010419", and we want the last 4 digits.
|
|
*/
|
|
RtlInitUnicodeString(&strLcidKF, pwszKLID + 4);
|
|
RtlUnicodeStringToInteger(&strLcidKF, 16, (PULONG)&lcidKF);
|
|
bCharSet = xxxClientGetCharsetInfo(lcidKF, &cs);
|
|
|
|
/*
|
|
* Keyboard Layout Handle object does not exist. Load keyboard layout file,
|
|
* if not already loaded.
|
|
*/
|
|
if ((pkf = LoadKeyboardLayoutFile(hFile, LOWORD(offTable), HIWORD(offTable), pwszKLID, pKbdTableMulti->wszDllName, 0, 0)) == NULL) {
|
|
goto freePiiex;
|
|
}
|
|
/*
|
|
* Allocate a new Keyboard Layout structure (hkl)
|
|
*/
|
|
pkl = (PKL)HMAllocObject(NULL, NULL, TYPE_KBDLAYOUT, sizeof(KL));
|
|
if (!pkl) {
|
|
RIPMSG0(RIP_WARNING, "Keyboard Layout: out of memory");
|
|
UserFreePool(pkf->hBase);
|
|
HMMarkObjectDestroy(pkf);
|
|
HMUnlockObject(pkf);
|
|
freePiiex:
|
|
if (piiex) {
|
|
UserFreePool(piiex);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Lock(&pkl->spkfPrimary, pkf);
|
|
|
|
/*
|
|
* Load extra keyboard layouts.
|
|
*/
|
|
UserAssert(pKbdTableMulti);
|
|
if (pKbdTableMulti->multi.nTables) {
|
|
RIPMSG0(RIP_WARNING, "xxxLoadKeyboardLayoutEx: going to read multiple tables.");
|
|
/*
|
|
* Allocate the array for extra keyboard layouts.
|
|
*/
|
|
UserAssert(pKbdTableMulti->multi.nTables < KBDTABLE_MULTI_MAX); // check exists in the stub
|
|
pkl->pspkfExtra = UserAllocPoolZInit(pKbdTableMulti->multi.nTables * sizeof(PKBDFILE), TAG_KBDTABLE);
|
|
if (pkl->pspkfExtra) {
|
|
UINT i;
|
|
UINT n;
|
|
|
|
/*
|
|
* Load the extra keyboard layouts and lock them.
|
|
*/
|
|
for (i = 0, n = 0; i < pKbdTableMulti->multi.nTables; ++i) {
|
|
UserAssert(i < KBDTABLE_MULTI_MAX);
|
|
if (pKbdTableMulti->files[i].hFile) {
|
|
// make sure dll name is null terminated.
|
|
pKbdTableMulti->multi.aKbdTables[i].wszDllName[ARRAY_SIZE(pKbdTableMulti->multi.aKbdTables[i].wszDllName) - 1] = 0;
|
|
// load it.
|
|
pkf = LoadKeyboardLayoutFile(pKbdTableMulti->files[i].hFile,
|
|
pKbdTableMulti->files[i].wTable,
|
|
pKbdTableMulti->files[i].wNls,
|
|
pwszKLID,
|
|
pKbdTableMulti->multi.aKbdTables[i].wszDllName,
|
|
pKbdTableMulti->multi.aKbdTables[i].dwType,
|
|
pKbdTableMulti->multi.aKbdTables[i].dwSubType);
|
|
if (pkf == NULL) {
|
|
// If allocation fails, simply exit this loop and continue KL creation.
|
|
RIPMSG0(RIP_WARNING, "xxxLoadKeyboardLayoutEx: failed to load the extra keyboard layout file(s).");
|
|
break;
|
|
}
|
|
|
|
Lock(&pkl->pspkfExtra[n], pkf);
|
|
++n;
|
|
} else {
|
|
RIPMSG2(RIP_WARNING, "xxxLoadKeyboardLayoutEx: pKbdTableMulti(%#p)->files[%x].hFile is NULL",
|
|
pKbdTableMulti, i);
|
|
}
|
|
}
|
|
pkl->uNumTbl = n;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Link to itself in case we have to DestroyKL
|
|
*/
|
|
pkl->pklNext = pkl;
|
|
pkl->pklPrev = pkl;
|
|
|
|
/*
|
|
* Init KL
|
|
*/
|
|
pkl->dwKL_Flags = 0;
|
|
pkl->wchDiacritic = 0;
|
|
pkl->hkl = (HKL)IntToPtr( KbdInputLocale );
|
|
RtlInitUnicodeString(&strKLID, pwszKLID);
|
|
RtlUnicodeStringToInteger(&strKLID, 16, &pkl->dwKLID);
|
|
TAGMSG2(DBGTAG_KBD, "xxxLoadKeyboardLayoutEx: hkl %08p KLID:%08x", pkl->hkl, pkl->dwKLID);
|
|
|
|
Lock(&pkl->spkf, pkl->spkfPrimary);
|
|
pkl->dwLastKbdType = pkl->spkf->pKbdTbl->dwType;
|
|
pkl->dwLastKbdSubType = pkl->spkf->pKbdTbl->dwSubType;
|
|
|
|
pkl->spkf->pKbdTbl->fLocaleFlags |= KLL_LAYOUT_ATTR_FROM_KLF(Flags);
|
|
|
|
pkl->piiex = piiex;
|
|
|
|
if (bCharSet) {
|
|
pkl->CodePage = (WORD)cs.ciACP;
|
|
pkl->dwFontSigs = cs.fs.fsCsb[1]; // font signature mask (FS_xxx values)
|
|
pkl->iBaseCharset = cs.ciCharset; // charset value
|
|
} else {
|
|
pkl->CodePage = CP_ACP;
|
|
pkl->dwFontSigs = FS_LATIN1;
|
|
pkl->iBaseCharset = ANSI_CHARSET;
|
|
}
|
|
|
|
/*
|
|
* Insert KL in the double-linked circular list, at the end.
|
|
*/
|
|
pklFirst = pwinsta->spklList;
|
|
if (pklFirst == NULL) {
|
|
Lock(&pwinsta->spklList, pkl);
|
|
} else {
|
|
pkl->pklNext = pklFirst;
|
|
pkl->pklPrev = pklFirst->pklPrev;
|
|
pklFirst->pklPrev->pklNext = pkl;
|
|
pklFirst->pklPrev = pkl;
|
|
}
|
|
|
|
AllPresentAndCorrectSir:
|
|
|
|
// FE_IME
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pkl, &tlpkl);
|
|
|
|
if (hklToBeReplaced) {
|
|
TL tlPKLToBeReplaced;
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pklToBeReplaced, &tlPKLToBeReplaced);
|
|
xxxSetPKLinThreads(pkl, pklToBeReplaced);
|
|
xxxInternalUnloadKeyboardLayout(pwinsta, pklToBeReplaced, KLF_INITTIME);
|
|
ThreadUnlock(&tlPKLToBeReplaced);
|
|
}
|
|
|
|
if (Flags & KLF_REORDER) {
|
|
ReorderKeyboardLayouts(pwinsta, pkl);
|
|
}
|
|
|
|
if (!(Flags & KLF_NOTELLSHELL) && IsHooked(PtiCurrent(), WHF_SHELL)) {
|
|
xxxCallHook(HSHELL_LANGUAGE, (WPARAM)NULL, (LPARAM)0, WH_SHELL);
|
|
gLCIDSentToShell = 0;
|
|
}
|
|
|
|
if (Flags & KLF_ACTIVATE) {
|
|
TL tlPKL;
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pkl, &tlPKL);
|
|
xxxInternalActivateKeyboardLayout(pkl, Flags, NULL);
|
|
ThreadUnlock(&tlPKL);
|
|
}
|
|
|
|
if (Flags & KLF_RESET) {
|
|
RIPMSG2(RIP_VERBOSE, "Flag & KLF_RESET, locking gspklBaseLayout(%08x) with new kl(%08x)",
|
|
gspklBaseLayout ? gspklBaseLayout->hkl : 0,
|
|
pkl->hkl);
|
|
Lock(&gspklBaseLayout, pkl);
|
|
xxxSetPKLinThreads(pkl, NULL);
|
|
}
|
|
|
|
/*
|
|
* Use the hkl as the layout handle
|
|
* If the KL is freed somehow, return NULL for safety. -- ianja --
|
|
*/
|
|
pkl = ThreadUnlock(&tlpkl);
|
|
if (pkl == NULL) {
|
|
return NULL;
|
|
}
|
|
return pkl->hkl;
|
|
}
|
|
|
|
HKL xxxActivateKeyboardLayout(
|
|
PWINDOWSTATION pwinsta,
|
|
HKL hkl,
|
|
UINT Flags,
|
|
PWND pwnd)
|
|
{
|
|
PKL pkl;
|
|
TL tlPKL;
|
|
HKL hklRet;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
CheckLock(pwnd);
|
|
|
|
pkl = HKLtoPKL(ptiCurrent, hkl);
|
|
if (pkl == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (Flags & KLF_REORDER) {
|
|
ReorderKeyboardLayouts(pwinsta, pkl);
|
|
}
|
|
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pkl, &tlPKL);
|
|
hklRet = xxxInternalActivateKeyboardLayout(pkl, Flags, pwnd);
|
|
ThreadUnlock(&tlPKL);
|
|
return hklRet;
|
|
}
|
|
|
|
VOID ReorderKeyboardLayouts(
|
|
PWINDOWSTATION pwinsta,
|
|
PKL pkl)
|
|
{
|
|
PKL pklFirst = pwinsta->spklList;
|
|
|
|
if (pwinsta->dwWSF_Flags & WSF_NOIO) {
|
|
RIPMSG1(RIP_WARNING, "ReorderKeyboardLayouts called for non-interactive windowstation %#p",
|
|
pwinsta);
|
|
return;
|
|
}
|
|
|
|
UserAssert(pklFirst != NULL);
|
|
|
|
/*
|
|
* If the layout is already at the front of the list there's nothing to do.
|
|
*/
|
|
if (pkl == pklFirst) {
|
|
return;
|
|
}
|
|
/*
|
|
* Cut pkl from circular list:
|
|
*/
|
|
pkl->pklPrev->pklNext = pkl->pklNext;
|
|
pkl->pklNext->pklPrev = pkl->pklPrev;
|
|
|
|
/*
|
|
* Insert pkl at front of list
|
|
*/
|
|
pkl->pklNext = pklFirst;
|
|
pkl->pklPrev = pklFirst->pklPrev;
|
|
|
|
pklFirst->pklPrev->pklNext = pkl;
|
|
pklFirst->pklPrev = pkl;
|
|
|
|
Lock(&pwinsta->spklList, pkl);
|
|
}
|
|
|
|
extern VOID AdjustPushStateForKL(PTHREADINFO ptiCurrent, PBYTE pbDone, PKL pklTarget, PKL pklPrev, PKL pklNew);
|
|
extern void ResetPushState(PTHREADINFO pti, UINT uVk);
|
|
|
|
VOID ManageKeyboardModifiers(PKL pklPrev, PKL pkl)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
if (ptiCurrent->pq) {
|
|
if (pklPrev) {
|
|
BYTE baDone[256 / 8];
|
|
|
|
RtlZeroMemory(baDone, sizeof baDone);
|
|
|
|
/*
|
|
* Clear the toggle state if needed. First check the modifier keys
|
|
* of pklPrev. Next check the modifier keys of pklNew.
|
|
*/
|
|
TAGMSG2(DBGTAG_IMM, "Changing KL from %08lx to %08lx", pklPrev->hkl, pkl->hkl);
|
|
AdjustPushStateForKL(ptiCurrent, baDone, pklPrev, pklPrev, pkl);
|
|
AdjustPushStateForKL(ptiCurrent, baDone, pkl, pklPrev, pkl);
|
|
|
|
if (pklPrev->spkf && (pklPrev->spkf->pKbdTbl->fLocaleFlags & KLLF_ALTGR)) {
|
|
if (!TestRawKeyDown(VK_CONTROL)) {
|
|
/*
|
|
* If the previous keyboard has AltGr, and if the Ctrl key is not
|
|
* physically down, clear the left control.
|
|
* See xxxAltGr().
|
|
*/
|
|
TAGMSG0(DBGTAG_KBD, "Clearing VK_LCONTROL for AltGr\n");
|
|
xxxKeyEvent(VK_LCONTROL | KBDBREAK, 0x1D | SCANCODE_SIMULATED, 0, 0,
|
|
#ifdef GENERIC_INPUT
|
|
NULL,
|
|
NULL,
|
|
#endif
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* If the current keyboard is unknown, clear all the push state.
|
|
*/
|
|
int i;
|
|
for (i = 0; i < CBKEYSTATE; i++) {
|
|
ptiCurrent->pq->afKeyState[i] &= KEYSTATE_TOGGLE_BYTEMASK;
|
|
gafAsyncKeyState[i] &= KEYSTATE_TOGGLE_BYTEMASK;
|
|
gafRawKeyState[i] &= KEYSTATE_TOGGLE_BYTEMASK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetGlobalKeyboardTableInfo(PKL pklNew)
|
|
{
|
|
CheckCritIn();
|
|
UserAssert(pklNew);
|
|
|
|
/*
|
|
* Set gpKbdTbl so foreground thread processes AltGr appropriately
|
|
*/
|
|
gpKbdTbl = pklNew->spkf->pKbdTbl;
|
|
if (gpKL != pklNew) {
|
|
gpKL = pklNew;
|
|
}
|
|
if (ISTS()) {
|
|
ghKbdTblBase = pklNew->spkf->hBase;
|
|
guKbdTblSize = pklNew->spkf->Size;
|
|
}
|
|
|
|
TAGMSG1(DBGTAG_KBD, "SetGlobalKeyboardTableInfo:Changing KL NLS Table: new HKL=%#p\n", pklNew->hkl);
|
|
TAGMSG1(DBGTAG_KBD, "SetGlobalKeyboardTableInfo: new gpKbdNlsTbl=%#p\n", pklNew->spkf->pKbdNlsTbl);
|
|
|
|
gpKbdNlsTbl = pklNew->spkf->pKbdNlsTbl;
|
|
}
|
|
|
|
VOID ChangeForegroundKeyboardTable(PKL pklOld, PKL pklNew)
|
|
{
|
|
CheckCritIn();
|
|
UserAssert(pklNew != NULL);
|
|
|
|
if ((pklOld == pklNew || (pklOld != NULL && pklOld->spkf == pklNew->spkf)) && gpKL) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Some keys (pressed to switch layout) may still be down. When these come
|
|
* back up, they may have different VK values due to the new layout, so the
|
|
* original key will be left stuck down. (eg: an ISV layout from Attachmate
|
|
* and the CAN/CSA layout, both of which redefine the right-hand Ctrl key's
|
|
* VK so switching to that layout with right Ctrl+Shift will leave the Ctrl
|
|
* stuck down).
|
|
* The solution is to clear all the keydown bits whenever we switch layouts
|
|
* (leaving the toggle bits alone to preserve CapsLock, NumLock etc.). This
|
|
* also solves the AltGr problem, where the simulated Ctrl key doesn't come
|
|
* back up if we switch to a non-AltGr layout before releasing AltGr - IanJa
|
|
*
|
|
* Clear down bits only if necessary --- i.e. if the VK value differs between
|
|
* old and new keyboard layout. We have to take complex path for some of the
|
|
* keys, like Ctrl or Alt, may have left and right equivalents. - HiroYama
|
|
*/
|
|
ManageKeyboardModifiers(pklOld, pklNew);
|
|
|
|
// Manage the VK_KANA toggle key for Japanese KL.
|
|
// Since VK_HANGUL and VK_KANA share the same VK value and
|
|
// VK_KANA is a toggle key, when keyboard layouts are switched,
|
|
// VK_KANA toggle status should be restored.
|
|
|
|
//
|
|
// If:
|
|
// 1) Old and New keyboard layouts are both Japanese, do nothing.
|
|
// 2) Old and New keyboard layouts are not Japanese, do nothing.
|
|
// 3) Old keyboard is Japanese and new one is not, clear the KANA toggle.
|
|
// 4) New keyboard is Japanese and old one is not, restore the KANA toggle.
|
|
//
|
|
|
|
{
|
|
enum { KANA_NOOP, KANA_SET, KANA_CLEAR } opKanaToggle = KANA_NOOP;
|
|
|
|
if (JAPANESE_KBD_LAYOUT(pklNew->hkl)) {
|
|
if (pklOld == NULL) {
|
|
/*
|
|
* Let's honor the current async toggle state
|
|
* if the old KL is not specified.
|
|
*/
|
|
TAGMSG0(DBGTAG_KBD, "VK_KANA: previous KL is NULL, honoring the async toggle state.");
|
|
gfKanaToggle = (TestAsyncKeyStateToggle(VK_KANA) != 0);
|
|
opKanaToggle = gfKanaToggle ? KANA_SET : KANA_CLEAR;
|
|
} else if (!JAPANESE_KBD_LAYOUT(pklOld->hkl)) {
|
|
/*
|
|
* We're switching from non JPN KL to JPN.
|
|
* Need to restore the KANA toggle state.
|
|
*/
|
|
opKanaToggle = gfKanaToggle ? KANA_SET : KANA_CLEAR;
|
|
}
|
|
} else if (pklOld && JAPANESE_KBD_LAYOUT(pklOld->hkl)) {
|
|
/*
|
|
* Previous KL was Japanese, and we're switching to the other language.
|
|
* Let's clear the KANA toggle status and preserve it for the future
|
|
* switch back to the Japanese KL.
|
|
*/
|
|
gfKanaToggle = (TestAsyncKeyStateToggle(VK_KANA) != 0);
|
|
opKanaToggle = KANA_CLEAR;
|
|
}
|
|
|
|
if (opKanaToggle == KANA_SET) {
|
|
TAGMSG0(DBGTAG_KBD, "VK_KANA is being set.\n");
|
|
SetAsyncKeyStateToggle(VK_KANA);
|
|
SetRawKeyToggle(VK_KANA);
|
|
if (gptiForeground && gptiForeground->pq) {
|
|
SetKeyStateToggle(gptiForeground->pq, VK_KANA);
|
|
}
|
|
} else if (opKanaToggle == KANA_CLEAR) {
|
|
TAGMSG0(DBGTAG_KBD, "VK_KANA is beging cleared.\n");
|
|
ClearAsyncKeyStateToggle(VK_KANA);
|
|
ClearRawKeyToggle(VK_KANA);
|
|
if (gptiForeground && gptiForeground->pq) {
|
|
ClearKeyStateToggle(gptiForeground->pq, VK_KANA);
|
|
}
|
|
}
|
|
|
|
if (opKanaToggle != KANA_NOOP) {
|
|
UpdateKeyLights(TRUE);
|
|
}
|
|
}
|
|
|
|
UserAssert(pklNew);
|
|
SetGlobalKeyboardTableInfo(pklNew);
|
|
}
|
|
|
|
|
|
//
|
|
// Toggle and push state adjusters:
|
|
//
|
|
// ResetPushState, AdjustPushState, AdjustPushStateForKL
|
|
//
|
|
|
|
void ResetPushState(PTHREADINFO pti, UINT uVk)
|
|
{
|
|
TAGMSG1(DBGTAG_IMM, "ResetPushState: has to reset the push state of vk=%x\n", uVk);
|
|
if (uVk != 0) {
|
|
ClearAsyncKeyStateDown(uVk);
|
|
ClearAsyncKeyStateDown(uVk);
|
|
ClearRawKeyDown(uVk);
|
|
ClearRawKeyToggle(uVk);
|
|
ClearKeyStateDown(pti->pq, uVk);
|
|
ClearKeyStateToggle(pti->pq, uVk);
|
|
}
|
|
}
|
|
|
|
void AdjustPushState(PTHREADINFO ptiCurrent, BYTE bBaseVk, BYTE bVkL, BYTE bVkR, PKL pklPrev, PKL pklNew)
|
|
{
|
|
BOOLEAN fDownL = FALSE, fDownR = FALSE;
|
|
BOOLEAN fVanishL = FALSE, fVanishR = FALSE;
|
|
|
|
UINT uScanCode1, uScanCode2;
|
|
|
|
if (bVkL) {
|
|
fDownL = TestRawKeyDown(bVkL) || TestAsyncKeyStateDown(bVkL) || TestKeyStateDown(ptiCurrent->pq, bVkL);
|
|
if (fDownL) {
|
|
uScanCode1 = InternalMapVirtualKeyEx(bVkL, 0, pklPrev->spkf->pKbdTbl);
|
|
uScanCode2 = InternalMapVirtualKeyEx(bVkL, 0, pklNew->spkf->pKbdTbl);
|
|
fVanishL = (uScanCode1 && uScanCode2 == 0);
|
|
if (fVanishL) {
|
|
TAGMSG2(DBGTAG_KBD, "AdjustPushState: clearing %02x (%02x)", bVkL, uScanCode1);
|
|
xxxKeyEvent((WORD)(bVkL | KBDBREAK), (WORD)(uScanCode1 | SCANCODE_SIMULATED), 0, 0,
|
|
#ifdef GENERIC_INPUT
|
|
NULL,
|
|
NULL,
|
|
#endif
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bVkR) {
|
|
fDownR = TestRawKeyDown(bVkR) || TestAsyncKeyStateDown(bVkR) || TestKeyStateDown(ptiCurrent->pq, bVkR);
|
|
if (fDownR) {
|
|
uScanCode1 = InternalMapVirtualKeyEx(bVkR, 0, pklPrev->spkf->pKbdTbl);
|
|
uScanCode2 = InternalMapVirtualKeyEx(bVkR, 0, pklNew->spkf->pKbdTbl);
|
|
fVanishR = (uScanCode1 && uScanCode2 == 0);
|
|
if (fVanishR) {
|
|
TAGMSG2(DBGTAG_KBD, "AdjustPushState: clearing %02x (%02x)", bVkR, uScanCode1);
|
|
xxxKeyEvent((WORD)(bVkR | KBDBREAK), (WORD)(uScanCode1 | SCANCODE_SIMULATED), 0, 0,
|
|
#ifdef GENERIC_INPUT
|
|
NULL,
|
|
NULL,
|
|
#endif
|
|
FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
UNREFERENCED_PARAMETER(bBaseVk);
|
|
}
|
|
|
|
VOID AdjustPushStateForKL(PTHREADINFO ptiCurrent, PBYTE pbDone, PKL pklTarget, PKL pklPrev, PKL pklNew)
|
|
{
|
|
CONST VK_TO_BIT* pVkToBits;
|
|
|
|
UserAssert(pklPrev);
|
|
UserAssert(pklNew);
|
|
|
|
if (pklTarget->spkf == NULL || pklPrev->spkf == NULL) {
|
|
return;
|
|
}
|
|
|
|
pVkToBits = pklTarget->spkf->pKbdTbl->pCharModifiers->pVkToBit;
|
|
|
|
for (; pVkToBits->Vk; ++pVkToBits) {
|
|
BYTE bVkVar1 = 0, bVkVar2 = 0;
|
|
|
|
//
|
|
// Is it already processed ?
|
|
//
|
|
UserAssert(pVkToBits->Vk < 0x100);
|
|
if (pbDone[pVkToBits->Vk >> 3] & (1 << (pVkToBits->Vk & 7))) {
|
|
continue;
|
|
}
|
|
|
|
switch (pVkToBits->Vk) {
|
|
case VK_SHIFT:
|
|
bVkVar1 = VK_LSHIFT;
|
|
bVkVar2 = VK_RSHIFT;
|
|
break;
|
|
case VK_CONTROL:
|
|
bVkVar1 = VK_LCONTROL;
|
|
bVkVar2 = VK_RCONTROL;
|
|
break;
|
|
case VK_MENU:
|
|
bVkVar1 = VK_LMENU;
|
|
bVkVar2 = VK_RMENU;
|
|
break;
|
|
}
|
|
|
|
TAGMSG3(DBGTAG_IMM, "Adjusting VK=%x var1=%x var2=%x\n", pVkToBits->Vk, bVkVar1, bVkVar2);
|
|
|
|
AdjustPushState(ptiCurrent, pVkToBits->Vk, bVkVar1, bVkVar2, pklPrev, pklNew);
|
|
|
|
pbDone[pVkToBits->Vk >> 3] |= (1 << (pVkToBits->Vk & 7));
|
|
}
|
|
}
|
|
|
|
|
|
__inline BOOL IsWinSrvInputThread(
|
|
PTHREADINFO pti)
|
|
{
|
|
UserAssert(pti);
|
|
UserAssert(pti->TIF_flags & TIF_CSRSSTHREAD);
|
|
|
|
if (gptiForeground && gptiForeground->rpdesk &&
|
|
gptiForeground->rpdesk->dwConsoleThreadId == TIDq(pti)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*****************************************************************************\
|
|
* xxxInternalActivateKeyboardLayout
|
|
*
|
|
* pkl - pointer to keyboard layout to switch the current thread to
|
|
* Flags - KLF_RESET
|
|
* KLF_SETFORPROCESS
|
|
* KLLF_SHIFTLOCK (any of KLLF_GLOBAL_ATTRS)
|
|
* others are ignored
|
|
* pwnd - If the current thread has no focus or active window, send the
|
|
* WM_INPUTLANGCHANGE message to this window (unless it is NULL too)
|
|
*
|
|
* History:
|
|
* 1998-10-14 IanJa Added pwnd parameter
|
|
\*****************************************************************************/
|
|
HKL xxxInternalActivateKeyboardLayout(
|
|
PKL pkl,
|
|
UINT Flags,
|
|
PWND pwnd)
|
|
{
|
|
HKL hklPrev;
|
|
PKL pklPrev;
|
|
TL tlpklPrev;
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
|
|
CheckLock(pkl);
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Remember what is about to become the "previously" active hkl
|
|
* for the return value.
|
|
*/
|
|
if (ptiCurrent->spklActive != (PKL)NULL) {
|
|
pklPrev = ptiCurrent->spklActive;
|
|
hklPrev = ptiCurrent->spklActive->hkl;
|
|
} else {
|
|
pklPrev = NULL;
|
|
hklPrev = (HKL)0;
|
|
}
|
|
|
|
/*
|
|
* ShiftLock/CapsLock is a global feature applying to all layouts
|
|
* Only Winlogon and the Input Locales cpanel applet set KLF_RESET.
|
|
*/
|
|
if (Flags & KLF_RESET) {
|
|
gdwKeyboardAttributes = KLL_GLOBAL_ATTR_FROM_KLF(Flags);
|
|
}
|
|
|
|
/*
|
|
* Early out
|
|
*/
|
|
if (!(Flags & KLF_SETFORPROCESS) && (pkl == ptiCurrent->spklActive)) {
|
|
return hklPrev;
|
|
}
|
|
|
|
/*
|
|
* Clear out diacritics when switching kbd layouts #102838
|
|
*/
|
|
pkl->wchDiacritic = 0;
|
|
|
|
/*
|
|
* Update the active layout in the pti. KLF_SETFORPROCESS will always be set
|
|
* when the keyboard layout switch is initiated by the keyboard hotkey.
|
|
*/
|
|
|
|
/*
|
|
* Lock the previous keyboard layout for it's used later.
|
|
*/
|
|
ThreadLockWithPti(ptiCurrent, pklPrev, &tlpklPrev);
|
|
|
|
/*
|
|
* Is this is a console thread, apply this change to any process in it's
|
|
* window. This can really help character-mode apps! (#58025)
|
|
*/
|
|
if (ptiCurrent->TIF_flags & TIF_CSRSSTHREAD) {
|
|
Lock(&ptiCurrent->spklActive, pkl);
|
|
try {
|
|
ptiCurrent->pClientInfo->CodePage = pkl->CodePage;
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
goto UnlockAndGo;
|
|
}
|
|
} else if ((Flags & KLF_SETFORPROCESS) && !(ptiCurrent->TIF_flags & TIF_16BIT)) {
|
|
/*
|
|
* For 16 bit app., only the calling thread will have its active layout updated.
|
|
*/
|
|
PTHREADINFO ptiT;
|
|
|
|
if (IS_IME_ENABLED()) {
|
|
/*
|
|
* Only allow *NOT* CSRSS to make this call
|
|
*/
|
|
UserAssert(PsGetCurrentProcess() != gpepCSRSS);
|
|
// pti->pClientInfo is updated in xxxImmActivateThreadsLayout()
|
|
if (!xxxImmActivateThreadsLayout(ptiCurrent->ppi->ptiList, NULL, pkl)) {
|
|
RIPMSG1(RIP_WARNING, "no layout change necessary via xxxImmActivateThreadLayout() for process %lx", ptiCurrent->ppi);
|
|
goto UnlockAndGo;
|
|
}
|
|
} else {
|
|
BOOL fKLChanged = FALSE;
|
|
|
|
for (ptiT = ptiCurrent->ppi->ptiList; ptiT != NULL; ptiT = ptiT->ptiSibling) {
|
|
if (ptiT->spklActive != pkl && (ptiT->TIF_flags & TIF_INCLEANUP) == 0) {
|
|
Lock(&ptiT->spklActive, pkl);
|
|
UserAssert(ptiT->pClientInfo != NULL);
|
|
try {
|
|
ptiT->pClientInfo->CodePage = pkl->CodePage;
|
|
ptiT->pClientInfo->hKL = pkl->hkl;
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
goto UnlockAndGo;
|
|
}
|
|
|
|
fKLChanged = TRUE;
|
|
}
|
|
}
|
|
if (!fKLChanged) {
|
|
RIPMSG1(RIP_WARNING, "no layout change necessary for process %lx ?", ptiCurrent->ppi);
|
|
goto UnlockAndGo;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (IS_IME_ENABLED()) {
|
|
xxxImmActivateLayout(ptiCurrent, pkl);
|
|
} else {
|
|
Lock(&ptiCurrent->spklActive, pkl);
|
|
}
|
|
UserAssert(ptiCurrent->pClientInfo != NULL);
|
|
if ((ptiCurrent->TIF_flags & TIF_INCLEANUP) == 0) {
|
|
try {
|
|
ptiCurrent->pClientInfo->CodePage = pkl->CodePage;
|
|
ptiCurrent->pClientInfo->hKL = pkl->hkl;
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
goto UnlockAndGo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If (
|
|
* 1a. The process is not CSRSS. or
|
|
* b. it's CSRSS input thread.
|
|
* 2. and, the process is foreground.
|
|
* )
|
|
* update gpKbdTbl for the proper AltGr processing,
|
|
* and let the shell hook (primarily Internat.exe)
|
|
* know the foreground app's new keyboard layout.
|
|
*/
|
|
|
|
// if ((ptiCurrent->TIF_flags & TIF_CSRSSTHREAD) == 0 || IsWinSrvInputThread(ptiCurrent)) {
|
|
|
|
if (gptiForeground && (gptiForeground->ppi == ptiCurrent->ppi)) {
|
|
ChangeForegroundKeyboardTable(pklPrev, pkl);
|
|
|
|
/*
|
|
* Call the Shell hook with the new language.
|
|
* Only call the hook if we are the foreground process, to prevent
|
|
* background apps from changing the indicator. (All console apps
|
|
* are part of the same process, but I have never seen a cmd window
|
|
* app change the layout, let alone in the background)
|
|
*/
|
|
if (gLCIDSentToShell != pkl->hkl && (ptiCurrent != gptiRit)) {
|
|
if (IsHooked(ptiCurrent, WHF_SHELL)) {
|
|
gLCIDSentToShell = pkl->hkl;
|
|
xxxCallHook(HSHELL_LANGUAGE, (WPARAM)NULL, (LPARAM)pkl->hkl, WH_SHELL);
|
|
}
|
|
}
|
|
}
|
|
// }
|
|
|
|
/*
|
|
* Tell the app what happened
|
|
*/
|
|
if (ptiCurrent->pq) {
|
|
PWND pwndT;
|
|
TL tlpwndT;
|
|
|
|
/*
|
|
* If we have no Focus window, use the Active window.
|
|
* eg: Console full-screen has NULL focus window.
|
|
*/
|
|
pwndT = ptiCurrent->pq->spwndFocus;
|
|
if (pwndT == NULL) {
|
|
pwndT = ptiCurrent->pq->spwndActive;
|
|
if (pwndT == NULL) {
|
|
pwndT = pwnd;
|
|
}
|
|
}
|
|
|
|
if (pwndT != NULL) {
|
|
ThreadLockAlwaysWithPti( ptiCurrent, pwndT, &tlpwndT);
|
|
xxxSendMessage(pwndT, WM_INPUTLANGCHANGE, (WPARAM)pkl->iBaseCharset, (LPARAM)pkl->hkl);
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tell IME to send mode update notification
|
|
*/
|
|
if (ptiCurrent && ptiCurrent->spwndDefaultIme &&
|
|
(ptiCurrent->TIF_flags & TIF_CSRSSTHREAD) == 0) {
|
|
if (IS_IME_KBDLAYOUT(pkl->hkl)
|
|
#ifdef CUAS_ENABLE
|
|
||
|
|
IS_CICERO_ENABLED_AND_NOT16BIT()
|
|
#endif // CUAS_ENABLE
|
|
) {
|
|
BOOL fForProcess = (ptiCurrent->TIF_flags & KLF_SETFORPROCESS) && !(ptiCurrent->TIF_flags & TIF_16BIT);
|
|
TL tlpwndIme;
|
|
|
|
TAGMSG1(DBGTAG_IMM, "Sending IMS_SENDNOTIFICATION to pwnd=%#p", ptiCurrent->spwndDefaultIme);
|
|
|
|
ThreadLockAlwaysWithPti(ptiCurrent, ptiCurrent->spwndDefaultIme, &tlpwndIme);
|
|
xxxSendMessage(ptiCurrent->spwndDefaultIme, WM_IME_SYSTEM, IMS_SENDNOTIFICATION, fForProcess);
|
|
ThreadUnlock(&tlpwndIme);
|
|
}
|
|
}
|
|
|
|
UnlockAndGo:
|
|
ThreadUnlock(&tlpklPrev);
|
|
|
|
return hklPrev;
|
|
}
|
|
|
|
BOOL xxxUnloadKeyboardLayout(
|
|
PWINDOWSTATION pwinsta,
|
|
HKL hkl)
|
|
{
|
|
PKL pkl;
|
|
|
|
/*
|
|
* Validate HKL and check to make sure an app isn't attempting to unload a system
|
|
* preloaded layout.
|
|
*/
|
|
pkl = HKLtoPKL(PtiCurrent(), hkl);
|
|
if (pkl == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return xxxInternalUnloadKeyboardLayout(pwinsta, pkl, 0);
|
|
}
|
|
|
|
HKL _GetKeyboardLayout(
|
|
DWORD idThread)
|
|
{
|
|
PTHREADINFO ptiT;
|
|
PLIST_ENTRY pHead, pEntry;
|
|
|
|
CheckCritIn();
|
|
|
|
/*
|
|
* If idThread is NULL return hkl of the current thread
|
|
*/
|
|
if (idThread == 0) {
|
|
PKL pklActive = PtiCurrentShared()->spklActive;
|
|
|
|
if (pklActive == NULL) {
|
|
return (HKL)0;
|
|
}
|
|
return pklActive->hkl;
|
|
}
|
|
/*
|
|
* Look for idThread
|
|
*/
|
|
pHead = &PtiCurrent()->rpdesk->PtiList;
|
|
for (pEntry = pHead->Flink; pEntry != pHead; pEntry = pEntry->Flink) {
|
|
ptiT = CONTAINING_RECORD(pEntry, THREADINFO, PtiLink);
|
|
if (GETPTIID(ptiT) == (HANDLE)LongToHandle(idThread)) {
|
|
if (ptiT->spklActive == NULL) {
|
|
return (HKL)0;
|
|
}
|
|
return ptiT->spklActive->hkl;
|
|
}
|
|
}
|
|
/*
|
|
* idThread doesn't exist
|
|
*/
|
|
return (HKL)0;
|
|
}
|
|
|
|
UINT _GetKeyboardLayoutList(
|
|
PWINDOWSTATION pwinsta,
|
|
UINT nItems,
|
|
HKL *ccxlpBuff)
|
|
{
|
|
UINT nHKL = 0;
|
|
PKL pkl, pklFirst;
|
|
|
|
if (!pwinsta) {
|
|
return 0;
|
|
}
|
|
|
|
pkl = pwinsta->spklList;
|
|
|
|
/*
|
|
* Windowstations that do not take input could have no layouts
|
|
*/
|
|
if (pkl == NULL) {
|
|
// SetLastError() ????
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The client/server thunk sets nItems to 0 if ccxlpBuff == NULL
|
|
*/
|
|
UserAssert(ccxlpBuff || (nItems == 0));
|
|
|
|
pklFirst = pkl;
|
|
if (nItems) {
|
|
try {
|
|
do {
|
|
if (!(pkl->dwKL_Flags & KL_UNLOADED)) {
|
|
if (nItems-- == 0) {
|
|
break;
|
|
}
|
|
nHKL++;
|
|
*ccxlpBuff++ = pkl->hkl;
|
|
}
|
|
pkl = pkl->pklNext;
|
|
} while (pkl != pklFirst);
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_ERROR,
|
|
"_GetKeyBoardLayoutList: exception writing ccxlpBuff %lx", ccxlpBuff);
|
|
return 0;
|
|
}
|
|
} else do {
|
|
if (!(pkl->dwKL_Flags & KL_UNLOADED)) {
|
|
nHKL++;
|
|
}
|
|
pkl = pkl->pklNext;
|
|
} while (pkl != pklFirst);
|
|
|
|
return nHKL;
|
|
}
|
|
|
|
/*
|
|
* Layouts are locked by each thread using them and possibly by:
|
|
* - pwinsta->spklList (head of windowstation's list)
|
|
* - gspklBaseLayout (default layout for new threads)
|
|
* The layout is marked for destruction when gets unloaded, so that it will be
|
|
* unlinked and freed as soon as an Unlock causes the lock count to go to 0.
|
|
* If it is reloaded before that time, it is unmarked for destruction. This
|
|
* ensures that laoded layouts stay around even when they go out of use.
|
|
*/
|
|
BOOL xxxInternalUnloadKeyboardLayout(
|
|
PWINDOWSTATION pwinsta,
|
|
PKL pkl,
|
|
UINT Flags)
|
|
{
|
|
PTHREADINFO ptiCurrent = PtiCurrent();
|
|
TL tlpkl;
|
|
|
|
UserAssert(pkl);
|
|
|
|
/*
|
|
* Never unload the default layout, unless we are destroying the current
|
|
* windowstation or replacing one user's layouts with another's.
|
|
*/
|
|
if ((pkl == gspklBaseLayout) && !(Flags & KLF_INITTIME)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Keeps pkl good, but also allows destruction when unlocked later
|
|
*/
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pkl, &tlpkl);
|
|
|
|
/*
|
|
* Mark it for destruction so it gets removed when the lock count reaches 0
|
|
* Mark it KL_UNLOADED so that it appears to be gone from the toggle list
|
|
*/
|
|
HMMarkObjectDestroy(pkl);
|
|
pkl->dwKL_Flags |= KL_UNLOADED;
|
|
|
|
/*
|
|
* If unloading this thread's active layout, helpfully activate the next one
|
|
* (Don't bother if KLF_INITTIME - unloading all previous user's layouts)
|
|
*/
|
|
if (!(Flags & KLF_INITTIME)) {
|
|
UserAssert(ptiCurrent->spklActive != NULL);
|
|
if (ptiCurrent->spklActive == pkl) {
|
|
PKL pklNext;
|
|
pklNext = HKLtoPKL(ptiCurrent, (HKL)HKL_NEXT);
|
|
if (pklNext != NULL) {
|
|
TL tlPKL;
|
|
ThreadLockAlwaysWithPti(ptiCurrent, pklNext, &tlPKL);
|
|
xxxInternalActivateKeyboardLayout(pklNext, Flags, NULL);
|
|
ThreadUnlock(&tlPKL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this pkl == pwinsta->spklList, give it a chance to be destroyed by
|
|
* unlocking it from pwinsta->spklList.
|
|
*/
|
|
if (pwinsta->spklList == pkl) {
|
|
UserAssert(pkl != NULL);
|
|
if (pkl != pkl->pklNext) {
|
|
pkl = Lock(&pwinsta->spklList, pkl->pklNext);
|
|
UserAssert(pkl != NULL); // gspklBaseLayout and ThreadLocked pkl
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This finally destroys the unloaded layout if it is not in use anywhere
|
|
*/
|
|
ThreadUnlock(&tlpkl);
|
|
|
|
/*
|
|
* Update keyboard list.
|
|
*/
|
|
if (IsHooked(ptiCurrent, WHF_SHELL)) {
|
|
xxxCallHook(HSHELL_LANGUAGE, (WPARAM)NULL, (LPARAM)0, WH_SHELL);
|
|
gLCIDSentToShell = 0;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID xxxFreeKeyboardLayouts(
|
|
PWINDOWSTATION pwinsta, BOOL bUnlock)
|
|
{
|
|
PKL pkl;
|
|
|
|
/*
|
|
* Unload all of the windowstation's layouts.
|
|
* They may still be locked by some threads (eg: console), so this
|
|
* may not destroy them all, but it will mark them all KL_UNLOADED.
|
|
* Set KLF_INITTIME to ensure that the default layout (gspklBaseLayout)
|
|
* gets unloaded too.
|
|
* Note: it's much faster to unload non-active layouts, so start with
|
|
* the next loaded layout, leaving the active layout till last.
|
|
*/
|
|
while ((pkl = HKLtoPKL(PtiCurrent(), (HKL)HKL_NEXT)) != NULL) {
|
|
xxxInternalUnloadKeyboardLayout(pwinsta, pkl, KLF_INITTIME);
|
|
}
|
|
|
|
/*
|
|
* The WindowStation is being destroyed, or one user's layouts are being
|
|
* replaced by another user's, so it's OK to Unlock spklList.
|
|
* Any layout still in the double-linked circular KL list will still be
|
|
* pointed to by gspklBaseLayout: this is important, since we don't want
|
|
* to leak any KL or KBDFILE objects by losing pointers to them.
|
|
* There are no layouts when we first come here (during bootup).
|
|
*/
|
|
if (bUnlock) {
|
|
Unlock(&pwinsta->spklList);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* DestroyKL
|
|
*
|
|
* Destroys a keyboard layout. Note that this function does not
|
|
* follow normal destroy function semantics. See IanJa.
|
|
*
|
|
* History:
|
|
* 25-Feb-1997 adams Created.
|
|
\***************************************************************************/
|
|
|
|
VOID DestroyKL(
|
|
PKL pkl)
|
|
{
|
|
PKBDFILE pkf;
|
|
|
|
/*
|
|
* Cut it out of the pwinsta->spklList circular bidirectional list.
|
|
* We know pwinsta->spklList != pkl, since pkl is unlocked.
|
|
*/
|
|
pkl->pklPrev->pklNext = pkl->pklNext;
|
|
pkl->pklNext->pklPrev = pkl->pklPrev;
|
|
|
|
/*
|
|
* Unlock its pkf
|
|
*/
|
|
pkf = Unlock(&pkl->spkf);
|
|
if (pkf && (pkf = Unlock(&pkl->spkfPrimary))) {
|
|
DestroyKF(pkf);
|
|
}
|
|
|
|
if (pkl->pspkfExtra) {
|
|
UINT i;
|
|
|
|
for (i = 0; i < pkl->uNumTbl && pkl->pspkfExtra[i]; ++i) {
|
|
pkf = Unlock(&pkl->pspkfExtra[i]);
|
|
if (pkf) {
|
|
DestroyKF(pkf);
|
|
}
|
|
}
|
|
UserFreePool(pkl->pspkfExtra);
|
|
}
|
|
|
|
if (pkl->piiex != NULL) {
|
|
UserFreePool(pkl->piiex);
|
|
}
|
|
|
|
if (pkl == gpKL) {
|
|
/*
|
|
* Nuke gpKL.
|
|
*/
|
|
gpKL = NULL;
|
|
}
|
|
|
|
/*
|
|
* Free the pkl itself.
|
|
*/
|
|
HMFreeObject(pkl);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* CleanupKeyboardLayouts
|
|
*
|
|
* Frees the all keyboard layouts in this session.
|
|
*
|
|
\***************************************************************************/
|
|
VOID CleanupKeyboardLayouts()
|
|
{
|
|
/*
|
|
* Unlock the keyboard layouts
|
|
*/
|
|
if (gspklBaseLayout != NULL) {
|
|
|
|
PKL pkl;
|
|
PKL pklNext;
|
|
|
|
pkl = gspklBaseLayout->pklNext;
|
|
|
|
while (pkl->pklNext != pkl) {
|
|
pklNext = pkl->pklNext;
|
|
|
|
DestroyKL(pkl);
|
|
|
|
pkl = pklNext;
|
|
}
|
|
|
|
UserAssert(pkl == gspklBaseLayout);
|
|
|
|
if (!HMIsMarkDestroy(gspklBaseLayout)) {
|
|
HMMarkObjectDestroy(gspklBaseLayout);
|
|
}
|
|
|
|
HYDRA_HINT(HH_KBDLYOUTGLOBALCLEANUP);
|
|
|
|
if (Unlock(&gspklBaseLayout)) {
|
|
DestroyKL(pkl);
|
|
}
|
|
}
|
|
|
|
UserAssert(gpkfList == NULL);
|
|
}
|