C: PMDG-style keyboard entry

From FSDeveloper Wiki
Jump to: navigation, search

If you have flown any of the PMDG aircraft, you may have found it quite convenient to be able to type your entries into the FMC with the keyboard by holding down the Tab key. Here is an example how to do this in a gauge using SimConnect. This sample is based on the blank multi thread D2D gauge template available in the FSDeveloper resources: http://www.fsdeveloper.com/forum/resources/blank-multi-thread-d2d-gauge-template.155/

First off, it helps to have an array of strings to loop through when setting up your key assignments in SimConnect:

const char* kstr[58] = 
{
	"Backspace\0", "Tab\0", "Space\0", "Num_Del\0", "0\0", "1\0", "2\0", "3\0", "4\0",
	"5\0", "6\0", "7\0", "8\0", "9\0", "A\0", "B\0", "C\0", "D\0", "E\0", "F\0", "G\0",
	"H\0", "I\0", "J\0", "K\0", "L\0", "M\0", "N\0", "O\0", "P\0",	"Q\0", "R\0", "S\0",
	"T\0", "U\0", "V\0", "W\0", "X\0", "Y\0", "Z\0", "VK_NUMPAD0\0", "VK_NUMPAD1\0",
	"VK_NUMPAD2\0", "VK_NUMPAD3\0", "VK_NUMPAD4\0", "VK_NUMPAD5\0", "VK_NUMPAD6\0",
	"VK_NUMPAD7\0", "VK_NUMPAD8\0", "VK_NUMPAD9\0", "VK_ADD\0", "VK_SUBTRACT\0",
	"VK_DECIMAL\0", "VK_DIVIDE\0", "VK_PLUS\0", "VK_MINUS\0", "VK_PERIOD\0",
	"VK_SLASH\0"
};

Next, you need to define your event ID and group ID enumerations (see CSimConnectData.h in the D2D sample)

static enum EVENT_ID 
{
	EVK_KBBASE = 1000,//This is for Key Down events
	EVK_KBBASEUP = 1100,//This is for Key Up events. Only used for the Tab key so we can know when to capture keyboard input.
};

static enum GROUP_ID
{
	G_KTOG,//This group only contains the Tab key.
	G_KEYS,//This group contains all the other keys used.
};

static enum INPUT_ID
{
	I_KTOG,//This group only contains the Tab key.
	I_KEYS,//This group contains all the other keys used.
};

In the AddDataDefs function in the D2D sample, set up your key definitions as shown:

void AddDataDefs(HANDLE hs)
{
	for(int i = 0; i < 58; i ++)
	{
		SimConnect_MapClientEventToSimEvent(hs, EVK_KBBASE+i);
		SimConnect_AddClientEventToNotificationGroup(hs, i==1?G_KTOG:G_KEYS, EVK_KBBASE+i, true);
		SimConnect_MapClientEventToSimEvent(hs, EVK_KBBASEUP+i);
		SimConnect_AddClientEventToNotificationGroup(hs, i==1?G_KTOG:G_KEYS, EVK_KBBASEUP+i, true);
	}
	//Note we have 2 key groups. The KTOG group contains only 1 key, the "Tab" key.
	//This is so that the other key group will only mask inputs from the sim and redirect into our code if the Tab key is held down
	SimConnect_SetNotificationGroupPriority(hs, G_KTOG, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE);
	SimConnect_SetNotificationGroupPriority(hs, G_KEYS, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE);
	for(int i = 0; i < 58; i ++)
	{
		SimConnect_MapInputEventToClientEvent(hs, i==1?I_KTOG:I_KEYS, kstr[i], EVK_KBBASE+i, 0, EVK_KBBASEUP+i, 0, 1);
	}

	SimConnect_SetInputGroupState(hs, I_KTOG, SIMCONNECT_STATE_ON);
	SimConnect_SetInputGroupState(hs, I_KEYS, SIMCONNECT_STATE_OFF);
	SimConnect_SetInputGroupPriority(hs, I_KTOG, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE);
	SimConnect_SetInputGroupPriority(hs, I_KEYS, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE);
}

Next, in your SimConnect callback under the SIMCONNECT_RECV_ID_EVENT case, call the event handler and toggle the keyboard input state as required:

case SIMCONNECT_RECV_ID_EVENT:
{
	SIMCONNECT_RECV_EVENT *evt = (SIMCONNECT_RECV_EVENT*)pData;
	if(evt->uGroupID == G_KTOG)
	{
		//if the Tab key is down, activate the masked keyboard key system so our key input will receive events
		if(evt->uEventID == EVK_KBBASE+1)
			SimConnect_SetInputGroupState(hSc, I_KEYS, SIMCONNECT_STATE_ON);
		else
			SimConnect_SetInputGroupState(hSc, I_KEYS, SIMCONNECT_STATE_OFF);
	}
	//Subtract 1000 from the key event id to match our key identifier system
	if(evt->uGroupID == G_KEYS)
		KeyInput(evt->uEventID-1000);
}
break;

Finally, set up your key input function where you can convert from key input ID to ASCII string as shown:

#define MAXINPUT 20
char istring[MAXINPUT];
int cursor = 0;
void KeyInput(int val)
{
	if(val < 100)//Note, we only accept Key Down events
	{
		//0 is Backspace, 3 is Delete. If you want Delete to wipe out the whole string, use memset(istring, 0x00, MAXINPUT); when val == 3
		if(val == 0 || val == 3)
		{
			cursor = cursor>0?cursor-1:cursor;
			istring[cursor] = 0x00;
		}
		else
		{
			//If val == 2, the value to put in is a blank space ' '.
			if(val == 2)
				istring[cursor]=' ';
			else
			{
				char kv = 0x00;
				//Values 4 to 13 are top row of numbers on keyboard. ASCII values are 0x30 to 0x39 for "0-9"
				if(val > 3 && val < 14)
					kv = 0x30+char(val-4);
				//Values 14 to 39 are letters on keyboard. ASCII values are 0x41 to 0x5A for "A-Z". Uppercase only in this case. If you want lowercase, use ASCII values 0x61 to 0x7A
				if(val > 13 && val < 40)
					kv = 0x41+char(val-14);
				//Values 40 to 49 are NUMPAD numbers. ASCII values are again 0x30 to 0x39 for "0-9"
				if(val > 39 && val < 50)
					kv = 0x30+char(val-40);
				//Values 50 to 51 are + and - respectively on top row of keyboard. Values 54 and 55 are + and - respectively on NUMPAD. ASCII values are 0x2B for "+" and 0x2D for "-".
				//Note that most CDUs have both on the same key, hence pressing either of these keys will not necessarely set the value of the screen to that of the keyboard, but rather toggle between  them.
				//Also note that the cursor will step back to the previous value to check if it is a + or -.
				if(val == 50 || val == 51 || val == 54 || val == 55)
				{
					if(istring[cursor-(cursor>0?1:0)] == 0x2B || istring[cursor-(cursor>0?1:0)] == 0x2D)
					{
						kv = istring[cursor-(cursor>0?1:0)]==0x2B?0x2D:0x2B;
						if(cursor>0)
							cursor--;
					}
					else
						kv = 0x2B;
				}
				//Values 52 to 53 are . and / respectively on main keyboard. Values 56 and 57 are . and / respectively on NUMPAD. ASCII values are 0x2E for "." and 0x2F for "/". 
				if(val == 52 || val == 53 || val == 56 || val == 57)
					kv = 0x2E+char(val%2);
				istring[cursor] = kv;
			}
			if(cursor < MAXINPUT-1)
				cursor++;
		}
	}
}