Click here to Skip to main content
15,878,945 members
Articles / Programming Languages / C++
Article

Mousey! Roll Over and Park

Rate me:
Please Sign up or sign in to vote.
4.79/5 (28 votes)
16 Sep 2003CPOL7 min read 148.5K   3.3K   59   31
Controlling the mouse using an application that runs in the background. Uses system wide Windows Hooks.

Settings dialog

Introduction

The mouse was, and still is, one of the greatest inventions in the field of personal computing and is of great help especially in a graphical user environment. Do you know who invented it? It was Dr. Douglas C. Engelbart. He also invented many other very useful stuff like teleconferencing, email, the internet etc. to name a few. He has many patents to his name and his name has also been inducted into the National Inventors Hall of Fame. I bet that less than 1% of all the mouse users around the world know about this great guy. Neither did I, before I started out to write this article.

Anyway, what matters is that more than 95% of the world’s population knows about the computer mouse. And I’m sure you could not have reached here to read this article without using it. But to some people, including me, if you are familiar with a particular environment or application, navigation or working in it is much easier and faster using the keyboard than the mouse. This is possible mainly due to the keyboard shortcuts provided by the application, which you learn by heart as you gain more experience.

Certain versions of the Microsoft Windows operating system like Windows NT 4.0, Windows 2000 and Windows XP etc., provides an option to automatically move the mouse pointer to the default button in a dialog box when it first appears on the screen. This settings is shown in the figure below –

Windows Mouse properties

I found the above-mentioned feature of windows to be very useful, as I had to move the mouse much less than when that option was not checked. But this proved to be a real hindrance when I was working with only the keyboard, like when typing some documentation or when coding, because every time I press some keyboard shortcut, a dialog would pop up that would move the mouse pointer to its default button, leaving the mouse pointer in the client area of the application. This posed to be a real problem especially when the cursor was some big animated cursor that kept rotating in front of your eyes (I had a rotating fender guitar as my cursor).

I had a lot of options to overcome the above-mentioned issue. I could uncheck the ‘Snap to default’ checkbox or I could change my cursor to the normal arrow cursor or I could manually move the mouse whenever it jumped up on the screen. But I thought of doing something else so that I could have the features and cursors that I liked sans the hindrance. I wrote a program that works in the background, which moves the mouse pointer to a specified location after a specified amount of mouse idle time.

Running the demo application

The basic technique used is installing a system wide mouse hook. In general, to install a system wide Windows hook, the hook procedure must be written in a separate DLL file and that DLL must be invoked from another executable.

The sample workspace consists of two projects. One is the DLLs project and the other is the executables project. To compile and run the sample code, first set the DLLs project as the active project and build. Then, set the EXEs project as the active project, build and run. Both the projects are written only using the windows SDK and no other libraries like MFC, ATL etc. are used except for the default runtime library. The DLL is implicitly linked with the EXE, i.e., the DLL is loaded into memory along with the EXE and stays in memory till the EXE is exited.

When the code is run a tray icon appears which is the same as the mouse cursor. Right clicking on the tray icon brings up a menu. The tray icon and the menu is shown in the figure below –

Tray Icon with Menu

Settings: This option brings up the dialog shown in the figure below. The ‘Time’ field specifies the mouse idle time in seconds after which the mouse must be parked. The ‘X Position’ and ‘Y Position’ specifies the screen coordinates where the mouse must be parked.

Settings dialog

Park Cursor: This option simply moves the cursor to the specified location in the ‘X Position’ and ‘Y Position’ fields.

Suspend: This option when checked will stop the mouse from being parked until the option is unchecked.

Stop Parking: This option will uninstall the system wide mouse hook and exit the application.

How to hook

The idle time of the mouse must be checked at all times irrespective of the application that you are working on. This necessitates the installation of a system wide hook. System wide hooks in windows must be written as a separate DLL.

When installing the hook using the SetWindowsHookEx API, we specify a function that handles all the events of that particular hook. This function is called the filter function. The operating system maintains a list or stack of such filter functions. When a new hook is installed, the filter function of the hook is added as the first function in the list. To ensure that the previous filter function in the list is called, the CallNextHookEx API must be called from within our filter function.

