cimdit/profilegenerator/ts/ProfileGenerator.ts

702 lines
20 KiB
TypeScript

type ProfileJson = {
name: string;
customimage?: string;
mappedbuttons?: EntityJson[];
mappedaxis?: EntityJson[];
mappedrotary?: EntityJson[];
macros?: Array<any[]>;
}
type EntityJson = {
source: number;
type: string;
target?: number;
axis?: number;
value?: number;
macro?: number;
}
type EntityProfileString = {
bytes: number;
count: number;
string: string;
};
/**
* Main application class
*/
class ProfileGenerator {
private json : HTMLTextAreaElement;
private jsonCounter : HTMLTextAreaElement;
private generatejson : HTMLButtonElement;
private generatestring : HTMLButtonElement;
private error : HTMLParagraphElement;
private profile : HTMLTextAreaElement;
private lineCountCache: number;
private profileEnum : Array<string> = [
"MAPPING_NONE",
"JOYSTICK_BUTTON",
"JOYSTICK_AXIS",
"MOUSE_BUTTON",
"MOUSE_REL_X_AXIS",
"MOUSE_REL_Y_AXIS",
"MOUSE_REL_WHEEL",
"NEXT_PROFILE",
"PREV_PROFILE",
"SWITCH_PROFILE",
"MACRO_PRESS",
"MACRO_RELEASE"
];
private macroEnum: Array<string> = [
"MACRO_NULL",
"MACRO_SPEED",
"MACRO_DELAY",
"MACRO_KEY_PRESS",
"MACRO_KEY_RELEASE",
"MACRO_JOY_PRESS",
"MACRO_JOY_RELEASE",
"MACRO_MOUSE_PRESS",
"MACRO_MOUSE_RELEASE",
"MACRO_MOUSE_WHEEL",
"MACRO_TYPE",
"MACRO_MOUSE_REL_X",
"MACRO_MOUSE_REL_Y",
"MACRO_MOUSE_REL_X_AXIS",
"MACRO_MOUSE_REL_Y_AXIS"
];
private keyLookup = {
"KEY_RESERVED":0,
"KEY_ERROR_ROLLOVER":1,
"KEY_POST_FAIL":2,
"KEY_ERROR_UNDEFINED":3,
"KEY_A":4,
"KEY_B":5,
"KEY_C":6,
"KEY_D":7,
"KEY_E":8,
"KEY_F":9,
"KEY_G":10,
"KEY_H":11,
"KEY_I":12,
"KEY_J":13,
"KEY_K":14,
"KEY_L":15,
"KEY_M":16,
"KEY_N":17,
"KEY_O":18,
"KEY_P":19,
"KEY_Q":20,
"KEY_R":21,
"KEY_S":22,
"KEY_T":23,
"KEY_U":24,
"KEY_V":25,
"KEY_W":26,
"KEY_X":27,
"KEY_Y":28,
"KEY_Z":29,
"KEY_1":30,
"KEY_2":31,
"KEY_3":32,
"KEY_4":33,
"KEY_5":34,
"KEY_6":35,
"KEY_7":36,
"KEY_8":37,
"KEY_9":38,
"KEY_0":39,
"KEY_ENTER":40,
"KEY_RETURN":40,
"KEY_ESC":41,
"KEY_BACKSPACE":42,
"KEY_TAB":43,
"KEY_SPACE":44,
"KEY_MINUS":45,
"KEY_EQUAL":46,
"KEY_LEFT_BRACE":47,
"KEY_RIGHT_BRACE":48,
"KEY_BACKSLASH":49,
"KEY_NON_US_NUM":50,
"KEY_SEMICOLON":51,
"KEY_QUOTE":52,
"KEY_TILDE":53,
"KEY_COMMA":54,
"KEY_PERIOD":55,
"KEY_SLASH":56,
"KEY_CAPS_LOCK":0x39,
"KEY_F1":0x3A,
"KEY_F2":0x3B,
"KEY_F3":0x3C,
"KEY_F4":0x3D,
"KEY_F5":0x3E,
"KEY_F6":0x3F,
"KEY_F7":0x40,
"KEY_F8":0x41,
"KEY_F9":0x42,
"KEY_F10":0x43,
"KEY_F11":0x44,
"KEY_F12":0x45,
"KEY_PRINT":0x46,
"KEY_PRINTSCREEN":0x46,
"KEY_SCROLL_LOCK":0x47,
"KEY_PAUSE":0x48,
"KEY_INSERT":0x49,
"KEY_HOME":0x4A,
"KEY_PAGE_UP":0x4B,
"KEY_DELETE":0x4C,
"KEY_END":0x4D,
"KEY_PAGE_DOWN":0x4E,
"KEY_RIGHT_ARROW":0x4F,
"KEY_LEFT_ARROW":0x50,
"KEY_DOWN_ARROW":0x51,
"KEY_UP_ARROW":0x52,
"KEY_RIGHT":0x4F,
"KEY_LEFT":0x50,
"KEY_DOWN":0x51,
"KEY_UP":0x52,
"KEY_NUM_LOCK":0x53,
"KEYPAD_DIVIDE":0x54,
"KEYPAD_MULTIPLY":0x55,
"KEYPAD_SUBTRACT":0x56,
"KEYPAD_ADD":0x57,
"KEYPAD_ENTER":0x58,
"KEYPAD_1":0x59,
"KEYPAD_2":0x5A,
"KEYPAD_3":0x5B,
"KEYPAD_4":0x5C,
"KEYPAD_5":0x5D,
"KEYPAD_6":0x5E,
"KEYPAD_7":0x5F,
"KEYPAD_8":0x60,
"KEYPAD_9":0x61,
"KEYPAD_0":0x62,
"KEYPAD_DOT":0x63,
"KEY_NON_US":0x64,
"KEY_APPLICATION":0x65,
"KEY_MENU":0x65,
"KEY_POWER":0x66,
"KEY_PAD_EQUALS":0x67,
"KEY_F13":0x68,
"KEY_F14":0x69,
"KEY_F15":0x6A,
"KEY_F16":0x6B,
"KEY_F17":0x6C,
"KEY_F18":0x6D,
"KEY_F19":0x6E,
"KEY_F20":0x6F,
"KEY_F21":0x70,
"KEY_F22":0x71,
"KEY_F23":0x72,
"KEY_F24":0x73,
"KEY_EXECUTE":0x74,
"KEY_HELP":0x75,
"KEY_MENU2":0x76,
"KEY_SELECT":0x77,
"KEY_STOP":0x78,
"KEY_AGAIN":0x79,
"KEY_UNDO":0x7A,
"KEY_CUT":0x7B,
"KEY_COPY":0x7C,
"KEY_PASTE":0x7D,
"KEY_FIND":0x7E,
"KEY_MUTE":0x7F,
"KEY_VOLUME_MUTE":0x7F,
"KEY_VOLUME_UP":0x80,
"KEY_VOLUME_DOWN":0x81,
"KEY_LOCKING_CAPS_LOCK":0x82,
"KEY_LOCKING_NUM_LOCK":0x83,
"KEY_LOCKING_SCROLL_LOCK":0x84,
"KEYPAD_COMMA":0x85,
"KEYPAD_EQUAL_SIGN":0x86,
"KEY_INTERNATIONAL1":0x87,
"KEY_INTERNATIONAL2":0x88,
"KEY_INTERNATIONAL3":0x89,
"KEY_INTERNATIONAL4":0x8A,
"KEY_INTERNATIONAL5":0x8B,
"KEY_INTERNATIONAL6":0x8C,
"KEY_INTERNATIONAL7":0x8D,
"KEY_INTERNATIONAL8":0x8E,
"KEY_INTERNATIONAL9":0x8F,
"KEY_LANG1":0x90,
"KEY_LANG2":0x91,
"KEY_LANG3":0x92,
"KEY_LANG4":0x93,
"KEY_LANG5":0x94,
"KEY_LANG6":0x95,
"KEY_LANG7":0x96,
"KEY_LANG8":0x97,
"KEY_LANG9":0x98,
"KEY_ALTERNATE_ERASE":0x99,
"KEY_SYSREQ_ATTENTION":0x9A,
"KEY_CANCEL":0x9B,
"KEY_CLEAR":0x9C,
"KEY_PRIOR":0x9D,
"KEY_RETURN2":0x9E,
"KEY_SEPARATOR":0x9F,
"KEY_OUT":0xA0,
"KEY_OPER":0xA1,
"KEY_CLEAR_AGAIN":0xA2,
"KEY_CRSEL_PROPS":0xA3,
"KEY_EXSEL":0xA4,
"KEY_PAD_00":0xB0,
"KEY_PAD_000":0xB1,
"KEY_THOUSANDS_SEPARATOR":0xB2,
"KEY_DECIMAL_SEPARATOR":0xB3,
"KEY_CURRENCY_UNIT":0xB4,
"KEY_CURRENCY_SUB_UNIT":0xB5,
"KEYPAD_LEFT_BRACE":0xB6,
"KEYPAD_RIGHT_BRACE":0xB7,
"KEYPAD_LEFT_CURLY_BRACE":0xB8,
"KEYPAD_RIGHT_CURLY_BRACE":0xB9,
"KEYPAD_TAB":0xBA,
"KEYPAD_BACKSPACE":0xBB,
"KEYPAD_A":0xBC,
"KEYPAD_B":0xBD,
"KEYPAD_C":0xBE,
"KEYPAD_D":0xBF,
"KEYPAD_E":0xC0,
"KEYPAD_F":0xC1,
"KEYPAD_XOR":0xC2,
"KEYPAD_CARET":0xC3,
"KEYPAD_PERCENT":0xC4,
"KEYPAD_LESS_THAN":0xC5,
"KEYPAD_GREATER_THAN":0xC6,
"KEYPAD_AMPERSAND":0xC7,
"KEYPAD_DOUBLEAMPERSAND":0xC8,
"KEYPAD_PIPE":0xC9,
"KEYPAD_DOUBLEPIPE":0xCA,
"KEYPAD_COLON":0xCB,
"KEYPAD_POUND_SIGN":0xCC,
"KEYPAD_SPACE":0xCD,
"KEYPAD_AT_SIGN":0xCE,
"KEYPAD_EXCLAMATION_POINT":0xCF,
"KEYPAD_MEMORY_STORE":0xD0,
"KEYPAD_MEMORY_RECALL":0xD1,
"KEYPAD_MEMORY_CLEAR":0xD2,
"KEYPAD_MEMORY_ADD":0xD3,
"KEYPAD_MEMORY_SUBTRACT":0xD4,
"KEYPAD_MEMORY_MULTIPLY":0xD5,
"KEYPAD_MEMORY_DIVIDE":0xD6,
"KEYPAD_PLUS_MINUS":0xD7,
"KEYPAD_CLEAR":0xD8,
"KEYPAD_CLEAR_ENTRY":0xD9,
"KEYPAD_BINARY":0xDA,
"KEYPAD_OCTAL":0xDB,
"KEYPAD_DECIMAL":0xDC,
"KEYPAD_HEXADECIMAL":0xDD,
"KEY_LEFT_CTRL":0xE0,
"KEY_LEFT_SHIFT":0xE1,
"KEY_LEFT_ALT":0xE2,
"KEY_LEFT_GUI":0xE3,
"KEY_LEFT_WINDOWS":0xE3,
"KEY_RIGHT_CTRL":0xE4,
"KEY_RIGHT_SHIFT":0xE5,
"KEY_RIGHT_ALT":0xE6,
"KEY_RIGHT_GUI":0xE7,
"KEY_RIGHT_WINDOWS":0xE7,
};
constructor() {
this.json = <HTMLTextAreaElement>document.getElementById("json");
this.jsonCounter = <HTMLTextAreaElement>document.getElementById("jsoncounter");
this.profile = <HTMLTextAreaElement>document.getElementById("string");
this.generatejson = <HTMLButtonElement>document.getElementById("generatejson");
this.generatestring = <HTMLButtonElement>document.getElementById("generatestring");
this.error = <HTMLParagraphElement>document.getElementById("error");
this.generatestring.addEventListener("click", this.createProfile.bind(this));
this.generatejson.addEventListener("click", this.createJson.bind(this));
this.lineCountCache = 0;
this.json.addEventListener('scroll', () => {
this.jsonCounter.scrollTop = this.json.scrollTop;
this.jsonCounter.scrollLeft = this.json.scrollLeft;
});
this.json.addEventListener('input', () => {
this.line_counter();
});
this.line_counter();
}
createProfile(event: Event) {
event.preventDefault();
var input = this.json.value;
var json = {};
try {
json = JSON.parse(input);
} catch (e) {
this.error.innerHTML = e.message;
return false;
}
var profiles = [];
var lengths = [];
var profileCount = 0;
for (var pNum in json) {
var p = this.parseProfile(json[pNum])
profiles.push(p[1]);
lengths.push(p[0]);
++profileCount;
}
profiles.unshift(this.hexString(profileCount));
var length = 3;
for (var i = 0; i < lengths.length; ++i) {
length += lengths[i];
}
profiles.unshift(this.hexString(length >> 8) + this.hexString(length & 255));
this.profile.value = "cimdit:F" + profiles.join("");
return false;
}
hexString(input: number) : string {
if (input < 0) {
input += 256;
}
var hex = input.toString(16);
if (hex.length < 2) {
hex = "0" + hex;
}
return hex;
}
parseProfile(json: Object) {
var out = "";
var length = 2;
// get name
var tmp = json["name"];
for (var pos in tmp) {
out += this.hexString(tmp.charCodeAt(pos));
}
out += "00";
length += out.length/2;
// custom image
if (json["customimage"]) {
out += "01";
out += json["customimage"];
length += 513;
} else {
out += "00";
++length;
}
// add buttons
if (json["mappedbuttons"] instanceof Array) {
var buttons = this.map(json["mappedbuttons"]);
out += this.hexString(buttons.count);
out += buttons.string;
length += buttons.bytes + 1;
} else {
out += "00";
++length;
}
// add axis
if (json["mappedaxis"] instanceof Array) {
var axis = this.map(json["mappedaxis"]);
out += this.hexString(axis.count);
out += axis.string;
length += axis.bytes + 1;
} else {
out += "00";
++length;
}
// add rotary
if (json["mappedrotary"] instanceof Array) {
var rotary = this.map(json["mappedrotary"]);
out += this.hexString(rotary.count);
out += rotary.string;
length += rotary.bytes + 1;
} else {
out += "00";
++length;
}
// add macro sequences
if (json["macros"] instanceof Array) {
var macro = this.createMacroString(json["macros"]);
out += this.hexString(macro.count);
out += macro.string;
length += macro.bytes + 1;
} else {
out += "00";
++length;
}
out = this.hexString(length >> 8) + this.hexString(length & 255) + out;
return [length, out];
}
map(entities: Array<EntityJson>) : EntityProfileString {
var out ="";
var bytes = 0
var count = 0;
for (var i = 0; i < entities.length; ++i) {
var entity = entities[i];
try {
var tmp = this.hexString(entity.source);
tmp += this.hexString(this.profileEnum.indexOf(entity.type));
if (entity.target !== undefined) {
tmp += this.hexString(entity.target);
} else if (entity.axis !== undefined) {
tmp += this.hexString(entity.axis);
} else if (entity.value !== undefined) {
tmp += this.hexString(entity.value);
} else if (entity.macro !== undefined) {
tmp += this.hexString(entity.macro);
} else {
tmp += "00";
}
out += tmp;
bytes += 3;
++count;
} catch(e) {
// print error
console.log(e);
}
}
return {bytes: bytes, count: count, string: out};
}
createMacroString(macros: any[][]) : EntityProfileString {
var bytes = 0;
var out = "";
for (var i = 0; i < macros.length; ++i) {
var macro = macros[i];
var append = false;
for (var j = 0; j < macro.length; ++j) {
var token = macro[j];
if (token === "MACRO_SPEED" || token === "MACRO_MOUSE_WHEEL" || token.toString().match(/^MACRO_(JOY|MOUSE)_(PRESS|RELEASE)$/)) { // expect an unsigned integer variable
if (append) {
append = false;
out += "00";
++bytes;
}
out += this.hexString(this.macroEnum.indexOf(token)) + this.hexString(macro[++j]);
bytes += 2;
} else if (token === "MACRO_DELAY") { // expect an 16 bit integer variable
if (append) {
append = false;
out += "00";
++bytes;
}
var value = macro[++j];
out += this.hexString(this.macroEnum.indexOf(token)) + this.hexString((value >> 8)) + this.hexString(value & 255);
bytes += 3;
} else if (token.toString().match(/^MACRO_KEY_(PRESS|RELEASE)$/) || token === "MACRO_TYPE") { // followed by a list of keys/buttons
if (append) {
append = false;
out += "00";
++bytes;
}
out += this.hexString(this.macroEnum.indexOf(token));
append = true;
++bytes;
} else if (token === "MACRO_MOUSE_REL_X" || token === "MACRO_MOUSE_REL_Y") { // expect one integer values
if (append) {
append = false;
out += "00";
++bytes;
}
out += this.hexString(this.macroEnum.indexOf(token)) + this.hexString(macro[++j]);
bytes +=2;
} else if (token === "MACRO_MOUSE_REL_X_AXIS" || token === "MACRO_MOUSE_REL_Y_AXIS") { // expect one two integer values, joystick axis and speed if axis != -1
if (append) {
append = false;
out += "00";
++bytes;
}
if (macro[j + 1] == -1) {
out += this.hexString(this.macroEnum.indexOf(token)) + this.hexString(macro[++j]);
bytes +=2;
} else {
out += this.hexString(this.macroEnum.indexOf(token)) + this.hexString(macro[++j]) + this.hexString(macro[++j]);
bytes +=3;
}
} else {
if (typeof(token) === "number") {
out += this.hexString(token);
++bytes;
} else {
if (token.startsWith("KEY_")) {
out += this.hexString(this.keyLookup[token]);
++bytes;
} else {
for (var k = 0; k < token.length; ++k) {
out += this.hexString(this.keyLookup["KEY_" + token[k].toUpperCase()]);
++bytes;
}
}
}
}
}
if (append) {
append = false;
out += "00";
++bytes;
}
out += "00";
++bytes;
}
return {bytes: bytes, count: macros.length, string: out};
}
fromHexS(input: string) : number {
var ret = parseInt(input, 16);
if (ret>127) ret -= 256;
return ret;
}
fromHexU(input: string) : number {
return parseInt(input, 16);
}
createJson(event: Event) {
event.preventDefault();
var string = this.profile.value;
// strip cimdit command
string = string.replace("cimdit:F","");
var tokens = string.match(/.{1,2}/g);
// parse token list
// ignore first 2 entries (length)
tokens.shift();
tokens.shift();
var json : ProfileJson[];
json = [];
var numProfile = this.fromHexU(tokens.shift());
for (var i = 0; i < numProfile; ++i) {
json.push(this.createProfileFromTokens(tokens));
}
this.json.value = JSON.stringify(json,null," ");
return false;
}
createProfileFromTokens(tokens) : ProfileJson {
// var bytes = tokens.shift() * 255 + tokens.shift() - 2;
tokens.shift(); tokens.shift();
var name = "";
var char: number;
do {
char = this.fromHexU(tokens.shift());
if (char)
name += String.fromCharCode(char);
} while (char !== 0 && tokens.length > 0);
var ret:ProfileJson = {name: name};
var customImage = tokens.shift();
if (customImage != "00") {
var cu = "";
for (var i = 0; i < 512; ++i) {
cu += tokens.shift();
}
ret.customimage = cu;
}
var mappedButtons:EntityJson[] = [];
var numButtons = this.fromHexU(tokens.shift());
for (var i = 0; i < numButtons; ++i) {
mappedButtons.push(this.createEntityFromTokens(tokens));
}
ret.mappedbuttons = mappedButtons;
var mappedAxis:EntityJson[] = [];
var numAxis = this.fromHexU(tokens.shift());
for (var i = 0; i < numAxis; ++i) {
mappedAxis.push(this.createEntityFromTokens(tokens));
}
ret.mappedaxis = mappedAxis;
var mappedRotary:EntityJson[] = [];
var numRotary = this.fromHexU(tokens.shift());
for (var i = 0; i < numRotary; ++i) {
mappedRotary.push(this.createEntityFromTokens(tokens));
}
ret.mappedrotary = mappedRotary;
var macros : Array<any[]> = [];
var numMacro = this.fromHexU(tokens.shift());
for (var i = 0; i < numMacro; ++i) {
macros.push(this.createMacrosFromTokens(tokens));
}
ret.macros = macros;
return ret;
}
createEntityFromTokens(tokens) :EntityJson{
var entity: EntityJson = {source: this.fromHexU(tokens.shift()), type: this.profileEnum[this.fromHexU(tokens.shift())]};
if (entity.type === 'JOYSTICK_BUTTON' || entity.type === 'MOUSE_BUTTON') {
entity.target = this.fromHexU(tokens.shift());
} else if (entity.type === 'JOYSTICK_AXIS') {
entity.axis = this.fromHexU(tokens.shift());
} else if (entity.type === 'MOUSE_REL_X_AXIS' || entity.type === 'MOUSE_REL_Y_AXIS' || entity.type === 'MOUSE_REL_WHEEL') {
entity.value = this.fromHexS(tokens.shift());
} else if (entity.type === 'MACRO_PRESS' || entity.type === 'MACRO_RELEASE') {
entity.macro = this.fromHexU(tokens.shift());
} else {
tokens.shift();
}
return entity;
}
getKey(input: number) : string {
return Object.keys(this.keyLookup).find(k=>this.keyLookup[k]===input);
}
createMacrosFromTokens(tokens: Array<string>) : Array<string | number> {
var macro : Array<string | number> = [];
do {
var token = this.macroEnum[this.fromHexU(tokens.shift())];
if (token !== "MACRO_NULL") {
macro.push(token);
}
switch(token) {
case "MACRO_NULL":
break;
case "MACRO_SPEED": // read in 1 extra numberic value 8 bit
case "MACRO_JOY_PRESS":
case "MACRO_JOY_RELEASE":
case "MACRO_MOUSE_PRESS":
case "MACRO_MOUSE_RELEASE":
case "MACRO_MOUSE_WHEEL":
macro.push(this.fromHexU(tokens.shift()));
break;
case "MACRO_DELAY": // read in 1 extra numberic value 16 bit
macro.push((this.fromHexU(tokens.shift())<<8) + this.fromHexU(tokens.shift()));
break;
case "MACRO_KEY_PRESS": // one or more keys
case "MACRO_KEY_RELEASE":
case "MACRO_TYPE":
var next: number;
var tmp = "";
do {
next = this.fromHexU(tokens.shift());
if (next != 0) {
var k = this.getKey(next);
if (k.match(/^KEY_\S$/)) {
tmp += k[4].toLowerCase();
} else {
if (tmp !== "")
macro.push(tmp);
tmp = "";
macro.push(k);
}
}
} while (next != 0);
if (tmp !== "") {
macro.push(tmp);
}
break;
case "MACRO_MOUSE_REL_X":
case "MACRO_MOUSE_REL_Y": // one bytes
var next :number = this.fromHexS(tokens.shift());
macro.push(next);
break;
case "MACRO_MOUSE_REL_X_AXIS":
case "MACRO_MOUSE_REL_Y_AXIS": // one or 2 bytes
var next :number = this.fromHexS(tokens.shift());
macro.push(next);
if (next != -1) {
macro.push(this.fromHexS(tokens.shift()));
}
break;
}
} while (token !== "MACRO_NULL" && tokens.length > 0);
return macro;
}
line_counter() {
var lineCount = this.json.value.split('\n').length;
var outarr = new Array();
if (this.lineCountCache != lineCount) {
for (var x = 0; x < lineCount; x++) {
outarr[x] = (x + 1);
}
this.jsonCounter.value = outarr.join('\n');
}
this.lineCountCache = lineCount;
}
}