mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-13 20:29:24 +00:00
687 lines
18 KiB
C++
687 lines
18 KiB
C++
// kmanalyze.cpp : This file contains the 'main' function. Program execution begins and ends there.
|
|
//
|
|
|
|
#include "pch.h"
|
|
#include "../../../common/windows/cpp/include/keymanversion.h"
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <codecvt>
|
|
#include <locale>
|
|
|
|
BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard);
|
|
|
|
void Err(const char *p);
|
|
int DoKeyboardAnalysis(LPKEYBOARD kbd, char *keyboardID, char *keyboardJSFilename, char *outputfilename);
|
|
void MapVirtualKeys(void);
|
|
|
|
struct TEST {
|
|
UINT key;
|
|
UINT shift;
|
|
std::wstring context;
|
|
};
|
|
|
|
typedef std::vector<TEST> TESTS;
|
|
|
|
enum GROUPREFTYPE { rule, match, nomatch };
|
|
|
|
struct GROUPTREE;
|
|
|
|
struct GROUPREF {
|
|
GROUPREFTYPE type;
|
|
GROUPTREE* target;
|
|
LPKEY rule;
|
|
};
|
|
|
|
struct GROUPTREE {
|
|
LPGROUP group;
|
|
std::vector<GROUPREF> refs;
|
|
};
|
|
|
|
TESTS *DoGroupAnalysis(LPKEYBOARD kbd, LPGROUP gp, std::vector<LPGROUP> & tree, TEST *test);
|
|
int run(int argc, char *argv[]);
|
|
|
|
wchar_t VKToChar[256][2];
|
|
UINT CharToVK[256];
|
|
UINT CharToShift[256];
|
|
|
|
#define KEYMAN_SENTRY_LOGGER_DEVELOPER_TOOLS_KMANALYZE KEYMAN_SENTRY_LOGGER_DEVELOPER_TOOLS ".kmanalyze"
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
return keyman_sentry_main(TRUE, KEYMAN_SENTRY_LOGGER_DEVELOPER_TOOLS_KMANALYZE, argc, argv, run);
|
|
}
|
|
|
|
int run(int argc, char *argv[])
|
|
{
|
|
LPKEYBOARD kbd;
|
|
char buf[_MAX_PATH], drive[_MAX_DRIVE], dir[_MAX_DIR], filename[_MAX_FNAME], ext[_MAX_EXT], jsfilename[_MAX_PATH];
|
|
|
|
if (argc < 2 || !strcmp(argv[1], "--help"))
|
|
{
|
|
printf(
|
|
"KMANALYZE: Extract rules from a Keyman .kmx keyboard to use for building automated tests\n"
|
|
"Version %s, %s\n"
|
|
"Usage: KMANALYZE <filename> [outputfilename]\n\n"
|
|
"Will create a keyboard.tests from keyboard.js; if outputfilename is not specified,\n"
|
|
"then will put the output file in the same folder as filename.",
|
|
KEYMAN_VersionWithTag, KEYMAN_Copyright
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
MapVirtualKeys();
|
|
|
|
if (!LoadKeyboard(argv[1], &kbd)) return 2;
|
|
|
|
_splitpath_s(argv[1], drive, dir, filename, ext);
|
|
if (argc >= 3) {
|
|
strcpy_s(buf, argv[2]);
|
|
}
|
|
else {
|
|
_makepath_s(buf, drive, dir, filename, ".tests");
|
|
}
|
|
_makepath_s(jsfilename, drive, dir, filename, ".js");
|
|
|
|
int n = DoKeyboardAnalysis(kbd, filename, jsfilename, buf);
|
|
|
|
delete kbd;
|
|
return n;
|
|
}
|
|
|
|
|
|
void Err(const char *p)
|
|
{
|
|
printf("Fatal Error: %s\n", p);
|
|
}
|
|
|
|
BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard)
|
|
{
|
|
DWORD sz, i, j;
|
|
LPBYTE buf;
|
|
HANDLE hFile;
|
|
PCOMP_KEYBOARD ckbp;
|
|
PCOMP_GROUP cgp;
|
|
PCOMP_STORE csp;
|
|
PCOMP_KEY ckp;
|
|
LPKEYBOARD kbp;
|
|
LPGROUP gp;
|
|
LPSTORE sp;
|
|
LPKEY kp;
|
|
|
|
if (!lpKeyboard)
|
|
{
|
|
Err("Internal error 001");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fileName)
|
|
{
|
|
Err("Bad Filename");
|
|
return FALSE;
|
|
}
|
|
|
|
hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
Err("Could not open file");
|
|
return FALSE;
|
|
}
|
|
|
|
sz = GetFileSize(hFile, NULL);
|
|
|
|
buf = new BYTE[sz];
|
|
|
|
if (!buf)
|
|
{
|
|
CloseHandle(hFile);
|
|
Err("Could not allocate memory");
|
|
return FALSE;
|
|
}
|
|
|
|
ReadFile(hFile, buf, sz, &sz, NULL);
|
|
|
|
CloseHandle(hFile);
|
|
|
|
kbp = (LPKEYBOARD)buf;
|
|
ckbp = (PCOMP_KEYBOARD)buf;
|
|
|
|
if (kbp->dwIdentifier != FILEID_COMPILED) { delete buf; Err("errNotFileID"); return FALSE; }
|
|
|
|
/* Check file version */
|
|
|
|
if (ckbp->dwFileVersion < VERSION_MIN ||
|
|
ckbp->dwFileVersion > VERSION_MAX)
|
|
{
|
|
kbp->dpStoreArray = (LPSTORE)(buf + ckbp->dpStoreArray);
|
|
for (sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++) {
|
|
if (sp->dwSystemID == TSS_COMPILEDVERSION)
|
|
{
|
|
char buf2[256];
|
|
wsprintf(buf2, "Wrong File Version: file version is %ls", ((PBYTE)kbp) + (INT_PTR)sp->dpString);
|
|
delete buf;
|
|
Err(buf2);
|
|
return FALSE;
|
|
}
|
|
}
|
|
delete buf;
|
|
Err("Unknown File Version: try using the latest version of KMDECOMP");
|
|
return FALSE;
|
|
}
|
|
|
|
kbp->dpStoreArray = (LPSTORE)(buf + ckbp->dpStoreArray);
|
|
kbp->dpGroupArray = (LPGROUP)(buf + ckbp->dpGroupArray);
|
|
|
|
//kbp->dpName = (PWSTR) (buf + ckbp->dpName);
|
|
//kbp->dpCopyright = (PWSTR) (buf + ckbp->dpCopyright);
|
|
//kbp->dpMessage = (PWSTR) (buf + ckbp->dpMessage);
|
|
//kbp->dpLanguageName = (PWSTR) (buf + ckbp->dpLanguageName);
|
|
|
|
for (sp = kbp->dpStoreArray, csp = (PCOMP_STORE)sp, i = 0; i < kbp->cxStoreArray; i++, sp++, csp++)
|
|
{
|
|
if (csp->dpName > 0) sp->dpName = (PWSTR)(buf + csp->dpName); else sp->dpName = NULL;
|
|
sp->dpString = (PWSTR)(buf + csp->dpString);
|
|
}
|
|
|
|
for (gp = kbp->dpGroupArray, cgp = (PCOMP_GROUP)gp, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++)
|
|
{
|
|
if (cgp->dpName > 0) gp->dpName = (PWSTR)(buf + cgp->dpName); else gp->dpName = NULL;
|
|
gp->dpKeyArray = (LPKEY)(buf + cgp->dpKeyArray);
|
|
if (cgp->dpMatch != NULL) gp->dpMatch = (PWSTR)(buf + cgp->dpMatch);
|
|
if (cgp->dpNoMatch != NULL) gp->dpNoMatch = (PWSTR)(buf + cgp->dpNoMatch);
|
|
|
|
for (kp = gp->dpKeyArray, ckp = (PCOMP_KEY)kp, j = 0; j < gp->cxKeyArray; j++, kp++, ckp++)
|
|
{
|
|
kp->dpOutput = (PWSTR)(buf + ckp->dpOutput);
|
|
kp->dpContext = (PWSTR)(buf + ckp->dpContext);
|
|
}
|
|
}
|
|
|
|
*lpKeyboard = kbp;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//void DoContextAnalysis(LPKEYBOARD kbd, GROUPTREE *gpref, std::vector<LPGROUP> tree, LPKEY kp, PWCHAR pc, GROUPREFTYPE type) {
|
|
//}
|
|
|
|
TESTS *DoGroupAnalysis(LPKEYBOARD kbd, LPGROUP gp, std::vector<LPGROUP> & tree, TEST *base_test) {
|
|
|
|
//
|
|
// Prevent recursion
|
|
//
|
|
|
|
std::vector<LPGROUP>::iterator it = std::find(tree.begin(), tree.end(), gp);
|
|
if(it != tree.end()) {
|
|
puts("Recursive groups are not yet supported");
|
|
return NULL;
|
|
}
|
|
|
|
tree.push_back(gp);
|
|
//printf("%*.*sEntering group %d [depth %d]\n", tree.size(), tree.size(), " ", gpref->group - kbd->dpGroupArray + 1, tree.size());
|
|
|
|
LPKEY kp = gp->dpKeyArray;
|
|
|
|
// Set of tests for this group.
|
|
TESTS *tests = new TESTS;
|
|
|
|
for (DWORD i = 0; i < gp->cxKeyArray; i++, kp++) {
|
|
// Set of expanding tests for this rule
|
|
TESTS *new_tests = NULL;
|
|
TEST t0;
|
|
|
|
if (gp->fUsingKeys && base_test && base_test->key != 0 && (base_test->key != kp->Key || base_test->shift != kp->ShiftFlags)) {
|
|
// We are in a subgroup that uses keys, and this rule's key doesn't match the parent group's test's key.
|
|
continue;
|
|
}
|
|
|
|
std::wstring context;
|
|
for (PWCHAR pc = kp->dpContext; pc && *pc; pc = incxstr(pc)) {
|
|
if (*pc == UC_SENTINEL) {
|
|
switch (*(pc + 1)) {
|
|
case CODE_ANY:
|
|
{
|
|
PWCHAR ps = kbd->dpStoreArray[*(pc + 2) - 1].dpString;
|
|
// TODO: Support any character in store
|
|
if (ps[0] == UC_SENTINEL) break;
|
|
context += ps[0];
|
|
if (Uni_IsSurrogate1(ps[0])) context += ps[1];
|
|
}
|
|
break;
|
|
case CODE_NUL:
|
|
// TODO: SUpport nul at start of context
|
|
break;
|
|
case CODE_DEADKEY:
|
|
// TODO: Support deadkeys for testing
|
|
break;
|
|
case CODE_CONTEXTEX:
|
|
break;
|
|
case CODE_NOTANY:
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
context += *pc;
|
|
if (Uni_IsSurrogate1(*pc)) context += *(pc + 1);
|
|
}
|
|
}
|
|
|
|
if (base_test) {
|
|
std::wstring c1 = context.length() > base_test->context.length() ? context : base_test->context;
|
|
//std::wstring c2 = context.length() > base_test->context.length() ? base_test->context : context;
|
|
|
|
//if(c1.compare(c1.length() - c2.length(), c2.length(), c2) != 0) {
|
|
// TODO: We are not comparing like with like ... so continue; but this naive test does not take into account
|
|
// stores, which we need to, in order to make this work well.
|
|
//}
|
|
|
|
// TODO: For now we are assuming that the contexts are congruent, so we take the longer one for our test.
|
|
// TODO: In reality, we could probably do both the longer and shorter one as separate tests...
|
|
//TODO: merge_context(context);
|
|
context = c1;
|
|
}
|
|
|
|
t0.context = context;
|
|
if (!gp->fUsingKeys) {
|
|
if (base_test) {
|
|
t0.key = base_test->key;
|
|
t0.shift = base_test->shift;
|
|
}
|
|
}
|
|
else {
|
|
t0.key = kp->Key;
|
|
t0.shift = kp->ShiftFlags;
|
|
}
|
|
|
|
BOOL found = FALSE;
|
|
for (PWCHAR pc = kp->dpOutput; pc && *pc; pc = incxstr(pc)) {
|
|
if (*pc == UC_SENTINEL && *(pc + 1) == CODE_USE) {
|
|
//printf("%*.*s: Matched rule %d of group %d\n", tree.size(), tree.size(), " ", kp ? kp - gpref->group->dpKeyArray + 1 : -1, gpref->group - kbd->dpGroupArray + 1);
|
|
LPGROUP gpTarget = &kbd->dpGroupArray[*(pc + 2) - 1];
|
|
new_tests = DoGroupAnalysis(kbd, gpTarget, tree, &t0);
|
|
if (!new_tests) return NULL;
|
|
// TODO: Then multiplying new_tests for each subsequent group I guess!?
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
// TODO: Support use() in rule and use() in match
|
|
// check for use in match or extra
|
|
PWCHAR pc = gp->dpMatch;
|
|
if (pc && *pc == UC_SENTINEL && *(pc + 1) == CODE_USE) {
|
|
new_tests = DoGroupAnalysis(kbd, &kbd->dpGroupArray[*(pc + 2) - 1], tree, &t0);
|
|
if (!new_tests) return NULL;
|
|
// TODO: JUST A SINGLE GROUP FOR NOW
|
|
}
|
|
}
|
|
|
|
tests->push_back(t0);
|
|
|
|
if (new_tests) {
|
|
for (size_t i = 0; i < new_tests->size(); i++)
|
|
tests->push_back(new_tests->at(i));
|
|
|
|
delete new_tests;
|
|
}
|
|
//t0.context = "";
|
|
//PWC
|
|
//t0.context = kp->dpContext;
|
|
//for (int i = 0; i < new_tests.size(); i++) {
|
|
//DoContextAnalysis(kbd, gp, tree, kp, kp->dpOutput, rule, gpref->group->dpMatch);
|
|
}
|
|
|
|
PWCHAR pc = gp->dpNoMatch;
|
|
if (pc && *pc == UC_SENTINEL && *(pc + 1) == CODE_USE) {
|
|
TESTS *new_tests = DoGroupAnalysis(kbd, &kbd->dpGroupArray[*(pc+2)-1], tree, base_test);
|
|
|
|
if (!new_tests) return NULL;
|
|
|
|
for (size_t i = 0; i < new_tests->size(); i++)
|
|
tests->push_back(new_tests->at(i));
|
|
|
|
delete new_tests;
|
|
|
|
// TODO: JUST A SINGLE GROUP FOR NOW
|
|
}
|
|
|
|
//printf("%*.*sExiting group %d [depth %d]\n", tree.size(), tree.size(), " ", gpref->group - kbd->dpGroupArray + 1, tree.size());
|
|
tree.pop_back();
|
|
|
|
return tests;
|
|
}
|
|
|
|
void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth);
|
|
|
|
std::string unicode_escape(std::wstring s) {
|
|
std::string r = "\"";
|
|
for (auto ch = s.begin(); ch != s.end(); ch++) {
|
|
char buf[16];
|
|
sprintf_s(buf, "\\u%04.4x", *ch);
|
|
r += buf;
|
|
}
|
|
r += "\"";
|
|
return r;
|
|
}
|
|
|
|
#include "../../../common/windows/cpp/include/vkeys.h"
|
|
|
|
std::wstring GetKeyName(UINT key) {
|
|
WCHAR buf[64];
|
|
|
|
// if (key < 256) {
|
|
// wsprintfW(buf, L"keyCodes.%s /* 0x%x */", VKeyNames[key], key);
|
|
// }
|
|
// else {
|
|
wsprintfW(buf, L"0x%x", key);
|
|
// }
|
|
|
|
std::wstring str(buf);
|
|
return str;
|
|
}
|
|
|
|
struct ModifierNames {
|
|
PCWSTR name;
|
|
UINT modifier;
|
|
};
|
|
|
|
// see kmwosk.ts
|
|
ModifierNames modifierNames[] = {
|
|
{ L"LCTRL", 0x0001 },
|
|
{ L"RCTRL", 0x0002 },
|
|
{ L"LALT", 0x0004 },
|
|
{ L"RALT", 0x0008 },
|
|
{ L"SHIFT", 0x0010 },
|
|
{ L"CTRL", 0x0020 },
|
|
{ L"ALT", 0x0040 },
|
|
{ L"CAPS", 0x0100 },
|
|
{ L"NO_CAPS", 0x0200 },
|
|
{ L"NUM_LOCK", 0x0400 },
|
|
{ L"NO_NUM_LOCK", 0x0800 },
|
|
{ L"SCROLL_LOCK", 0x1000 },
|
|
{ L"NO_SCROLL_LOCK", 0x2000 },
|
|
{ L"VIRTUAL_KEY", 0x4000 },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
std::wstring GetModifierName(UINT modifier) {
|
|
modifier &= ~ISVIRTUALKEY; // we don't need to dump this in every rule
|
|
std::wstring str;
|
|
WCHAR buf[64];
|
|
UINT originalModifier = modifier;
|
|
|
|
for (int i = 0; modifierNames[i].name; i++) {
|
|
if (modifier & modifierNames[i].modifier) {
|
|
wsprintfW(buf, L"modCodes.%s", modifierNames[i].name);
|
|
if (str.size()) str += L" | ";
|
|
str += buf;
|
|
modifier &= ~modifierNames[i].modifier;
|
|
}
|
|
}
|
|
|
|
if (modifier) {
|
|
wsprintfW(buf, L"%x", modifier);
|
|
if (str.size()) str += L" | ";
|
|
str += buf;
|
|
}
|
|
|
|
wsprintfW(buf, L" /* 0x%x */", originalModifier);
|
|
if (!str.size()) str += L"0";
|
|
str += buf;
|
|
return str;
|
|
}
|
|
|
|
void PrintTests(TESTS *tests, char *keyboardID, wchar_t *keyboardName, char *keyboardJSFilename, char *outputfilename) {
|
|
FILE *fp;
|
|
fopen_s(&fp, outputfilename, "wt");
|
|
|
|
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
|
//std::wstring keyboardName_t = keyboardName;
|
|
|
|
fprintf(fp,
|
|
/*"(function(){\n"
|
|
"\"use strict\";\n"
|
|
"var modCodes = testRunner.modCodes;\n"
|
|
"var keyCodes = testRunner.keyCodes;\n"
|
|
"testRunner.register(*/"{\n"
|
|
" \"keyboard\": {\n"
|
|
" \"id\": \"%s\",\n"
|
|
//" \"name\" : \"%s\",\n" //TODO: utf8
|
|
//" \"filename\" : \"%s\",\n"
|
|
" \"languages\" : [{\n"
|
|
" \"id\": \"en\",\n"
|
|
" \"name\" : \"English\",\n"
|
|
" \"region\" : \"Europe\"\n"
|
|
" }]\n"
|
|
" },\n"
|
|
" \"inputTests\": {\n",
|
|
keyboardID
|
|
//utf8_conv.to_bytes(keyboardName).c_str(),
|
|
//keyboardJSFilename
|
|
);
|
|
|
|
for (size_t i = 0; i < tests->size(); i++) {
|
|
UINT key = tests->at(i).key, shift = tests->at(i).shift;
|
|
|
|
if (!(shift & ISVIRTUALKEY)) {
|
|
shift = CharToShift[key] | ISVIRTUALKEY;
|
|
key = CharToVK[key];
|
|
}
|
|
|
|
std::wstring keyName = GetKeyName(key);
|
|
std::wstring modifierName = GetModifierName(shift);
|
|
fprintf(fp, " \"%d\": {\"key\": %d", i, key); // keyName.c_str());
|
|
|
|
if (shift != ISVIRTUALKEY && shift != 0)
|
|
fprintf(fp, ", \"modifier\": %d", shift & ~ISVIRTUALKEY); // %ws", modifierName.c_str());
|
|
|
|
if (tests->at(i).context.size())
|
|
fprintf(fp, ", \"context\": %s", unicode_escape(tests->at(i).context).c_str());
|
|
fprintf(fp, "}%s\n", (i == tests->size() - 1 ? "" : ","));
|
|
}
|
|
|
|
fprintf(fp,
|
|
" }\n"
|
|
"}"/*);\n"
|
|
"})();\n"*/
|
|
);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
void RemoveDuplicateTests(TESTS *tests) {
|
|
// TODO: sort tests by key, modifier, context
|
|
|
|
// Then remove duplicates
|
|
}
|
|
|
|
int DoKeyboardAnalysis(LPKEYBOARD kbd, char *keyboardID, char *keyboardJSFilename, char *outputfilename) {
|
|
std::vector<std::vector<GROUPREF>> groupPaths;
|
|
|
|
//GROUPTREE tree;
|
|
LPGROUP gp = &kbd->dpGroupArray[kbd->StartGroup[1]]; // Unicode start group
|
|
|
|
std::vector<LPGROUP> tt;
|
|
|
|
// each rule in the base group is a base rule. Then, each
|
|
// rule can be expanded out by the use() tree from the rule.
|
|
TESTS *tests = DoGroupAnalysis(kbd, gp, tt, NULL);
|
|
if (!tests) {
|
|
// Error exit
|
|
return 3;
|
|
}
|
|
|
|
wchar_t *keyboardName = NULL, keyboardNameBuf[32];
|
|
|
|
for (UINT i = 0; i < kbd->cxStoreArray; i++) {
|
|
if (kbd->dpStoreArray[i].dwSystemID == TSS_NAME) {
|
|
keyboardName = kbd->dpStoreArray[i].dpString;
|
|
}
|
|
}
|
|
|
|
if (!keyboardName) {
|
|
wsprintfW(keyboardNameBuf, L"%s", keyboardID);
|
|
keyboardName = keyboardNameBuf;
|
|
}
|
|
|
|
RemoveDuplicateTests(tests);
|
|
|
|
PrintTests(tests, keyboardID, keyboardName, keyboardJSFilename, outputfilename);
|
|
|
|
delete tests;
|
|
// print the tree
|
|
|
|
//PrintTree(kbd, &tree, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD NextUTF32(PWCHAR pc) {
|
|
if (Uni_IsSurrogate1(*pc)) {
|
|
return Uni_SurrogateToUTF32(*pc, *(pc + 1));
|
|
}
|
|
return *pc;
|
|
}
|
|
/*
|
|
void PrintRule(LPKEYBOARD kbd, LPKEY kp) {
|
|
LPSTORE sp;
|
|
std::vector<DWORD> context, output;
|
|
|
|
// Always take first char in each referenced store
|
|
for (PWCHAR pc = kp->dpContext; pc && *pc; pc = incxstr(pc)) {
|
|
if (*pc == UC_SENTINEL) {
|
|
switch (*(pc + 1)) {
|
|
case CODE_ANY:
|
|
sp = &kbd->dpStoreArray[*(pc + 2) - 1];
|
|
context.push_back(NextUTF32(sp->dpString));
|
|
break;
|
|
case CODE_NOTANY:
|
|
assert(FALSE); //TODO
|
|
case CODE_INDEX:
|
|
assert(FALSE); //TODO
|
|
case CODE_DEADKEY:
|
|
|
|
case CODE_EXTENDED: p += 2; while (*p != UC_SENTINEL_EXTENDEDEND) p++; return p + 1;
|
|
case CODE_CLEARCONTEXT: return p + 1;
|
|
case CODE_CALL: return p + 1;
|
|
case CODE_CONTEXTEX: return p + 1;
|
|
case CODE_IFOPT: return p + 3;
|
|
case CODE_IFSYSTEMSTORE: return p + 3;
|
|
case CODE_SETOPT: return p + 2;
|
|
case CODE_SETSYSTEMSTORE: return p + 2;
|
|
case CODE_RESETOPT: return p + 1;
|
|
case CODE_SAVEOPT: return p + 1;
|
|
}
|
|
}
|
|
else {
|
|
// Character (either 1 or 2 word)
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
int groupindex(LPKEYBOARD kbd, LPGROUP gp) {
|
|
return gp - kbd->dpGroupArray;
|
|
}
|
|
|
|
void PrintTree(LPKEYBOARD kbd, GROUPTREE *t, int depth) {
|
|
if (t->group->dpName)
|
|
printf("%*.*s%ws\n", depth * 2, depth * 2, " ", t->group->dpName);
|
|
else
|
|
printf("%*.*sgroup%d\n", depth * 2, depth * 2, " ", groupindex(kbd, t->group));
|
|
|
|
// LPKEY kp = t->group->dpKeyArray;
|
|
// for (DWORD i = 0; i < t->group->cxKeyArray; i++) {
|
|
// AnalyzeRule(kp);
|
|
// }
|
|
|
|
for (size_t i = 0; i < t->refs.size(); i++) {
|
|
PrintTree(kbd, t->refs[i].target, (depth + 1));
|
|
}
|
|
}
|
|
|
|
|
|
// MapVirtualKeys is copied from syskbd.cpp (keyman32)
|
|
#define VK_COLON 0xBA
|
|
#define VK_EQUAL 0xBB
|
|
#define VK_COMMA 0xBC
|
|
#define VK_HYPHEN 0xBD
|
|
#define VK_PERIOD 0xBE
|
|
#define VK_SLASH 0xBF
|
|
#define VK_ACCENT 0xC0
|
|
#define VK_LBRKT 0xDB
|
|
#define VK_BKSLASH 0xDC
|
|
#define VK_RBRKT 0xDD
|
|
#define VK_QUOTE 0xDE
|
|
#define VK_xDF 0xDF
|
|
|
|
WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags)
|
|
{
|
|
char shiftedDigit[] = ")!@#$%^&*(";
|
|
int n, Shift;
|
|
|
|
if (shiftFlags & (LCTRLFLAG | RCTRLFLAG | LALTFLAG | RALTFLAG)) return 0;
|
|
|
|
if (keyCode >= '0' && keyCode <= '9')
|
|
{
|
|
n = keyCode - '0';
|
|
return ((shiftFlags & K_SHIFTFLAG) ? shiftedDigit[n] : keyCode);
|
|
}
|
|
|
|
if (keyCode >= 'A' && keyCode <= 'Z')
|
|
{
|
|
Shift = (shiftFlags & K_SHIFTFLAG);
|
|
if (shiftFlags & (CAPITALFLAG)) Shift = !Shift;
|
|
return (Shift ? keyCode : keyCode + 32);
|
|
}
|
|
|
|
if (keyCode >= VK_NUMPAD0 && keyCode <= VK_NUMPAD9)
|
|
{
|
|
if (!(shiftFlags & NUMLOCKFLAG)) return 0;
|
|
return keyCode - (VK_NUMPAD0 - '0');
|
|
}
|
|
|
|
Shift = (shiftFlags & K_SHIFTFLAG);
|
|
|
|
switch (keyCode)
|
|
{
|
|
case VK_ACCENT:
|
|
return Shift ? '~' : '`';
|
|
case VK_HYPHEN:
|
|
return Shift ? '_' : '-';
|
|
case VK_EQUAL:
|
|
return Shift ? '+' : '=';
|
|
case VK_BKSLASH:
|
|
case 0xE2: // I5332
|
|
return Shift ? '|' : 92;
|
|
case VK_LBRKT:
|
|
return Shift ? '{' : '[';
|
|
case VK_RBRKT:
|
|
return Shift ? '}' : ']';
|
|
case VK_COLON:
|
|
return Shift ? ':' : ';';
|
|
case VK_QUOTE:
|
|
return Shift ? '"' : 39;
|
|
case VK_COMMA:
|
|
return Shift ? '<' : ',';
|
|
case VK_PERIOD:
|
|
return Shift ? '>' : '.';
|
|
case VK_SLASH:
|
|
return Shift ? '?' : '/';
|
|
case VK_SPACE:
|
|
return ' ';
|
|
}
|
|
return 0;
|
|
//keyCode;
|
|
}
|
|
|
|
void MapVirtualKeys(void) {
|
|
for (int i = 0; i < 256; i++) {
|
|
VKToChar[i][0] = MapVirtualKeys(i, 0);
|
|
VKToChar[i][1] = MapVirtualKeys(i, K_SHIFTFLAG);
|
|
CharToVK[VKToChar[i][0]] = i;
|
|
CharToVK[VKToChar[i][1]] = i;
|
|
CharToShift[VKToChar[i][0]] = 0;
|
|
CharToShift[VKToChar[i][1]] = K_SHIFTFLAG;
|
|
}
|
|
}
|