diff --git a/capsicain/capsicain.cpp b/capsicain/capsicain.cpp index 4c189c1..17b9bac 100644 --- a/capsicain/capsicain.cpp +++ b/capsicain/capsicain.cpp @@ -1,12 +1,16 @@ -#pragma once; +#pragma once #include "pch.h" #include #include #include #include +#include +#include #include #include +#include +#include #include //for Sleep() #include "capsicain.h" @@ -15,7 +19,26 @@ #include "scancodes.h" #include "resource.h" #include "led.h" -#include +#include "utils.h" + +typedef int (*AHKTHREAD)(const wchar_t* aScript, const wchar_t* aCmdLine, const wchar_t* aTitle); +typedef int (*AHKREADY)(int threadid); +typedef int (*AHKADDSCRIPT)(const wchar_t * script, int waitexecute, int threadid); +typedef int (*AHKEXEC)(const wchar_t *, int threadid); +typedef const wchar_t * (*AHKFINDFUNC)(const wchar_t *, int threadid); +typedef const wchar_t * (*AHKFUNCTION)(const wchar_t* func, const wchar_t * param1, const wchar_t * param2, const wchar_t * param3, const wchar_t * param4, const wchar_t * param5, const wchar_t * param6, const wchar_t * param7, const wchar_t * param8, const wchar_t * param9, const wchar_t * param10, int threadid); +struct Ahk +{ + HMODULE handle; + int threadid; + AHKTHREAD thread; + AHKREADY ready; + AHKADDSCRIPT addScript; + AHKEXEC exec; + AHKFINDFUNC findFunc; + AHKFUNCTION function; + AHKFUNCTION postFunction; +} ahk; using namespace std; @@ -41,6 +64,8 @@ struct Globals int capsicainOnOffKey = -1; bool protectConsole = true; //drop Pause and Break signals when console is foreground bool translateMessyKeys = true; //translate various DOS keys (e.g. Ctrl+Pause=SC_Break -> SC_Pause, Alt+Print=SC_altprint -> sc_print) + set disableEscKey; + set forwardEscKey; } globals; static const struct Globals defaultGlobals; @@ -53,6 +78,10 @@ struct Options bool flipAltWinOnAppleKeyboards = false; bool LControlLWinBlocksAlphaMapping = false; bool processOnlyFirstKeyboard = false; + bool holdRepeatsAllKeys = false; + bool disableAHKDelay = false; + string defaultFunction = "key(%s, m)"; + bool enableMouse = false; } options; static const struct Options defaultOptions; @@ -60,10 +89,13 @@ struct ModifierCombo { int vkey = SC_NOP; unsigned char deadkey = 0; - unsigned short modAnd = 0; - unsigned short modOr = 0; - unsigned short modNot = 0; - unsigned short modTap = 0; + MOD modAnd = 0; + MOD modOr = 0; + MOD modNot = 0; + MOD modTap = 0; + MOD modTapAnd = 0; + DEV devAnd = 0; + DEV devNot = 0; vector keyEventSequence; }; @@ -73,9 +105,18 @@ struct AllMaps //-1 = undefined key int rewiremap[REWIRE_ROWS][REWIRE_COLS] = { }; //MUST initialize this manually to -1 !! - vector modCombos;// = new vector(); + map > modCombos{ + { INI_TAG_COMBOS, {} }, + { INI_TAG_UPCOMBOS, {} }, + { INI_TAG_TAPCOMBOS, {} }, + { INI_TAG_SLOWCOMBOS, {} }, + { INI_TAG_REPEATCOMBOS, {} } + }; int alphamap[MAX_VCODES] = { }; //MUST initialize this manually to 1 1, 2 2, 3 3, ... + + map executables; + map devices; } allMaps; struct InterceptionState @@ -87,6 +128,8 @@ struct InterceptionState InterceptionKeyStroke currentIKstroke = { SC_NOP, 0 }; InterceptionKeyStroke previousIKstroke1 = { SC_NOP, 0 }; //remember history InterceptionKeyStroke previousIKstroke2 = { SC_NOP, 0 }; + InterceptionDevice lastMouse = NULL; + InterceptionDevice lastKeyboard = NULL; } interceptionState; struct GlobalState @@ -108,6 +151,7 @@ struct GlobalState int keysDownSentCounter = 0; //tracks how many keys are actually down that Windows knows about bool keysDownSent[256] = { false }; //Remember all forwarded to Windows. Sent keys must be 8 bit bool keysDownTempReleased[256] = { false }; //Remember all keys that were temporarily released, e.g. to send an Alt-Numpad combo + set holdKeys[VK_MAX]; //Remember all replaced hold() keys while the physical key is still down bool secretSequenceRecording = false; bool secretSequencePlayback = false; @@ -119,8 +163,9 @@ static const struct GlobalState defaultGlobalState; struct ModifierState { unsigned char activeDeadkey = 0; //it's not really a modifier though... - unsigned short modifierDown = 0; - unsigned short modifierTapped = 0; + MOD modifierDown = 0; + MOD modifierTapped = 0; + MOD modifierForceDown = 0; vector modsTempAltered; int tapAndHoldKey = -1; //remember the tap-and-hold key as long as it is down } modifierState; @@ -135,6 +180,7 @@ struct LoopState bool tapped = false; bool tappedSlow = false; //autorepeat set in before key release bool tapHoldMake = false; //tap-and-hold action (like LAlt > mod12 // LAlt) + bool repeat = false; vector resultingVKeyEventSequence; @@ -205,6 +251,119 @@ void InterceptionSendCurrentKeystroke() interception_send(interceptionState.interceptionContext, interceptionState.interceptionDevice, (InterceptionStroke*)&interceptionState.currentIKstroke, 1); } +void loadAHK() +{ + if (!ahk.handle) + ahk.handle = LoadLibrary(TEXT("AutoHotkey64.dll")); + if (!ahk.handle) + ahk.handle = LoadLibrary(TEXT("AutoHotkey.dll")); + if (!ahk.handle) { + cout << endl + << "AHK: No AutoHotkey64.dll found. Get one from " + "https://github.com/thqby/AutoHotkey_H"; + } + else + { + ahk.thread = (AHKTHREAD)GetProcAddress(ahk.handle, "NewThread"); + ahk.ready = (AHKREADY)GetProcAddress(ahk.handle, "ahkReady"); + ahk.addScript = (AHKADDSCRIPT)GetProcAddress(ahk.handle, "addScript"); + ahk.exec = (AHKEXEC)GetProcAddress(ahk.handle, "ahkExec"); + ahk.findFunc = (AHKFINDFUNC)GetProcAddress(ahk.handle, "ahkFindFunc"); + ahk.function = (AHKFUNCTION)GetProcAddress(ahk.handle, "ahkFunction"); + ahk.postFunction = (AHKFUNCTION)GetProcAddress(ahk.handle, "ahkPostFunction"); + + auto script = LoadUtf8FileToString(L"capsicain.ini"); + auto idx = script.find(L"[ahk]"); + if (idx == string::npos) + idx = script.find(L"[AHK]"); + if (idx == string::npos) + { + cout << endl << "AHK: INI has no [ahk] section..."; + unloadAHK(); + return; + } + script = script.substr(idx + 6); + if (script.find(L"ersistent") == string::npos && script.find(L"::") == string::npos) + cout << endl << "AHK: You should add \"Persistent\" to your AHK script if it doesn't have hotkeys..."; + if (script != L"") + { + if (ahk.threadid) + ahk.exec(L"ExitApp", ahk.threadid); + ahk.threadid = ahk.thread(script.c_str(), L"", L""); + if (ahk.threadid) + cout << endl << "AHK: Loaded [ahk] to AutoHotkey64.dll"; + else + cout << endl << "AHK: Failed to load [ahk] to AutoHotkey64.dll"; + } + } +} + +void unloadAHK() +{ + if (ahk.threadid) + ahk.exec(L"ExitApp", ahk.threadid); + if (ahk.handle) + FreeLibrary(ahk.handle); + ahk.handle = 0; + ahk.threadid = 0; +} + +int mousetoKey(InterceptionMouseStroke &mstroke, InterceptionKeyStroke *kstroke) +{ + auto state = mstroke.state; + auto roll = mstroke.rolling; + int n = 0; + if (state & INTERCEPTION_MOUSE_BUTTON_1_DOWN || state & INTERCEPTION_MOUSE_BUTTON_1_UP) + { + kstroke[n].code = VM_LEFT; + kstroke[n].state = (int)(bool)!(state & INTERCEPTION_MOUSE_BUTTON_1_DOWN); + n++; + } + if (state & INTERCEPTION_MOUSE_BUTTON_2_DOWN || state & INTERCEPTION_MOUSE_BUTTON_2_UP) + { + kstroke[n].code = VM_RIGHT; + kstroke[n].state = (int)(bool)!(state & INTERCEPTION_MOUSE_BUTTON_2_DOWN); + n++; + } + if (state & INTERCEPTION_MOUSE_BUTTON_3_DOWN || state & INTERCEPTION_MOUSE_BUTTON_3_UP) + { + kstroke[n].code = VM_MIDDLE; + kstroke[n].state = (int)(bool)!(state & INTERCEPTION_MOUSE_BUTTON_3_DOWN); + n++; + } + if (state & INTERCEPTION_MOUSE_BUTTON_4_DOWN || state & INTERCEPTION_MOUSE_BUTTON_4_UP) + { + kstroke[n].code = VM_BUTTON4; + kstroke[n].state = (int)(bool)!(state & INTERCEPTION_MOUSE_BUTTON_4_DOWN); + n++; + } + if (state & INTERCEPTION_MOUSE_BUTTON_5_DOWN || state & INTERCEPTION_MOUSE_BUTTON_5_UP) + { + kstroke[n].code = VM_BUTTON5; + kstroke[n].state = (int)(bool)!(state & INTERCEPTION_MOUSE_BUTTON_5_DOWN); + n++; + } + if(state & INTERCEPTION_MOUSE_WHEEL) + { + if (roll > 0) + kstroke[n].code = VM_WHEEL_UP; + else if (roll < 0) + kstroke[n].code = VM_WHEEL_DOWN; + kstroke[n].state = 0; + n++; + } + if(state & INTERCEPTION_MOUSE_HWHEEL) + { + if (roll < 0) + kstroke[n].code = VM_WHEEL_LEFT; + else if (roll > 0) + kstroke[n].code = VM_WHEEL_RIGHT; + kstroke[n].state = 0; + n++; + } + return n; +} + int main() { if (!initConsoleWindow()) @@ -214,6 +373,8 @@ int main() return 0; } + interceptionState.interceptionContext = interception_create_context(); + IFPROF profiler.stopwatchRestart(); printHelloHeader(); @@ -228,14 +389,12 @@ int main() } parseIniGlobals(); - switchConfig(globals.activeConfigOnStartup, true); if (globals.startAHK) - { - string msg = startProgramSameFolder(PROGRAM_NAME_AHK); - cout << endl << endl << "starting AHK... "; - cout << (msg == "" ? "OK" : "Not. '" + msg + "'"); - } + loadAHK(); + + switchConfig(globals.activeConfigOnStartup, true); + cout << endl << endl << "[ESC] + [X] to stop." << endl << "[ESC] + [H] for Help"; cout << endl << endl << "capsicain running.... "; @@ -255,261 +414,316 @@ int main() raise_process_priority(); //careful: if we spam key events, other processes get no timeslots to process them. Sleep a bit... - interceptionState.interceptionContext = interception_create_context(); interception_set_filter(interceptionState.interceptionContext, interception_is_keyboard, INTERCEPTION_FILTER_KEY_ALL); + if (options.enableMouse) + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_ALL & ~INTERCEPTION_FILTER_MOUSE_MOVE); + else + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_NONE); + + InterceptionDevice device; + InterceptionStroke stroke; //CORE LOOP - while (true) + bool exit = false; + while (!exit) { - //remember previous two keys to detect tapping and Pause sequence - interceptionState.previousIKstroke2 = interceptionState.previousIKstroke1; - interceptionState.previousIKstroke1 = interceptionState.currentIKstroke; - //wait for the next key from Interception - (interception_receive(interceptionState.interceptionContext, - interceptionState.interceptionDevice = interception_wait(interceptionState.interceptionContext), - (InterceptionStroke*)&interceptionState.currentIKstroke, 1) > 0); + int n = interception_receive(interceptionState.interceptionContext, + device = interception_wait_with_timeout(interceptionState.interceptionContext, 2), + &stroke, 1); - IFPROF + InterceptionKeyStroke strokes[10] = {0}; + + if (device && n) { - //Measure Timing. sleep() is not precise; just a rough outline. Expect occasional 30ms sleeps from thread scheduling. - profiler.timepointPreviousKeyEvent = profiler.timepointLoopStart; - profiler.timepointLoopStart = profiler.getTimepointNow(); - profiler.stopwatchRestart(); - profiler.countIncoming++; - } + if (interception_is_mouse(device)) + { + InterceptionMouseStroke &mstroke = *(InterceptionMouseStroke *) &stroke; + interceptionState.lastMouse = device; + int m = mousetoKey(mstroke, (InterceptionKeyStroke *)&strokes); + if (m == 0) + continue; + n = m; + } + else + { + interceptionState.lastKeyboard = device; + strokes[0] = *(InterceptionKeyStroke *)&stroke; + } - //low level debugging, show incoming raw key - IFTRACE printIKStrokeState(interceptionState.currentIKstroke); + if (allMaps.devices.find(device) == allMaps.devices.end()) + getHardwareId(); - //clear loop state - loopState = defaultLoopState; + globalState.deviceIdKeyboard = allMaps.devices[device].id; + globalState.deviceIsAppleKeyboard = allMaps.devices[device].apple; - //copy InterceptionKeyStroke (unpleasant to use) to plain VKeyEvent - VKeyEvent originalVKeyEvent = convertIkstroke2VKeyEvent(interceptionState.currentIKstroke); - loopState.scancode = originalVKeyEvent.vcode; //scancode is write-once (except for the AppleWinAlt option) - loopState.vcode = loopState.scancode; //vcode may be altered below - loopState.isDownstroke = originalVKeyEvent.isDownstroke; + for (int i = 0; i < n; ++i) + { + //remember previous two keys to detect tapping and Pause sequence + interceptionState.previousIKstroke2 = interceptionState.previousIKstroke1; + interceptionState.previousIKstroke1 = interceptionState.currentIKstroke; - //if GLOBAL capsicainEnableDisable is configured, it toggles the ON/OFF state - if (globals.capsicainOnOffKey != -1) - { - if (processOnOffKey()) - continue; - } - //if disabled, just forward - if (!globalState.capsicainOn) - { - InterceptionSendCurrentKeystroke(); - continue; - } + interceptionState.interceptionDevice = device; + interceptionState.currentIKstroke = strokes[i]; - IFDEBUG if(globalState.activeConfig == 0) cout << ". "; + IFPROF + { + //Measure Timing. sleep() is not precise; just a rough outline. Expect occasional 30ms sleeps from thread scheduling. + profiler.timepointPreviousKeyEvent = profiler.timepointLoopStart; + profiler.timepointLoopStart = profiler.getTimepointNow(); + profiler.stopwatchRestart(); + profiler.countIncoming++; + } - //ignore secondary keyboard? - if (options.processOnlyFirstKeyboard - && (interceptionState.previousInterceptionDevice != NULL) - && (interceptionState.previousInterceptionDevice != interceptionState.interceptionDevice)) - { - IFDEBUG cout << endl << "Ignore 2nd board (" << interceptionState.interceptionDevice << ") scancode: " << interceptionState.currentIKstroke.code; - InterceptionSendCurrentKeystroke(); - continue; - } + //low level debugging, show incoming raw key + IFTRACE printIKStrokeState(interceptionState.currentIKstroke); - //device id changed / check for Apple Keyboard - if (interceptionState.previousInterceptionDevice == NULL //startup - || interceptionState.previousInterceptionDevice != interceptionState.interceptionDevice) //keyboard changed - { - getHardwareId(); - //detail to debug the "new device after sleep, reboot after 10 new devices" - cout << endl - << "<" << endl - << "new keyboard: " << (globalState.deviceIsAppleKeyboard ? "Apple keyboard" : "IBM keyboard") << endl - << "new keyboard count: " << ++interceptionState.newKeyboardCounter << endl - << "keyboard device id: " << globalState.deviceIdKeyboard << endl - << "interceptionDevice: " << interceptionState.interceptionDevice << endl - << getTimestamp() - << ">" << endl; + //clear loop state + loopState = defaultLoopState; + //copy InterceptionKeyStroke (unpleasant to use) to plain VKeyEvent + VKeyEvent originalVKeyEvent = convertIkstroke2VKeyEvent(interceptionState.currentIKstroke); + loopState.scancode = originalVKeyEvent.vcode; //scancode is write-once (except for the AppleWinAlt option) + loopState.vcode = loopState.scancode; //vcode may be altered below + loopState.isDownstroke = originalVKeyEvent.isDownstroke; + //if GLOBAL capsicainEnableDisable is configured, it toggles the ON/OFF state + if (globals.capsicainOnOffKey != -1) + { + if (processOnOffKey()) + continue; + } + //if disabled, just forward + if (!globalState.capsicainOn) + { + InterceptionSendCurrentKeystroke(); + continue; + } - interceptionState.previousInterceptionDevice = interceptionState.interceptionDevice; - } + IFDEBUG if(globalState.activeConfig == 0) cout << ". "; - //sanity check - if (interceptionState.currentIKstroke.code >= 0x80) - { - error("Received unexpected extended Interception Key Stroke code > 0x79: " + to_string(interceptionState.currentIKstroke.code)); - cout << endl << "Please open a ticket on github"; - continue; - } - if (interceptionState.currentIKstroke.code == 0) - { - error("Received unexpected SC_NOP Key Stroke code 0. Ignoring this."); - continue; - } + //ignore secondary keyboard? + if (options.processOnlyFirstKeyboard + && (interceptionState.previousInterceptionDevice != NULL) + && (interceptionState.previousInterceptionDevice != interceptionState.interceptionDevice)) + { + IFDEBUG cout << endl << "Ignore 2nd board (" << interceptionState.interceptionDevice << ") scancode: " << interceptionState.currentIKstroke.code; + InterceptionSendCurrentKeystroke(); + continue; + } - //ESC Commands - if (loopState.scancode == SC_ESCAPE) - { - IFDEBUG cout << endl << "(Hard ESC" << (loopState.isDownstroke ? "v " : "^ ") << ")"; - globalState.realEscapeIsDown = loopState.isDownstroke; + //device id changed / check for Apple Keyboard + if (interceptionState.previousInterceptionDevice == NULL //startup + || interceptionState.previousInterceptionDevice != interceptionState.interceptionDevice) //keyboard changed + { + //getHardwareId(); + //detail to debug the "new device after sleep, reboot after 10 new devices" + IFTRACE cout << endl + << "<" << endl + << "new keyboard: " << (globalState.deviceIsAppleKeyboard ? "Apple keyboard" : "IBM keyboard") << endl + << "new keyboard count: " << ++interceptionState.newKeyboardCounter << endl + << "keyboard device id: " << globalState.deviceIdKeyboard << endl + << "interceptionDevice: " << interceptionState.interceptionDevice << endl + << getTimestamp() + << ">" << endl; - //stop macro recording? - if (globalState.recordingMacro > 0) - { - IFDEBUG cout << endl << "Stop recording macro #" << globalState.recordingMacro; - //wrap macro in tokens to tmprelease / restore keys, to deal with the physical 'Ctrl down' that started the macro - if (globalState.recordedMacros[globalState.recordingMacro].size() > 0) - globalState.secretSequenceRecording = false; + + + interceptionState.previousInterceptionDevice = interceptionState.interceptionDevice; + } + + //sanity check + if (interceptionState.currentIKstroke.code >= 0x80 && interceptionState.currentIKstroke.code < VM_LEFT) { - globalState.recordedMacros[globalState.recordingMacro].push_back({ VK_CPS_TEMPRESTOREKEYS,true }); - globalState.recordedMacros[globalState.recordingMacro].insert(globalState.recordedMacros[globalState.recordingMacro].begin(), { VK_CPS_TEMPRELEASEKEYS,true }); + error("Received unexpected extended Interception Key Stroke code > 0x79: " + to_string(interceptionState.currentIKstroke.code)); + cout << endl << "Please open a ticket on github"; + continue; + } + if (interceptionState.currentIKstroke.code == 0) + { + error("Received unexpected SC_NOP Key Stroke code 0. Ignoring this."); + continue; } - globalState.recordingMacro = -1; - updateTrayIcon(true, globalState.recordingMacro >= 0, globalState.activeConfig); - continue; - } - } - else if (globalState.realEscapeIsDown && loopState.isDownstroke) - { - if (processCommand()) - continue; - else - { - setLED(SC_NOP, true); // sync LEDs with Windows state. - ShowInTaskbar(); //exit - break; - } - } - //TESTING the layer shift feature - /* - if (loopState.vcode == TESTING_LAYER_SHIFT_KEY) - { - if (loopState.isDownstroke) - { - if (globalState.activeConfig != TESTING_LAYER_SHIFT_TO) + //ESC Commands + if (loopState.scancode == SC_ESCAPE) { - TESTING_LAYER_SHIFT_FROM = globalState.activeConfig; - switchConfig(TESTING_LAYER_SHIFT_TO, false); + IFDEBUG cout << endl << "(Hard ESC" << (loopState.isDownstroke ? "v " : "^ ") << ")"; + globalState.realEscapeIsDown = loopState.isDownstroke; + + //stop macro recording? + if (globalState.recordingMacro > 0) + { + IFDEBUG cout << endl << "Stop recording macro #" << globalState.recordingMacro; + //wrap macro in tokens to tmprelease / restore keys, to deal with the physical 'Ctrl down' that started the macro + if (globalState.recordedMacros[globalState.recordingMacro].size() > 0) + globalState.secretSequenceRecording = false; + { + globalState.recordedMacros[globalState.recordingMacro].push_back({ VK_CPS_TEMPRESTOREKEYS,true }); + globalState.recordedMacros[globalState.recordingMacro].insert(globalState.recordedMacros[globalState.recordingMacro].begin(), { VK_CPS_TEMPRELEASEKEYS,true }); + } + globalState.recordingMacro = -1; + updateTrayIcon(true, globalState.recordingMacro >= 0, globalState.activeConfig); + continue; + } } - } - else if (TESTING_LAYER_SHIFT_FROM >= 0) - { - if (TESTING_LAYER_SHIFT_FROM != globalState.activeConfig) + else if (globalState.realEscapeIsDown && loopState.isDownstroke) + { + if (globals.forwardEscKey.find(loopState.scancode) == globals.forwardEscKey.end()) + continue; + } + else if (globalState.realEscapeIsDown && !loopState.isDownstroke) + { + if (globals.disableEscKey.find(loopState.scancode) == globals.disableEscKey.end()) + { + if (processCommand()) + { + continue; + } + else + { + setLED(SC_NOP, true); // sync LEDs with Windows state. + ShowInTaskbar(); //exit + exit = true; + } + } + if (globals.forwardEscKey.find(loopState.scancode) == globals.forwardEscKey.end()) + continue; + } + + //TESTING the layer shift feature + /* + if (loopState.vcode == TESTING_LAYER_SHIFT_KEY) { - switchConfig(TESTING_LAYER_SHIFT_FROM, false); + if (loopState.isDownstroke) + { + if (globalState.activeConfig != TESTING_LAYER_SHIFT_TO) + { + TESTING_LAYER_SHIFT_FROM = globalState.activeConfig; + switchConfig(TESTING_LAYER_SHIFT_TO, false); + } + } + else if (TESTING_LAYER_SHIFT_FROM >= 0) + { + if (TESTING_LAYER_SHIFT_FROM != globalState.activeConfig) + { + switchConfig(TESTING_LAYER_SHIFT_FROM, false); + } + + TESTING_LAYER_SHIFT_FROM = -1; + } + + continue; + } + */ + + //Config 0: standard keyboard, no further processing, just forward everything + if (globalState.activeConfig == DISABLED_CONFIG_NUMBER) + { + InterceptionSendCurrentKeystroke(); + continue; } - TESTING_LAYER_SHIFT_FROM = -1; - } + //consider include/exclude deviceID options + if (!globalState.includeDeviceId.empty() + && globalState.deviceIdKeyboard.find(globalState.includeDeviceId) == string::npos) + { + IFDEBUG cout << endl << "Ignore board, deviceId is not included with this config"; + InterceptionSendCurrentKeystroke(); + continue; + } + if (!globalState.excludeDeviceId.empty() + && globalState.deviceIdKeyboard.find(globalState.excludeDeviceId) != string::npos) + { + IFDEBUG cout << endl << "Ignore board, deviceId is excluded in this config"; + InterceptionSendCurrentKeystroke(); + continue; + } - continue; - } - */ - - //Config 0: standard keyboard, no further processing, just forward everything - if (globalState.activeConfig == DISABLED_CONFIG_NUMBER) - { - InterceptionSendCurrentKeystroke(); - continue; - } - //consider include/exclude deviceID options - if (!globalState.includeDeviceId.empty() - && globalState.deviceIdKeyboard.find(globalState.includeDeviceId) == string::npos) - { - IFDEBUG cout << endl << "Ignore board, deviceId is not included with this config"; - InterceptionSendCurrentKeystroke(); - continue; - } - if (!globalState.excludeDeviceId.empty() - && globalState.deviceIdKeyboard.find(globalState.excludeDeviceId) != string::npos) - { - IFDEBUG cout << endl << "Ignore board, deviceId is excluded in this config"; - InterceptionSendCurrentKeystroke(); - continue; - } + //flip Win+Alt only for Apple keyboards. + if (options.flipAltWinOnAppleKeyboards && globalState.deviceIsAppleKeyboard) + { + switch (loopState.vcode) + { + case SC_LALT: loopState.vcode = SC_LWIN; break; + case SC_LWIN: loopState.vcode = SC_LALT; break; + case SC_RALT: loopState.vcode = SC_RWIN; break; + case SC_RWIN: loopState.vcode = SC_RALT; break; + } + loopState.scancode = loopState.vcode; //only time where scancode is rewritten. Simplifies tapping and rewiring + } - //flip Win+Alt only for Apple keyboards. - if (options.flipAltWinOnAppleKeyboards && globalState.deviceIsAppleKeyboard) - { - switch (loopState.vcode) - { - case SC_LALT: loopState.vcode = SC_LWIN; break; - case SC_LWIN: loopState.vcode = SC_LALT; break; - case SC_RALT: loopState.vcode = SC_RWIN; break; - case SC_RWIN: loopState.vcode = SC_RALT; break; - } + //Handle Sysrq, ScrLock, Pause, NumLock + if (!processMessyKeys()) + continue; - loopState.scancode = loopState.vcode; //only time where scancode is rewritten. Simplifies tapping and rewiring - } + //Tapdance + detectTapping(); + //slow tap breaks tapping + if (loopState.tappedSlow) + modifierState.modifierTapped = 0; - //Handle Sysrq, ScrLock, Pause, NumLock - if (!processMessyKeys()) - continue; + //hard rewire all REWIREd keys + processRewireScancodeToVirtualcode(); + if (loopState.vcode == SC_NOP) //rewired to NOP to disable keys + { + IFDEBUG cout << " (r2NOP)"; + continue; + } - //Tapdance - detectTapping(); - //slow tap breaks tapping - if (loopState.tappedSlow) - modifierState.modifierTapped = 0; + IFDEBUG + { + cout << endl; + IFPROF cout << "(" << setw(5) << dec << timeBetweenTimepointsUS(profiler.timepointPreviousKeyEvent, profiler.timepointLoopStart) / 1000 << " m) "; + printLoopState1Input(); + } - //hard rewire all REWIREd keys - processRewireScancodeToVirtualcode(); - if (loopState.vcode == SC_NOP) //rewired to NOP to disable keys - { - IFDEBUG cout << " (r2NOP)"; - continue; - } + //evaluate modifiers + processModifierState(); + + IFDEBUG printLoopState2Modifier(); - IFDEBUG - { - cout << endl; - IFPROF cout << "(" << setw(5) << dec << timeBetweenTimepointsUS(profiler.timepointPreviousKeyEvent, profiler.timepointLoopStart) / 1000 << " m) "; - printLoopState1Input(); - } + //evaluate modified keys + processCombos(); - //evaluate modifiers - processModifierState(); - - IFDEBUG printLoopState2Modifier(); + //alphakeys: basic character key layout. Don't remap the Ctrl combos? + processMapAlphaKeys(); - //evaluate modified keys - processCombos(); + //break tapped state? + if (!isModifier(loopState.vcode)) + modifierState.modifierTapped = 0; - //alphakeys: basic character key layout. Don't remap the Ctrl combos? - processMapAlphaKeys(); + IFPROF + { + unsigned long mappingtime = profiler.stopwatchRestart(); + profiler.totalMappingTimeUS += mappingtime; + profiler.countOutgoing++; + if (mappingtime > profiler.worstMappingTimeUS) + profiler.worstMappingTimeUS = mappingtime; + IFDEBUG printLoopStateMappingTime(mappingtime); + } - //break tapped state? - if (!isModifier(loopState.vcode)) - modifierState.modifierTapped = 0; + sendResultingKeyOrSequence(); + IFPROF + { + unsigned long sendingtime = profiler.stopwatchReadUS(); + profiler.totalSendingTimeUS += sendingtime; + if (sendingtime > profiler.worstSendingTimeUS) + profiler.worstSendingTimeUS = sendingtime; + if (sendingtime > 1000) + cout << "\t (slow send: " << dec << sendingtime << " u)"; + } - IFPROF - { - unsigned long mappingtime = profiler.stopwatchRestart(); - profiler.totalMappingTimeUS += mappingtime; - profiler.countOutgoing++; - if (mappingtime > profiler.worstMappingTimeUS) - profiler.worstMappingTimeUS = mappingtime; - IFDEBUG printLoopStateMappingTime(mappingtime); + IFDEBUG printLoopState4TapState(); + } } - - sendResultingKeyOrSequence(); - IFPROF + else { - unsigned long sendingtime = profiler.stopwatchReadUS(); - profiler.totalSendingTimeUS += sendingtime; - if (sendingtime > profiler.worstSendingTimeUS) - profiler.worstSendingTimeUS = sendingtime; - if (sendingtime > 1000) - cout << "\t (slow send: " << dec << sendingtime << " u)"; + // TODO: do other stuff in the loop } - - IFDEBUG printLoopState4TapState(); } interception_destroy_context(interceptionState.interceptionContext); @@ -534,7 +748,7 @@ void betaTest() //ESC+B // // Press a unicode "key" // ip.ki.dwFlags = KEYEVENTF_UNICODE; // ip.ki.wVk = 0; - // ip.ki.wScan = 0x0E8; // è + // ip.ki.wScan = 0x0E8; // è // SendInput(1, &ip, sizeof(INPUT)); // // Release key @@ -722,12 +936,17 @@ void detectTapping() loopState.tapHoldMake = true; } + if ((interceptionState.currentIKstroke.state & 1) == 0 && + (interceptionState.previousIKstroke1.state & 1) == 0 && + interceptionState.previousIKstroke1.code == interceptionState.currentIKstroke.code) + loopState.repeat = true; + //cannot detect tapHold Break here. This is done by ProcessRewire() } void processModifierState() { - unsigned short modBitmask = getModifierBitmaskForVcode(loopState.vcode); + MOD modBitmask = getModifierBitmaskForVcode(loopState.vcode); //set internal modifier state if (loopState.isDownstroke) @@ -739,6 +958,8 @@ void processModifierState() //Tapped mod key sets tapped bitmask. You can combine mod-taps (like tap-Ctrl then tap-Alt). if (loopState.tapped) modifierState.modifierTapped |= modBitmask; + + modifierState.modifierDown |= modifierState.modifierForceDown; } //handle all REWIRE configs. Rewire to new vcode; check for Tapped rules @@ -769,7 +990,7 @@ void processRewireScancodeToVirtualcode() //clear the 'modifier down' state for preceding "to mod" def if (isModifier(loopState.vcode)) { - unsigned short modBitmask = getModifierBitmaskForVcode(loopState.vcode); + MOD modBitmask = getModifierBitmaskForVcode(loopState.vcode); if (modBitmask != 0) modifierState.modifierDown &= ~modBitmask; //undo previous key down, e.g. clear internal 'MOD10 is down' } @@ -795,11 +1016,11 @@ void processRewireScancodeToVirtualcode() //clear the preceding tapped state(s) int rewtappedkey = allMaps.rewiremap[loopState.scancode][REWIRE_TAP]; //1. Tap&Hold of a key rewired to modifier always first triggers the generic "modifier tapped" - unsigned short modBitmask1 = getModifierBitmaskForVcode(rewoutkey); + MOD modBitmask1 = getModifierBitmaskForVcode(rewoutkey); if (modBitmask1 != 0) modifierState.modifierTapped &= ~modBitmask1; //2. Explicit "Rewire in out ifTapped" (should probably never combine ifTapped with ifTappedAndHold, but not sure) - unsigned short modBitmask2 = getModifierBitmaskForVcode(rewtappedkey); + MOD modBitmask2 = getModifierBitmaskForVcode(rewtappedkey); if (modBitmask2 != 0) modifierState.modifierTapped &= ~modBitmask2; @@ -834,29 +1055,57 @@ void processRewireScancodeToVirtualcode() loopState.isModifier = isModifier(loopState.vcode) ? true : false; } +bool testDeviceMask(DEV maskAnd, DEV maskNot, int dev) +{ + if (maskAnd == 0xFFFFFFFF && maskNot == 0) + return true; + if (dev < 1 || dev > INTERCEPTION_MAX_DEVICE) + return false; + DEV mask = 1 << (dev - 1); + if ((mask & maskAnd) == mask && (mask & maskNot) == 0) + return true; + return false; +} void processCombos() { - if (!loopState.isDownstroke) //this check breaks 'x []' : // || (modifierState.modifierDown == 0 && modifierState.modifierTapped == 0 && modifierState.activeDeadkey == 0)) - return; - - for (ModifierCombo modcombo : allMaps.modCombos) - { - if (modcombo.vkey == loopState.vcode) + auto process = [](vector &combos, bool clearTapped = false){ + for (ModifierCombo modcombo : combos) { - if ( - (modifierState.activeDeadkey == modcombo.deadkey) && - (modifierState.modifierDown & modcombo.modAnd) == modcombo.modAnd && - (modcombo.modOr == 0 || (modifierState.modifierDown & modcombo.modOr) > 0) && - (modifierState.modifierDown & modcombo.modNot) == 0 && - ((modifierState.modifierTapped & modcombo.modTap) == modcombo.modTap) - ) + if (modcombo.vkey == loopState.vcode && testDeviceMask(modcombo.devAnd, modcombo.devNot, interceptionState.interceptionDevice)) { - loopState.resultingVKeyEventSequence = modcombo.keyEventSequence; - modifierState.modifierTapped = 0; - break; + if ( + (modifierState.activeDeadkey == modcombo.deadkey) && + (modifierState.modifierDown & modcombo.modAnd) == modcombo.modAnd && + (modcombo.modOr == 0 || (modifierState.modifierDown & modcombo.modOr) > 0) && + (modifierState.modifierDown & modcombo.modNot) == 0 && + (modifierState.modifierTapped & modcombo.modTap) == modcombo.modTap && + ((modifierState.modifierTapped & modcombo.modTapAnd) == modcombo.modTapAnd || + (modifierState.modifierDown & modcombo.modTapAnd) == modcombo.modTapAnd) + ) + { + loopState.resultingVKeyEventSequence = modcombo.keyEventSequence; + if (clearTapped) + modifierState.modifierTapped = 0; + break; + } } } + }; + + if (loopState.isDownstroke) + { + process(allMaps.modCombos[INI_TAG_COMBOS], true); + if (loopState.repeat) + process(allMaps.modCombos[INI_TAG_REPEATCOMBOS]); + } + else + { + process(allMaps.modCombos[INI_TAG_UPCOMBOS]); + if (loopState.tappedSlow) + process(allMaps.modCombos[INI_TAG_SLOWCOMBOS]); + if (loopState.tapped) + process(allMaps.modCombos[INI_TAG_TAPCOMBOS]); } if(!loopState.isModifier) modifierState.activeDeadkey = 0; @@ -961,13 +1210,13 @@ bool processCommand() break; case SC_R: cout << "RELOAD INI"; - reload(); getHardwareId(); + reload(); cout << endl << (globalState.deviceIsAppleKeyboard ? "APPLE keyboard (flipping Win<>Alt)" : "PC keyboard"); break; case SC_Y: cout << "Stop AHK"; - closeOrKillProgram("autohotkey.exe"); + unloadAHK(); break; case SC_I: { @@ -980,9 +1229,7 @@ bool processCommand() case SC_A: { cout << "Start AHK"; - string msg = startProgramSameFolder("autohotkey.exe"); - if (msg != "") - cout << endl << "Cannot start: " << msg; + loadAHK(); break; } case SC_S: @@ -1060,8 +1307,24 @@ bool processCommand() options.delayForKeySequenceMS += 1; cout << "delay between characters in key sequences (ms): " << dec << options.delayForKeySequenceMS; break; - case SC_B: + /*case SC_B: betaTest(); + break;*/ + case SC_M: + options.enableMouse ^= true; + if (interceptionState.interceptionContext) + { + if (options.enableMouse) + { + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_ALL & ~INTERCEPTION_FILTER_MOUSE_MOVE); + cout << endl << "MOUSE INPUT ENABLED"; + } + else + { + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_NONE); + cout << endl << "MOUSE INPUT DISABLED"; + } + } break; default: { @@ -1079,35 +1342,37 @@ bool processCommand() -void getHardwareId() +std::map* getHardwareId(bool refresh) { + if (refresh) { - wchar_t hardware_id[500] = { 0 }; - string id; - size_t length = interception_get_hardware_id(interceptionState.interceptionContext, interceptionState.interceptionDevice, hardware_id, sizeof(hardware_id)); - if (length > 0 && length < sizeof(hardware_id)) + allMaps.devices.clear(); + for (int i = 0; i <= INTERCEPTION_MAX_DEVICE; ++i) { - //forced conversion will replace special characters > 127 with "?" - for (wchar_t c : hardware_id) + wchar_t hardware_id[500] = { 0 }; + string id; + size_t length = interception_get_hardware_id(interceptionState.interceptionContext, i, hardware_id, sizeof(hardware_id)); + if (length > 0 && length < sizeof(hardware_id)) { - if (c > 127) - id += '?'; - else if (c == 0) - break; - else - id += (char)c; - } - } - else - id = "UNKNOWN_ID"; - - id = stringToLower(id); - globalState.deviceIdKeyboard = id; - globalState.deviceIsAppleKeyboard = (id.find("vid_05ac") != string::npos) || (id.find("vid&000205ac") != string::npos); - - IFDEBUG cout << endl << endl << "getHardwareId:" << id << " / Apple keyboard: " << globalState.deviceIsAppleKeyboard; + //forced conversion will replace special characters > 127 with "?" + for (wchar_t c : hardware_id) + { + if (c > 127) + id += '?'; + else if (c == 0) + break; + else + id += (char)c; + } + } + else + continue; + id = stringToLower(id); + allMaps.devices[i] = { id, (bool)interception_is_keyboard(i), (id.find("vid_05ac") != string::npos) || (id.find("vid&000205ac") != string::npos) }; + } } - } + return &allMaps.devices; +} bool initConsoleWindow() @@ -1184,6 +1449,24 @@ void parseIniGlobals() globals.protectConsole = false; else if ((token == "activeconfigonstartup") || (token == "activelayeronstartup")) cout << endl; + else if (token == "disableesckey") + { + auto keys = stringSplit(stringGetRestBehindFirstToken(line), ' '); + for (auto s : keys) + { + int key = getVcode(s, PRETTY_VK_LABELS); + globals.disableEscKey.insert(key); + } + } + else if (token == "forwardesckey") + { + auto keys = stringSplit(stringGetRestBehindFirstToken(line), ' '); + for (auto s : keys) + { + int key = getVcode(s, PRETTY_VK_LABELS); + globals.forwardEscKey.insert(key); + } + } else cout << endl << "WARNING: unknown GLOBAL " << token; } @@ -1266,6 +1549,22 @@ bool parseIniOptions(std::vector assembledIni) << endl << " COMBO LSHF [& ....] > key(CAPSOFF)" << endl << " COMBO RSHF[.&] > key(CAPSON)" << endl; } + else if (token == "holdrepeatsallkeys") + { + options.holdRepeatsAllKeys = true; + } + else if (token == "disableahkdelay") + { + options.disableAHKDelay = true; + } + else if (token == "defaultfunction") + { + options.defaultFunction = stringGetRestBehindFirstToken(line); + } + else if (token == "enablemouse") + { + options.enableMouse = true; + } else { cout << endl << "WARNING: ignoring unknown OPTION " << line << endl; @@ -1313,56 +1612,68 @@ void parseIniRewires(std::vector assembledIni) bool parseIniCombos(std::vector assembledIni) { - allMaps.modCombos.clear(); - vector sectLines = getTaggedLinesFromIni(INI_TAG_COMBOS, assembledIni); - if (sectLines.size() == 0) - return false; - - unsigned short mods[5] = { 0 }; //deadkey, and, or, not, tap - vector keyEventSequence; - - for (string line : sectLines) - { - int key; - if (parseKeywordCombo(line, key, mods, keyEventSequence, PRETTY_VK_LABELS)) - { - bool isDuplicate = false; - for (ModifierCombo testcombo : allMaps.modCombos) + auto parseSect = [](vector& sectLines, vector &combos) { + MOD mods[6] = { 0 }; //deadkey, and, or, not, tap, tap/and + vector keyEventSequence; + for (string line : sectLines) + { + int key; + DEV devs[2] = { 0xFFFFFFFF, 0 }; + if (parseKeywordCombo(line, key, mods, devs, keyEventSequence, PRETTY_VK_LABELS, options.defaultFunction)) { - if (key == testcombo.vkey && mods[0] == testcombo.deadkey && mods[1] == testcombo.modAnd - && mods[2] == testcombo.modOr && mods[3] == testcombo.modNot && mods[4] == testcombo.modTap) + bool isDuplicate = false; + for (ModifierCombo testcombo : combos) { - //warn only if the combos are different - bool redefined = false; - if (testcombo.keyEventSequence.size() == keyEventSequence.size()) + if (key == testcombo.vkey && devs[0] == testcombo.devAnd && devs[1] == testcombo.devNot && mods[0] == testcombo.deadkey && mods[1] == testcombo.modAnd + && mods[2] == testcombo.modOr && mods[3] == testcombo.modNot && mods[4] == testcombo.modTap && mods[5] == testcombo.modTapAnd) { - for (int i = 0; i < keyEventSequence.size(); i++) + //warn only if the combos are different + bool redefined = false; + if (testcombo.keyEventSequence.size() == keyEventSequence.size()) { - if (keyEventSequence[i].vcode != testcombo.keyEventSequence[i].vcode - || keyEventSequence[i].isDownstroke != testcombo.keyEventSequence[i].isDownstroke) + for (int i = 0; i < keyEventSequence.size(); i++) { - redefined = true; - break; + if (keyEventSequence[i].vcode != testcombo.keyEventSequence[i].vcode + || keyEventSequence[i].isDownstroke != testcombo.keyEventSequence[i].isDownstroke) + { + redefined = true; + break; + } } } - } - else - redefined = true; + else + redefined = true; - if(redefined) - cout << endl << "WARNING: Ignoring redefinition of Combo: " << line; + if(redefined) + cout << endl << "WARNING: Ignoring redefinition of Combo: " << line; - isDuplicate = true; - break; + isDuplicate = true; + break; + } } + if(!isDuplicate) + combos.push_back({ key, (unsigned char) mods[0], mods[1], mods[2], mods[3], mods[4], mods[5], devs[0], devs[1], keyEventSequence }); } - if(!isDuplicate) - allMaps.modCombos.push_back({ key, (unsigned char) mods[0], mods[1], mods[2], mods[3], mods[4], keyEventSequence }); + else + error("Cannot parse combo rule: " + line); } - else - error("Cannot parse combo rule: " + line); + return sectLines.size(); + }; + + for (auto& kv : allMaps.modCombos) + kv.second.clear(); + + size_t totalLines = 0; + { + auto combolines = getTaggedLinesFromIni("COMBO", assembledIni); + totalLines += parseSect(combolines, allMaps.modCombos[INI_TAG_COMBOS]); } - return true; + for (auto& kv : allMaps.modCombos) + { + auto lines = getTaggedLinesFromIni(kv.first, assembledIni); + totalLines += parseSect(lines, kv.second); + } + return totalLines > 0; } bool parseIniAlphaLayout(std::vector assembledIni) @@ -1445,9 +1756,65 @@ std::vector assembleConfig(int config) return assembledIni; } +void parseIniExecutables(std::vector assembledIni) +{ + allMaps.executables.clear(); + vector sectLines = getTaggedLinesFromIni(INI_TAG_EXE, assembledIni); + int tagCounter = 0; + for (string line : sectLines) + { + size_t idIdx = line.find_first_of(' '); + if (idIdx == string::npos) + { + error("Invalid EXE: " + line); + continue; + } + int id; + if (!stringToInt(line.substr(0, idIdx), id)) + { + error("Invalid EXE: " + line); + continue; + } + stringstream paramss(line.substr(idIdx + 1)); + string param; + vector params; + while(getline(paramss, param, ',')) + { + ltrim(param); + rtrim(param); + params.push_back(param); + } + if (params.size() < 2) + { + error("Invalid EXE: " + line); + continue; + } + string verb = params[0]; + string path = params[1]; + string args; + string dir; + int mode = SW_SHOWDEFAULT; + if (params.size() > 2) + args = params[2]; + if (params.size() > 3) + dir = params[3]; + if (params.size() > 4) + stringToInt(params[4], mode); + + allMaps.executables[id] = {verb, path, args, dir, mode, NULL}; + tagCounter++; + } + IFDEBUG cout << endl << "Exe Definitions: " << dec << tagCounter; +} + + void initializeAllMaps() { - allMaps.modCombos.clear(); + for (auto kv : allMaps.modCombos) + kv.second.clear(); + + allMaps.executables.clear(); + getHardwareId(); //resetAlphamap() { @@ -1488,8 +1855,14 @@ bool parseProcessIniConfig(int config) parseIniRewires(assembledConfig); + parseIniExecutables(assembledConfig); + parseIniCombos(assembledConfig); - IFDEBUG cout << endl << "Combo Definitions: " << dec << allMaps.modCombos.size(); + IFDEBUG cout << endl << "Down Definitions: " << dec << allMaps.modCombos[INI_TAG_COMBOS].size(); + IFDEBUG cout << endl << "Up Definitions: " << dec << allMaps.modCombos[INI_TAG_UPCOMBOS].size(); + IFDEBUG cout << endl << "Tap Definitions: " << dec << allMaps.modCombos[INI_TAG_TAPCOMBOS].size(); + IFDEBUG cout << endl << "Slow Definitions: " << dec << allMaps.modCombos[INI_TAG_SLOWCOMBOS].size(); + IFDEBUG cout << endl << "Repeat Definitions: " << dec << allMaps.modCombos[INI_TAG_REPEATCOMBOS].size(); parseIniAlphaLayout(assembledConfig); IFDEBUG @@ -1534,6 +1907,14 @@ void switchConfig(int config, bool forceReloadSameConfig) globalState.activeConfigName = DISABLED_CONFIG_NAME; } + if (interceptionState.interceptionContext) + { + if (options.enableMouse) + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_ALL & ~INTERCEPTION_FILTER_MOUSE_MOVE); + else + interception_set_filter(interceptionState.interceptionContext, interception_is_mouse, INTERCEPTION_FILTER_MOUSE_NONE); + } + updateTrayIcon(true, globalState.recordingMacro >= 0, globalState.activeConfig); cout << endl << endl << "ACTIVE CONFIG: " << globalState.activeConfig << " = " << globalState.activeConfigName; } @@ -1558,7 +1939,7 @@ void reset() loopState = defaultLoopState; modifierState = defaultModifierState; - + IFPROF { chrono::steady_clock::time_point tps = profiler.timepointStopwatch; @@ -1590,6 +1971,12 @@ void reload() readSanitizeIniFile(sanitizedIniContent); parseIniGlobals(); + + if (globals.startAHK) + loadAHK(); + else + unloadAHK(); + switchConfig(globalState.activeConfig, true); } @@ -1597,8 +1984,13 @@ void reload() void releaseAllSentKeys() { IFDEBUG cout << endl << "Resetting all sent DOWN keys to UP: " << endl; - for (int i = 0; i < 255; i++) + + modifierState.modifierForceDown = 0; + + // release backwards to release modifiers and esc last + for (int i = 255; i >= 0; --i) { + globalState.holdKeys[i].clear(); if (globalState.keysDownSent[i]) { sendVKeyEvent({ i, false }); @@ -1667,6 +2059,19 @@ void printStatus() << endl << "Worst sending time: " << profiler.worstSendingTimeUS ; + IFDEBUG { + cout << endl << endl << "Interception keyboards:"; + for (int i = 1; i <= INTERCEPTION_MAX_DEVICE; ++i) + { + if (allMaps.devices.find(i) != allMaps.devices.end()) + { + if (i == 11) + cout << endl << "Interception mice:"; + cout << endl << i << ": " << allMaps.devices[i].id; + } + } + } + printOptions(); } @@ -1682,7 +2087,7 @@ void printLoopState1Input() { cout << " [" - << hex << setw(2) << interceptionState.currentIKstroke.code << " " << interceptionState.currentIKstroke.state + << dec << setw(2) << interceptionState.interceptionDevice << " " << hex << setw(2) << interceptionState.currentIKstroke.code << " " << interceptionState.currentIKstroke.state << "= " << setw(8) << (loopState.vcode == loopState.scancode ? "" : PRETTY_VK_LABELS[loopState.scancode] + " > ") << setw(8) << getPrettyVKLabel(loopState.vcode) << setw(2) << left << getSymbolForIKStrokeState(interceptionState.currentIKstroke.state) << right << "] "; @@ -1692,8 +2097,8 @@ void printLoopState2Modifier() { string mdown = modifierState.modifierDown > 0 ? stringIntToHex(modifierState.modifierDown,0) : ""; string mtapp = modifierState.modifierTapped > 0 ? stringIntToHex(modifierState.modifierTapped,0) : ""; - cout << "[M:" << setw(4) << mdown - << " T:" << setw(4) << mtapp + cout << "[M:" << setw(8) << mdown + << " T:" << setw(8) << mtapp << " D:" << setw(6) << (modifierState.activeDeadkey > 0 ? getPrettyVKLabel(modifierState.activeDeadkey): "") << "] "; } @@ -1740,7 +2145,8 @@ void printHelp() << "[Y] autohotkeY stop" << endl << "[J][K][L][;] Macro Recording: Start,Stop,Playback,Copy macro definition to clipboard." << endl << "[,] and [.]: delay between keys in sequences -/+ 1ms " << endl - << "[Q] (dev feature) Stop the debug build if both release and debug are running" << endl + /*<< "[Q] (dev feature) Stop the debug build if both release and debug are running" << endl*/ + << "[M] Toggle mouse input support" << endl << endl << "These commands work anywhere, Capsicain does not have to be the active window." ; } @@ -1865,6 +2271,90 @@ void sendResultingKeyOrSequence() } } +bool runExecutable(Executable &exe) +{ + char path[MAX_PATH]; + char args[MAX_PATH]; + char dir[MAX_PATH]; + ZeroMemory(path, MAX_PATH); + ZeroMemory(args, MAX_PATH); + ZeroMemory(dir, MAX_PATH); + ExpandEnvironmentStringsA(exe.path.c_str(), path, MAX_PATH); + ExpandEnvironmentStringsA(exe.args.c_str(), args, MAX_PATH); + ExpandEnvironmentStringsA(exe.dir.c_str(), dir, MAX_PATH); + + SHELLEXECUTEINFOA info = {0}; + info.cbSize = sizeof(SHELLEXECUTEINFO); + info.fMask = SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS; + info.lpVerb = exe.verb.c_str(); + info.lpFile = path; + info.lpParameters = args; + info.lpDirectory = dir; + info.nShow = exe.mode; + ShellExecuteExA(&info); + exe.proc = info.hProcess; + exe.pid = GetProcessId(exe.proc); + exe.proc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, exe.pid); + auto ret = (INT_PTR)info.hInstApp > 32; + return ret; +} + +void sendAHK(std::string msg) +{ + if (!ahk.handle) + { + IFDEBUG error("AHK dll is not loaded"); + return; + } + if (!ahk.threadid) + { + IFDEBUG error("AHK thread not started"); + return; + } + auto args = stringSplit(msg, ','); + std::vector wargs; + for (int i = 0; i <= 10; ++i) + { + if (i < args.size()) + wargs.push_back(widen(args[i])); + else + wargs.push_back(L""); + } + auto wmsg = widen(msg); + ahk.postFunction(wargs[0].c_str(), wargs[1].c_str(), wargs[2].c_str(), wargs[3].c_str(), wargs[4].c_str(), wargs[5].c_str(), wargs[6].c_str(), wargs[7].c_str(), wargs[8].c_str(), wargs[9].c_str(), wargs[10].c_str(), ahk.threadid); +} + +void killExecutableByPath(string path) +{ + auto slash = path.find_last_of("\\/"); + if (slash != string::npos) + path = path.substr(slash + 1); + string ext = ".exe"; + if (!std::equal(ext.rbegin(), ext.rend(), path.rbegin())) + path = path + ext; + closeOrKillProgram(path); +} + +void killExecutable(Executable &exe) +{ + if (exe.pid && GetProcessId(exe.proc) == exe.pid) + { + HANDLE hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, exe.pid); + EnumWindows((WNDENUMPROC)TerminateAppEnum, (LPARAM)exe.pid); + int result = 1; // 0=fail; 1=close; 2=kill + if (WaitForSingleObject(hProc, 1000) != WAIT_OBJECT_0) + result = (TerminateProcess(hProc, 0) ? 2 : 0); + CloseHandle(hProc); + } + else + { + killExecutableByPath(exe.path); + } + exe.proc = 0; + exe.pid = 0; + exe.hwnd = 0; +} + //Send out all keys in a sequence //Sequences are created for anything that requires more than one key event, like AltChar(123) //Catch and process CPS virtual keys that have a value following in the next key @@ -1885,10 +2375,12 @@ void playKeyEventSequence(vector keyEventSequence) IFDEBUG if (!globalState.secretSequencePlayback && keyEventSequence.at(0).vcode != VK_CPS_OBFUSCATED_SEQUENCE_START) - cout << " --> SEQUENCE (" << dec << keyEventSequence.size() << ") "; + cout << " --> SEQUENCE (" << dec << keyEventSequence.size() << ") "; for (VKeyEvent keyEvent : keyEventSequence) { + // can be changed during key sequence by delay() + delayBetweenKeyEventsMS = options.delayForKeySequenceMS; int vc = keyEvent.vcode; if (globalState.secretSequencePlayback) vc = deObfuscateVKey(vc); @@ -1896,6 +2388,7 @@ void playKeyEventSequence(vector keyEventSequence) //test if this is the param for the preceding func key in "command + value" sequence if (expectParamForFuncKey != -1) { + IFDEBUG cout << "{" + PRETTY_VK_LABELS[expectParamForFuncKey] + "}"; switch (expectParamForFuncKey) { case VK_CPS_SLEEP: @@ -1957,6 +2450,118 @@ void playKeyEventSequence(vector keyEventSequence) } break; } + case VK_CPS_HOLDKEY: + { + IFTRACE cout << endl << "vk_cps_holdkey: " << getPrettyVKLabelPadded(loopState.vcode, 0) << " -> " << getPrettyVKLabelPadded(vc, 0); + if (!getKeyHolding(vc)) + { + globalState.holdKeys[loopState.vcode].emplace(vc); + sendVKeyEvent(keyEvent, false); + } + else + sendVKeyEvent(keyEvent); + break; + } + case VK_CPS_HOLDMOD: + { + if (!getKeyHolding(vc)) + { + if (modifierState.modifierDown) + { + for (int i = 0; i < NUMBER_OF_MODIFIERS; i++) + { + MOD mask = 1 << i; + if (modifierState.modifierDown & mask) + { + int mod = getModifierForBitmask(mask); + IFTRACE cout << endl << "vk_cps_holdmod: " << getPrettyVKLabelPadded(mod, 0) << " -> " << getPrettyVKLabelPadded(vc, 0); + globalState.holdKeys[mod].emplace(vc); + break; + } + } + } + else + { + globalState.holdKeys[loopState.vcode].emplace(vc); + } + sendVKeyEvent(keyEvent, false); + } + else + sendVKeyEvent(keyEvent); + break; + } + case VK_CPS_DELAY: + IFTRACE cout << endl << "vk_cps_delay: " << vc; + options.delayForKeySequenceMS = vc; + break; + case VK_CPS_KEYDOWN: + if (vc < 0xFF) + sendVKeyEvent(keyEvent); + if (isModifier(vc)) + { + if (keyEvent.isDownstroke) + { + modifierState.modifierForceDown |= getModifierBitmaskForVcode(vc); + modifierState.modifierDown |= modifierState.modifierForceDown; + } + else + { + modifierState.modifierForceDown &= ~getModifierBitmaskForVcode(vc); + modifierState.modifierDown &= modifierState.modifierForceDown; + } + } + break; + case VK_CPS_KEYTOGGLE: + bool state; + if (isModifier(vc)) + { + auto mask = getModifierBitmaskForVcode(vc); + state = modifierState.modifierForceDown & mask; + modifierState.modifierForceDown ^= mask; + modifierState.modifierDown &= modifierState.modifierForceDown; + } + else + { + state = globalState.keysDownSent[vc & 0xFF]; + } + if (vc < 0xFF) + sendVKeyEvent({vc, !state}); + break; + case VK_CPS_KEYTAP: + if (!isModifier(vc)) + break; + if (keyEvent.isDownstroke) + modifierState.modifierTapped |= getModifierBitmaskForVcode(vc); + else + modifierState.modifierTapped &= ~getModifierBitmaskForVcode(vc); + break; + case VK_CPS_EXECUTE: + { + if (allMaps.executables.find(vc) == allMaps.executables.end()) + { + IFDEBUG cout << "Can't find executable " << vc << endl; + break; + } + runExecutable(allMaps.executables[vc]); + break; + } + case VK_CPS_KILL: + { + if (allMaps.executables.find(vc) == allMaps.executables.end()) + { + IFDEBUG cout << "Can't find executable " << vc << endl; + break; + } + killExecutable(allMaps.executables[vc]); + break; + } + case VK_CPS_SENDAHK: + { + auto msg = getAHKmsg(vc); + if (msg != "") + sendAHK(msg); + break; + } default: cout << endl << "BUG? unknown expectParamForFuncKey"; } @@ -1990,6 +2595,12 @@ void playKeyEventSequence(vector keyEventSequence) } } } + else if (vc == VK_CPS_RELEASEKEYS) //release all keys that are physically down + { + for (int i = 0; i <= 255; i++) + if (globalState.keysDownSent[i]) + sendVKeyEvent({ i, false }, false); + } //func key with param; wait for next key which is the param else if (vc == VK_CPS_SLEEP || vc == VK_CPS_DEADKEY @@ -1997,6 +2608,15 @@ void playKeyEventSequence(vector keyEventSequence) || vc == VK_CPS_RECORDMACRO || vc == VK_CPS_RECORDSECRETMACRO || vc == VK_CPS_PLAYMACRO + || vc == VK_CPS_HOLDKEY + || vc == VK_CPS_HOLDMOD + || vc == VK_CPS_DELAY + || vc == VK_CPS_KEYDOWN + || vc == VK_CPS_KEYTOGGLE + || vc == VK_CPS_KEYTAP + || vc == VK_CPS_EXECUTE + || vc == VK_CPS_KILL + || vc == VK_CPS_SENDAHK ) { expectParamForFuncKey = vc; @@ -2007,7 +2627,7 @@ void playKeyEventSequence(vector keyEventSequence) sendVKeyEvent({ deObfuscateVKey(keyEvent.vcode) , keyEvent.isDownstroke }); else sendVKeyEvent(keyEvent); - if (vc == AHK_HOTKEY1 || vc == AHK_HOTKEY2) + if (!options.disableAHKDelay && (vc == AHK_HOTKEY1 || vc == AHK_HOTKEY2)) Sleep(DEFAULT_DELAY_FOR_AHK_MS); else Sleep(delayBetweenKeyEventsMS); @@ -2020,8 +2640,97 @@ void playKeyEventSequence(vector keyEventSequence) error("BUG: func key with param: " + getPrettyVKLabel(expectParamForFuncKey) + "is unfinished"); } +int getKeyHolding(int vcode) +{ + for (int i = 0; i < VK_MAX; i++) + { + if (globalState.holdKeys[i].find(vcode) != globalState.holdKeys[i].end()) + return i; + } + return 0; +} + +std::string getHoldKeyString(std::set &v, std::string delim) +{ + std::string out; + for (auto it = v.rbegin(); it != v.rend(); ++it) + { + out += PRETTY_VK_LABELS[*it]; + out += delim; + } + for (int i = 0; i < delim.size(); ++i) + out.pop_back(); + return out; +} + +map KEY_TO_MOUSE{ + { VM_LEFT, INTERCEPTION_MOUSE_LEFT_BUTTON_DOWN }, + { VM_RIGHT, INTERCEPTION_MOUSE_RIGHT_BUTTON_DOWN }, + { VM_MIDDLE, INTERCEPTION_MOUSE_MIDDLE_BUTTON_DOWN }, + { VM_BUTTON4, INTERCEPTION_MOUSE_BUTTON_4_DOWN }, + { VM_BUTTON5, INTERCEPTION_MOUSE_BUTTON_5_DOWN }, +}; +bool vkeyToMouse(VKeyEvent keyEvent) +{ + if (keyEvent.vcode < VM_LEFT || keyEvent.vcode > VM_WHEEL_RIGHT) + return false; + + InterceptionMouseStroke mstroke{0}; + + if (keyEvent.vcode >= VM_LEFT && keyEvent.vcode <= VM_BUTTON5) + { + mstroke.state = KEY_TO_MOUSE[keyEvent.vcode]; + if (!keyEvent.isDownstroke) + mstroke.state = mstroke.state << 1; + } + else if (keyEvent.vcode == VM_WHEEL_UP) + { + mstroke.state = INTERCEPTION_MOUSE_WHEEL; + mstroke.rolling = 120; //FIXME + } + else if (keyEvent.vcode == VM_WHEEL_DOWN) + { + mstroke.state = INTERCEPTION_MOUSE_WHEEL; + mstroke.rolling = -120; //FIXME + } + else if (keyEvent.vcode == VM_WHEEL_LEFT) + { + mstroke.state = INTERCEPTION_MOUSE_HWHEEL; + mstroke.rolling = -120; //FIXME + } + else if (keyEvent.vcode == VM_WHEEL_RIGHT) + { + mstroke.state = INTERCEPTION_MOUSE_HWHEEL; + mstroke.rolling = 120; //FIXME + } + else + { + return false; + } + + if (mstroke.rolling && !keyEvent.isDownstroke) + return true; -void sendVKeyEvent(VKeyEvent keyEvent) + int dev; + if (interception_is_mouse(interceptionState.interceptionDevice)) + { + dev = interceptionState.interceptionDevice; + } + else if(interceptionState.lastMouse) + { + dev = interceptionState.lastMouse; + } + else + { + cout << endl << "Error: Don't know which mouse to send this to, use one!"; + return false; + } + + interception_send(interceptionState.interceptionContext, dev, (InterceptionStroke *)&mstroke, 1); + return true; +} + +void sendVKeyEvent(VKeyEvent keyEvent, bool hold) { IFTRACE cout << endl << "sendVkeyEvent(" << keyEvent.vcode << ")"; if (keyEvent.vcode < 0) @@ -2029,11 +2738,43 @@ void sendVKeyEvent(VKeyEvent keyEvent) cout << endl << "BUG: vcode<0"; return; } + if (keyEvent.vcode == 0) { - cout << endl << "{blocked NOP}"; + IFDEBUG cout << endl << "{blocked NOP}"; return; } + + if (globalState.holdKeys[keyEvent.vcode].size() && hold) + { + int code = keyEvent.vcode; + set release; + IFDEBUG cout << " {" << PRETTY_VK_LABELS[code] << (keyEvent.isDownstroke ? " holding " : " released ") << globalState.holdKeys[code].size() << ": " << getHoldKeyString(globalState.holdKeys[code], "+") << "}"; + if (keyEvent.isDownstroke) + { + if (options.holdRepeatsAllKeys) + { + for (auto it = globalState.holdKeys[code].begin(); it != globalState.holdKeys[code].end(); ++it) + sendVKeyEvent({*it, true}, false); + } + else + { + sendVKeyEvent({*globalState.holdKeys[code].begin(), true}, false); + } + return; + } + else + { + for (auto it = globalState.holdKeys[code].rbegin(); it != globalState.holdKeys[code].rend(); ++it) + release.emplace(*it); + globalState.holdKeys[code].clear(); + for (auto key : release) + sendVKeyEvent({key, false}, false); + if (release.find(keyEvent.vcode) != release.end()) + return; + } + } + if (keyEvent.vcode > 0xFF || keyEvent.vcode == VK_CPS_PAUSE) { sendCapsicainCodeHandler(keyEvent); @@ -2051,13 +2792,23 @@ void sendVKeyEvent(VKeyEvent keyEvent) return; } + auto holdingkey = getKeyHolding(scancode); + if (!keyEvent.isDownstroke && holdingkey) //ignore up when other key is holding it + { + IFDEBUG cout << " {blocked " << PRETTY_VK_LABELS[scancode] << " UP: " << PRETTY_VK_LABELS[holdingkey] << " is holding}"; + return; + } + //consistency check - if (globalState.keysDownSent[scancode] == 0 && keyEvent.isDownstroke) - globalState.keysDownSentCounter++; - else if (globalState.keysDownSent[scancode] == 1 && !keyEvent.isDownstroke) - globalState.keysDownSentCounter--; + if (scancode < VM_WHEEL_UP) + { + if (globalState.keysDownSent[scancode] == 0 && keyEvent.isDownstroke) + globalState.keysDownSentCounter++; + else if (globalState.keysDownSent[scancode] == 1 && !keyEvent.isDownstroke) + globalState.keysDownSentCounter--; - globalState.keysDownSent[scancode] = keyEvent.isDownstroke; + globalState.keysDownSent[scancode] = keyEvent.isDownstroke; + } //handle live macro recording if (globalState.recordingMacro >= 0) @@ -2082,14 +2833,32 @@ void sendVKeyEvent(VKeyEvent keyEvent) } } } - + + if (vkeyToMouse(keyEvent)) + return; + InterceptionKeyStroke iks = convertVkeyEvent2ikstroke(keyEvent); //hide secret macro recording? IFDEBUG if(!globalState.secretSequencePlayback) cout << " {" << PRETTY_VK_LABELS[keyEvent.vcode] << (keyEvent.isDownstroke ? "v" : "^") << " #" << globalState.keysDownSentCounter << "}"; - interception_send(interceptionState.interceptionContext, interceptionState.interceptionDevice, (InterceptionStroke *)&iks, 1); + int dev; + if (interception_is_keyboard(interceptionState.interceptionDevice)) + { + dev = interceptionState.interceptionDevice; + } + else if(interceptionState.lastKeyboard) + { + dev = interceptionState.lastKeyboard; + } + else + { + cout << endl << "Don't know which keyboard to send this to, use one!"; + return; + } + + interception_send(interceptionState.interceptionContext, dev, (InterceptionStroke *)&iks, 1); //restore LEDs for ON/OFF indication? if (globals.capsicainOnOffKey >0 diff --git a/capsicain/capsicain.h b/capsicain/capsicain.h index dfa867e..4e5bd3c 100644 --- a/capsicain/capsicain.h +++ b/capsicain/capsicain.h @@ -1,4 +1,6 @@ -#define PROGRAM_NAME_AHK "autohotkey.exe" +#include +#include + #include "interception.h" #include "utils.h" #include "configUtils.h" @@ -8,6 +10,23 @@ #define IFTRACE if(false) #define IFPROF if(false) //measuring time takes some time +struct Executable +{ + std::string verb; + std::string path; + std::string args; + std::string dir; + int mode; + HANDLE proc; + DWORD pid; + HWND hwnd; +}; + +struct Device { + std::string id; + bool keyboard; + bool apple; +}; enum KEYSTATE { @@ -39,7 +58,7 @@ void playKeyEventSequence(std::vector keyEventSequence); void printOptions(); -void sendVKeyEvent(VKeyEvent keyEvent); +void sendVKeyEvent(VKeyEvent keyEvent, bool hold = true); void sendResultingKeyOrSequence(); @@ -47,7 +66,7 @@ VKeyEvent convertIkstroke2VKeyEvent(InterceptionKeyStroke ikStroke); void normalizeIKStroke(InterceptionKeyStroke &ikstroke); InterceptionKeyStroke convertVkeyEvent2ikstroke(VKeyEvent keyEvent); -void getHardwareId(); +std::map* getHardwareId(bool refresh = true); bool initConsoleWindow(); void parseIniGlobals(); @@ -73,3 +92,11 @@ void resetCapsNumScrollLock(); int obfuscateVKey(int vk); int deObfuscateVKey(int vk); +int getKeyHolding(int vcode); +bool runExecutable(Executable &exe); +void killExecutable(Executable &exe); +void killExecutableByPath(std::string path); + +void loadAHK(); +void unloadAHK(); +void sendAHK(std::string msg); diff --git a/capsicain/capsicain.newfeatures.ini b/capsicain/capsicain.newfeatures.ini new file mode 100644 index 0000000..9a170e3 --- /dev/null +++ b/capsicain/capsicain.newfeatures.ini @@ -0,0 +1,178 @@ +# This is not a complete working ini, it just has random examples of the +# new features in https://github.com/cajhin/capsicain/pull/101 + +# Forward some Esc keys to combos or OS, but also do the default Esc action, unless disabled +GLOBAL forwardEscKey A + +# Disable some Esc actions you don't need and don't want to end up in some weird mode accidentally +# Disabled keys can also be forwarded to OS +GLOBAL disableEscKey Q W E Y U P A S H J K L ; Z B , . + +# Repeat all held keys in combos, instead of the last key only. +# Standard keyboard behavior is to only repeat the last key pressed. +#OPTION holdRepeatsAllKeys + +# Set default formatter to wrap around combo parameters when no function is specified +# This is the default default already +#OPTION defaultFunction key(%s, m) + +# Modstring is also optional, so this is the simplest form for combos +DOWN A > LSHF+B # Equivalent to: COMBO A [] > key(LSHF+B, m) + +REWIRE RCTRL MOD9 +REWIRE LWIN MOD11 LWIN + +# You can rewire the Esc key and use it in combos if the combo key is included in forwardEscKey +REWIRE ESC MOD12 ESC +DOWN A [&^^^ ^^^^ ^^^^] > B + +# Simple example with RCTRL emulating the Fn layer on a 65% +# with no physical INS,HOME,END keys and a horrible real Fn layer +COMBO DEL [...& .... ....] > hold(INS) +COMBO PGUP [...& .... ....] > hold(HOME) +COMBO PGDOWN [...& .... ....] > hold(END) + +# More confusing examples with real modifiers +COMBO Z [.&] > hold(LSHF + A) # Shift+Z will hold Shift+A until Z is released and also block physical Shift release +COMBO Z [&.] > hold(LSHF + A) # Ctrl+Z will hold Shift+A until Z is released, but not touch Ctrl +COMBO X [&.] > moddedhold(LSHF + A) # Ctrl+X will release Ctrl and hold Shift+A until X is released +# After pressing MOD11+SPACE, MOD11 holds LWIN, SPACE holds SPACE, cause language switcher demands it +COMBO SPACE [^&^^ ^^^^ ^^^^] > holdmods(LWIN + SPACE) + +# This is just weird, but still works +COMBO C [...& .... ....] > hold(A + ..&& + LWIN + X) # RCtrl+C will hold Shift+Ctrl+A+Win+X until C is released + +# key(combo, type) can be used as an alias for different functions, where type is any of +# 42: repeat combo 42 times, can be combined with r but not h or m +# r: release all keys (temporarily, unless combined with hold) before the combo +# h: hold all keys in the combo until triggering main physical key is released +# m: hold modifiers with physical modifier and other keys with other keys separately + +# key(LSHF+X) == combo(LSHF+X) +# key(LSHF+X, r) == moddedkey(X + ...&) +# key(LSHF+X, 10) == combontimes(LSHF+X, 10) +# key(LSHF+X, h) == hold(LSHF+X) +# key(LSHF+X, m) == holdmods(LSHF+X) +# key(LSHF+X, hr) == moddedhold(LSHF+X) +# key(LSHF+X, rm) == moddedholdmods(LSHF+X) + +# h is great for simple one-to-one momentary Fn layer keys that should be held +# Example: MOD11+DEL will hold INS until DEL is released +COMBO DEL [...& .... ....] > key(INS, h) +# Example: MOD11+UP will hold LCTRL+UP until UP is released +COMBO UP [...& .... ....] > key(LCTRL+UP, h) + +# use m when pressing MOD+X opens and cycles something, but releasing MOD closes it +# perfect for the language switcher or cycling windows of pinned taskbar items +# Example: when MOD11+. is pressed, send LWIN + SPACE, +# hold LWIN until MOD11 is released, +# hold SPACE until . is released +COMBO . [^&^^ ^^^^ ^^^^] > key(LWIN + SPACE, m) + +# Use r with real modifiers that shouldn't be included in the resulting combo +# Example: when LSHF+A is pressed, release LSHF, but +# hold LCTRL until physical LSHF is released, +# hold A until A is released +COMBO A [^^^^ ^^^^ ^^^&] > key(LCTRL + A, rm) + +# Example: LCTRL+UP will release LCTRL, press UP 10 times and press LCTRL again +COMBO UP [^^^^ ^^^^ ^^&^] > key(UP, 10r) + +# Support multiple functions per combo, and some new functions to go with that +COMBO 1 [^^^^ ^^^^ ^^&^] > release() altchar(0169) key(SPACE) sequence(&LSHF_d_^LSHF_r_e_g_u) +COMBO 2 [^^^^ ^^^^ ^^&^] > release() delay(33) sequence(h_e_l_l_o_SPACE_delay:0_w_o_r_l_d) + + +# Tap OR Hold MOD9+4 to F4, without adding two combos for & and t +# DOWN == COMBO, I aliased it when I added UP to make it clear which event triggers it +DOWN 4 [.../ .... ....] > hold(F4) +# TapAndHold Alt+4 to Alt+F4 +DOWN 4 [.... .... *...] > key(F4) +# Press B after releasing A +# Note that you have to specifically release keys with release() or up(key) if you hijack their UP/TAP/SLOW events +UP A [.... .... ....] > up(A) key(B) + + +# Set MOD9 down with slow tap (one long enough to trigger repeat) +# Modifiers are forced down even if pressed and released physically, you have to specifically up() them +# Normal keys will pop back up when pressed once +SLOW MOD9 [.... ^^^^ ^^^^] > down(MOD9) +# Set MOD9 up with tap if it's forced down, but also mark it untapped, +# cause we don't want the tap to trigger on the next key, as this tap was to disable the mod +TAP MOD9 [...& ^^^^ ^^^^] > up(MOD9) untap(MOD9) + + +# Add executables to run with exe(num) function +# The params are passed to ShellExecute(lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd) as is +EXE 1 open,wt,,,10 +EXE 2 open,WindowsTerminal.exe +# Launch terminal with MOD11+RET +DOWN RET [^&^^ ^^^^ ^^^^] > exe(1) +# Kill previously launched program if we still have a valid handle to it, or any process matching the filename +# kill(1) wouldn't work here, cause wt is not the name of the process, nor is the handle from ShellExecute valid any more +DOWN RET [^&^^ ^^^| ^^^|] > kill(2) + + +# "Mod-Tap", send small m when tapping fast, and big M when tapping slow enough to trigger key repeat +# The repeat delay depends on your Windows keyboard settings and can't be changed here +DOWN M > nop() +TAP M > key(M) +SLOW M > key(LSHF+M) + + +# Block repeat strokes for a key +REPEAT A > nop() + + +# Add {&deviceid} on combos to trigger only on matching devices +DOWN A {&003d} [&.] > B +# Add {^deviceid} on combos to exclude all matching devices +DOWN A {^003d} [&.] > C + + +# Enable experimental mouse support for a config, otherwise mouse is ignored +# Mouse has some limitations atm, like skipping events if you release two buttons at exactly the same time... +# M->KB and KB->M combos are sent through the last seen device of the other kind +OPTION enableMouse +# Ctrl + left click releases Ctrl and sends right click +DOWN MOUSE1 [&.] > key(MOUSE2, r) +# PGDOWN sends wheel down +DOWN PGDOWN [] > MWDOWN +# Middle mouse sends Return +DOWN MOUSE3 [] > RET +# Toggle thumb buttons to keys with MOD9+M, +# cause it's *checks calendar* 2024(!) and games still don't support thumb buttons +DOWN M [^^.^ ^^^& ^^^^ ^^^^] > toggle(MOD14) +DOWN MOUSE4 [^^&^ ^^^^ .... ....] > PGDOWN +DOWN MOUSE5 [^^&^ ^^^^ .... ....] > PGUP +# Or you could just do this and switch configs I guess +#REWIRE MOUSE4 PGDOWN +#REWIRE MOUSE5 PGUP + + +# Load AutoHotkey_H 2.0+ as DLL and call AHK functions directly (or use cryptic hotkeys if you really want to) +# Get AutoHotkey64.dll from https://github.com/thqby/AutoHotkey_H and put next to capsicain.exe +GLOBAL startAHK +# Run some AHK functions +DOWN T [^^^& ^^^^ ^^^^] > ahk(test, 42, 27) +DOWN X [^^^& ^^^^ ^^^^] > ahk(maximize) + +# Everything after [ahk] will be loaded as an AHK script and reloaded on Esc+A/R +# This has to be the last section in the ini +[ahk] + +; Remember to add Persistent if you don't have real hotkeys in here, AHK won't run without hotkeys by default +Persistent + +test(a, b) { + c := a + b + MsgBox "Quick maths: " . a . " + " . b . " = " . c +} + +maximize() { + If (WinGetMinMax("A")) { + WinRestore "A" + } else { + WinMaximize "A" + } +} diff --git a/capsicain/configUtils.cpp b/capsicain/configUtils.cpp index e99fa45..74fb370 100644 --- a/capsicain/configUtils.cpp +++ b/capsicain/configUtils.cpp @@ -1,4 +1,4 @@ -#pragma oncce +#pragma once #include "pch.h" #include #include @@ -60,7 +60,13 @@ int checkSyntax(std::vector iniLines) stringStartsWith(*t, "include") || stringStartsWith(*t, "rewire") || stringStartsWith(*t, "combo") || - stringStartsWith(*t, "option") + stringStartsWith(*t, "down") || + stringStartsWith(*t, "up") || + stringStartsWith(*t, "tap") || + stringStartsWith(*t, "slow") || + stringStartsWith(*t, "repeat") || + stringStartsWith(*t, "option") || + stringStartsWith(*t, "exe") ) continue; @@ -93,6 +99,8 @@ bool readSanitizeIniFile(std::vector &iniLines) normalizeLine(line); if (line == "") continue; + if (stringStartsWith(line, "[ahk]")) + break; if (stringStartsWith(line, "[reference")) inReferenceSection = true; else if (stringStartsWith(line, "[") && ! stringStartsWith(line, "[ ")) @@ -310,22 +318,20 @@ bool parseKeywordRewire(std::string line, int &keyA, int &keyB, int &keyC, int & } //convert ("xyz_&.", '&') to 000010 -unsigned short parseModString(string modString, char filter) +MOD parseModString(string modString, char filter) { - string binString = "0"; - for (int i = 0; i < modString.length(); i++) - { - if (modString[i] == filter) - binString += '1'; - else - binString += '0'; - } - return std::stoi(binString, nullptr, 2); + modString.erase(std::remove(modString.begin(), modString.end(), ' '), modString.end()); + MOD mask = 0; + for (int i = 0; i < min(modString.size(), 32); ++i) + if (modString[modString.size() - i - 1] == filter) + mask |= 1 << i; + return mask; } -bool parseFunctionCombo(std::string funcParams, std::string * scLabels, std::vector &strokeSeq) +// parses + separated combo that can also include a modstring to keycodes +bool parseComboParams(string funcParams, vector &vcodes, string * scLabels) { - //fix 'NP+ + X' + size_t count = vcodes.size(); bool nppFound = stringReplace(funcParams, "np+", "np@"); vector labels = stringSplit(funcParams, '+'); if (nppFound) @@ -333,79 +339,140 @@ bool parseFunctionCombo(std::string funcParams, std::string * scLabels, std::vec if (labels[i] == "np@") labels[i] = "np+"; + // support both ..&. and labeled modifiers, but always put modstring modifiers first + for (auto it = labels.begin(); it != labels.end();) { + int modsPress = parseModString(*it, '&'); + if (!modsPress) + { + ++it; + } + else + { + for (int i = 0; i < 8; i++) + { + int currentMod = modsPress & (1 << i); + if (currentMod > 0) { + vcodes.push_back(getModifierForBitmask(currentMod)); + } + } + it = labels.erase(it); + } + } + int isc; for (string label : labels) { isc = getVcode(label, scLabels); if (isc < 0) - return false; - strokeSeq.push_back({ (unsigned char)isc, true }); + continue; + vcodes.push_back(isc); } - size_t len = strokeSeq.size(); - for (size_t i = len; i > 0; i--) //copy upstrokes in reverse order - strokeSeq.push_back({ strokeSeq.at(i - 1).vcode,false }); - return true; + return vcodes.size() > count; } -bool parseFunctionModdedkey(std::string funcParams, std::string scLabels[], std::vector &strokeSeq) +// omni function to call the others based on modifiers +bool parseFunctionKey(std::string funcParams, std::string * scLabels, std::vector &strokeSeq) { - //fix 'NP+ + X' - bool nppFound = stringReplace(funcParams, "np+", "np@"); + size_t idx_comma = (int)funcParams.rfind(','); + size_t idx_plus = (int)funcParams.rfind('+'); + if (idx_comma == string::npos || idx_comma == funcParams.size() - 1 || (idx_plus != string::npos && idx_plus > idx_comma)) + return parseFunctionCombo(funcParams, scLabels, strokeSeq); + string combo = funcParams.substr(0, idx_comma); + string type = funcParams.substr(idx_comma + 1); + bool hold = type.find('h') != string::npos; + bool release = type.find('r') != string::npos; + bool holdmods = type.find('m') != string::npos; + int times = strtol(type.c_str(), nullptr, 10); + if (times <= 0) + times = 1; + + if (hold || holdmods) + return parseFunctionHold(combo, scLabels, strokeSeq, release, holdmods); + else + return parseFunctionCombo(combo, scLabels, strokeSeq, release, times); - vector modKeyParams = stringSplit(funcParams, '+'); - if (modKeyParams.size() != 2) - return false; + return false; +} - string param0 = modKeyParams[0]; - if (nppFound) - param0 = "np+"; - int vkey = getVcode(param0, scLabels); - if (vkey < 0) +bool parseFunctionCombo(std::string funcParams, std::string * scLabels, std::vector &strokeSeq, bool releaseTemp, int times) +{ + vector keys; + if (!parseComboParams(funcParams, keys, scLabels)) return false; - - strokeSeq.push_back({ VK_CPS_TEMPRELEASEKEYS, true }); - - int modsPress = parseModString(modKeyParams[1], '&'); //and (press if up) - //now disabling the ^ character. All mods are always released - unsigned short testObsoleteReleaseChar = parseModString(modKeyParams[1], '^'); //not (release if down) - if (testObsoleteReleaseChar > 0) + if (releaseTemp) + strokeSeq.push_back({ VK_CPS_TEMPRELEASEKEYS, true }); + for (int i = 0; i < times; ++i) { - cout << endl << "WARNING: the '^' release key symbol is now ignored in moddedKey(). All modifiers are always released for moddedKey()"; + for (auto it = keys.begin(); it != keys.end(); ++it) + { + strokeSeq.push_back({ *it, true }); + } + for (auto it = keys.rbegin(); it != keys.rend(); ++it) + { + strokeSeq.push_back({ *it, false }); + } } + if (releaseTemp) + strokeSeq.push_back({ VK_CPS_TEMPRESTOREKEYS, true }); + return true; +} - //send all "&" modifier down - for (int i = 0; i<8; i++) +bool parseFunctionHold(std::string funcParams, std::string * scLabels, std::vector &strokeSeq, bool releaseAll, bool holdMods) +{ + vector keys; + if (!parseComboParams(funcParams, keys, scLabels)) + return false; + if (releaseAll) + strokeSeq.push_back({ VK_CPS_RELEASEKEYS, true }); + if (holdMods) { - int currentMod = modsPress & (1 << i); - if (currentMod > 0) + for (auto it = keys.begin(); it != keys.end(); ++it) { - int mod = getModifierForBitmask(currentMod); - strokeSeq.push_back({ mod, true }); + if (isModifier(*it)) + strokeSeq.push_back({ VK_CPS_HOLDMOD, true }); + else + strokeSeq.push_back({ VK_CPS_HOLDKEY, true }); + strokeSeq.push_back({ *it, true }); } } - - strokeSeq.push_back({ (unsigned char)vkey, true }); - strokeSeq.push_back({ (unsigned char)vkey, false }); - - //send all "&" modifier up - for (int i = 0; i < 8; i++) + else { - int currentMod = modsPress & (1 << i); - if (currentMod > 0) + for (auto it = keys.begin(); it != keys.end(); ++it) { - int mod = getModifierForBitmask(currentMod); - strokeSeq.push_back({ mod, false }); + strokeSeq.push_back({ VK_CPS_HOLDKEY, true }); + strokeSeq.push_back({ *it, true }); } } - - strokeSeq.push_back({ VK_CPS_TEMPRESTOREKEYS, false }); return true; } +DEV parseDeviceMask(string devstr, char filter) { + if (devstr.empty()) + { + if (filter == '&') + return 0xFFFFFFFF; + else + return 0; + } + DEV mask = 0; + auto* devices = getHardwareId(true); + for (auto dev : stringSplit(devstr, ',')) + { + if (dev.empty()) + continue; + for (int i = 1; i <= INTERCEPTION_MAX_DEVICE; ++i) + { + if (dev[0] == filter && (*devices)[i].id.find(dev.substr(1)) != string::npos) + mask |= 1 << (i - 1); + } + } + return mask; +} + //parse {deadkey-x} keyLabel [&|^t ....] > function(param) //returns false if the rule is not valid. //this translates functions() in the .ini to key sequences (usually with special VK_CPS keys) -bool parseKeywordCombo(std::string line, int &key, unsigned short(&mods)[5], std::vector &strokeSequence, std::string scLabels[]) +bool parseKeywordCombo(std::string line, int &key, MOD(&mods)[6], DEV(&devs)[2], std::vector &strokeSequence, std::string scLabels[], std::string defaultFunction) { string strkey = stringCutFirstToken(line); if (strkey.length() < 1) @@ -431,239 +498,400 @@ bool parseKeywordCombo(std::string line, int &key, unsigned short(&mods)[5], std return false; key = itmpKey; + mods[0] = (unsigned char) deadKey; + line.erase(std::remove(line.begin(), line.end(), ' '), line.end()); + size_t devIdx1 = line.find_first_of('{'); + size_t devIdx2 = line.find_first_of('}'); + if (devIdx1 > devIdx2 || (devIdx1 == string::npos) != (devIdx2 == string::npos)) + return false; + + if (devIdx1 != string::npos && devIdx2 != string::npos) + { + devIdx1++; + devs[0] = parseDeviceMask(line.substr(devIdx1, devIdx2 - devIdx1), '&'); + devs[1] = parseDeviceMask(line.substr(devIdx1, devIdx2 - devIdx1), '^'); + } + + string mod; size_t modIdx1 = line.find_first_of('['); size_t modIdx2 = line.find_first_of(']'); - if (modIdx1 < 0 || modIdx1 > modIdx2 || modIdx1 == string::npos || modIdx2 == string::npos) + if (modIdx1 > modIdx2 || (modIdx1 == string::npos) != (modIdx2 == string::npos)) return false; - string mod = line.substr(modIdx1, modIdx2 - modIdx1); - - mods[0] = (unsigned char) deadKey; - mods[1] = parseModString(mod, '&'); //and - mods[2] = parseModString(mod, '|'); //or - mods[3] = parseModString(mod, '^'); //not - mods[4] = parseModString(mod, 't'); //tap + if (modIdx1 != string::npos && modIdx2 != string::npos) + { + modIdx1++; + mod = line.substr(modIdx1, modIdx2 - modIdx1); + mods[1] = parseModString(mod, '&'); //and + mods[2] = parseModString(mod, '|'); //or + mods[3] = parseModString(mod, '^'); //not + mods[4] = parseModString(mod, 't'); //tap + mods[5] = parseModString(mod, '/'); //tap OR hold, but doesn't need separate COMBOs for t and & + mods[1] |= parseModString(mod, '*'); //tap AND hold, but doesn't need a TapAndHold rewrite + mods[4] |= parseModString(mod, '*'); + } //extract function name + param size_t funcIdx1 = line.find_first_of('>') + 1; - if (funcIdx1 == string::npos || funcIdx1 < 2) + if (funcIdx1 == string::npos || (modIdx2 != string::npos && funcIdx1 < modIdx2)) { cout << endl << "ERROR in ini: missing '>' in: " << line; return false; } - size_t funcIdx2 = line.find_first_of('('); - if (funcIdx2 == string::npos || funcIdx2 < funcIdx1 + 2) - { - cout << endl << "ERROR in ini: missing '(' in: " << line; - return false; - } - size_t funcIdx3 = line.find_first_of(')'); - if (funcIdx3 == string::npos || funcIdx3 < funcIdx2 + 1) - { - cout << endl << "ERROR in ini: missing ')' in: " << line; - return false; - } - string funcName = line.substr(funcIdx1, funcIdx2 - funcIdx1); - funcIdx2++; - string funcParams = line.substr(funcIdx2, funcIdx3 - funcIdx2); - //translate 'function' into a key sequence + stringstream funcss(line.substr(funcIdx1)); + string funcstr; + vector funcs; + while(getline(funcss, funcstr, ')')) + funcs.push_back(funcstr); vector strokeSeq; - if (funcName == "key") + for (auto func : funcs) { - int isc = getVcode(funcParams, scLabels); - if (isc < 0) - return false; - strokeSeq.push_back({ isc, true }); - strokeSeq.push_back({ isc, false }); - } - else if (funcName == "combo") - { - if (!parseFunctionCombo(funcParams, scLabels, strokeSeq)) - return false; - } - else if (funcName == "combontimes") - { - size_t idx = (int)funcParams.rfind(','); - if (idx == string::npos) - return false; - string combo = funcParams.substr(0, idx); - string stime = funcParams.substr(idx + 1); - if (!parseFunctionCombo(combo, scLabels, strokeSeq)) - return false; - int times = stoi(stime); - auto len = strokeSeq.size(); - for (int j = 1; j < times; j++) - for (int i = 0; i < len; i++) - strokeSeq.push_back(strokeSeq.at(i)); - } - else if (funcName == "altchar") - { - strokeSeq.push_back({ VK_CPS_TEMPRELEASEKEYS, true }); //temp release LSHIFT if it is currently down - strokeSeq.push_back({ SC_LALT , true }); - for (int i = 0; i < funcParams.length(); i++) - { - char c = funcParams[i]; - string altkey="NP"; - if (c >= '0' && c <= '9') - altkey.push_back(c); - else if (c == '+') - altkey = "NP+"; - else if (c >= 'a' && c <= 'f') - { - altkey = ""; - altkey.push_back(c); - } - else + string funcName; + string funcParams; + size_t sep = func.find_first_of('('); + if (sep == string::npos) + { + char str[255]; + sprintf_s(str, 255, defaultFunction.c_str(), func.c_str()); + func = string(str); + func.erase(func.find_last_not_of(')') + 1); + sep = func.find_first_of('('); + size_t end = func.find_last_of(')'); + if (sep == string::npos) return false; + funcName = func.substr(0, sep); + sep++; + funcParams = func.substr(sep, string::npos); + } + else + { + funcName = func.substr(0, sep); + sep++; + funcParams = func.substr(sep, string::npos); + } - int isc = getVcode(altkey, scLabels); - if (isc < 0) + //translate 'function' into a key sequence + if (funcName == "key") + { + if (!parseFunctionKey(funcParams, scLabels, strokeSeq)) return false; - strokeSeq.push_back({ (unsigned char)isc, true }); - strokeSeq.push_back({ (unsigned char)isc, false }); } - strokeSeq.push_back({ SC_LALT , false }); - strokeSeq.push_back({ VK_CPS_TEMPRESTOREKEYS, false }); - } - else if (funcName == "moddedkey") - { - if (!parseFunctionModdedkey(funcParams, scLabels, strokeSeq)) - return false; - } - else if (funcName == "sequence") - { - vector params = stringSplit(funcParams, '_'); - bool downkeys[256] = { 0 }; - const string SLEEP_TAG = "sleep:"; - const string CONFIGSWITCH_TAG = "configswitch:"; - - for (string param : params) - { - // &key is down, ^key is up, key is both. - bool downstroke = true; - bool upstroke = true; - if (param.at(0) == '&') - upstroke = false; - else if (param.at(0) == '^') - downstroke = false; - if (!downstroke || !upstroke) - param = param.substr(1); - - if (stringStartsWith(param, "pause:")) + else if (funcName == "combo") + { + if (!parseFunctionCombo(funcParams, scLabels, strokeSeq)) + return false; + } + else if (funcName == "hold") + { + if (!parseFunctionHold(funcParams, scLabels, strokeSeq)) + return false; + } + else if (funcName == "moddedhold") + { + if (!parseFunctionHold(funcParams, scLabels, strokeSeq, true)) + return false; + } + else if (funcName == "holdmods") + { + if (!parseFunctionHold(funcParams, scLabels, strokeSeq, false, true)) + return false; + } + else if (funcName == "moddedholdmods") + { + if (!parseFunctionHold(funcParams, scLabels, strokeSeq, true, true)) + return false; + } + else if (funcName == "combontimes") + { + size_t idx = (int)funcParams.rfind(','); + if (idx == string::npos) + return false; + string combo = funcParams.substr(0, idx); + string stime = funcParams.substr(idx + 1); + if (!parseFunctionCombo(combo, scLabels, strokeSeq, false, stoi(stime))) + return false; + } + else if (funcName == "altchar") + { + strokeSeq.push_back({ VK_CPS_TEMPRELEASEKEYS, true }); //temp release LSHIFT if it is currently down + strokeSeq.push_back({ SC_LALT , true }); + for (int i = 0; i < funcParams.length(); i++) { - cout << endl << "WARNING: '_pause:10_' is now written as '_sleep:1000_'." << endl << "Ignoring " << param; - continue; + char c = funcParams[i]; + string altkey="NP"; + if (c >= '0' && c <= '9') + altkey.push_back(c); + else if (c == '+') + altkey = "NP+"; + else if (c >= 'a' && c <= 'f') + { + altkey = ""; + altkey.push_back(c); + } + else + return false; + + int isc = getVcode(altkey, scLabels); + if (isc < 0) + return false; + strokeSeq.push_back({ (unsigned char)isc, true }); + strokeSeq.push_back({ (unsigned char)isc, false }); } - //handle the "sleep:10" items - if (stringStartsWith(param, SLEEP_TAG)) + strokeSeq.push_back({ SC_LALT , false }); + strokeSeq.push_back({ VK_CPS_TEMPRESTOREKEYS, false }); + } + else if (funcName == "moddedkey") + { + if (!parseFunctionCombo(funcParams, scLabels, strokeSeq, true)) + return false; + } + else if (funcName == "sequence") + { + vector params = stringSplit(funcParams, '_'); + bool downkeys[256] = { 0 }; + const string SLEEP_TAG = "sleep:"; + const string DELAY_TAG = "delay:"; + const string CONFIGSWITCH_TAG = "configswitch:"; + + for (string param : params) { - string sleeptime = param.substr(SLEEP_TAG.length()); - int stime = stoi(sleeptime); - if (stime > 30000) + // &key is down, ^key is up, key is both. + bool downstroke = true; + bool upstroke = true; + if (param.at(0) == '&') + upstroke = false; + else if (param.at(0) == '^') + downstroke = false; + if (!downstroke || !upstroke) + param = param.substr(1); + + if (stringStartsWith(param, "pause:")) { - cout << endl << "Sequence() defines sleep: > 30000 Reducing to 30000 (30 seconds)"; - stime = 30000; + cout << endl << "WARNING: '_pause:10_' is now written as '_sleep:1000_'." << endl << "Ignoring " << param; + continue; } - if (stime <= 0) + //handle the "sleep:10" items + if (stringStartsWith(param, SLEEP_TAG)) { - cout << endl << "Sequence() defines sleep: <=0. Ignoring the pause."; + string sleeptime = param.substr(SLEEP_TAG.length()); + int stime; + if (stringToInt(sleeptime, stime) && stime > 0 && stime <= 30000) + { + strokeSeq.push_back({ VK_CPS_SLEEP, true }); + strokeSeq.push_back({ stime, true }); + } continue; } - strokeSeq.push_back({ VK_CPS_SLEEP, true }); - strokeSeq.push_back({ stime, true }); - continue; - } - //handle the "configswitch:2" items - if (stringStartsWith(param, CONFIGSWITCH_TAG)) { - string configParam = param.substr(CONFIGSWITCH_TAG.length()); - int configuration = stoi(configParam); - if (configuration > 9) { - cout << endl << "Sequence() defines configswitch: > 9. Not switching."; + if (stringStartsWith(param, DELAY_TAG)) + { + string sleeptime = param.substr(DELAY_TAG.length()); + int stime; + if (stringToInt(sleeptime, stime) && stime >= 0 && stime <= 1000) + { + strokeSeq.push_back({ VK_CPS_DELAY, true }); + strokeSeq.push_back({ stime, true }); + } continue; } - if (configuration < 0) { - cout << endl << "Sequence() defines configswitch: < 0. Not switching."; + //handle the "configswitch:2" items + if (stringStartsWith(param, CONFIGSWITCH_TAG)) { + string configParam = param.substr(CONFIGSWITCH_TAG.length()); + int configuration = stoi(configParam); + if (configuration > 9) { + cout << endl << "Sequence() defines configswitch: > 9. Not switching."; + continue; + } + if (configuration < 0) { + cout << endl << "Sequence() defines configswitch: < 0. Not switching."; + continue; + } + strokeSeq.push_back({VK_CPS_CONFIGSWITCH, true}); + strokeSeq.push_back({configuration, true}); continue; } - strokeSeq.push_back({VK_CPS_CONFIGSWITCH, true}); - strokeSeq.push_back({configuration, true}); - continue; + int isc = getVcode(param, scLabels); + if (isc < 0) + { + cout << endl << "WARNING: Unknown key label in sequence(): " << param; + return false; + } + + if (downstroke) + { + strokeSeq.push_back({ (unsigned char)isc, true }); + downkeys[(unsigned char)isc] = true; + } + if (upstroke) + { + strokeSeq.push_back({ (unsigned char)isc, false }); + downkeys[(unsigned char)isc] = false; + } } - int isc = getVcode(param, scLabels); - if (isc < 0) + //check if all keys were released + for (int i = 0; i < 256; i++) { - cout << endl << "WARNING: Unknown key label in sequence(): " << param; + if (downkeys[i]) + { + cout << endl << "Sequence() does not release key: " << scLabels[i] << " (discarding this rule)"; + return false; + } + } + } + else if (funcName == "deadkey") + { + int isc = getVcode(funcParams, scLabels); + if (isc < 0 || isc > 255) + return false; + strokeSeq.push_back({ VK_CPS_DEADKEY, true }); + strokeSeq.push_back({ isc, true }); + } + else if ( ((funcName == "configswitch")) || (funcName == "layerswitch")) + { + int isc; + bool valid = stringToInt(funcParams, isc); + if (!valid || isc < 0 || isc > 10) + { + cout << endl << "Invalid config switch to: " << funcParams; + return false; + } + strokeSeq.push_back({ VK_CPS_CONFIGSWITCH, true }); + strokeSeq.push_back({ isc, true }); + } + else if ((funcName == "configprevious") || (funcName == "layerprevious")) + { + strokeSeq.push_back({ VK_CPS_CONFIGPREVIOUS, true }); + } + else if (funcName == "recordmacro" || funcName == "recordsecretmacro" || funcName == "playmacro") + { + int macroNum; + bool valid = stringToInt(funcParams, macroNum); + if (!valid || macroNum < 1 || macroNum >= MAX_NUM_MACROS) + { + cout << endl << "Invalid macro number : " << funcParams << " (must be 1.."<< MAX_NUM_MACROS-1 <<")"; return false; } - if (downstroke) + if(funcName == "recordmacro") + strokeSeq.push_back({ VK_CPS_RECORDMACRO, true }); + else if (funcName == "recordsecretmacro") + strokeSeq.push_back({ VK_CPS_RECORDSECRETMACRO, true }); + else if (funcName == "playmacro") + strokeSeq.push_back({ VK_CPS_PLAYMACRO, true }); + + strokeSeq.push_back({ macroNum, true }); + } + else if (funcName == "sleep") + { + int stime; + if (stringToInt(funcParams, stime)) { - strokeSeq.push_back({ (unsigned char)isc, true }); - downkeys[(unsigned char)isc] = true; + strokeSeq.push_back({ VK_CPS_SLEEP, true }); + strokeSeq.push_back({ stime, true }); } - if (upstroke) + } + else if (funcName == "delay") + { + int stime; + if (stringToInt(funcParams, stime)) { - strokeSeq.push_back({ (unsigned char)isc, false }); - downkeys[(unsigned char)isc] = false; + strokeSeq.push_back({ VK_CPS_DELAY, true }); + strokeSeq.push_back({ stime, true }); } } - //check if all keys were released - for (int i = 0; i < 256; i++) + else if (funcName == "release") { - if (downkeys[i]) + strokeSeq.push_back({ VK_CPS_RELEASEKEYS, true }); + } + else if (funcName == "nop") + { + strokeSeq.push_back({ SC_NOP, true }); + } + else if (funcName == "down") + { + vector keys; + parseComboParams(funcParams, keys, scLabels); + for (auto isc : keys) { - cout << endl << "Sequence() does not release key: " << scLabels[i] << " (discarding this rule)"; + strokeSeq.push_back({ VK_CPS_KEYDOWN, true }); + strokeSeq.push_back({ isc, true }); + } + } + else if (funcName == "up") + { + vector keys; + parseComboParams(funcParams, keys, scLabels); + for (auto isc : keys) + { + strokeSeq.push_back({ VK_CPS_KEYDOWN, true }); + strokeSeq.push_back({ isc, false }); + } + } + else if (funcName == "toggle") + { + vector keys; + parseComboParams(funcParams, keys, scLabels); + for (auto isc : keys) + { + strokeSeq.push_back({ VK_CPS_KEYTOGGLE, true }); + strokeSeq.push_back({ isc, true }); + } + } + else if (funcName == "tap") + { + vector keys; + parseComboParams(funcParams, keys, scLabels); + for (auto isc : keys) + { + strokeSeq.push_back({ VK_CPS_KEYTAP, true }); + strokeSeq.push_back({ isc, true }); + } + } + else if (funcName == "untap") + { + vector keys; + parseComboParams(funcParams, keys, scLabels); + for (auto isc : keys) + { + strokeSeq.push_back({ VK_CPS_KEYTAP, true }); + strokeSeq.push_back({ isc, false }); + } + } + else if (funcName == "exe") + { + int exeNum; + bool valid = stringToInt(funcParams, exeNum); + if (!valid) + { + cout << endl << "Invalid executable : " << funcParams; return false; } + strokeSeq.push_back({ VK_CPS_EXECUTE, true }); + strokeSeq.push_back({ exeNum, true }); } - } - else if (funcName == "deadkey") - { - int isc = getVcode(funcParams, scLabels); - if (isc < 0 || isc > 255) - return false; - strokeSeq.push_back({ VK_CPS_DEADKEY, true }); - strokeSeq.push_back({ isc, true }); - } - else if ( ((funcName == "configswitch")) || (funcName == "layerswitch")) - { - int isc; - bool valid = stringToInt(funcParams, isc); - if (!valid || isc < 0 || isc > 10) + else if (funcName == "ahk") { - cout << endl << "Invalid config switch to: " << funcParams; - return false; + strokeSeq.push_back({ VK_CPS_SENDAHK, true }); + strokeSeq.push_back({ getAHKid(funcParams), true }); } - strokeSeq.push_back({ VK_CPS_CONFIGSWITCH, true }); - strokeSeq.push_back({ isc, true }); - } - else if ((funcName == "configprevious") || (funcName == "layerprevious")) - { - strokeSeq.push_back({ VK_CPS_CONFIGPREVIOUS, true }); - } - else if (funcName == "recordmacro" || funcName == "recordsecretmacro" || funcName == "playmacro") - { - int macroNum; - bool valid = stringToInt(funcParams, macroNum); - if (!valid || macroNum < 1 || macroNum >= MAX_NUM_MACROS) + else if (funcName == "kill") { - cout << endl << "Invalid macro number : " << funcParams << " (must be 1.."<< MAX_NUM_MACROS-1 <<")"; + int exeNum; + bool valid = stringToInt(funcParams, exeNum); + if (!valid) + { + cout << endl << "Invalid executable : " << funcParams; + return false; + } + strokeSeq.push_back({ VK_CPS_KILL, true }); + strokeSeq.push_back({ exeNum, true }); + } + else + { return false; } - - if(funcName == "recordmacro") - strokeSeq.push_back({ VK_CPS_RECORDMACRO, true }); - else if (funcName == "recordsecretmacro") - strokeSeq.push_back({ VK_CPS_RECORDSECRETMACRO, true }); - else if (funcName == "playmacro") - strokeSeq.push_back({ VK_CPS_PLAYMACRO, true }); - - strokeSeq.push_back({ macroNum, true }); } - else - return false; strokeSequence = strokeSeq; return true; diff --git a/capsicain/configUtils.h b/capsicain/configUtils.h index 18bb0c0..fb83b08 100644 --- a/capsicain/configUtils.h +++ b/capsicain/configUtils.h @@ -2,6 +2,7 @@ #include #include #include "constants.h" +#include "modifiers.h" const int CPS_ESC_SEQUENCE_TYPE_TEMPALTERMODIFIERS = 1; const int CPS_ESC_SEQUENCE_TYPE_SLEEP = 2; @@ -22,7 +23,9 @@ bool getStringValueForTaggedKey(std::string tag, std::string key, std::string & bool getStringValueForKey(std::string key, std::string & value, std::vector sectionLines); bool getIntValueForTaggedKey(std::string tag, std::string key, int & value, std::vector sectionLines); bool getIntValueForKey(std::string key, int & value, std::vector sectionLines); -bool parseFunctionModdedkey(std::string& funcParams, std::string scLabels[], std::vector& strokeSeq, bool& retflag); -bool parseKeywordCombo(std::string line, int &key, unsigned short(&mods)[5], std::vector &strokeSequence, std::string scLabels[]); +bool parseFunctionCombo(std::string funcParams, std::string * scLabels, std::vector &strokeSeq, bool releaseTemp = false, int times = 1); +bool parseFunctionHold(std::string funcParams, std::string *scLabels, std::vector &strokeSeq, bool releaseAll = false, bool holdMods = false); +bool parseKeywordCombo(std::string line, int &key, MOD(&mods)[6], DEV(&devs)[2], std::vector & strokeSequence, std::string scLabels[], std::string defaultFunction); bool parseKeywordsAlpha_FromTo(std::string mapFromTo, int(&alphamap)[MAX_VCODES], std::string scLabels[]); bool parseKeywordRewire(std::string line, int & keyA, int & keyB, int & keyC, int & keyD, std::string scLabels[]); +bool parseComboParams(std::string funcParams, std::vector &vcodes, std::string *scLabels); \ No newline at end of file diff --git a/capsicain/constants.h b/capsicain/constants.h index 3a73b16..bd787e1 100644 --- a/capsicain/constants.h +++ b/capsicain/constants.h @@ -3,7 +3,7 @@ #define VERSION "96" //arbitray limits -#define MAX_VCODES 0x121 //biggest defined code in scancodes.h must be smaller than this +#define MAX_VCODES 0x140 //biggest defined code in scancodes.h must be smaller than this #define MAX_MACRO_LENGTH 200 //stop recording at some point if it was forgotten. #define MAX_NUM_MACROS 21 //max number of stored macros (mapped later to 1..20, and the 'hard' macro 0) @@ -34,7 +34,14 @@ const std::string INI_TAG_INCLUDE = "INCLUDE"; const std::string INI_TAG_GLOBAL = "GLOBAL"; const std::string INI_TAG_OPTIONS = "OPTION"; const std::string INI_TAG_REWIRE = "REWIRE"; -const std::string INI_TAG_COMBOS = "COMBO"; +const std::string INI_TAG_COMBOS = "DOWN"; +const std::string INI_TAG_UPCOMBOS = "UP"; +const std::string INI_TAG_TAPCOMBOS = "TAP"; +const std::string INI_TAG_SLOWCOMBOS = "SLOW"; +const std::string INI_TAG_REPEATCOMBOS = "REPEAT"; const std::string INI_TAG_ALPHA_FROM = "ALPHA_FROM"; const std::string INI_TAG_ALPHA_TO = "ALPHA_TO"; const std::string INI_TAG_ALPHA_END = "ALPHA_END"; +const std::string INI_TAG_EXE = "EXE"; + +using DEV = uint32_t; \ No newline at end of file diff --git a/capsicain/modifiers.cpp b/capsicain/modifiers.cpp index 2ad3976..457c6cc 100644 --- a/capsicain/modifiers.cpp +++ b/capsicain/modifiers.cpp @@ -4,21 +4,29 @@ #include "modifiers.h" //stores {SC_LSHIFT/42 = 00001b},{VK_LCTRL = 010b}, ... {VK_MOD15, 100000000000000b} -unsigned short modifierToBitmask[2][NUMBER_OF_MODIFIERS] = +MOD modifierToBitmask[2][NUMBER_OF_MODIFIERS] = { {SC_LSHIFT, SC_LCTRL, SC_LALT, SC_LWIN, SC_RSHIFT, SC_RCTRL, SC_RALT, SC_RWIN, VK_MOD9, VK_MOD10, VK_MOD11, VK_MOD12, - VK_MOD13, VK_MOD14, VK_MOD15} , + VK_MOD13, VK_MOD14, VK_MOD15, VK_MOD16, + VK_MOD17, VK_MOD18, VK_MOD19, VK_MOD20, + VK_MOD21, VK_MOD22, VK_MOD23, VK_MOD24, + VK_MOD25, VK_MOD26, VK_MOD27, VK_MOD28, + VK_MOD29, VK_MOD30, VK_MOD31, VK_MOD32}, {BITMASK_LSHIFT, BITMASK_LCTRL, BITMASK_LALT, BITMASK_LWIN, BITMASK_RSHIFT, BITMASK_RCTRL, BITMASK_RALT, BITMASK_RWIN, BITMASK_MOD9, BITMASK_MOD10, BITMASK_MOD11, BITMASK_MOD12, - BITMASK_MOD13, BITMASK_MOD14, BITMASK_MOD15} + BITMASK_MOD13, BITMASK_MOD14, BITMASK_MOD15, BITMASK_MOD16, + BITMASK_MOD17, BITMASK_MOD18, BITMASK_MOD19, BITMASK_MOD20, + BITMASK_MOD21, BITMASK_MOD22, BITMASK_MOD23, BITMASK_MOD24, + BITMASK_MOD25, BITMASK_MOD26, BITMASK_MOD27, BITMASK_MOD28, + BITMASK_MOD29, BITMASK_MOD30, BITMASK_MOD31, BITMASK_MOD32} }; //returns 0 if vcode is not a modifier -unsigned short getModifierBitmaskForVcode(int vcode) +MOD getModifierBitmaskForVcode(int vcode) { if (vcode < 0) return 0; @@ -28,7 +36,7 @@ unsigned short getModifierBitmaskForVcode(int vcode) return modifierToBitmask[1][i]; return 0; } -unsigned short getModifierForBitmask(unsigned short bitmask) +unsigned short getModifierForBitmask(MOD bitmask) { for (int i = 0; i < NUMBER_OF_MODIFIERS; i++) if (modifierToBitmask[1][i] == bitmask) @@ -46,11 +54,11 @@ bool isModifier(int vcode) bool isRealModifier(int vcode) { - unsigned short bitmask = getModifierBitmaskForVcode(vcode); + MOD bitmask = getModifierBitmaskForVcode(vcode); return ((bitmask & 0xFF) > 0); } bool isVirtualModifier(int vcode) { - unsigned short bitmask = getModifierBitmaskForVcode(vcode); + MOD bitmask = getModifierBitmaskForVcode(vcode); return ((bitmask & 0x7F00) > 0); } diff --git a/capsicain/modifiers.h b/capsicain/modifiers.h index b41ab36..fd343dc 100644 --- a/capsicain/modifiers.h +++ b/capsicain/modifiers.h @@ -4,11 +4,12 @@ #ifndef MAPPINGS_H #define MAPPINGS_H -const int NUMBER_OF_MODIFIERS = 15; +const int NUMBER_OF_MODIFIERS = 32; +using MOD = uint32_t; -unsigned short getModifierBitmaskForVcode(int vcode); +MOD getModifierBitmaskForVcode(int vcode); -unsigned short getModifierForBitmask(unsigned short bitmask); +unsigned short getModifierForBitmask(MOD bitmask); bool isModifier(int vcode); @@ -31,7 +32,23 @@ bool isVirtualModifier(int vcode); #define BITMASK_MOD13 0x1000 #define BITMASK_MOD14 0x2000 #define BITMASK_MOD15 0x4000 - +#define BITMASK_MOD16 0x8000 +#define BITMASK_MOD17 0x10000 +#define BITMASK_MOD18 0x20000 +#define BITMASK_MOD19 0x40000 +#define BITMASK_MOD20 0x80000 +#define BITMASK_MOD21 0x100000 +#define BITMASK_MOD22 0x200000 +#define BITMASK_MOD23 0x400000 +#define BITMASK_MOD24 0x800000 +#define BITMASK_MOD25 0x1000000 +#define BITMASK_MOD26 0x2000000 +#define BITMASK_MOD27 0x4000000 +#define BITMASK_MOD28 0x8000000 +#define BITMASK_MOD29 0x10000000 +#define BITMASK_MOD30 0x20000000 +#define BITMASK_MOD31 0x40000000 +#define BITMASK_MOD32 0x80000000 #define IS_SHIFT_DOWN (modifierState.modifierDown & BITMASK_LSHIFT || modifierState.modifierDown & BITMASK_RSHIFT) #define IS_LSHIFT_DOWN (modifierState.modifierDown & BITMASK_LSHIFT) @@ -49,6 +66,23 @@ bool isVirtualModifier(int vcode); #define IS_MOD13_DOWN (modifierState.modifierDown & BITMASK_MOD13) #define IS_MOD14_DOWN (modifierState.modifierDown & BITMASK_MOD14) #define IS_MOD15_DOWN (modifierState.modifierDown & BITMASK_MOD15) +#define IS_MOD16_DOWN (modifierState.modifierDown & BITMASK_MOD16) +#define IS_MOD17_DOWN (modifierState.modifierDown & BITMASK_MOD17) +#define IS_MOD18_DOWN (modifierState.modifierDown & BITMASK_MOD18) +#define IS_MOD19_DOWN (modifierState.modifierDown & BITMASK_MOD19) +#define IS_MOD20_DOWN (modifierState.modifierDown & BITMASK_MOD20) +#define IS_MOD21_DOWN (modifierState.modifierDown & BITMASK_MOD21) +#define IS_MOD22_DOWN (modifierState.modifierDown & BITMASK_MOD22) +#define IS_MOD23_DOWN (modifierState.modifierDown & BITMASK_MOD23) +#define IS_MOD24_DOWN (modifierState.modifierDown & BITMASK_MOD24) +#define IS_MOD25_DOWN (modifierState.modifierDown & BITMASK_MOD25) +#define IS_MOD26_DOWN (modifierState.modifierDown & BITMASK_MOD26) +#define IS_MOD27_DOWN (modifierState.modifierDown & BITMASK_MOD27) +#define IS_MOD28_DOWN (modifierState.modifierDown & BITMASK_MOD28) +#define IS_MOD29_DOWN (modifierState.modifierDown & BITMASK_MOD29) +#define IS_MOD30_DOWN (modifierState.modifierDown & BITMASK_MOD30) +#define IS_MOD31_DOWN (modifierState.modifierDown & BITMASK_MOD31) +#define IS_MOD32_DOWN (modifierState.modifierDown & BITMASK_MOD32) #endif \ No newline at end of file diff --git a/capsicain/scancodes.cpp b/capsicain/scancodes.cpp index 042473f..1add6c8 100644 --- a/capsicain/scancodes.cpp +++ b/capsicain/scancodes.cpp @@ -1,6 +1,7 @@ #pragma once #include "pch.h" #include +#include #include //for Sleep() #include "scancodes.h" @@ -146,6 +147,7 @@ void defineAllPrettyVKLabels(string arr[]) checkAddLabel(SC_F21, "F21", arr); checkAddLabel(SC_F22, "F22", arr); checkAddLabel(SC_F23, "F23", arr); + checkAddLabel(SC_F24, "F24", arr); checkAddLabel(SC_KANA, "KANA", arr); checkAddLabel(SC_LANG2, "LANG2", arr); checkAddLabel(SC_LANG1, "LANG1", arr); @@ -209,7 +211,7 @@ void defineAllPrettyVKLabels(string arr[]) //fill all undefined scancodes with "SC_0XNN" so they can be referenced in ini char buffer[9]; - for (int i = 0; i <= 255; i++) + for (int i = 0; i <= 0xf0; i++) { if (arr[i] == "") { @@ -234,6 +236,23 @@ void defineAllPrettyVKLabels(string arr[]) checkAddLabel(VK_MOD13, "MOD13", arr); checkAddLabel(VK_MOD14, "MOD14", arr); checkAddLabel(VK_MOD15, "MOD15", arr); + checkAddLabel(VK_MOD16, "MOD16", arr); + checkAddLabel(VK_MOD17, "MOD17", arr); + checkAddLabel(VK_MOD18, "MOD18", arr); + checkAddLabel(VK_MOD19, "MOD19", arr); + checkAddLabel(VK_MOD20, "MOD20", arr); + checkAddLabel(VK_MOD21, "MOD21", arr); + checkAddLabel(VK_MOD22, "MOD22", arr); + checkAddLabel(VK_MOD23, "MOD23", arr); + checkAddLabel(VK_MOD24, "MOD24", arr); + checkAddLabel(VK_MOD25, "MOD25", arr); + checkAddLabel(VK_MOD26, "MOD26", arr); + checkAddLabel(VK_MOD27, "MOD27", arr); + checkAddLabel(VK_MOD28, "MOD28", arr); + checkAddLabel(VK_MOD29, "MOD29", arr); + checkAddLabel(VK_MOD30, "MOD30", arr); + checkAddLabel(VK_MOD31, "MOD31", arr); + checkAddLabel(VK_MOD32, "MOD32", arr); checkAddLabel(VK_CPS_CAPSON, "CAPSON", arr); checkAddLabel(VK_CPS_CAPSOFF, "CAPSOFF", arr); checkAddLabel(VK_CPS_RECORDMACRO, "RECMAC", arr); @@ -241,6 +260,26 @@ void defineAllPrettyVKLabels(string arr[]) checkAddLabel(VK_CPS_PLAYMACRO, "PLAYMAC", arr); checkAddLabel(VK_CPS_OBFUSCATED_SEQUENCE_START, "OBFUSEQSTART", arr); checkAddLabel(VK_CPS_PAUSE, "PAUSE", arr); //this is not a real scancode, used to map the PAUSE combo E1 LCTRL SCRLCK + checkAddLabel(VK_CPS_HOLDKEY, "HOLDKEY", arr); + checkAddLabel(VK_CPS_HOLDMOD, "HOLDMOD", arr); + checkAddLabel(VK_CPS_RELEASEKEYS, "RELEASEKEYS", arr); + checkAddLabel(VK_CPS_DELAY, "DELAY", arr); + checkAddLabel(VK_CPS_KEYDOWN, "KEYDOWN", arr); + checkAddLabel(VK_CPS_KEYTOGGLE, "KEYTOGGLE", arr); + checkAddLabel(VK_CPS_KEYTAP, "KEYTAP", arr); + checkAddLabel(VK_CPS_EXECUTE, "EXECUTE", arr); + checkAddLabel(VK_CPS_KILL, "KILL", arr); + checkAddLabel(VK_CPS_SENDAHK, "SENDAHK", arr); + + checkAddLabel(VM_LEFT, "MOUSE1", arr); + checkAddLabel(VM_RIGHT, "MOUSE2", arr); + checkAddLabel(VM_MIDDLE, "MOUSE3", arr); + checkAddLabel(VM_BUTTON4, "MOUSE4", arr); + checkAddLabel(VM_BUTTON5, "MOUSE5", arr); + checkAddLabel(VM_WHEEL_UP, "MWUP", arr); + checkAddLabel(VM_WHEEL_DOWN, "MWDOWN", arr); + checkAddLabel(VM_WHEEL_LEFT, "MWLEFT", arr); + checkAddLabel(VM_WHEEL_RIGHT, "MWRIGHT", arr); /* testing the VMK style config shift checkAddLabel(VK_SHFCFG0, "SHFCFG0", arr); checkAddLabel(VK_SHFCFG1, "SHFCFG1", arr); @@ -255,3 +294,21 @@ void defineAllPrettyVKLabels(string arr[]) */ } + +std::vector AHKids; +int getAHKid(std::string msg) +{ + for (int i = 0; i < AHKids.size(); ++i) { + if (AHKids[i] == msg) + return i + 1; + } + AHKids.push_back(msg); + return (int)AHKids.size(); +} + +std::string getAHKmsg(int id) +{ + if (AHKids.size() >= id) + return AHKids[id - 1]; + return ""; +} \ No newline at end of file diff --git a/capsicain/scancodes.h b/capsicain/scancodes.h index 8d234ce..fbcaadd 100644 --- a/capsicain/scancodes.h +++ b/capsicain/scancodes.h @@ -116,6 +116,7 @@ enum ScanCode { SC_LANG2 = 0x71, // (Korean 'Hanja key' according to Firefox spec) SC_LANG1 = 0x72, // (Korean 'Han/Yeong key' according to Firefox spec) SC_ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards + SC_F24 = 0x76, SC_CONVERT = 0x79, // (Japanese keyboard) SC_NOCONVERT = 0x7B, // (Japanese keyboard) SC_YEN = 0x7D, // (Japanese keyboard) @@ -136,7 +137,7 @@ enum ScanCode { SC_CALCULATOR = 0xA1, // Calculator SC_PLAYPAUSE = 0xA2, // Play / Pause SC_MEDIASTOP = 0xA4, // Media Stop - SC_E0LSHF = 0xAA, // produced by Print key without modifier. '²' on French AZERTY keyboard (same place as ~ ` on QWERTY) + SC_E0LSHF = 0xAA, // produced by Print key without modifier. '²' on French AZERTY keyboard (same place as ~ ` on QWERTY) SC_VOLUMEDOWN = 0xAE, // Volume - SC_VOLUMEUP = 0xB0, // Volume + SC_WEBHOME = 0xB2, // Web home @@ -175,6 +176,15 @@ enum ScanCode { //defined by capsicain, non standard enum VirtualCode { + VM_LEFT = 0xF1, + VM_RIGHT, + VM_MIDDLE, + VM_BUTTON4, + VM_BUTTON5, + VM_WHEEL_UP, + VM_WHEEL_DOWN, + VM_WHEEL_LEFT, + VM_WHEEL_RIGHT, //special escape code for Capsicain key sequences //VK_CPS_ESC = 0x100, VK_CPS_TEMPRELEASEKEYS = 0x101, @@ -190,13 +200,41 @@ enum VirtualCode VK_MOD13 = 0x10D, VK_MOD14 = 0x10E, VK_MOD15 = 0x10F, - VK_CPS_CAPSON = 0x110, //turns CapsLock on, regardless of current state - VK_CPS_CAPSOFF = 0x111, - VK_CPS_RECORDMACRO = 0x112, - VK_CPS_RECORDSECRETMACRO = 0x113, - VK_CPS_PLAYMACRO = 0x114, - VK_CPS_OBFUSCATED_SEQUENCE_START = 0x115, - VK_CPS_PAUSE = 0x116, // not a real scancode; Cherry Pause key sends an escaped key combo E1 LCTRL NUMLOCK + VK_MOD16 = 0x110, + VK_MOD17 = 0x111, + VK_MOD18 = 0x112, + VK_MOD19 = 0x113, + VK_MOD20 = 0x114, + VK_MOD21 = 0x115, + VK_MOD22 = 0x116, + VK_MOD23 = 0x117, + VK_MOD24 = 0x118, + VK_MOD25 = 0x119, + VK_MOD26 = 0x11A, + VK_MOD27 = 0x11B, + VK_MOD28 = 0x11C, + VK_MOD29 = 0x11D, + VK_MOD30 = 0x11E, + VK_MOD31 = 0x11F, + VK_MOD32 = 0x120, + VK_CPS_CAPSON, //turns CapsLock on, regardless of current state + VK_CPS_CAPSOFF, + VK_CPS_RECORDMACRO, + VK_CPS_RECORDSECRETMACRO, + VK_CPS_PLAYMACRO, + VK_CPS_OBFUSCATED_SEQUENCE_START, + VK_CPS_PAUSE, // not a real scancode; Cherry Pause key sends an escaped key combo E1 LCTRL NUMLOCK + VK_CPS_HOLDKEY, + VK_CPS_HOLDMOD, + VK_CPS_RELEASEKEYS, + VK_CPS_DELAY, + VK_CPS_KEYDOWN, + VK_CPS_KEYTOGGLE, + VK_CPS_KEYTAP, + VK_CPS_EXECUTE, + VK_CPS_KILL, + VK_CPS_SENDAHK, + VK_MAX /* testing the VMK style config shift VK_SHFCFG0 = 0x117, //shift config, i.e. shift back when the key is released VK_SHFCFG1 = 0x118, @@ -209,4 +247,7 @@ enum VirtualCode VK_SHFCFG8 = 0x11F, VK_SHFCFG9 = 0x120, */ -}; \ No newline at end of file +}; + +int getAHKid(std::string msg); +std::string getAHKmsg(int id); \ No newline at end of file diff --git a/capsicain/utils.cpp b/capsicain/utils.cpp index 92ebb4a..68cb7f8 100644 --- a/capsicain/utils.cpp +++ b/capsicain/utils.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "utils.h" @@ -71,7 +73,6 @@ DWORD FindProcessId(string processName) return 0; } - string startProgram(string processName, string dir) { string ret = ""; @@ -268,4 +269,47 @@ std::string stringIntToHex(const unsigned int i, unsigned int minLength) std::stringstream s; s << setfill('0') << setw(minLength) << std::hex << i; return s.str(); +} + +size_t GetSizeOfFile(const std::wstring& path) +{ + struct _stat fileinfo; + _wstat(path.c_str(), &fileinfo); + return fileinfo.st_size; +} + +std::wstring LoadUtf8FileToString(const std::wstring& filename) +{ + std::wstring buffer; // stores file contents + FILE *f; + _wfopen_s(&f, filename.c_str(), L"rtS, ccs=UTF-8"); + + // Failed to open file + if (f == NULL) + { + // ...handle some error... + return buffer; + } + + size_t filesize = GetSizeOfFile(filename); + + // Read entire file contents in to memory + if (filesize > 0) + { + buffer.resize(filesize); + size_t wchars_read = fread(&(buffer.front()), sizeof(wchar_t), filesize, f); + buffer.resize(wchars_read); + buffer.shrink_to_fit(); + } + + fclose(f); + + return buffer; +} + +std::wstring widen(const std::string& s) +{ + std::wstring temp(s.length(),L' '); + std::copy(s.begin(), s.end(), temp.begin()); + return temp; } \ No newline at end of file diff --git a/capsicain/utils.h b/capsicain/utils.h index c263148..bb0701b 100644 --- a/capsicain/utils.h +++ b/capsicain/utils.h @@ -8,6 +8,7 @@ void copyToClipBoard(std::string text); std::string startProgram(std::string processname, std::string dir); std::string startProgramSameFolder(std::string path); void closeOrKillProgram(std::string processName); +DWORD FindProcessId(std::string processName); unsigned long timeSinceTimepointMS(std::chrono::steady_clock::time_point timepoint); unsigned long timeSinceTimepointUS(std::chrono::steady_clock::time_point timepoint); @@ -25,3 +26,20 @@ std::vector stringSplit(const std::string &line, char delimiter); bool stringToInt(std::string strval, int& result); bool stringReplace(std::string& haystack, const std::string& needle, const std::string& newneedle); std::string stringIntToHex(const unsigned int i, unsigned int minLength); +BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM lParam); + +inline void ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); +} + +inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +size_t GetSizeOfFile(const std::wstring &path); +std::wstring LoadUtf8FileToString(const std::wstring &filename); +std::wstring widen(const std::string &s); diff --git a/x64/Release/capsicain.exe b/x64/Release/capsicain.exe index 73094b1..1dffb60 100644 Binary files a/x64/Release/capsicain.exe and b/x64/Release/capsicain.exe differ