Also to share the values of certain variables across processes, the variables must be declared in a shared data section. The shared data section can be created by first declaring a new data section using the #pragma data_seg segment_name directive and then stating that the section is shared using segment_name SHARED in the .DEF file.

Understanding the sample code

The DLL in the sample application is a very simple DLL written in SDK, which exports 5 functions –

InstallHook - Calls the SetWindowsHookEx API, which actually installs the system wide mouse hook. It also creates a timer with a time-out value of 1 second.

extern "C" void InstallHook()
{
    if(s_hHook)
        return;

    s_hHook = SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);
    if(NULL == s_hHook)
        MessageBox(0, "Hook Failed", "", 0);

    g_nTimer = SetTimer(NULL, NULL, 1000, TimerProc);
}

UninstallHook - Calls the UnhookWindowsHookEx API, which uninstalls the system wide mouse hook. It also kills the timer created in the InstallHook function.

extern "C" void UninstallHook()
{
    if(s_hHook)
    {
        KillTimer(NULL, g_nTimer);
        UnhookWindowsHookEx(s_hHook);
        s_hHook = NULL;
    }
}

SetValues - Sets the mouse idle time, the X screen coordinate and the Y screen coordinate, which are passed as parameters to the functions to global variables.

extern "C" void SetValues(int* lCount, int* lXPos, int* lYPos)
{
    if(*lCount > 1)
        g_lCount = *lCount;

    if(*lXPos >= 0)
        g_lXPos = *lXPos;

    if(*lYPos >= 0)
        g_lYPos = *lYPos;

    *lCount = g_lCount;
    *lXPos = g_lXPos;
    *lYPos = g_lYPos;
}

Suspend - Toggles a flag ON and OFF.

extern "C" bool Suspend()
{
    s_bSuspend ^= 1;
    return s_bSuspend;
}

ParkCursor - Calls the SetCursorPos API, which actually moves the mouse to the specified screen coordinates.

extern "C" void ParkCursor()
{
    SetCursorPos(g_lXPos, g_lYPos);
}

The hook needs to maintain certain variables that must be accessible across processes. Putting the necessary variables into a shared data segment does this. It does this by first declaring a data section by the name ‘ParkMouse’ as shown below –

#pragma data_seg("ParkMouse")
    HHOOK s_hHook = 0;   //# Handle to the hook
    int s_lCount = 0;    //# Count of idle time

    int g_lCount = 5;    //# Timeout for parking mouse
    int g_lXPos = 3;     //# X coordinate for parking mouse
    int g_lYPos = 3;     //# Y coordinate for parking mouse

    //# Flag indicating whether parking temporarily suspended
    bool s_bSuspend = 0;
#pragma data_seg()

This new data section is made readable, writable and sharable by giving ParkMouse READ WRITE SHARED in the SECTIONS section of the .DEF file.

The working of the hook is very simple. The name of our filter function is MouseProc. All this function does is call the CallNextHookEx API and if any mouse event has occurred, set the variable s_lCount to 0.

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = CallNextHookEx(s_hHook, nCode, wParam, lParam);

    if(HC_ACTION == nCode)
        s_lCount = 0;

    return lResult;
}

The actual work of moving the mouse when the specified idle time expires happens in the TimerProc function which is called by the system every second. This happens because we had called the SetTimer API with a time-out value of 1 second from the InstallHook function. Each time the TimerProc function is called, the value of s_lCount is incremented by 1. If the values of s_lCount and g_lCount become equal, the mouse is moved using the SetCursorPos API to the location specified in g_lXPos and g_lYPos. Any of the mouse buttons currently down is also checked before moving the mouse. If the suspend flag is set, the TimerProc function does nothing and simply returns.

