I'm messing about, writing some code that simulates keyboard/input device events to automate various things under linux.
I've been using /usr/include/linux/input-event-codes.h as a bit of a reference for what key-codes are available. Within that header, it mentions that some of the content is based on a USB standard:
/*
* Keys and buttons
*
* Most of the keys/buttons are modeled after USB HUT 1.12
* (see http://www.usb.org/developers/hidpage).
* Abbreviations in the comments:
* AC - Application Control
* AL - Application Launch Button
* SC - System Control
*/
I assume it's referring to an old version of this HID User Table doc...
For example Volume up/down:
Consumer Page (0x0C)
Usage ID: E9 = Volume increment
Usage ID: EA = Volume decrement
So the USB scan codes of 0x0C00E9 and 0x0C00EA somehow map through to the key codes from the header:
#define KEY_VOLUMEDOWN 114
#define KEY_VOLUMEUP 115
Which I confirmed on my keyboard with evtest:
Testing ... (interrupt to exit)
Event: time 1670822773.520848, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1670822773.520848, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 1
Event: time 1670822773.520848, -------------- SYN_REPORT ------------
Event: time 1670822773.521763, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1670822773.521763, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 0
Event: time 1670822773.521763, -------------- SYN_REPORT ------------
Event: time 1670822775.779771, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1670822775.779771, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1
Event: time 1670822775.779771, -------------- SYN_REPORT ------------
Event: time 1670822775.780761, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1670822775.780761, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0
Event: time 1670822775.780761, -------------- SYN_REPORT ------------
My question is, where is that mapping defined (udev? kernel usb driver?), because if I find other functions in the USB standard, I'd like to see if and what key code they map to. The volume example was straightforward to figure out based on the names and the fact I could test with a physical device, but others won't be.
Specifically I want to start with scan code 0x0b0026 (Telephony Device - Drop), which I would call hangup, because I can't find a key code for that.
My search engine results fail to find much, but like always, I'm sure I'm just searching the wrong terms.
Looks like scan code to key code mapping is a combination of kernel driver and udev.
The default mapping looks to be done in the kernel driver: see here for the example volume up/down in the hid-input driver:
case 0x0e9: map_key_clear(KEY_VOLUMEUP); break;
case 0x0ea: map_key_clear(KEY_VOLUMEDOWN); break;
After that, there's a whole bunch of extra mapping and/or remapping done by udev hwdb. See the Arch wiki for a good write-up of that here.
So that means the final mapping is by no means 'standard'. There could be a whole range of mappings for a single USB HID usage table function, depending on the driver and the hardware id. For example, you might have two different vendor USB keyboard that both have a volume button that both send the same scan code, but they could produce two different key codes. Or you could load a different driver for the same keyboard and get another key code.
Now, for the specific question of a key code for a hang-up button (scan code 0x0b0026, Telephony Device - Drop): There's no default mapping in the hid-input driver:
case HID_UP_TELEPHONY:
switch (usage->hid & HID_USAGE) {
case 0x2f: map_key_clear(KEY_MICMUTE); break;
case 0xb0: map_key_clear(KEY_NUMERIC_0); break;
case 0xb1: map_key_clear(KEY_NUMERIC_1); break;
case 0xb2: map_key_clear(KEY_NUMERIC_2); break;
case 0xb3: map_key_clear(KEY_NUMERIC_3); break;
case 0xb4: map_key_clear(KEY_NUMERIC_4); break;
case 0xb5: map_key_clear(KEY_NUMERIC_5); break;
case 0xb6: map_key_clear(KEY_NUMERIC_6); break;
case 0xb7: map_key_clear(KEY_NUMERIC_7); break;
case 0xb8: map_key_clear(KEY_NUMERIC_8); break;
case 0xb9: map_key_clear(KEY_NUMERIC_9); break;
case 0xba: map_key_clear(KEY_NUMERIC_STAR); break;
case 0xbb: map_key_clear(KEY_NUMERIC_POUND); break;
case 0xbc: map_key_clear(KEY_NUMERIC_A); break;
case 0xbd: map_key_clear(KEY_NUMERIC_B); break;
case 0xbe: map_key_clear(KEY_NUMERIC_C); break;
case 0xbf: map_key_clear(KEY_NUMERIC_D); break;
default: goto ignore;
}
break;
And there's no udev hwdb mappings I can find:
me#computer:/usr/lib/udev/hwdb.d$ grep -n -e "[bB]0026" ./*
me#computer:/usr/lib/udev/hwdb.d$
at least on ubuntu 20.04.
So in summary No there is not a an even somewhat standardised key code for a hang-up button.
I have a standard dialog resource which has some radio controls on it.
At the moment it is all done the normal way so the first radio is mapped to a int variable.
DDX_Radio(pDX, IDC_RADIO_DISPLAY_EVERYONE, m_iDisplayMode);
DDX_Radio(pDX, IDC_RADIO_SELECT_EVERYONE, m_iSelectMode);
Here is the thing ... I have these associated enumerations:
enum class DisplayMode { Everyone = 0, Brother, Sister };
enum class SelectMode { Everyone = 0, Elders, MinisterialServants, Appointed, Custom, None };
Therefore, whenever I need to do some comparisons of the mapped variable I have to do it like this:
Example 1:
m_iDisplayMode = to_underlying(DisplayMode::Everyone);
m_iSelectMode = to_underlying(SelectMode::None);
Example 2:
if (m_iDisplayMode == to_underlying(DisplayMode::Everyone))
bInclude = true;
else if (m_iDisplayMode == to_underlying(DisplayMode::Brother) && mapPublisher.second.eGender == Gender::Male)
bInclude = true;
else if (m_iDisplayMode == to_underlying(DisplayMode::Sister) && mapPublisher.second.eGender == Gender::Female)
bInclude = true;
The to_underlying function is a helper function that has been suggested previously to me here on SO and has been invaluable:
template <typename E>
constexpr auto to_underlying(E e) noexcept
{
return static_cast<std::underlying_type_t<E>>(e);
}
What I want to know is whether it is possible to map those radio controls directly to the DisplayMode or SelectMode objects? So instead of mapping to 1 etc. it maps to DisplayMode::Everyone etc. This would simplfy the code in this context and avoid the need for all the to_underlying calls.
This is the MFC source for DDX_Radio:
void AFXAPI DDX_Radio(CDataExchange* pDX, int nIDC, int& value)
// must be first in a group of auto radio buttons
{
pDX->PrepareCtrl(nIDC);
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl);
ASSERT(::GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP);
ASSERT(::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON);
if (pDX->m_bSaveAndValidate)
value = -1; // value if none found
// walk all children in group
int iButton = 0;
do
{
if (::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON)
{
// control in group is a radio button
if (pDX->m_bSaveAndValidate)
{
if (::SendMessage(hWndCtrl, BM_GETCHECK, 0, 0L) != 0)
{
ASSERT(value == -1); // only set once
value = iButton;
}
}
else
{
// select button
::SendMessage(hWndCtrl, BM_SETCHECK, (iButton == value), 0L);
}
iButton++;
}
else
{
TRACE(traceAppMsg, 0, "Warning: skipping non-radio button in group.\n");
}
hWndCtrl = ::GetWindow(hWndCtrl, GW_HWNDNEXT);
} while (hWndCtrl != NULL &&
!(GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP));
}
I am trying to use the code in the answer but get this error:
MFC supports mapping between data (class members) and UI state. The standard mechanism is called Dialog Data Exchange (DDX) which the code in the question is using already (DDX_Radio). The data exchange is two-way, triggered by a call to UpdateData, where an argument of TRUE translates the UI state into values, and FALSE reads the associated values and adjusts the UI appropriately.
There are a number of standard dialog data exchange routines provided by MFC already, but clients can provide their own in case none of them fit the immediate use case. The question falls into this category, and conveniently provides the implementation of DDX_Radio to serve as a starting point.
The implementation looks a fair bit intimidating, though things start to make sense once the code has been augmented with a few comments here and there:
CustomDDX.h:
template<typename E>
void AFXAPI DDX_RadioEnum(CDataExchange* pDX, int nIDC, E& value)
{
// (1) Prepare the control for data exchange
pDX->PrepareCtrl(nIDC);
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(nIDC, &hWndCtrl);
// (2) Make sure this routine is associated with the first
// radio button in a radio button group
ASSERT(::GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP);
// And verify, that it is indeed a radio button
ASSERT(::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON);
// (3) Iterate over all radio buttons in this group
using value_t = std::underlying_type_t<E>;
value_t rdbtn_index {};
do {
if (::SendMessage(hWndCtrl, WM_GETDLGCODE, 0, 0L) & DLGC_RADIOBUTTON) {
// (4) Control is a radio button
if (pDX->m_bSaveAndValidate) {
// (5) Transfer data from UI to class member
if (::SendMessage(hWndCtrl, BM_GETCHECK, 0, 0L) != 0) {
value = static_cast<E>(rdbtn_index);
}
} else {
// (6) Transfer data from class member to UI
::SendMessage(hWndCtrl, BM_SETCHECK,
(static_cast<E>(rdbtn_index) == value), 0L);
}
++rdbtn_index;
} else {
// (7) Not a radio button -> Issue warning
TRACE(traceAppMsg, 0,
"Warning: skipping non-radio button in group.\n");
}
// (8) Move to next control in tab order
hWndCtrl = ::GetWindow(hWndCtrl, GW_HWNDNEXT);
}
// (9) Until there are no more, or we moved to the next group
while (hWndCtrl != NULL && !(GetWindowLong(hWndCtrl, GWL_STYLE) & WS_GROUP));
}
This declares a function template that can be instantiated for arbitrary scoped enum types, and implements the logic to translate between UI state and enum value. The integral underlying value of the enum serves as the zero-based index into the radio button group selection.
The implementation needs a bit of explanation, though. The following list provides a bit more information regarding the numbered // (n) code comments:
This initializes internal state used by the framework. The precise details aren't very important, as long as the correct function is called. There are 3 implementations, one for OLE controls, one for edit controls, and one for every thing else. We're in the "everything else" category.
Perform sanity checks. This verifies that the control identified by nIDC is the first control in a radio button group (WS_GROUP), and that it is indeed a radio button control. This helps weed out bugs early when running a debug build.
Initialize the radio button index counter (rdbtn_index), and start iterating over radio buttons.
Make sure the control we're operating on in this iteration is a radio button control (if not, see 7.).
When translating UI state back to member variables, verify whether the current control is checked, and store its index in the group as a scoped enum value.
Otherwise (i.e. when translating data to UI state) set the check mark if the numeric value of the enum matches the control index, and uncheck it otherwise. The latter is not strictly required when using BS_AUTORADIOBUTTON controls, but it's not harmful either.
If we encounter a control that isn't a radio button control, issue a warning. Closely watch the debug output for this message; it designates a bug in the dialog template. Make sure to set the WS_GROUP style on the first control following this radio button group (in tab order).
Move on to the next control in tab order.
Terminate the loop if either there is no trailing control, or the control starts a new group, designated by the WS_GROUP style.
That's a fair bit to digest. Luckily, use of this function template is far less cumbersome. For purposes of illustration, let's use the following scoped enums:
enum class Season {
Spring,
Summer,
Fall,
Winter
};
enum class Color {
Red,
Green,
Blue
};
and add the following class members to the dialog class:
private:
Season season_ {};
Color color_ { Color::Green };
All that's left is setting up the DDX associations, i.e.:
void CRadioEnumDlg::DoDataExchange(CDataExchange* pDX) {
CDialogEx::DoDataExchange(pDX);
DDX_RadioEnum(pDX, IDC_RADIO_SPRING, season_);
DDX_RadioEnum(pDX, IDC_RADIO_RED, color_);
}
(with CRadioEnumDlg deriving from CDialogEx). All the template machinery is neatly hidden, with the template type argument getting inferred from the final argument.
For completeness, here is the dialog template used:
IDD_RADIOENUM_DIALOG DIALOGEX 0, 0, 178, 107
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,59,86,50,14
PUSHBUTTON "Cancel",IDCANCEL,121,86,50,14
CONTROL "Spring",IDC_RADIO_SPRING,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,7,7,39,10
CONTROL "Summer",IDC_RADIO_SUMMER,"Button",BS_AUTORADIOBUTTON,7,20,39,10
CONTROL "Fall",IDC_RADIO_FALL,"Button",BS_AUTORADIOBUTTON,7,33,39,10
CONTROL "Winter",IDC_RADIO_WINTER,"Button",BS_AUTORADIOBUTTON,7,46,39,10
CONTROL "Red",IDC_RADIO_RED,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,54,7,39,10
CONTROL "Green",IDC_RADIO_GREEN,"Button",BS_AUTORADIOBUTTON,54,20,39,10
CONTROL "Blue",IDC_RADIO_BLUE,"Button",BS_AUTORADIOBUTTON,54,33,39,10
END
as well as its accompanying resource.h:
#define IDD_RADIOENUM_DIALOG 102
#define IDC_RADIO_SPRING 1000
#define IDC_RADIO_SUMMER 1001
#define IDC_RADIO_FALL 1002
#define IDC_RADIO_WINTER 1003
#define IDC_RADIO_RED 1004
#define IDC_RADIO_GREEN 1005
#define IDC_RADIO_BLUE 1006
Adjusting a default-generated MFC application (dialog-based) with the above produces the following result when launched:
That's sweet, actually. Note in particular that the second row of radio buttons has the second item checked, which matches the initial value set in the dialog class' implementation (Color color_ { Color::Green }).
So all's good then?
Well, yeah. I guess. Sort of, anyway. Let's talk about the things that aren't quite as cool, things to watch out for, and problems that simply don't have a solution.
The implementation provided above makes a number of assumptions, none of which can be verified at compile time, and only some of them can (and are) verified at run time:
Enum values need to be backed by integral values, starting at 0, and counting up without any gaps. To my knowledge, there's no way to enforce this today (C++20), and the most effective way to ensure this is a code comment.
The order of enum values must match the tab order of radio button controls. Again, this is nothing that can be enforced nor verified.
The control ID specified in the DDX_RadioEnum call must be the start of a radio button group. This is verified at run time (the first ASSERT).
The control ID specified in the DDX_RadioEnum call must identify a radio button control. Again, this is verified at run time (the second ASSERT).
The first control following the radio button group (in tab order) must have the WS_GROUP style set. This is verified at run time, in part. If the control following is not a radio button control, a warning is issued. If the control happens to be a radio button, then this is not something that can be verified.
Those assumptions certainly aren't impossible to match. The hard part is keeping those invariants valid over time. If you can, then this implementation is worth a try.
Currently, I have come up with the following code:
Prerequisites:
CONST MinTime = 1000; // ms //
VAR ObjHandle : THandle := CreateEvent(NIL,TRUE,FALSE,'Event');
VAR T : LongWord;
VAR Signalled : BOOLEAN;
Code:
T:=GetTickCount; Signalled:=FALSE;
REPEAT
CASE WaitForSingleObject(ObjHandle,MinTime) OF
WAIT_OBJECT_0 : Signalled:=TRUE
END
UNTIL Signalled AND (GetTickCount-T>=MinTime);
The aim of this code is to wait for an externally signalled event (from another thread), but only respond to this AT MOST once every second. Is this the proper way to implement this, or is there a more optimized (CPU-resource-wise) way of doing it?
You can use 2 objects to wait on. One being the event. The other being a one-shot waitable timer set to the desired sleep interval. Then use WaitForMultipleObjects() with bWaitAll=TRUE to wait for both objects to be signaled, no loop needed.
Your code has a problem whith the event. There are two options: auto-reset and manual-reset events Look at Microsoft doc. With auto-reset event, when the WaitForSingleObject returns because the event is signaled, if the event if auto-reset will be reset and you'll loose the event.
You can either use manually-reset event and manage that your self (more complex) or once you grabbed the event and time from previous handling is too short, use a simple Sleep to wait the missing time.
You don't need an event for that.
Just use a variable, with a critical section if it should be thread-safe.
Something like:
var signal: boolean;
signal := false;
sleep(minTime);
if signal then
begin
// do something
signal := false;
end;
Replace the signal boolean with whatever condition, like if somelist.count>0 then...
I am using Visual Studio 2013 and making MFC Dialog based application. I am running into strange issue with Kill Focus of Edit Control.
Please see below:
==========================================================================
In my application, I have two Edit Controls on Dialog Box.
1st Edit Control -> IDC_EDIT_QUALITY1
2nd Edit Control -> IDC_EDIT_QUALITY2
I have handled both's EN_KILLFOCUS event to validate the value.
BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
ON_EN_KILLFOCUS(IDC_EDIT_QUALITY1, &CTestDlg::OnQuality1EditKillFocus)
ON_EN_KILLFOCUS(IDC_EDIT_QUALITY2, &CTestDlg::OnQuality2EditKillFocus)
END_MESSAGE_MAP()
void CTestDlg::OnQuality1EditKillFocus()
{
ValidateQualityParams(IDC_EDIT_QUALITY1);
}
void CTestDlg::OnQuality2EditKillFocus()
{
ValidateQualityParams(IDC_EDIT_QUALITY2);
}
#define MIN_QUALITY_VALUE 1
#define MAX_QUALITY_VALUE 100
void CTestDlg::ValidateQualityParams(int qualityParamID)
{
CString strQuality1;
if (IDC_EDIT_QUALITY1 == qualityParamID)
{
m_ctrlQuality1.GetWindowText(strQuality1);
if ((_ttoi(strQuality1) < MIN_QUALITY_VALUE) || (_ttoi(strQuality1) > MAX_QUALITY_VALUE))
{
CString strMessage;
strMessage.Format(_T("Quality1 value must be between %d to %d."), MIN_QUALITY_VALUE, MAX_QUALITY_VALUE);
**AfxMessageBox(strMessage);**
m_ctrlQuality1.SetSel(0, -1);
m_ctrlQuality1.SetFocus();
return;
}
}
CString strQuality2;
if (IDC_EDIT_QUALITY2 == qualityParamID)
{
m_ctrlQuality2.GetWindowText(strQuality2);
if ((_ttoi(strQuality2) < MIN_QUALITY_VALUE) || (_ttoi(strQuality2) > MAX_QUALITY_VALUE))
{
CString strMessage;
strMessage.Format(_T("Quality2 value must be between %d to %d."), MIN_QUALITY_VALUE, MAX_QUALITY_VALUE);
AfxMessageBox(strMessage);
m_ctrlQuality2.SetSel(0, -1);
m_ctrlQuality2.SetFocus();
return;
}
}
}
Now, the issue happens when, after changing the value in 1st Edit Control (IDC_EDIT_QUALITY1), say entering 0 in it and pressing TAB key, the flow goes as below:
void CTestDlg::OnQuality1EditKillFocus() is called.
It calls ValidateQualityParams(IDC_EDIT_QUALITY1)
Inside ValidateQualityParams, it goes to if (IDC_EDIT_QUALITY1 == qualityParamID) condition.
As the value I entered is less than MIN_QUALITY_VALUE, so it shows the Message by calling AfxMessageBox.
- Now, even from the callstack of AfxMessageBox, it hits void CTestDlg::OnQuality2EditKillFocus() internally.
Although callstack of OnQuality1EditKillFocus is NOT finished yet, OnQuality2EditKillFocus gets called from the callstack of AfxMessageBox.
I don't understand the cause of this issue. Has anyone encountered such issue before?
In my resource.h, I have two distinct values for IDC_EDIT_QUALITY1 and IDC_EDIT_QUALITY2
#define IDC_EDIT_QUALITY1 1018
#define IDC_EDIT_QUALITY2 1020
Please help on this issue.
I believe the EN_KILLFOCUS notification for the IDC_EDIT_QUALITY2 control you are receiving is caused not by the m_ctrlQuality1.SetFocus() call, but instead by the AfxMessageBox() call.
When you press the [Tab] key IDC_EDIT_QUALITY1 loses the focus, and IDC_EDIT_QUALITY2 gets the focus. Then you receive the EN_KILLFOCUS notification for IDC_EDIT_QUALITY1. You display the error-message, which causes the application to "yield" (start processing messages again), while the message-box is displayed. The m_ctrlQuality1.SetFocus() call won't take place before the AfxMessageBox() returns, ie before you close the message-box, and therefore the EN_KILLFOCUS notification for IDC_EDIT_QUALITY2 can't be the result of that call. I guess it's the result of displaying the message-box (IDC_EDIT_QUALITY2 has got the focus, but the message-box makes it lose it).
You may work around it by adding a memeber variable, as Staytuned123 suggested, but in a different setting: name it, say m_bKillFocusProcessing, and set it to TRUE while you are processing ANY EN_KILLFOCUS notification (AfxMessageBox() plus SetFocus()), and to FALSE when you are done processing it; if it's already TRUE exit without doing anything. That is, only one EN_KILLFOCUS notification may be processed at a time.
However, such a user-interface (displaying a message-box on exiting a field) is rather weird. And why reinvent the wheel and not instead use the DDX/DDV feature, which MFC already offers? You can define member variables associated with controls, and perform various checks, including range-check. Call UpdateData(TRUE) to perform the checks (for all controls on the dialog) and transfer the data to the member variables. Or you can put some error-displaying controls (usually in red color), activated when an error is found, like in .net or the web.
When you pressed TAB key, IDC_EDIT_QUALITY2 got focus. But because value entered was out of bound, the program called m_ctrlQuality1.SetFocus(), which in turn caused OnQuality2EditKillFocus() to get called.
Add a member variable says m_bQuality1OutOfBound and set it to true right before calling m_ctrlQuality1.SetFocus(). In OnQuality2EditKillFocus(), when m_bQuality1OutOfBound is true, set it to false and don't call ValidateQualityParams(IDC_EDIT_QUALITY2).
I'm using WinCE 7 Visual Studio 2008 and writing a driver code for ALS (MAX44009). I have written the following code for reading the interrupt status register and displaying messages when the interrupt has occurs. But, it works randomly for a few times only. For eg., when I close the sensor with my hand, I get the messages only few times, and then, it doesn't go into the data==1 condition even when it has to interrupt and continues to loop. The threshold timer is 0. The AlsRegRead function does an I2CRead. pAlsDrvInfo is the driver context. ADD_ALS_INT_STATUS is 0. DumpAlsRegistry function will print the content of all the registers except register 0x0.
while(1)
{
AlsRegRead(pAlsDrvInfo, ADD_ALS_INT_STATUS, &data, sizeof(UINT8));
if (data == 1)
{
DumpAlsRegistry(pAlsDrvInfo);
RETAILMSG(1,(L"Interrupt Received...\r\n"));
}
}
Please guide me where I'm making mistake.
I have found the reason behind this. Two issues had been behind this and both of them are equally important.
1) The sensor had been in a partially damaged state.
2) It requires some delay. So, I added Sleep(1000) at the start of the loop.
while(1)
{
Sleep(1000);
AlsRegRead(pAlsDrvInfo, ADD_ALS_INT_STATUS, &data, sizeof(UINT8));
if (data == 1)
{
DumpAlsRegistry(pAlsDrvInfo);
RETAILMSG(1,(L"Interrupt Received...\r\n"));
}
}
Thanks.