Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1649 lines
52 KiB

/*++
*
* Component: hidserv.dll
* File: appcmd.c
* Purpose: routines to run the HID Audio server.
*
* Copyright (C) Microsoft Corporation 1997,1998. All rights reserved.
*
* WGJ
--*/
#define GLOBALS
#include "hidserv.h"
#define HIDSERV_FROM_SPEAKER 0x8000
/*++
* IMPORTANT - All work within this service is synchronized by the
* message procedure HidServProc() except the per device work thread
* HidThreadProc(). All concurrent access to shared data is within the
* message procedure thread and therefore is serialized. For example,
* HidThreadProc() posts messages to the message thread when it needs
* to perform a serialized action. Any deviation from this scheme must
* be protected by critical section.
--*/
DWORD
WINAPI
HidServMain(
HANDLE InitDoneEvent
)
/*++
Routine Description:
Creates the main message loop and executes the
Hid Audio server.
--*/
{
MSG msg;
HANDLE thread;
BOOLEAN classRegistered = FALSE;
// Some controls have Auto Repeat timers. This mutex prevents
// concurrent access to data by these async timers.
hMutexOOC = CreateMutex(NULL, FALSE, TEXT("OOC State Mutex"));
if (!hMutexOOC) {
goto HidServMainBail;
}
// Use CreateMutex to detect previous instances of the app.
if (GetLastError() == ERROR_ALREADY_EXISTS){
WARN(("Exiting multiple Hid Service instance."));
goto HidServMainBail;
}
hInputEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!hInputEvent) {
goto HidServMainBail;
}
hInputDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!hInputDoneEvent) {
goto HidServMainBail;
}
hDesktopSwitch = OpenEvent(SYNCHRONIZE, FALSE, TEXT("WinSta0_DesktopSwitch"));
if (!hDesktopSwitch) {
goto HidServMainBail;
}
InputThreadEnabled = TRUE;
// Register the window class
{
WNDCLASSEX wce;
wce.cbSize = sizeof(WNDCLASSEX);
wce.style = 0;
wce.lpfnWndProc = HidServProc;
wce.cbClsExtra = 0;
wce.cbWndExtra = 0;
wce.hInstance = hInstance;
wce.hIcon = NULL;
wce.hIconSm = NULL;
wce.hCursor = NULL;
wce.hbrBackground = NULL;
wce.lpszMenuName = NULL;
wce.lpszClassName = TEXT("HidServClass");
if (!RegisterClassEx(&wce)){
WARN(("Cannot register thread window class: 0x%.8x\n", GetLastError()));
goto HidServMainBail;
}
classRegistered = TRUE;
}
// Create the app window.
// Most events will be processed through this hidden window. Look at HidServProc() to see
// what work this window message loop does.
hWndHidServ = CreateWindow(TEXT("HidServClass"),
TEXT("HID Input Service"),
WS_OVERLAPPEDWINDOW,
0,
0,
0,
0,
(HWND) NULL,
(HMENU) NULL,
hInstance,
(LPVOID) NULL);
TRACE(("hWndHidServ == %x", hWndHidServ));
// If the window cannot be created, terminate
if (!hWndHidServ){
WARN(("Window creation failed."));
goto HidServMainBail;
}
// Register for selective device nofication
// This only required for NT5
{
DEV_BROADCAST_DEVICEINTERFACE DevHdr;
ZeroMemory(&DevHdr, sizeof(DevHdr));
DevHdr.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
DevHdr.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
HidD_GetHidGuid (&DevHdr.dbcc_classguid);
hNotifyArrival =
RegisterDeviceNotification( hWndHidServ,
&DevHdr,
DEVICE_NOTIFY_WINDOW_HANDLE);
if (!hNotifyArrival){
WARN(("RegisterDeviceNotification failure (%x).", GetLastError()));
goto HidServMainBail;
}
}
// We do this here, not in WM_CREATE handler, because the init routines need
// to know the new window handle.
HidServInit();
InputSessionId = 0;
InputSessionLocked = FALSE;
WinStaDll = NULL;
WinStaDll = LoadLibrary(TEXT("winsta.dll"));
if (!WinStaDll) {
goto HidServMainBail;
}
WinStaProc = (WINSTATIONSENDWINDOWMESSAGE)
GetProcAddress(WinStaDll, "WinStationSendWindowMessage");
if (!WinStaProc) {
goto HidServMainBail;
}
thread = CreateThread(
NULL, // pointer to thread security attributes
0, // initial thread stack size, in bytes (0 = default)
HidThreadInputProc, // pointer to thread function
NULL, // argument for new thread
0, // creation flags
&InputThreadId // pointer to returned thread identifier
);
if (!thread) {
goto HidServMainBail;
}
if (InitDoneEvent) {
SetEvent(InitDoneEvent);
}
SET_SERVICE_STATE(SERVICE_RUNNING);
// Start the message loop. This is terminated by system shutdown
// or End Task. There is no UI to close the app.
while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// To terminate, we only need to destroy the window. MmHidExit() was
// already called on WM_CLOSE.
DestroyWindow(hWndHidServ);
INFO(("UnRegistering window class"));
UnregisterClass(TEXT("HidServClass"),
hInstance);
// Don't let this process go until all HidThreadProc() threads are complete.
// Lets first wait for the thread to complete so that the thread has a chance
// to at least increment the cThreadRef
WaitForSingleObject(thread,
INFINITE);
//
// Since we don't have the per-device thread handles, we will just wait for
// the ref count to hid zero
//
while (cThreadRef) SleepEx(1000, FALSE);
return 0;
HidServMainBail:
if (hMutexOOC) {
CloseHandle(hMutexOOC);
}
if (hInputEvent) {
CloseHandle(hInputEvent);
}
if (hInputDoneEvent) {
CloseHandle(hInputDoneEvent);
}
if (hDesktopSwitch) {
CloseHandle(hDesktopSwitch);
}
if (hWndHidServ) {
DestroyWindow(hWndHidServ);
}
if (classRegistered) {
UnregisterClass(TEXT("HidServClass"),
hInstance);
}
if (WinStaDll) {
FreeLibrary(WinStaDll);
}
// unstick ServiceMain
if (InitDoneEvent) {
SetEvent(InitDoneEvent);
}
SET_SERVICE_STATE(SERVICE_STOPPED);
return 0;
}
void
HidservSetPnP(
BOOL Enable
)
{
if (Enable) {
if (!PnpEnabled){
// Enable device refresh.
PnpEnabled = TRUE;
PostMessage(hWndHidServ, WM_HIDSERV_PNP_HID, 0, 0);
}
} else {
// Prevent any device refresh.
PnpEnabled = FALSE;
DestroyHidDeviceList();
}
}
void
HidServStart(
void
)
/*++
Routine Description:
Restart the Hid Audio server if it has been stopped.
--*/
{
HidservSetPnP(TRUE);
SET_SERVICE_STATE(SERVICE_RUNNING);
}
void
HidServStop(
void
)
/*++
Routine Description:
Stop all activity, but keep static data, and keep
the message queue running.
--*/
{
// Prevent any device refresh.
HidservSetPnP(FALSE);
SET_SERVICE_STATE(SERVICE_STOPPED);
}
BOOL
HidServInit(
void
)
/*++
Routine Description:
Setup all data structures and open system handles.
--*/
{
HidServStart();
return TRUE;
}
void
HidServExit(
void
)
/*++
Routine Description:
Close all system handles.
--*/
{
if (WinStaDll) {
FreeLibrary(WinStaDll);
}
UnregisterDeviceNotification(hNotifyArrival);
HidServStop();
CloseHandle(hMutexOOC);
if (InputThreadEnabled) {
InputThreadEnabled = FALSE;
SetEvent(hInputEvent);
}
}
VOID
HidThreadChangeDesktop (
)
{
HDESK hDesk, hPrevDesk;
BOOL result;
HWINSTA prevWinSta, winSta = NULL;
hPrevDesk = GetThreadDesktop(GetCurrentThreadId());
prevWinSta = GetProcessWindowStation();
INFO(("Setting the input thread's desktop"));
winSta = OpenWindowStation(TEXT("WinSta0"), FALSE, MAXIMUM_ALLOWED);
if (!winSta) {
WARN(("Couldn't get the window station! Error: 0x%x", GetLastError()));
goto HidThreadChangeDesktopError;
}
if (!SetProcessWindowStation(winSta)) {
WARN(("Couldn't set the window station! Error: 0x%x", GetLastError()));
goto HidThreadChangeDesktopError;
}
hDesk = OpenInputDesktop(0,
FALSE,
MAXIMUM_ALLOWED);
if (!hDesk) {
WARN(("Couldn't get the input desktop! Error: 0x%x", GetLastError()));
goto HidThreadChangeDesktopError;
}
if (!SetThreadDesktop(hDesk)) {
WARN(("Couldn't set the thread's desktop to the input desktop! Error: 0x%x", GetLastError()));
}
HidThreadChangeDesktopError:
if (hPrevDesk) {
CloseDesktop(hPrevDesk);
}
if (prevWinSta) {
CloseWindowStation(prevWinSta);
}
}
DWORD
WINAPI
HidThreadInputProc(
PVOID Ignore
)
{
GUITHREADINFO threadInfo;
HWND hWndForeground;
INPUT input;
HANDLE events[2];
DWORD ret;
DWORD nEvents = 0;
InterlockedIncrement(&cThreadRef);
events[nEvents++] = hDesktopSwitch;
events[nEvents++] = hInputEvent;
//
// This thread needs to run on the input desktop.
//
HidThreadChangeDesktop();
while (TRUE) {
ret = WaitForMultipleObjects(nEvents, events, FALSE, INFINITE);
if (!InputThreadEnabled) {
break;
}
if (0 == (ret - WAIT_OBJECT_0)) {
HidThreadChangeDesktop();
continue;
}
if (InputIsAppCommand) {
threadInfo.cbSize = sizeof(GUITHREADINFO);
if (GetGUIThreadInfo(0, &threadInfo)) {
hWndForeground = threadInfo.hwndFocus ? threadInfo.hwndFocus : threadInfo.hwndActive;
if (hWndForeground) {
INFO(("Sending app command 0x%x", InputAppCommand));
SendNotifyMessage(hWndForeground,
WM_APPCOMMAND,
(WPARAM)hWndForeground,
((InputAppCommand | FAPPCOMMAND_OEM)<<16));
} else {
WARN(("No window available to send to, error %x", GetLastError()));
}
} else {
WARN(("Unable to get the focus window, error %x", GetLastError()));
}
} else {
ZeroMemory(&input, sizeof(INPUT));
input.type = INPUT_KEYBOARD;
input.ki.dwFlags = InputDown ? 0 : KEYEVENTF_KEYUP;
if (InputIsChar) {
input.ki.wScan = InputVKey;
input.ki.dwFlags |= KEYEVENTF_UNICODE;
INFO(("Sending character %c %s", InputVKey, InputDown ? "down" : "up"));
} else {
input.ki.wVk = InputVKey;
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
INFO(("Sending VK 0x%x %s", InputVKey, InputDown ? "down" : "up"));
}
SendInput(1, &input, sizeof(INPUT));
}
SetEvent(hInputDoneEvent);
}
CloseHandle(hDesktopSwitch);
CloseHandle(hInputEvent);
CloseHandle(hInputDoneEvent);
InterlockedDecrement(&cThreadRef);
return 0;
}
DWORD
WINAPI
HidThreadProc(
PHID_DEVICE HidDevice
)
/*++
Routine Description:
Create this I/O thread for each Consumer Collection we have
open. The thread dies when we close our handle on the HID device.
--*/
{
DWORD Ret;
DWORD bytesRead;
BOOL bRet;
DWORD dwError;
USAGE_AND_PAGE *pPrevious;
PHID_DATA data = HidDevice->InputData;
TRACE(("Entering HidThreadProc. Device(%x)", HidDevice));
InterlockedIncrement(&cThreadRef);
// wait for an async read
INFO(("HidThreadProc waiting for read event..."));
WaitForSingleObject(HidDevice->ReadEvent, INFINITE);
while (HidDevice->fThreadEnabled){
TRACE(("Reading from Handle(%x)", HidDevice->HidDevice));
bRet = ReadFile (HidDevice->HidDevice,
HidDevice->InputReportBuffer,
HidDevice->Caps.InputReportByteLength,
&bytesRead,
&HidDevice->Overlap);
dwError = GetLastError();
// wait for read to complete
TRACE(("HidThreadProc waiting for completion."));
if(bRet){
TRACE(("Read completed synchronous."));
}else{
if (dwError == ERROR_IO_PENDING) {
TRACE(("Read pending."));
// work thread waits for completion
while (TRUE) {
Ret = WaitForSingleObject(HidDevice->CompletionEvent, 5000);
if (Ret == WAIT_OBJECT_0) {
TRACE(("Read completed on device (%x).", HidDevice));
break;
}
if (!HidDevice->fThreadEnabled) {
if (CancelIo(HidDevice->HidDevice)) {
TRACE(("CancelIo succeeded for device (%x).", HidDevice));
break;
}
}
}
TRACE(("Read complete async."));
} else {
WARN(("Read Failed with error %x. device = %x, handle = %x", dwError, HidDevice, HidDevice->HidDevice));
INFO(("Device may no longer be connected. Waiting for device notification from pnp..."));
// Just wait for the device notification to come thru from PnP.
// Then we'll remove the device.
WaitForSingleObject(HidDevice->ReadEvent, INFINITE);
break;
}
}
// don't parse data if we are exiting.
if (!HidDevice->fThreadEnabled) {
WaitForSingleObject(HidDevice->ReadEvent, INFINITE);
break;
}
// parse the hid report
ParseReadReport(HidDevice);
// post message to dispatch this report
HidServReportDispatch(HidDevice);
}
// Exit Thread means completely clean up this device instance
TRACE(("HidThreadProc (%x) Exiting...", HidDevice));
//
// Send any leftover button up events
//
if (data->IsButtonData) {
pPrevious = data->ButtonData.PrevUsages;
while (pPrevious->Usage){
int j;
// find the client that handled the button down.
for(j=0; j<MAX_PENDING_BUTTONS; j++){
if ( PendingButtonList[j].Collection == data->LinkUsage &&
PendingButtonList[j].Page == pPrevious->UsagePage &&
PendingButtonList[j].Usage == pPrevious->Usage){
PendingButtonList[j].Collection = 0;
PendingButtonList[j].Page = 0;
PendingButtonList[j].Usage = 0;
break;
}
}
PostMessage(hWndHidServ,
WM_CI_USAGE,
(WPARAM)MakeLongUsage(data->LinkUsage,pPrevious->Usage),
(LPARAM)MakeLongUsage(pPrevious->UsagePage, 0));
pPrevious++;
}
}
CloseHandle(HidDevice->HidDevice);
CloseHandle(HidDevice->ReadEvent);
CloseHandle(HidDevice->CompletionEvent);
INFO(("Free device data. (%x)", HidDevice));
HidFreeDevice (HidDevice);
InterlockedDecrement(&cThreadRef);
TRACE(("HidThreadProc Exit complete."));
return 0;
}
BOOL
UsageInList(
PUSAGE_AND_PAGE pUsage,
PUSAGE_AND_PAGE pUsageList
)
/*++
Routine Description:
This utility function returns TRUE if the usage is found in the array.
--*/
{
while (pUsageList->Usage){
if ( (pUsage->Usage == pUsageList->Usage) &&
(pUsage->UsagePage == pUsageList->UsagePage))
return TRUE;
pUsageList++;
}
return FALSE;
}
void
HidServReportDispatch(
PHID_DEVICE HidDevice
)
/*++
Routine Description:
Look at the HID input structure and determine what button down,
button up, or value data events have occurred. We send info about these events
to the most appropriate client.
--*/
{
USAGE_AND_PAGE * pUsage;
USAGE_AND_PAGE * pPrevious;
DWORD i;
PHID_DATA data = HidDevice->InputData;
TRACE(("Input data length = %d", HidDevice->InputDataLength));
TRACE(("Input data -> %.8x", HidDevice->InputData));
for (i = 0;
i < HidDevice->InputDataLength;
i++, data++) {
// If Collection is 0, then make it default
if (!data->LinkUsage)
data->LinkUsage = CInputCollection_Consumer_Control;
if (data->Status != HIDP_STATUS_SUCCESS){
// never try to process errored data
//TRACE(("Input data is invalid. Status = %x", data->Status));
}else if (data->IsButtonData){
TRACE(("Input data is button data:"));
TRACE((" Input Usage Page = %x, Collection = %x", data->UsagePage, data->LinkUsage));
pUsage = data->ButtonData.Usages;
pPrevious = data->ButtonData.PrevUsages;
/// Notify clients of any button down events
//
while (pUsage->Usage){
int j;
TRACE((" Button Usage Page = %x", pUsage->UsagePage));
TRACE((" Button Usage = %x", pUsage->Usage));
if (HidDevice->Speakers) {
pUsage->Usage |= HIDSERV_FROM_SPEAKER;
}
// is this button already down?
for(j=0; j<MAX_PENDING_BUTTONS; j++)
// The Pending Button List is used to keep state for all
// currently pressed buttons.
if ( PendingButtonList[j].Collection == data->LinkUsage &&
PendingButtonList[j].Page == pUsage->UsagePage &&
PendingButtonList[j].Usage == pUsage->Usage)
break;
// discard successive button downs
if (j<MAX_PENDING_BUTTONS){
pUsage++;
continue;
}
// post the message
PostMessage(hWndHidServ,
WM_CI_USAGE,
(WPARAM)MakeLongUsage(data->LinkUsage,pUsage->Usage),
(LPARAM)MakeLongUsage(pUsage->UsagePage, 1)
);
// Add to the pending button list
for(j=0; j<MAX_PENDING_BUTTONS; j++){
if (!PendingButtonList[j].Collection &&
!PendingButtonList[j].Page &&
!PendingButtonList[j].Usage){
PendingButtonList[j].Collection = data->LinkUsage;
PendingButtonList[j].Page = pUsage->UsagePage;
PendingButtonList[j].Usage = pUsage->Usage;
break;
}
}
// if it didn't make the list, send button up now.
if (j==MAX_PENDING_BUTTONS){
PostMessage( hWndHidServ,
WM_CI_USAGE,
(WPARAM)MakeLongUsage(data->LinkUsage,pUsage->Usage),
(LPARAM)MakeLongUsage(pUsage->UsagePage, 0)
);
WARN(("Emitting immediate button up (C=%.2x,U=%.2x,P=%.2x)", data->LinkUsage, pUsage->Usage, pUsage->UsagePage));
}
pUsage++;
}
/// Notify clients of any button up events
//
while (pPrevious->Usage){
int j;
if (!UsageInList(pPrevious, pUsage)){
// we have a button up.
//
TRACE((" Button Up (C=%.2x,U=%.2x,P=%.2x)", data->LinkUsage, pPrevious->Usage, pPrevious->UsagePage));
// find the client that handled the button down.
for(j=0; j<MAX_PENDING_BUTTONS; j++){
if ( PendingButtonList[j].Collection == data->LinkUsage &&
PendingButtonList[j].Page == pPrevious->UsagePage &&
PendingButtonList[j].Usage == pPrevious->Usage){
PendingButtonList[j].Collection = 0;
PendingButtonList[j].Page = 0;
PendingButtonList[j].Usage = 0;
break;
}
}
// post the message if client found
if (j<MAX_PENDING_BUTTONS){
PostMessage( hWndHidServ,
WM_CI_USAGE,
(WPARAM)MakeLongUsage(data->LinkUsage,pPrevious->Usage),
(LPARAM)MakeLongUsage(pPrevious->UsagePage, 0)
);
} else {
WARN(("Button Up client not found (C=%.2x,U=%.2x,P=%.2x)", data->LinkUsage, pPrevious->Usage, pPrevious->UsagePage));
}
}
pPrevious++;
}
// Remember what buttons were down, so next time we can
// detect if they come up.
pPrevious = data->ButtonData.Usages;
data->ButtonData.Usages = data->ButtonData.PrevUsages;
data->ButtonData.PrevUsages = pPrevious;
} else {
TRACE(("Input data is value data:"));
TRACE((" Input Usage Page = %x, Collection = %x", data->UsagePage, data->LinkUsage));
TRACE((" Input Usage = %x", data->ValueData.Usage));
// don't send zeroes or invalid range.
if ( data->ValueData.ScaledValue &&
data->ValueData.LogicalRange){
// post the message
// rescale the data to a standard range
PostMessage(hWndHidServ,
WM_CI_USAGE,
(WPARAM)MakeLongUsage(data->LinkUsage,data->ValueData.Usage),
(LPARAM)MakeLongUsage(data->UsagePage,(USHORT)(((double)data->ValueData.ScaledValue/data->ValueData.LogicalRange)*65536)));
}
}
}
}
void
SendVK(
UCHAR VKey,
SHORT Down
)
{
if (InputThreadEnabled && !InputSessionLocked) {
if (InputSessionId == 0) {
InputVKey = VKey;
InputDown = Down;
InputIsAppCommand = FALSE;
InputIsChar = FALSE;
SetEvent(hInputEvent);
WaitForSingleObject(hInputDoneEvent, INFINITE);
} else {
CrossSessionWindowMessage(Down ? WM_KEYDOWN : WM_KEYUP, VKey, 0);
}
}
}
void
SendChar(
UCHAR wScan,
SHORT Down
)
{
if (InputThreadEnabled && !InputSessionLocked) {
if (InputSessionId == 0) {
InputVKey = wScan;
InputDown = Down;
InputIsAppCommand = FALSE;
InputIsChar = TRUE;
SetEvent(hInputEvent);
WaitForSingleObject(hInputDoneEvent, INFINITE);
} else {
CrossSessionWindowMessage(Down ? WM_KEYDOWN : WM_KEYUP, 0, wScan);
}
}
}
void
SendAppCommand(
USHORT AppCommand
)
{
if (InputThreadEnabled && !InputSessionLocked) {
if (InputSessionId == 0) {
InputAppCommand = AppCommand;
InputIsAppCommand = TRUE;
InputIsChar = FALSE;
SetEvent(hInputEvent);
WaitForSingleObject(hInputDoneEvent, INFINITE);
} else {
CrossSessionWindowMessage(WM_APPCOMMAND, AppCommand, 0);
}
}
}
VOID
VolumeTimerHandler(
WPARAM TimerID
)
/*++
Routine Description:
This timer handler routine is called for all timeouts on auto-repeat capable
contols.
--*/
{
INFO(("Timer triggered, TimerId = %d", TimerID));
WaitForSingleObject(hMutexOOC, INFINITE);
switch (TimerID){
case TIMERID_VOLUMEUP_VK:
if (OOC(TIMERID_VOLUMEUP_VK)){
SendVK(VK_VOLUME_UP, 0x1);
OOC(TIMERID_VOLUMEUP_VK) = SetTimer(hWndHidServ, TIMERID_VOLUMEUP_VK, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_VOLUMEDN_VK:
if (OOC(TIMERID_VOLUMEDN_VK)){
SendVK(VK_VOLUME_DOWN, 0x1);
OOC(TIMERID_VOLUMEDN_VK) = SetTimer(hWndHidServ, TIMERID_VOLUMEDN_VK, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_VOLUMEUP:
if (OOC(TIMERID_VOLUMEUP)){
SendAppCommand(APPCOMMAND_VOLUME_UP);
OOC(TIMERID_VOLUMEUP) = SetTimer(hWndHidServ, TIMERID_VOLUMEUP, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_VOLUMEDN:
if (OOC(TIMERID_VOLUMEDN)){
SendAppCommand(APPCOMMAND_VOLUME_DOWN);
OOC(TIMERID_VOLUMEDN) = SetTimer(hWndHidServ, TIMERID_VOLUMEDN, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_BASSUP:
if (OOC(TIMERID_BASSUP)){
SendAppCommand(APPCOMMAND_BASS_UP);
OOC(TIMERID_BASSUP) = SetTimer(hWndHidServ, TIMERID_BASSUP, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_BASSDN:
if (OOC(TIMERID_BASSDN)){
SendAppCommand(APPCOMMAND_BASS_DOWN);
OOC(TIMERID_BASSDN) = SetTimer(hWndHidServ, TIMERID_BASSDN, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_TREBLEUP:
if (OOC(TIMERID_TREBLEUP)){
SendAppCommand(APPCOMMAND_TREBLE_UP);
OOC(TIMERID_TREBLEUP) = SetTimer(hWndHidServ, TIMERID_TREBLEUP, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_TREBLEDN:
if (OOC(TIMERID_TREBLEDN)){
SendAppCommand(APPCOMMAND_TREBLE_DOWN);
OOC(TIMERID_TREBLEDN) = SetTimer(hWndHidServ, TIMERID_TREBLEDN, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_APPBACK:
if (OOC(TIMERID_APPBACK)){
SendVK(VK_BROWSER_BACK, 0x1);
OOC(TIMERID_APPBACK) = SetTimer(hWndHidServ, TIMERID_APPBACK, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_APPFORWARD:
if (OOC(TIMERID_APPFORWARD)){
SendVK(VK_BROWSER_FORWARD, 0x1);
OOC(TIMERID_APPFORWARD) = SetTimer(hWndHidServ, TIMERID_APPFORWARD, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_PREVTRACK:
if (OOC(TIMERID_PREVTRACK)){
SendVK(VK_MEDIA_PREV_TRACK, 0x1);
OOC(TIMERID_PREVTRACK) = SetTimer(hWndHidServ, TIMERID_PREVTRACK, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_NEXTTRACK:
if (OOC(TIMERID_NEXTTRACK)){
SendVK(VK_MEDIA_NEXT_TRACK, 0x1);
OOC(TIMERID_NEXTTRACK) = SetTimer(hWndHidServ, TIMERID_NEXTTRACK, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_KEYPAD_LPAREN:
if (OOC(TIMERID_KEYPAD_LPAREN)) {
SendChar(L'(', 0x1);
OOC(TIMERID_KEYPAD_LPAREN) = SetTimer(hWndHidServ, TIMERID_KEYPAD_LPAREN, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_KEYPAD_RPAREN:
if (OOC(TIMERID_KEYPAD_RPAREN)) {
SendChar(L')', 0x1);
OOC(TIMERID_KEYPAD_RPAREN) = SetTimer(hWndHidServ, TIMERID_KEYPAD_RPAREN, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_KEYPAD_AT:
if (OOC(TIMERID_KEYPAD_AT)) {
SendChar(L'@', 0x1);
OOC(TIMERID_KEYPAD_AT) = SetTimer(hWndHidServ, TIMERID_KEYPAD_AT, REPEAT_INTERVAL, NULL);
}
break;
case TIMERID_KEYPAD_EQUAL:
if (OOC(TIMERID_KEYPAD_EQUAL)) {
SendChar(L'=', 0x1);
OOC(TIMERID_KEYPAD_EQUAL) = SetTimer(hWndHidServ, TIMERID_KEYPAD_EQUAL, REPEAT_INTERVAL, NULL);
}
break;
}
ReleaseMutex(hMutexOOC);
}
void
HidRepeaterCharButtonDown(
UINT TimerId,
SHORT Value,
UCHAR WScan
)
{
INFO(("Received update char,value = %d, TimerId = %d", Value, TimerId));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TimerId)){
SendChar(WScan, 0x1);
OOC(TimerId) = SetTimer(hWndHidServ, TimerId, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TimerId);
OOC(TimerId) = 0;
SendChar(WScan, 0x0);
}
ReleaseMutex(hMutexOOC);
}
void
HidRepeaterVKButtonDown(
UINT TimerId,
SHORT Value,
UCHAR VKey
)
{
INFO(("Received update vk,value = %d, TimerId = %d", Value, TimerId));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TimerId)){
SendVK(VKey, 0x1);
OOC(TimerId) = SetTimer(hWndHidServ, TimerId, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TimerId);
OOC(TimerId) = 0;
SendVK(VKey, 0x0);
}
ReleaseMutex(hMutexOOC);
}
void
HidServUpdate(
DWORD LongUsage,
DWORD LongValue
)
/*++
Routine Description:
This is the client routine for the default handler. This client attempts to satisfy
input events by injecting appcommands or keypresses to the current input window.
--*/
{
USAGE Collection = (USAGE)HIWORD(LongUsage);
USAGE Usage = (USAGE)LOWORD(LongUsage);
USAGE Page = (USAGE)HIWORD(LongValue);
SHORT Value = (SHORT)LOWORD(LongValue);
BOOLEAN fromSpeaker = ((Usage & HIDSERV_FROM_SPEAKER) == HIDSERV_FROM_SPEAKER);
Usage &= ~HIDSERV_FROM_SPEAKER;
INFO(("Update collection = %x", Collection));
INFO(("Update page = %x", Page));
INFO(("Update usage = %x", Usage));
INFO(("Update data = %d", Value));
if (Collection == CInputCollection_Consumer_Control){
// NOTE: If we ever choose to support this page thing, keep in mind
// that the Altec Lansing ADA 70s report page zero. Should take out
// the consumer page and make it the default.
switch (Page) {
case HID_USAGE_PAGE_UNDEFINED:
case HID_USAGE_PAGE_CONSUMER:
switch (Usage){
/// Button Usages
//
//
// These buttons have auto repeat capability...
// delay for .5 sec before auto repeat kicks in.
//
case CInputUsage_Volume_Increment:
INFO(("Volume increment."));
if (fromSpeaker) {
INFO(("From speaker."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_VOLUMEUP)){
SendAppCommand(APPCOMMAND_VOLUME_UP);
OOC(TIMERID_VOLUMEUP) = SetTimer(hWndHidServ, TIMERID_VOLUMEUP, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_VOLUMEUP);
OOC(TIMERID_VOLUMEUP) = 0;
}
ReleaseMutex(hMutexOOC);
} else {
INFO(("From keyboard."));
HidRepeaterVKButtonDown(TIMERID_VOLUMEUP_VK, Value, VK_VOLUME_UP);
}
break;
case CInputUsage_Volume_Decrement:
INFO(("Volume decrement."));
if (fromSpeaker) {
INFO(("From speaker."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_VOLUMEDN)){
SendAppCommand(APPCOMMAND_VOLUME_DOWN);
OOC(TIMERID_VOLUMEDN) = SetTimer(hWndHidServ, TIMERID_VOLUMEDN, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_VOLUMEDN);
OOC(TIMERID_VOLUMEDN) = 0;
}
ReleaseMutex(hMutexOOC);
} else {
INFO(("From keyboard."));
HidRepeaterVKButtonDown(TIMERID_VOLUMEDN_VK, Value, VK_VOLUME_DOWN);
}
break;
case CInputUsage_App_Back:
INFO(("App Back."));
HidRepeaterVKButtonDown(TIMERID_APPBACK, Value, VK_BROWSER_BACK);
break;
case CInputUsage_App_Forward:
INFO(("App Forward."));
HidRepeaterVKButtonDown(TIMERID_APPFORWARD, Value, VK_BROWSER_FORWARD);
break;
case CInputUsage_Scan_Previous_Track:
INFO(("Media Previous Track."));
HidRepeaterVKButtonDown(TIMERID_PREVTRACK, Value, VK_MEDIA_PREV_TRACK);
break;
case CInputUsage_Scan_Next_Track:
INFO(("Media Next Track."));
HidRepeaterVKButtonDown(TIMERID_NEXTTRACK, Value, VK_MEDIA_NEXT_TRACK);
break;
case CInputUsage_Bass_Increment:
INFO(("Bass increment."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_BASSUP)){
SendAppCommand(APPCOMMAND_BASS_UP);
OOC(TIMERID_BASSUP) = SetTimer(hWndHidServ, TIMERID_BASSUP, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_BASSUP);
OOC(TIMERID_BASSUP) = 0;
}
ReleaseMutex(hMutexOOC);
break;
case CInputUsage_Bass_Decrement:
INFO(("Bass decrement."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_BASSDN)){
SendAppCommand(APPCOMMAND_BASS_DOWN);
OOC(TIMERID_BASSDN) = SetTimer(hWndHidServ, TIMERID_BASSDN, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_BASSDN);
OOC(TIMERID_BASSDN) = 0;
}
ReleaseMutex(hMutexOOC);
break;
case CInputUsage_Treble_Increment:
INFO(("Treble increment."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_TREBLEUP)){
SendAppCommand(APPCOMMAND_TREBLE_UP);
OOC(TIMERID_TREBLEUP) = SetTimer(hWndHidServ, TIMERID_TREBLEUP, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_TREBLEUP);
OOC(TIMERID_TREBLEUP) = 0;
}
ReleaseMutex(hMutexOOC);
break;
case CInputUsage_Treble_Decrement:
INFO(("Treble decrement."));
WaitForSingleObject(hMutexOOC, INFINITE);
if (Value){
if (!OOC(TIMERID_TREBLEDN)){
SendAppCommand(APPCOMMAND_TREBLE_DOWN);
OOC(TIMERID_TREBLEDN) = SetTimer(hWndHidServ, TIMERID_TREBLEDN, INITIAL_WAIT, NULL);
}
} else {
KillTimer(hWndHidServ, TIMERID_TREBLEDN);
OOC(TIMERID_TREBLEDN) = 0;
}
ReleaseMutex(hMutexOOC);
break;
// These buttons do not auto repeat...
case CInputUsage_Loudness:
if (Value){
INFO(("Toggle Loudness."));
//SendAppCommandEx(??);
}
break;
case CInputUsage_Bass_Boost:
if (Value) {
INFO(("Toggle BassBoost."));
SendAppCommand(APPCOMMAND_BASS_BOOST);
}
break;
case CInputUsage_Mute:
INFO(("Toggle Mute."));
if (fromSpeaker) {
INFO(("From speaker."));
if (Value) {
SendAppCommand(APPCOMMAND_VOLUME_MUTE);
}
} else {
INFO(("From keyboard."));
SendVK(VK_VOLUME_MUTE, Value);
}
break;
case CInputUsage_Play_Pause:
INFO(("Media Play/Pause."));
SendVK(VK_MEDIA_PLAY_PAUSE, Value);
break;
case CInputUsage_Stop:
INFO(("Media Stop."));
SendVK(VK_MEDIA_STOP, Value);
break;
case CInputUsage_Launch_Configuration:
INFO(("Launch Configuration."));
SendVK(VK_LAUNCH_MEDIA_SELECT, Value);
break;
case CInputUsage_Launch_Email:
INFO(("Launch Email."));
SendVK(VK_LAUNCH_MAIL, Value);
break;
case CInputUsage_Launch_Calculator:
INFO(("Launch Calculator."));
SendVK(VK_LAUNCH_APP2, Value);
break;
case CInputUsage_Launch_Browser:
INFO(("Launch Browser."));
SendVK(VK_LAUNCH_APP1, Value);
break;
case CInputUsage_App_Search:
INFO(("App Search."));
SendVK(VK_BROWSER_SEARCH, Value);
break;
case CInputUsage_App_Home:
INFO(("App Home."));
SendVK(VK_BROWSER_HOME, Value);
break;
case CInputUsage_App_Stop:
INFO(("App Stop."));
SendVK(VK_BROWSER_STOP, Value);
break;
case CInputUsage_App_Refresh:
INFO(("App Refresh."));
SendVK(VK_BROWSER_REFRESH, Value);
break;
case CInputUsage_App_Bookmarks:
INFO(("App Bookmarks."));
SendVK(VK_BROWSER_FAVORITES, Value);
break;
case CInputUsage_App_Previous:
if (Value){
INFO(("App Previous."));
//SendAppCommand(??);
}
break;
case CInputUsage_App_Next:
if (Value){
INFO(("App Next."));
//SendAppCommand(??);
}
break;
#if(0)
// New buttons
case CInputUsage_App_Help:
if (Value) {
INFO(("App Help"));
SendAppCommand(APPCOMMAND_HELP);
}
break;
case CInputUsage_App_Find:
if (Value) {
INFO(("App Find"));
SendAppCommand(APPCOMMAND_FIND);
}
break;
case CInputUsage_App_New:
if (Value) {
INFO(("App New"));
SendAppCommand(APPCOMMAND_NEW);
}
break;
case CInputUsage_App_Open:
if (Value) {
INFO(("App Open"));
SendAppCommand(APPCOMMAND_OPEN);
}
break;
case CInputUsage_App_Close:
if (Value) {
INFO(("App Close"));
SendAppCommand(APPCOMMAND_CLOSE);
}
break;
case CInputUsage_App_Save:
if (Value) {
INFO(("App Save"));
SendAppCommand(APPCOMMAND_SAVE);
}
break;
case CInputUsage_App_Print:
if (Value) {
INFO(("App Print"));
SendAppCommand(APPCOMMAND_PRINT);
}
break;
case CInputUsage_App_Undo:
if (Value) {
INFO(("App Undo"));
SendAppCommand(APPCOMMAND_UNDO);
}
break;
case CInputUsage_App_Redo:
if (Value) {
INFO(("App Redo"));
SendAppCommand(APPCOMMAND_REDO);
}
break;
case CInputUsage_App_Copy:
if (Value) {
INFO(("App Copy"));
SendAppCommand(APPCOMMAND_COPY);
}
break;
case CInputUsage_App_Cut:
if (Value) {
INFO(("App Cut"));
SendAppCommand(APPCOMMAND_CUT);
}
break;
case CInputUsage_App_Paste:
if (Value) {
INFO(("App Paste"));
SendAppCommand(APPCOMMAND_PASTE);
}
break;
case CInputUsage_App_Reply_To_Mail:
if (Value) {
INFO(("App Reply To Mail"));
SendAppCommand(APPCOMMAND_REPLY_TO_MAIL);
}
break;
case CInputUsage_App_Forward_Mail:
if (Value) {
INFO(("App Forward Mail"));
SendAppCommand(APPCOMMAND_FORWARD_MAIL);
}
break;
case CInputUsage_App_Send_Mail:
if (Value) {
INFO(("App Send Mail"));
SendAppCommand(APPCOMMAND_SEND_MAIL);
}
break;
case CInputUsage_App_Spell_Check:
if (Value) {
INFO(("App Spell Check"));
SendAppCommand(APPCOMMAND_SPELL_CHECK);
}
break;
#endif
/// Value Usages
// These are not buttons, but are "value" events and do not have
// a corresponding button up event. Also, these never have an
// auto repeat function.
case CInputUsage_Volume:
INFO(("Volume dial"));
if (Value>0) SendAppCommand(APPCOMMAND_VOLUME_UP);
else if (Value<0)SendAppCommand(APPCOMMAND_VOLUME_DOWN);
break;
case CInputUsage_Bass:
INFO(("Bass dial"));
if (Value>0) SendAppCommand(APPCOMMAND_BASS_UP);
else if (Value<0)SendAppCommand(APPCOMMAND_BASS_DOWN);
break;
case CInputUsage_Treble:
INFO(("Treble dial"));
if (Value>0) SendAppCommand(APPCOMMAND_TREBLE_UP);
else if (Value<0)SendAppCommand(APPCOMMAND_TREBLE_DOWN);
break;
////
/// Media Select usages are not handled in this sample.
//
default:
INFO(("Unhandled Usage (%x)", Usage));
break;
}
break;
#if(0)
case HID_USAGE_PAGE_KEYBOARD:
switch (Usage) {
case CInputUsage_Keypad_Equals:
INFO(("Keypad ="));
HidRepeaterCharButtonDown(TIMERID_KEYPAD_EQUAL, Value, L'=');
break;
case CInputUsage_Keypad_LParen:
INFO(("Keypad ("));
HidRepeaterCharButtonDown(TIMERID_KEYPAD_LPAREN, Value, L'(');
break;
case CInputUsage_Keypad_RParen:
INFO(("Keypad )"));
HidRepeaterCharButtonDown(TIMERID_KEYPAD_RPAREN, Value, L')');
break;
case CInputUsage_Keypad_At:
INFO(("Keypad @"));
HidRepeaterCharButtonDown(TIMERID_KEYPAD_AT, Value, L'@');
break;
}
break;
#endif
default:
INFO(("Unhandled Page (%x)", Page));
break;
}
} else {
INFO(("Unhandled Collection (%x), usage = %x", Collection, Usage));
}
}
BOOL
DeviceChangeHandler(
WPARAM wParam,
LPARAM lParam
)
/*++
Routine Description:
This is the handler for WM_DEVICECHANGE messages and is called
whenever a device node is added or removed in the system. This
event will cause us to refrsh our device information.
--*/
{
struct _DEV_BROADCAST_HEADER *pdbhHeader;
pdbhHeader = (struct _DEV_BROADCAST_HEADER *)lParam;
switch (wParam) {
case DBT_DEVICEQUERYREMOVE :
TRACE(("DBT_DEVICEQUERYREMOVE, fall through to..."));
//
// Fall thru.
//
case DBT_DEVICEREMOVECOMPLETE:
TRACE(("DBT_DEVICEREMOVECOMPLETE"));
TRACE(("dbcd_devicetype %x", pdbhHeader->dbcd_devicetype));
if (pdbhHeader->dbcd_devicetype==DBT_DEVTYP_HANDLE)
{
PDEV_BROADCAST_HANDLE pdbHandle = (PDEV_BROADCAST_HANDLE)lParam;
INFO(("Closing HID device (%x).", pdbHandle->dbch_handle));
DestroyDeviceByHandle(pdbHandle->dbch_handle);
break;
}
break;
case DBT_DEVICEQUERYREMOVEFAILED:
TRACE(("DBT_DEVICEQUERYREMOVEFAILED, fall through to..."));
// The notification handle has already been closed
// so we should never actually get this message. If we do,
// falling through to device arrival is the correct thing to do.
//
// Fall thru.
//
case DBT_DEVICEARRIVAL:
TRACE(("DBT_DEVICEARRIVAL: reenumerate"));
TRACE(("dbcd_devicetype %x", pdbhHeader->dbcd_devicetype));
if (pdbhHeader->dbcd_devicetype==DBT_DEVTYP_DEVICEINTERFACE)
{
// We will refresh our device info for any devnode arrival or removal.
INFO(("HID device refresh."));
PostMessage(hWndHidServ, WM_HIDSERV_PNP_HID, 0, 0);
break;
}
}
return TRUE;
}
VOID
HidKeyboardSettingsChange(WPARAM WParam)
{
if (WParam == SPI_SETKEYBOARDSPEED ||
WParam == SPI_SETKEYBOARDDELAY) {
DWORD dwV;
int v;
//
// The repeat rate has changed. Adjust the timer interval.
// The keyboard delay has changed. Adjust the timer interval.
//
INFO(("Getting keyboard repeat rate."));
SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &dwV, 0);
REPEAT_INTERVAL = 400 - (12*dwV);
INFO(("Getting keyboard delay."));
SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &v, 0);
INITIAL_WAIT = (1+v)*250;
}
}
LRESULT
CALLBACK
HidServProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
/*++
Routine Description:
The primary message queue for the app.
--*/
{
TRACE(("HidServProc uMsg=%x", uMsg));
switch (uMsg)
{
// init
case WM_CREATE :
TRACE(("WM_CREATE"));
//
// Find out the default key values
//
HidKeyboardSettingsChange(SPI_SETKEYBOARDSPEED);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
// start
case WM_HIDSERV_START :
TRACE(("WM_HIDSERV_START"));
HidServStart();
break;
// stop
case WM_HIDSERV_STOP :
TRACE(("WM_HIDSERV_STOP"));
HidServStop();
break;
// configuration change
case WM_DEVICECHANGE:
TRACE(("WM_DEVICECHANGE"));
DeviceChangeHandler(wParam, lParam);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
// Process Consumer Input usage
case WM_CI_USAGE:
TRACE(("WM_CI_USAGE"));
HidServUpdate((DWORD)wParam, (DWORD)lParam);
break;
// HID device list refresh.
case WM_HIDSERV_PNP_HID:
TRACE(("WM_HIDSERV_PNP_HID"));
if (PnpEnabled){
INFO(("HID DeviceChange rebuild."));
RebuildHidDeviceList();
TRACE(("DeviceChange rebuild done."));
}
break;
#if WIN95_BUILD
// Stop the specified hid device that has already been removed from
// the global list.
case WM_HIDSERV_STOP_DEVICE:
StopHidDevice((PHID_DEVICE) lParam);
break;
#endif // WIN95_BUILD
// Process Timer
case WM_TIMER:
TRACE(("WM_TIMER"));
// All auto-repeat controls handled here.
VolumeTimerHandler(wParam); // wParam is Timer ID.
break;
// Usually an app need not respond to suspend/resume events, but there
// have been problems with keeping some system handles open. So on
// suspend, we close everything down except this message loop. On resume,
// we bring it all back.
case WM_POWERBROADCAST:
TRACE(("WM_POWERBROADCAST"));
switch ( (DWORD)wParam )
{
case PBT_APMQUERYSUSPEND:
TRACE(("\tPBT_APMQUERYSUSPEND"));
HidservSetPnP(FALSE);
break;
case PBT_APMQUERYSUSPENDFAILED:
TRACE(("\tPBT_APMQUERYSUSPENDFAILED"));
HidservSetPnP(TRUE);
break;
case PBT_APMSUSPEND:
TRACE(("\tPBT_APMSUSPEND"));
// Handle forced suspend
if(PnpEnabled) {
// Prevent any device refresh.
HidservSetPnP(FALSE);
}
break;
case PBT_APMRESUMESUSPEND:
TRACE(("\tPBT_APMRESUMESUSPEND"));
HidservSetPnP(TRUE);
break;
case PBT_APMRESUMEAUTOMATIC:
TRACE(("\tPBT_APMRESUMEAUTOMATIC"));
HidservSetPnP(TRUE);
break;
}
break;
// close
case WM_CLOSE :
TRACE(("WM_CLOSE"));
HidServExit();
PostMessage(hWndHidServ, WM_QUIT, 0, 0);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
case WM_WTSSESSION_CHANGE:
WARN(("WM_WTSSESSION_CHANGE type %x, session %d", wParam, lParam));
switch (wParam) {
case WTS_CONSOLE_CONNECT:
InputSessionId = (ULONG)lParam;
InputSessionLocked = FALSE;
break;
case WTS_CONSOLE_DISCONNECT:
if (InputSessionId == (ULONG)lParam) {
InputSessionId = 0;
}
break;
case WTS_SESSION_LOCK:
if (InputSessionId == (ULONG)lParam) {
InputSessionLocked = TRUE;
}
break;
case WTS_SESSION_UNLOCK:
if (InputSessionId == (ULONG)lParam) {
InputSessionLocked = FALSE;
}
break;
}
break;
case WM_SETTINGCHANGE:
HidKeyboardSettingsChange(wParam);
TRACE(("WM_SETTINGCHANGE"));
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return FALSE;
}