void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    if(s_bSuspend)                //# Do nothing if suspended
        return;

    s_lCount++;

    if(g_lCount == s_lCount)
    {
        //# Check for any mouse button pressed
        if(GetAsyncKeyState(VK_LBUTTON) >= 0 &&
            GetAsyncKeyState(VK_MBUTTON) >= 0 &&
            GetAsyncKeyState(VK_RBUTTON) >= 0)
        {
            SetCursorPos(g_lXPos, g_lYPos);
        }
    }
}

The executable that loads the DLL into memory in the first place is a simple program also written in SDK, which creates a tray icon and a hidden window to process the messages from the tray icon. The tray icon is the same as your default cursor. This is done by loading the default cursor using LoadCursor(NULL, IDC_ARROW) and passing the returned handle as the handle to the icon of the tray.

The dialog box that pops up on clicking the ‘Settings’ menu from the tray is not created using a dialog template resource, but is created dynamically using many CreateWindow API calls in the CreateSettingsDialog function. Why did I do that? Well, I had nothing better to do, that’s why.

void CreateSettingsDialog()
{
    g_hWnd = CreateWindowEx(WS_EX_DLGMODALFRAME, "Park",
        "Park Mouse - Settings", WS_POPUPWINDOW|WS_CAPTION,
        0, 0, DLGWIDTH, DLGHEIGHT, 0, 0, hInst, 0);

    g_hTime = CreateWindow("STATIC", 0, WS_CHILD|WS_VISIBLE,
        10, 10, 301, 18, g_hWnd, 0, hInst, 0);
    SetWindowText(g_hTime, "Time :               seconds "
        "(Must be more than 1)");

    g_hTime = CreateWindow("STATIC", 0, WS_CHILD|WS_VISIBLE,
        17, 38, 80, 18, g_hWnd, 0, hInst, 0);
    SetWindowText(g_hTime, "X Position :");

    g_hTime = CreateWindow("STATIC", 0, WS_CHILD|WS_VISIBLE,
        167, 38, 80, 18, g_hWnd, 0, hInst, 0);
    SetWindowText(g_hTime, "Y Position :");

    g_hTime = CreateWindow("STATIC", 0,
        SS_ETCHEDFRAME|WS_CHILD|WS_VISIBLE, 5, 5, 311, 28,
        g_hWnd, 0, hInst, 0);

    g_hTime = CreateWindow("STATIC", 0,
        SS_ETCHEDFRAME|WS_CHILD|WS_VISIBLE, 5, 31, 311, 28,
        g_hWnd, 0, hInst, 0);

    g_hOK = CreateWindow("BUTTON", 0, WS_CHILD|WS_VISIBLE,
        70, 70, 80, 25, g_hWnd, 0, hInst, 0);
    SetWindowText(g_hOK, "Apply");

    g_hCancel = CreateWindow("BUTTON", 0, WS_CHILD|WS_VISIBLE,
        190, 70, 80, 25, g_hWnd, 0, hInst, 0);
    SetWindowText(g_hCancel, "Close");
 
    g_hTime = CreateWindow("EDIT", 0,
        WS_BORDER|WS_CHILD|WS_VISIBLE, 56, 10, 50, 18,
        g_hWnd, 0, hInst, 0);

    g_hXPos = CreateWindow("EDIT", 0,
        WS_BORDER|WS_CHILD|WS_VISIBLE, 98, 37, 50, 18,
        g_hWnd, 0, hInst, 0);

    g_hYPos = CreateWindow("EDIT", 0,
        WS_BORDER|WS_CHILD|WS_VISIBLE, 248, 37, 50, 18,
        g_hWnd, 0, hInst, 0);
}

Conclusion

I agree that it is a stupid application. But I hope it will be a useful introduction to system wide Windows hooks for people who don’t know about it. Then again, I guess it is a fancy application. And hey, I bet most of you didn’t know about that great inventor.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
Santosh works as a Technical Architect in God's own Country, Kerala, India. He has been involved with C/C++ since 1996. Started with a few game clones and then went on to commercial software. He started his stint with software training and then went on to professional software development, design and architecture. Unix and C were his favorite in his early days but later grew very fond of Windows especially with the release of Windows NT 4.0 and Visual C++ 6.0. Technologies like COM and .Net fascinate him. He still lurks around Unix once in a while.

