620 lines
20 KiB
C++
620 lines
20 KiB
C++
/*++
|
|
Copyright (c) 2000 Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
buttons.cpp
|
|
|
|
Abstract:
|
|
This module contains all the tablet pc button functions.
|
|
|
|
Author:
|
|
Michael Tsang (MikeTs) 29-Sep-2000
|
|
|
|
Environment:
|
|
User mode
|
|
|
|
Revision History:
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#define HOTKEY_TIMEOUT 200 //200msec
|
|
#define REPEAT_DELAY 500 //500msec
|
|
#define PAGEUPDOWN_TIMEOUT 200 //200msec
|
|
#define ALTTAB_TIMEOUT 500 //500msec
|
|
|
|
typedef struct _HOTKEYTIMER
|
|
{
|
|
UINT_PTR TimerID;
|
|
BUTTON_ACTION Action;
|
|
ULONG dwButtonTag;
|
|
ULONG dwButtonState;
|
|
} HOTKEYTIMER, *PHOTKEYTIMER;
|
|
|
|
typedef struct _REPEATKEYTIMER
|
|
{
|
|
UINT_PTR TimerID;
|
|
UINT uiRepeatInterval;
|
|
INPUT KbdInput;
|
|
} REPEATKEYTIMER, *PREPEATKEYTIMER;
|
|
|
|
HOTKEYTIMER gHotKeyTimer = {0};
|
|
REPEATKEYTIMER gPageUpDownTimer = {0};
|
|
REPEATKEYTIMER gAltTabTimer = {0};
|
|
|
|
/*++
|
|
@doc INTERNAL
|
|
|
|
@func VOID | ProcessButtonsReport | Process a button input report.
|
|
|
|
@parm IN PCHAR | pBuff | Buffer containing report data.
|
|
|
|
@rvalue None.
|
|
--*/
|
|
|
|
VOID
|
|
ProcessButtonsReport(
|
|
IN PCHAR pBuff
|
|
)
|
|
{
|
|
TRACEPROC("ProcessButtonsReport", 3)
|
|
static ULONG dwLastButtons = 0;
|
|
static ULONG dwPendingUpButtons = 0;
|
|
NTSTATUS status;
|
|
ULONG ulLength;
|
|
|
|
TRACEENTER(("(pBuff=%p)\n", pBuff));
|
|
|
|
ulLength = gdevButtons.dwcButtons;
|
|
status = HidP_GetButtons(HidP_Input,
|
|
HID_USAGE_PAGE_BUTTON,
|
|
0,
|
|
gdevButtons.pDownButtonUsages,
|
|
&ulLength,
|
|
gdevButtons.pPreParsedData,
|
|
pBuff,
|
|
gdevButtons.hidCaps.InputReportByteLength);
|
|
|
|
if (status == HIDP_STATUS_SUCCESS)
|
|
{
|
|
ULONG i, dwMask;
|
|
ULONG dwCurrentButtons = 0;
|
|
ULONG dwChangedButtons, dwButtons;
|
|
|
|
for (i = 0; i < ulLength; i++)
|
|
{
|
|
TRACEINFO(3, ("%d: %d\n", i, gdevButtons.pDownButtonUsages[i]));
|
|
dwCurrentButtons |= 1 << (gdevButtons.pDownButtonUsages[i] - 1);
|
|
}
|
|
|
|
dwChangedButtons = dwCurrentButtons^dwLastButtons;
|
|
TRACEINFO(1, ("LastButtons=%x,CurrentButtons=%x,ChangedButtons=%x,PendingUpButtons=%x\n",
|
|
dwLastButtons, dwCurrentButtons, dwChangedButtons,
|
|
dwPendingUpButtons));
|
|
|
|
//
|
|
// If there are any released buttons that are in the PendingUpButtons
|
|
// list, eat them.
|
|
//
|
|
dwButtons = dwChangedButtons & dwPendingUpButtons & ~dwCurrentButtons;
|
|
dwChangedButtons &= ~dwButtons;
|
|
dwPendingUpButtons &= ~dwButtons;
|
|
if (dwButtons)
|
|
{
|
|
TRACEINFO(1, ("Released PendingUpButtons=%x\n", dwButtons));
|
|
}
|
|
|
|
if ((gHotKeyTimer.TimerID != 0) &&
|
|
(dwChangedButtons & gConfig.ButtonSettings.dwHotKeyButtons) &&
|
|
(dwCurrentButtons == gConfig.ButtonSettings.dwHotKeyButtons))
|
|
{
|
|
//
|
|
// One of the hotkey buttons changes state and both hotkey
|
|
// buttons are down, so send the hotkey event.
|
|
//
|
|
TRACEINFO(1, ("HotKey buttons pressed, kill HotKey timer.\n"));
|
|
KillTimer(NULL, gHotKeyTimer.TimerID);
|
|
gHotKeyTimer.TimerID = 0;
|
|
|
|
SetEvent(ghHotkeyEvent);
|
|
dwPendingUpButtons |= dwCurrentButtons;
|
|
TRACEINFO(1, ("Detected HotKey (PendingUpButtons=%x)\n",
|
|
dwPendingUpButtons));
|
|
}
|
|
else
|
|
{
|
|
if ((dwCurrentButtons ^ -((long)dwCurrentButtons)) &
|
|
dwCurrentButtons)
|
|
{
|
|
//
|
|
// More than 1 buttons pressed, ignored the new buttons
|
|
// pressed.
|
|
//
|
|
TRACEINFO(1, ("More than 1 buttons pressed (NewPressedButtons=%x)\n",
|
|
dwChangedButtons & dwCurrentButtons));
|
|
dwPendingUpButtons |= dwChangedButtons & dwCurrentButtons;
|
|
dwChangedButtons &= ~(dwChangedButtons & dwCurrentButtons);
|
|
}
|
|
|
|
//
|
|
// Determine which remaining buttons have changed states and do
|
|
// corresponding actions.
|
|
//
|
|
for (i = 0, dwMask = 1; i < NUM_BUTTONS; i++, dwMask <<= 1)
|
|
{
|
|
dwButtons = dwChangedButtons & dwMask;
|
|
if (dwButtons != 0)
|
|
{
|
|
if (dwButtons & gConfig.ButtonSettings.dwHotKeyButtons)
|
|
{
|
|
//
|
|
// One of the hotkey buttons has changed state.
|
|
//
|
|
if (dwButtons & dwCurrentButtons)
|
|
{
|
|
//
|
|
// It's a hotkey button down, we delay the
|
|
// action for 200msec. If the second hotkey
|
|
// button is pressed within the period, it is
|
|
// a hotkey, otherwise the action will be
|
|
// performed when the timer expires.
|
|
//
|
|
TRACEINFO(1, ("HotKey button is pressed (Button=%x)\n",
|
|
dwButtons));
|
|
gHotKeyTimer.Action =
|
|
gConfig.ButtonSettings.ButtonMap[i];
|
|
gHotKeyTimer.dwButtonTag = dwButtons;
|
|
gHotKeyTimer.dwButtonState = dwCurrentButtons &
|
|
dwButtons;
|
|
gHotKeyTimer.TimerID = SetTimer(NULL,
|
|
0,
|
|
HOTKEY_TIMEOUT,
|
|
ButtonTimerProc);
|
|
TRACEASSERT(gHotKeyTimer.TimerID);
|
|
if (gHotKeyTimer.TimerID == 0)
|
|
{
|
|
TABSRVERR(("Failed to set hotkey timer (err=%d).\n",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The hotkey button is released.
|
|
//
|
|
if (gHotKeyTimer.TimerID != 0)
|
|
{
|
|
//
|
|
// It is released before timeout, so kill
|
|
// the timer and perform the delayed down
|
|
// action.
|
|
//
|
|
KillTimer(NULL, gHotKeyTimer.TimerID);
|
|
gHotKeyTimer.TimerID = 0;
|
|
TRACEINFO(1, ("HotKey button released before timeout (Button=%x)\n",
|
|
dwButtons));
|
|
DoButtonAction(gHotKeyTimer.Action,
|
|
gHotKeyTimer.dwButtonTag,
|
|
gHotKeyTimer.dwButtonState != 0);
|
|
}
|
|
//
|
|
// Do the HotKey button release.
|
|
//
|
|
TRACEINFO(1, ("HotKey button released (Button=%x)\n",
|
|
dwButtons));
|
|
DoButtonAction(gConfig.ButtonSettings.ButtonMap[i],
|
|
dwButtons,
|
|
FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Not a hotkey button, do the normal press/release
|
|
// action.
|
|
//
|
|
TRACEINFO(1, ("Non HotKey buttons (Button=%x,Current=%x)\n",
|
|
dwButtons, dwCurrentButtons));
|
|
DoButtonAction(gConfig.ButtonSettings.ButtonMap[i],
|
|
dwButtons,
|
|
(dwCurrentButtons & dwButtons) != 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dwLastButtons = dwCurrentButtons;
|
|
}
|
|
else
|
|
{
|
|
TABSRVERR(("failed getting data (status=%d)\n", status));
|
|
}
|
|
|
|
TRACEEXIT(("!\n"));
|
|
return;
|
|
} //ProcessButtonsReport
|
|
|
|
/*++
|
|
@doc INTERNAL
|
|
|
|
@func VOID | ButtonTimerProc | Button timer proc.
|
|
|
|
@parm IN HWND | hwnd | Not used.
|
|
@parm IN UINT | uMsg | WM_TIMER.
|
|
@parm IN UINT_PTR | idEvent | Not used.
|
|
@parm IN DWORD | dwTime | Not used.
|
|
|
|
@rvalue None.
|
|
--*/
|
|
|
|
VOID
|
|
CALLBACK
|
|
ButtonTimerProc(
|
|
IN HWND hwnd,
|
|
IN UINT uMsg,
|
|
IN UINT_PTR idEvent,
|
|
IN DWORD dwTime
|
|
)
|
|
{
|
|
TRACEPROC("ButtonTimerProc", 3)
|
|
|
|
TRACEENTER(("hwnd=%x,Msg=%s,idEvent=%x,Time=%x)\n",
|
|
hwnd, LookupName(uMsg, WMMsgNames), idEvent, dwTime));
|
|
TRACEASSERT(idEvent != 0);
|
|
|
|
KillTimer(NULL, idEvent);
|
|
if (idEvent == gHotKeyTimer.TimerID)
|
|
{
|
|
//
|
|
// HotKey timer times out. So we will do the pending action.
|
|
//
|
|
gHotKeyTimer.TimerID = 0;
|
|
TRACEINFO(3, ("HotKey timer expired, do pending action (Action=%d,Button=%x,fDown=%x).\n",
|
|
gHotKeyTimer.Action, gHotKeyTimer.dwButtonTag,
|
|
gHotKeyTimer.dwButtonState != 0));
|
|
DoButtonAction(gHotKeyTimer.Action,
|
|
gHotKeyTimer.dwButtonTag,
|
|
gHotKeyTimer.dwButtonState != 0);
|
|
}
|
|
else if (idEvent == gPageUpDownTimer.TimerID)
|
|
{
|
|
TRACEINFO(3, ("Page Up/Down timer expired, send another Page Up/Down.\n"));
|
|
SendInput(1, &gPageUpDownTimer.KbdInput, sizeof(INPUT));
|
|
gPageUpDownTimer.TimerID = SetTimer(NULL,
|
|
0,
|
|
gPageUpDownTimer.uiRepeatInterval,
|
|
ButtonTimerProc);
|
|
}
|
|
else if (idEvent == gAltTabTimer.TimerID)
|
|
{
|
|
TRACEINFO(3, ("Alt-tab timer expired, send another tab.\n"));
|
|
SendInput(1, &gAltTabTimer.KbdInput, sizeof(INPUT));
|
|
gAltTabTimer.TimerID = SetTimer(NULL,
|
|
0,
|
|
gAltTabTimer.uiRepeatInterval,
|
|
ButtonTimerProc);
|
|
}
|
|
else
|
|
{
|
|
TABSRVERR(("Unknown timer (TimerID=%x).\n", idEvent));
|
|
}
|
|
|
|
TRACEEXIT(("!\n"));
|
|
return;
|
|
} //ButtonTimerProc
|
|
|
|
/*++
|
|
@doc INTERNAL
|
|
|
|
@func BOOL | DoButtonAction | Do the button action.
|
|
|
|
@parm IN BUTTON_ACTION | Action | Button action to be performed.
|
|
@parm IN DWORD | dwButtonTag | Specifies which button.
|
|
@parm IN BOOL | fButtonDown | TRUE if the button is in down state.
|
|
|
|
@rvalue SUCCESS | Return TRUE.
|
|
@rvalue FAILURE | Return FALSE.
|
|
--*/
|
|
|
|
BOOL
|
|
DoButtonAction(
|
|
IN BUTTON_ACTION Action,
|
|
IN DWORD dwButtonTag,
|
|
IN BOOL fButtonDown
|
|
)
|
|
{
|
|
TRACEPROC("DoButtonAction", 2)
|
|
BOOL rc = TRUE;
|
|
INPUT KbdInput[2];
|
|
WORD wVk;
|
|
|
|
TRACEENTER(("(Action=%d,ButtonTag=%x,fDown=%x)\n",
|
|
Action, dwButtonTag, fButtonDown));
|
|
|
|
memset(KbdInput, 0, sizeof(KbdInput));
|
|
KbdInput[0].type = INPUT_KEYBOARD;
|
|
KbdInput[1].type = INPUT_KEYBOARD;
|
|
switch (Action)
|
|
{
|
|
case InvokeNoteBook:
|
|
if (!fButtonDown)
|
|
{
|
|
TRACEINFO(1, ("Invoke NoteBook on button down (Button=%x)\n",
|
|
dwButtonTag));
|
|
rc = DoInvokeNoteBook();
|
|
}
|
|
break;
|
|
|
|
case PageUp:
|
|
wVk = VK_PRIOR;
|
|
TRACEINFO(1, ("PageUp (Button=%x,fDown=%x)\n",
|
|
dwButtonTag, fButtonDown));
|
|
goto PageUpDownCommon;
|
|
|
|
case PageDown:
|
|
wVk = VK_NEXT;
|
|
TRACEINFO(1, ("PageDown (Button=%x,fDown=%x)\n",
|
|
dwButtonTag, fButtonDown));
|
|
|
|
PageUpDownCommon:
|
|
if (fButtonDown)
|
|
{
|
|
if (gPageUpDownTimer.TimerID != 0)
|
|
{
|
|
//
|
|
// There is an existing Page Up/Down timer, cancel it.
|
|
//
|
|
KillTimer(NULL, gPageUpDownTimer.TimerID);
|
|
gPageUpDownTimer.TimerID = 0;
|
|
}
|
|
|
|
//
|
|
// Send PageUpDown-down.
|
|
//
|
|
memset(&gPageUpDownTimer, 0, sizeof(gPageUpDownTimer));
|
|
gPageUpDownTimer.uiRepeatInterval = PAGEUPDOWN_TIMEOUT;
|
|
gPageUpDownTimer.KbdInput.type = INPUT_KEYBOARD;
|
|
gPageUpDownTimer.KbdInput.ki.wVk = wVk;
|
|
SendInput(1, &gPageUpDownTimer.KbdInput, sizeof(INPUT));
|
|
|
|
gPageUpDownTimer.TimerID = SetTimer(NULL,
|
|
0,
|
|
REPEAT_DELAY,
|
|
ButtonTimerProc);
|
|
TRACEASSERT(gPageUpDownTimer.TimerID);
|
|
if (gPageUpDownTimer.TimerID == 0)
|
|
{
|
|
TABSRVERR(("Failed to set Page Up/Down timer (err=%d).\n",
|
|
GetLastError()));
|
|
rc = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACEASSERT(gPageUpDownTimer.TimerID != 0);
|
|
KillTimer(NULL, gPageUpDownTimer.TimerID);
|
|
gPageUpDownTimer.TimerID = 0;
|
|
//
|
|
// Send PageUpDown-up.
|
|
//
|
|
KbdInput[0].ki.wVk = wVk;
|
|
KbdInput[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(1, KbdInput, sizeof(INPUT));
|
|
}
|
|
break;
|
|
|
|
case AltTab:
|
|
if (fButtonDown)
|
|
{
|
|
TRACEINFO(1, ("AltTab down (Button=%x)\n", dwButtonTag));
|
|
TRACEASSERT(gAltTabTimer.TimerID == 0);
|
|
//
|
|
// Send Alt-down, Tab-down.
|
|
//
|
|
KbdInput[0].ki.wVk = VK_MENU;
|
|
KbdInput[1].ki.wVk = VK_TAB;
|
|
SendInput(2, KbdInput, sizeof(INPUT));
|
|
|
|
|
|
memset(&gAltTabTimer, 0, sizeof(gAltTabTimer));
|
|
gAltTabTimer.uiRepeatInterval = ALTTAB_TIMEOUT;
|
|
gAltTabTimer.KbdInput.type = INPUT_KEYBOARD;
|
|
gAltTabTimer.KbdInput.ki.wVk = VK_TAB;
|
|
gAltTabTimer.TimerID = SetTimer(NULL,
|
|
0,
|
|
REPEAT_DELAY,
|
|
ButtonTimerProc);
|
|
TRACEASSERT(gAltTabTimer.TimerID);
|
|
if (gAltTabTimer.TimerID == 0)
|
|
{
|
|
TABSRVERR(("Failed to set Alt-Tab timer (err=%d).\n",
|
|
GetLastError()));
|
|
rc = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACEINFO(1, ("AltTab up (Button=%x)\n", dwButtonTag));
|
|
TRACEASSERT(gAltTabTimer.TimerID != 0);
|
|
KillTimer(NULL, gAltTabTimer.TimerID);
|
|
gAltTabTimer.TimerID = 0;
|
|
//
|
|
// Send Tab-up, Alt-up.
|
|
//
|
|
KbdInput[0].ki.wVk = VK_TAB;
|
|
KbdInput[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
KbdInput[1].ki.wVk = VK_MENU;
|
|
KbdInput[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(2, KbdInput, sizeof(INPUT));
|
|
}
|
|
break;
|
|
|
|
case AltEsc:
|
|
if (fButtonDown)
|
|
{
|
|
//
|
|
// Send Alt-down, Esc-down.
|
|
//
|
|
TRACEINFO(1, ("AltEsc down (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_MENU;
|
|
KbdInput[1].ki.wVk = VK_ESCAPE;
|
|
SendInput(2, KbdInput, sizeof(INPUT));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Send Esc-up, Alt-up.
|
|
//
|
|
TRACEINFO(1, ("AltEsc up (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_ESCAPE;
|
|
KbdInput[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
KbdInput[1].ki.wVk = VK_MENU;
|
|
KbdInput[1].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(2, KbdInput, sizeof(INPUT));
|
|
}
|
|
break;
|
|
|
|
case Enter:
|
|
if (fButtonDown)
|
|
{
|
|
//
|
|
// Send Enter-down.
|
|
//
|
|
TRACEINFO(1, ("Return down (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_RETURN;
|
|
SendInput(1, KbdInput, sizeof(INPUT));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Send Enter-up.
|
|
//
|
|
TRACEINFO(1, ("Return up (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_RETURN;
|
|
KbdInput[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(1, KbdInput, sizeof(INPUT));
|
|
}
|
|
break;
|
|
|
|
case Esc:
|
|
if (fButtonDown)
|
|
{
|
|
//
|
|
// Send Esc-down.
|
|
//
|
|
TRACEINFO(1, ("Escape down (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_ESCAPE;
|
|
SendInput(1, KbdInput, sizeof(INPUT));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Send Esc-up.
|
|
//
|
|
TRACEINFO(1, ("Escape up (Button=%x)\n", dwButtonTag));
|
|
KbdInput[0].ki.wVk = VK_ESCAPE;
|
|
KbdInput[0].ki.dwFlags = KEYEVENTF_KEYUP;
|
|
SendInput(1, KbdInput, sizeof(INPUT));
|
|
}
|
|
break;
|
|
}
|
|
|
|
TRACEEXIT(("=%x\n", rc));
|
|
return rc;
|
|
} //DoButtonAction
|
|
|
|
/*++
|
|
@doc INTERNAL
|
|
|
|
@func BOOL | DoInvokeNoteBook | Invoke the Notebook app.
|
|
|
|
@parm None.
|
|
|
|
@rvalue SUCCESS | Returns TRUE.
|
|
@rvalue FAILURE | Returns FALSE.
|
|
--*/
|
|
|
|
BOOL
|
|
DoInvokeNoteBook(
|
|
VOID
|
|
)
|
|
{
|
|
TRACEPROC("DoInvokeNoteBook", 3)
|
|
BOOL rc = FALSE;
|
|
PTSTHREAD pThread = FindThread(TSF_BUTTONTHREAD);
|
|
|
|
TRACEENTER(("()\n"));
|
|
|
|
if (!(pThread->dwfThread & THREADF_DESKTOP_WINLOGON))
|
|
{
|
|
TCHAR szNoteBook[MAX_PATH];
|
|
DWORD dwcb = sizeof(szNoteBook);
|
|
HRESULT hr;
|
|
|
|
hr = GetRegValueString(HKEY_LOCAL_MACHINE,
|
|
TEXT("SOFTWARE\\Microsoft\\MSNotebook"),
|
|
TEXT("InstallDir"),
|
|
szNoteBook,
|
|
&dwcb);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lstrcat(szNoteBook, "NoteBook.exe");
|
|
rc = RunProcessAsUser(szNoteBook);
|
|
}
|
|
else
|
|
{
|
|
TABSRVERR(("Failed to read MSNoteBook install path from the registry (hr=%d).\n",
|
|
hr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACEWARN(("Cannot run NoteBook app. in WinLogon desktop.\n"));
|
|
}
|
|
|
|
TRACEEXIT(("=%x\n", rc));
|
|
return rc;
|
|
} //DoInvokeNoteBook
|
|
|
|
/*++
|
|
@doc INTERNAL
|
|
|
|
@func VOID | UpdateButtonRepeatRate | Update button repeat rate.
|
|
|
|
@parm None.
|
|
|
|
@rvalue None.
|
|
--*/
|
|
|
|
VOID
|
|
UpdateButtonRepeatRate(
|
|
VOID
|
|
)
|
|
{
|
|
TRACEPROC("UpdateButtonRepeatRate", 3)
|
|
static DWORD KbdDelayTime[] = {1000, 750, 500, 250};
|
|
int iKbdDelay;
|
|
int iKbdSpeed;
|
|
|
|
TRACEENTER(("()\n"));
|
|
#if 0
|
|
if (!SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &iKbdDelay, 0))
|
|
{
|
|
TRACEWARN(("Get keyboard delay failed (err=%d).\n", GetLastError()));
|
|
}
|
|
else if (!SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &iKbdSpeed, 0))
|
|
{
|
|
TRACEWARN(("Get keyboard speed failed (err=%d).\n", GetLastError()));
|
|
}
|
|
else
|
|
{
|
|
TRACEASSERT((iKbdDelay >= 0) && (iKbdDelay <= 3));
|
|
gdwKbdDelayTime = KbdDelayTime[iKbdDelay];
|
|
gdwKbdRepeatTime =
|
|
}
|
|
#endif
|
|
|
|
TRACEEXIT(("!\n"));
|
|
return;
|
|
} //UpdateButtonRepeatRate
|