type ProfileJson = { name: string; customimage?: string; mappedbuttons?: EntityJson[]; mappedaxis?: EntityJson[]; mappedrotary?: EntityJson[]; macros?: Array; } 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 = [ "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 = [ "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 = document.getElementById("json"); this.jsonCounter = document.getElementById("jsoncounter"); this.profile = document.getElementById("string"); this.generatejson = document.getElementById("generatejson"); this.generatestring = document.getElementById("generatestring"); this.error = 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) : 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 = []; 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) : Array { var macro : Array = []; 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; } }