Music and the guitar are his second favorites and he manages to impress his friends with these skills when there are nobody better than him around. He is a patient and fun loving character who does not think too much about the future. He hates wasting time and so is working hard (in his dreams) to perfect instant transportation and time travel.

Oh! Yes. He loves Superman. Always did and always will. He would love to become a Superman and rid the world of all evil.

He used to be a Microsoft Visual C++ MVP (October 2009 - September 2013)

Comments and Discussions

 
GeneralVery interressant - how to change the application to compute the distance covered by the mouse Pin
pycolle4-Jan-08 4:44
pycolle4-Jan-08 4:44 
GeneralRe: Very interressant - how to change the application to compute the distance covered by the mouse Pin
«_Superman_»4-Jan-08 18:35
professional«_Superman_»4-Jan-08 18:35 
Generalmouse_event() with MOUSE_MOVE Pin
tuantrang28-Nov-06 20:06
tuantrang28-Nov-06 20:06 
GeneralMouse whell API Pin
eligazit1-Apr-06 21:50
eligazit1-Apr-06 21:50 
AnswerRe: Mouse whell API Pin
«_Superman_»12-May-06 0:05
professional«_Superman_»12-May-06 0:05 
Questionmoving when left mouse is down? Pin
zer0-19-Mar-06 4:23
zer0-19-Mar-06 4:23 
AnswerRe: moving when left mouse is down? Pin
«_Superman_»19-Mar-06 20:48
professional«_Superman_»19-Mar-06 20:48 
GeneralRe: moving when left mouse is down? Pin
zer0-19-Mar-06 21:48
zer0-19-Mar-06 21:48 
GeneralRe: moving when left mouse is down? Pin
zer0-21-Mar-06 4:15
zer0-21-Mar-06 4:15 
Generalusing #pragma data_seg Pin
RichardF12-Oct-04 5:25
RichardF12-Oct-04 5:25 
GeneralRe: using #pragma data_seg Pin
«_Superman_»13-Oct-04 1:10
professional«_Superman_»13-Oct-04 1:10 
GeneralRe: using #pragma data_seg Pin
RichardF13-Oct-04 2:31
RichardF13-Oct-04 2:31 
QuestionError in TimerProc? Pin
AndrewRees11-May-04 13:42
AndrewRees11-May-04 13:42 
AnswerRe: Error in TimerProc? Pin
«_Superman_»11-May-04 14:41
professional«_Superman_»11-May-04 14:41 
GeneralRe: Error in TimerProc? Pin
AndrewRees19-May-04 12:32
AndrewRees19-May-04 12:32 
GeneralRe: Error in TimerProc? Pin
«_Superman_»19-May-04 14:19
professional«_Superman_»19-May-04 14:19 
Questionhow to drup and pull Pin
geoege_pc chen15-Feb-04 16:51
geoege_pc chen15-Feb-04 16:51 
AnswerRe: how to drup and pull Pin
«_Superman_»15-Feb-04 17:15
professional«_Superman_»15-Feb-04 17:15 
GeneralRe: how to drup and pull Pin
geoege_pc chen16-Feb-04 5:58
geoege_pc chen16-Feb-04 5:58 
GeneralRe: how to drup and pull Pin
«_Superman_»16-Feb-04 13:24
professional«_Superman_»16-Feb-04 13:24 
GeneralRe: how to drup and pull Pin
geoege_pc chen16-Feb-04 20:26
geoege_pc chen16-Feb-04 20:26 
GeneralRe: how to drup and pull Pin
«_Superman_»16-Feb-04 20:46
professional«_Superman_»16-Feb-04 20:46 
GeneralRe: how to drup and pull Pin
geoege_pc chen16-Feb-04 21:02
geoege_pc chen16-Feb-04 21:02 
GeneralRe: how to drup and pull Pin
Sathyavady26-Feb-04 19:21
sussSathyavady26-Feb-04 19:21 
GeneralRe: how to drup and pull Pin
geoege_pc chen28-Feb-04 3:09
geoege_pc chen28-Feb-04 3:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.