/***************************************************************************** * * mapps.c - Property sheet handler * *****************************************************************************/ #include "map.h" /***************************************************************************** * * The sqiffle for this file. * *****************************************************************************/ #define sqfl sqflPs /***************************************************************************** * * Strings. * * The Scancode map registry value looks like this: * * DWORD dwVersion; // Must be zero * DWORD dwFlags; // Must be zero * DWORD dwNumRemaps; // Number of remaps, including terminating 0 * REMAPENTRY rgRemap[...]; // dwNumRemaps remap entries * * The last remap entry must be all-zero. * * * Each remap entry looks like this: * * WORD wTo; * WORD wFrom; * * where wFrom is the source scancode and wTo is the target scancode. * If the key being remapped is an extended key, then the high word * of the scancode is 0xE0. Otherwise, the high word is zero. * * NOTE! When we load the scancode map into memory, we make * dwNumRemaps *not* include the terminating zero. When we write * it out, we re-adjust it back. This is to avoid off-by-one errors * in the code. * *****************************************************************************/ typedef union REMAPENTRY { union { DWORD dw; /* Accessed as a dword */ }; struct { WORD wTo; /* Accessed as two words */ WORD wFrom; }; } REMAPENTRY, *PREMAPENTRY; #define MAX_REMAPENTRY (IDS_NUMKEYS+1) typedef struct SCANCODEMAP { DWORD dwVersion; DWORD dwFlags; DWORD dwNumRemaps; REMAPENTRY rgRemap[MAX_REMAPENTRY]; } SCANCODEMAP, *PSCANCODEMAP; #pragma BEGIN_CONST_DATA TCHAR c_tszKeyboard[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\") TEXT("Keyboard Layout"); TCHAR c_tszMapping[] = TEXT("Scancode Map"); #pragma END_CONST_DATA /***************************************************************************** * * rgwRemap * * Maps each key to its scancode. This must match the list of strings. * *****************************************************************************/ #pragma BEGIN_CONST_DATA WORD rgwRemap[] = { 0x003A, // IDS_CAPSLOCK 0x001D, // IDS_LCTRL 0xE01D, // IDS_RCTRL 0x0038, // IDS_LALT 0xE038, // IDS_RALT 0x002A, // IDS_LSHIFT 0x0036, // IDS_RSHIFT 0xE05B, // IDS_LWIN 0xE05C, // IDS_RWIN 0xE05D, // IDS_APPS }; #pragma END_CONST_DATA /***************************************************************************** * * KEYMAPDATA * * Instance data for the property sheet. * *****************************************************************************/ typedef struct KEYMAPDATA { SCANCODEMAP map; /* The mapping to apply */ int ilbFrom; /* What's in ID_FROM? */ int ilbTo; /* What's in ID_TO? */ } KMD, *PKMD; #define pkmdHdlg(hdlg) (PKMD)GetWindowPointer(hdlg, DWLP_USER) /***************************************************************************** * * MapPs_GetLbCurSel * * Get the current selection from a listbox. * *****************************************************************************/ int PASCAL MapPs_GetLbCurSel(HWND hdlg, UINT idc) { return (int)SendDlgItemMessage(hdlg, idc, LB_GETCURSEL, 0, 0); } /***************************************************************************** * * MapPs_FindEntry * * Locate a mapping table entry, or -1 if not found. * *****************************************************************************/ int PASCAL MapPs_FindEntry(PKMD pkmd, WORD wFrom) { DWORD iMap; for (iMap = 0; iMap < pkmd->map.dwNumRemaps; iMap++) { if (pkmd->map.rgRemap[iMap].wFrom == wFrom) { return (int)iMap; } } return -1; } /***************************************************************************** * * MapPs_WordToIndex * * Given a mapping in the form of a word (rgwRemap), convert it back * to the index that it came from. This is the reverse of the rgwRemap * array. * *****************************************************************************/ int PASCAL MapPs_WordToIndex(WORD w) { int i; for (i = 0; i < IDS_NUMKEYS; i++) { if (rgwRemap[i] == w) { return i; } } return -1; } /***************************************************************************** * * MapPs_SaveCurSel * * Stash what's in the current selection. * *****************************************************************************/ void PASCAL MapPs_SaveCurSel(HWND hdlg, PKMD pkmd) { int iTo = MapPs_GetLbCurSel(hdlg, IDC_TO); int iMap; WORD wFrom = rgwRemap[pkmd->ilbFrom]; WORD wTo = rgwRemap[iTo]; iMap = MapPs_FindEntry(pkmd, wFrom); if (iMap < 0) { /* * Not found; must allocate. Note that we check against * MAX_REMAPENTRY-1 because the trailing null eats one slot. */ if (pkmd->map.dwNumRemaps < MAX_REMAPENTRY - 1) { iMap = (int)pkmd->map.dwNumRemaps++; } else { /* * No room in the table. Oh well. */ return; } } /* * If the item is mapping to itself, then delete it entirely. */ if (wFrom == wTo) { pkmd->map.dwNumRemaps--; pkmd->map.rgRemap[iMap].dw = pkmd->map.rgRemap[pkmd->map.dwNumRemaps].dw; } else { pkmd->map.rgRemap[iMap].wFrom = wFrom; pkmd->map.rgRemap[iMap].wTo = wTo; } } /***************************************************************************** * * MapPs_TrackSel * * Select the corresponding item in idcTo given what's in idcFrom. * *****************************************************************************/ void PASCAL MapPs_TrackSel(HWND hdlg, PKMD pkmd) { int iFrom = pkmd->ilbFrom; int iMap, iTo; iMap = MapPs_FindEntry(pkmd, rgwRemap[iFrom]); if (iMap >= 0) { iTo = MapPs_WordToIndex(pkmd->map.rgRemap[iMap].wTo); if (iTo < 0) { /* * Target not recognized; just map it to itself. */ iTo = iFrom; } } else { /* * Key not mapped. Therefore, it maps to itself. */ iTo = iFrom; } pkmd->ilbTo = iTo; SendDlgItemMessage(hdlg, IDC_TO, LB_SETCURSEL, iTo, 0); } /***************************************************************************** * * MapPs_OnInitDialog * * Read the current scancode mapping and fill in the dialog box. * *****************************************************************************/ BOOL NEAR PASCAL MapPs_OnInitDialog(HWND hdlg) { PKMD pkmd = LocalAlloc(LPTR, cbX(KMD)); HKEY hk; LONG lRc; DWORD dwDisp; SetWindowPointer(hdlg, DWLP_USER, pkmd); lRc = RegCreateKeyEx(HKEY_LOCAL_MACHINE, c_tszKeyboard, 0, TEXT(""), REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE | KEY_SET_VALUE, NULL, &hk, &dwDisp); if (lRc == ERROR_SUCCESS) { DWORD dwType; DWORD cb; int dids; cb = sizeof(pkmd->map); lRc = RegQueryValueEx(hk, c_tszMapping, NULL, &dwType, (LPBYTE)&pkmd->map, &cb); RegCloseKey(hk); /* * Note that ERROR_MORE_DATA is an error here. * But ERROR_FILE_NOT_FOUND is okay. */ if (lRc == ERROR_SUCCESS) { /* * Sanity-check all the data. */ if ( /* Must be binary data */ dwType == REG_BINARY && /* Version zero */ pkmd->map.dwVersion == 0 && /* No flags */ pkmd->map.dwFlags == 0 && /* Sane number of remaps */ pkmd->map.dwNumRemaps > 0 && pkmd->map.dwNumRemaps <= MAX_REMAPENTRY && /* Structure is the correct size */ cb == (DWORD)FIELD_OFFSET(SCANCODEMAP, rgRemap[pkmd->map.dwNumRemaps]) && /* Last remap must be zero */ pkmd->map.rgRemap[pkmd->map.dwNumRemaps - 1].dw == 0 ) { } else { goto fail; } pkmd->map.dwNumRemaps--; /* Don't count the trailing null */ } else if (lRc == ERROR_FILE_NOT_FOUND) { /* * Set it up for a null mapping. */ ZeroMemory(&pkmd->map, sizeof(pkmd->map)); } else { goto fail; } /* * Now init the dialog items. */ for (dids = 0; dids < IDS_NUMKEYS; dids++) { TCHAR tsz[256]; LoadString(g_hinst, IDS_KEYFIRST + dids, tsz, cA(tsz)); SendDlgItemMessage(hdlg, IDC_FROM, LB_ADDSTRING, 0, (LPARAM)tsz); SendDlgItemMessage(hdlg, IDC_TO, LB_ADDSTRING, 0, (LPARAM)tsz); } } else { fail:; /* * User does not have permission to remap keys, or the key * contents aren't something we like. Gray the controls. */ EnableWindow(GetDlgItem(hdlg, IDC_TO), FALSE); } SendDlgItemMessage(hdlg, IDC_FROM, LB_SETCURSEL, 0, 0); MapPs_TrackSel(hdlg, pkmd); return 1; } /***************************************************************************** * * MapPs_OnSelChange * * Somebody changed a selection. Save the selection and set * the new one. * *****************************************************************************/ void PASCAL MapPs_OnSelChange(HWND hdlg, PKMD pkmd) { MapPs_SaveCurSel(hdlg, pkmd); /* Save it */ pkmd->ilbFrom = MapPs_GetLbCurSel(hdlg, IDC_FROM); MapPs_TrackSel(hdlg, pkmd); /* And update for the new one */ } /***************************************************************************** * * MapPs_OnCommand * * Ooh, we got a command. * *****************************************************************************/ BOOL PASCAL MapPs_OnCommand(HWND hdlg, int id, UINT codeNotify) { PKMD pkmd = pkmdHdlg(hdlg); switch (id) { case IDC_FROM: switch (codeNotify) { case LBN_SELCHANGE: MapPs_OnSelChange(hdlg, pkmd); break; } break; case IDC_TO: switch (codeNotify) { case LBN_SELCHANGE: if (MapPs_GetLbCurSel(hdlg, IDC_TO) != pkmd->ilbTo) { PropSheet_Changed(GetParent(hdlg), hdlg); } break; } break; } return 0; } /***************************************************************************** * * MapPs_Apply * * Write the changes to the registry and nudge the VxD. We might have * to load the VxD if the user is playing with KeyRemap immediately * after installing, without rebooting in the interim. * *****************************************************************************/ BOOL PASCAL MapPs_Apply(HWND hdlg) { PKMD pkmd = pkmdHdlg(hdlg); MapPs_SaveCurSel(hdlg, pkmd); if (IsWindowEnabled(GetDlgItem(hdlg, IDC_TO))) { LONG lRc; HKEY hk; lRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_tszKeyboard, 0, KEY_SET_VALUE, &hk); if (lRc == ERROR_SUCCESS) { DWORD cb; /* * Count the trailing null again. And make sure * it's a trailing null! */ pkmd->map.rgRemap[pkmd->map.dwNumRemaps].dw = 0; pkmd->map.dwNumRemaps++; cb = (DWORD)FIELD_OFFSET(SCANCODEMAP, rgRemap[pkmd->map.dwNumRemaps]); lRc = RegSetValueEx(hk, c_tszMapping, 0, REG_BINARY, (LPBYTE)&pkmd->map, cb); pkmd->map.dwNumRemaps--; RegCloseKey(hk); } if (lRc == ERROR_SUCCESS) { PropSheet_RebootSystem(GetParent(hdlg)); } } return 1; } /***************************************************************************** * * MapPs_OnNotify * * Ooh, we got a notification. * *****************************************************************************/ BOOL PASCAL MapPs_OnNotify(HWND hdlg, NMHDR FAR *pnm) { switch (pnm->code) { case PSN_APPLY: MapPs_Apply(hdlg); break; } return 0; } /***************************************************************************** * * MapPs_OnDestroy * * Clean up. * *****************************************************************************/ BOOL PASCAL MapPs_OnDestroy(HWND hdlg) { PKMD pkmd = pkmdHdlg(hdlg); FreePv(pkmd); return 1; } /***************************************************************************** * * MapPs_DlgProc * * Our property sheet dialog procedure. * *****************************************************************************/ INT_PTR CALLBACK MapPs_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_INITDIALOG: return MapPs_OnInitDialog(hdlg); case WM_COMMAND: return MapPs_OnCommand(hdlg, (int)GET_WM_COMMAND_ID(wParam, lParam), (UINT)GET_WM_COMMAND_CMD(wParam, lParam)); case WM_NOTIFY: return MapPs_OnNotify(hdlg, (NMHDR FAR *)lParam); case WM_DESTROY: return MapPs_OnDestroy(hdlg); default: return 0; /* Unhandled */ } return 1; /* Handled */ }