Introduction
A project I had to do on the Pocket PC for a client had to be somewhat secure. For instance, if the Pocket PC had been left on the desk, it had to lock itself out after a certain period of inactivity. The solution I came up with was to make the Pocket PC go into standby mode after a given period of inactivity (even on AC) and, when the user pressed the standby button, to display a password screen. Of course, you can do whatever after the user has pressed the standby button; you could, for example, just go back to your application. I also had to restrict access to the shell. To do this, you have to make the application stay in full screen mode; this is the purpose of this article.
Making the Application Full Screen
<script language=JavaScript> </script>
There are various articles on how to make a dialog appear full screen. I simply use the following code to do this:
SHFullScreen( m_hWnd, SHFS_HIDESTARTICON| SHFS_HIDETASKBAR| SHFS_HIDESIPBUTTON ); MoveWindow(0,0,240,320,TRUE); ::CommandBar_Show(m_pWndEmptyCB->m_hWnd, FALSE );
This code is put into the dialog's OnActivate method. The reason is because that, when you have gone into standby mode, the communication ports on the Pocket PC also get shut down. Thus, on coming out of standby mode, if the Pocket PC is connected to a desktop PC, ActiveSync will kick in and take the application out of full screen, thus allowing the user to access the explorer shell—not what we want.
You could disable ActiveSync if you wanted and then re-enable it if you don't want to use a timer, but I have found that, on re-enabling ActiveSync, it doesn't re-enable itself. Don't ask me why; maybe it's a bug?
The SHFullScreen removes the user's access to the shell and gives your application full control over your screen.
MoveWindow changes the position and dimensions of the dialog. I set the width and height to 240 and 320, respectively, and positioned the dialog at the top left of the screen.
::CommandBar_Show is very important; it is used to hide or show the command bar. In this application, you hide the shell's command bar (m_pWndEmptyCB->m_hWnd holds the handle to this).
How Do You Make the Pocket PC Go into Standby Mode?
This is very simple; the following code accomplishes it:
::keybd_event( VK_OFF, 0, 0, 0 );
This function simply synthesizes a keystroke. This is the same message that is sent when the Standby button is pressed to make the device go into standby mode. Of course, you want this to happen after a given period of inactivity. The first choice I used here was to create a thread to do this, but because this thread wouldn't be doing much, a Windows timer was used instead (more on this later).
So, how do you detect periods of inactivity? Inactivity is when the user isn't doing anything on the device, such as moving the mouse cursor or pressing the screen. These are messages that are sent to the dialog window in the disguise of WM_MOUSEMOVE and WM_LBUTTONDOWN. You need to capture these messages; a perfect place to do this is in the dialogs PreTranslateMessage function.
virtual BOOL PreTranslateMessage( MSG* pMsg );
You want to catch these messages before they have been dispatched. The code for this function looks like this:
BOOL CSGLockDialog::PreTranslateMessage(MSG* pMsg) { switch(pMsg->message) { case WM_LBUTTONDOWN: case WM_MOUSEMOVE: m_nTimeCount = 0; break; } return CDialog::PreTranslateMessage(pMsg); }
The member m_nTimeCount is used to hold the period of inactivity. You can see here that it gets set to zero when the user presses down the mouse button or moves it; you may want to add more messages here.
Repeatedly Checking for Periods of Inactivity
As mentioned previously, you need to actively keep checking for this period of inactivity. This is done within a Windows timer. Within the dialog OnInitDialog function, you set the timer:
m_nTimerID = SetTimer(1234,1000,NULL);
This just sets a timer to run every second. The timer function looks like the following:
void CSGLockDialog::OnTimer(UINT nIDEvent) { //increment inactivity timer count m_nTimeCount ++; CDialog::OnTimer(nIDEvent); //if reached trigger level, suspend the device if(m_nTimeCount == 100 ) { m_nTimeCount = 0; ::keybd_event(VK_OFF, 0, 0, 0); } }
You can see here that when m_nTimeCount is equal to the hard-coded value of 100 (this is the period of inactivity in seconds; I take this from the Registry in my application, but for simplicity's stake, I just hard code it here), you put the Pocket PC into standby mode and reset the time count back to zero.
About the Author
I'm a Software Engineer with a company which writes Diagnostic Applications for vehicles. In my spare time ( what I have of it! ) I love playing Golf and Football and spending as much time as I can with my lovely baby son David.
Detecting Wakeup
Okay, one question still needs to be answered: How do you detect when the user takes the device out of standby mode? You need to check this continuously; to do this, you put it in a separate thread. Create this thread in the dialog's OnInitDialog:
ThreadParam* pstThreadParam = new ThreadParam; pstThreadParam->m_hWnd = this->GetSafeHwnd(); pstThreadParam->m_hCloseEvent = m_hCloseEvent; m_hWakeupThread = AfxBeginThread( WakeupThread, pstThreadParam );
You also need to create an event to tell the wakeup thread that it is time to shut down. You use CreateEvent to do this:
m_hCloseEvent = CreateEven(NULL,TRUE,FALSE,NULL);
Okay, so you have a separate thread for the wakeup, but what does this thread actually do? The thread has to detect the device coming out of standby. How do you do that?! Well, there is a nice API call that you can use:
CeSetUserNotificationEx( HANDLE hNotification, CE_NOTIFICATION_TRIGGER *pcnt, CE_USER_NOTIFICATION *pceun );
This function creates a new user notification or modifies an existing one. The CE_NOTIFICATION_TRIGGER structure defines what event activates a notification. Looking at this structure, the dwEvent member is of certain interest. This member specifies the type of event you want to be notified on. There are quite a number of events you can set here. I'm not going to list them all; you can find them in the help. The one you are interested in is NOTIFICATION_EVENT_WAKEUP, which is triggered when the device comes out of Standby. You also need to set the dwType member to the type of event you are interested in; this is a system notification (CNT_EVENT).
The member lpszApplication can be used to set the name of an application to execute when the even is triggered. I didn't want an application to be executed, so I set this member to a named event instead. To set it as a named event, you have to use the following format:
.//Notifications//NamedEvents//Event Name
where Event Name represents the application-defined name of the event to signal. This is the code used to fill this structure:
CE_NOTIFICATION_TRIGGER st; memset(&st,0,sizeof(st)); st.dwSize = sizeof(CE_NOTIFICATION_TRIGGER); st.dwType = CNT_EVENT; st.dwEvent = NOTIFICATION_EVENT_WAKEUP; st.lpszApplication = _T (".//Notifications//NamedEvents//Wakeup"); st.lpszArguments = NULL;
and to set the notification:
HANDLE hNotification = CeSetUserNotificationEx(0,&st,NULL);
You see here that you are using a named event, but you haven't created this named event yet! You use your old friend CreateEvent to do this:
CreateEvent(NULL,TRUE,FALSE,_T("Wakeup"));
You now need to know when the device has woken up. Because this function is in a thread, you can use the WaitForMultipleObjects function. If this function returns WAIT_OBJECT_0, you know a close event was signalled and can exit the thread and clear your notification. If the function returns WAIT_OBJECT_0 + 1, you know the device has just come out of Standby mode and therefore can do what you want. In my case, I needed to reset the time count back to zero and display a password screen.
The full function for the wakeup thread looks like this:
UINT CSGLockDialog::WakeupThread(LPVOID pParam ) { ThreadParam* pstThreadParam = (ThreadParam*) pParam; CE_NOTIFICATION_TRIGGER st; memset(&st,0,sizeof(st)); st.dwSize = sizeof(CE_NOTIFICATION_TRIGGER); st.dwType = CNT_EVENT; st.dwEvent = NOTIFICATION_EVENT_WAKEUP; st.lpszApplication = _T(".//Notifications//NamedEvents//Wakeup"); st.lpszArguments = NULL; HANDLE hNotification = CeSetUserNotificationEx(0,&st,NULL); if(hNotification) { HANDLE hEventArray[2]; hEventArray[0] = pstThreadParam>m_hCloseEvent; hEventArray[1] = CreateEvent(NULL,TRUE,FALSE,_T("Wakeup")); DWORD dwRet; BOOL bContinue = TRUE; while(bContinue) { dwRet = WaitForMultipleObjects(2,hEventArray,FALSE,INFINITE); switch(dwRet) { case WAIT_OBJECT_0: { //close event signalled. //time to exit thread.. bContinue = FALSE; } break; case WAIT_OBJECT_0 + 1: { //device just woke up.. //send custom message to pstThreadParam->m_hWnd ::PostMessage(pstThreadParam->m_hWnd, WM_DEVICE_JUST_WOKEUP, 0,0); ResetEvent(hEventArray[1]); } break; default:break; } } CeClearUserNotification(hNotification); CloseHandle(hEventArray[1]); } delete pstThreadParam; return 0; }
You can see that there is a PostMessage call within this function; this is a message that the dialog picks up. In this message, I just display a password dialog; this message is called when the device comes out of Standby.
Summary
So, to summarise, I have a base class CSGLockDialog, which is derived from CDialog so that you can override the PreTranslateMessage function and capture mouse events; if you get a mouse event, you reset the period of inactivity counter. You have a timer in this class that repeatedly increments the period of inactivity counter; if this counter reaches a certain value, you go into standby mode. You have a user notification message that detects when the device has come out of Standby; when it does, I display a password dialog. The dialog is put into full screen continuously in the OnActivate function which is very important. If you take this out, the screen would be taken out of full screen.
The included code shows all the above functionality except that it doesn't show the password dialog; it just displays a message. You can use the CSGLockDialogDlg in your own dialog applications; you just need to derive from this instead of CDialog and, in your OnInitDialog, make sure that you call CSGLockDialogDlg::OnInitDialog. Also, make sure you change all CDialog calls to CSGLockDialogDlg (for example, Message map and data exchange).
I hope you have found this article of some interest and use. I have learnt quite a lot myself from doing it and thought it would be a good idea to share my technique.
Many thanks have to go to Kiran Thonse at CodeGuru for checking this article over and helping with the wakeup event.
Steve Green
About the Author
I'm a Software Engineer with a company which writes Diagnostic Applications for vehicles. In my spare time ( what I have of it! ) I love playing Golf and Football and spending as much time as I can with my lovely baby son David.