/*++ 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