SCRAWL.CPP

/************************************************************************** 

SCRAWL.CPP - A dumb drawing app demo for DirectInput

Collects mouse data in various modes to demonstrate how it's done.

**************************************************************************/
/**************************************************************************

(C) Copyright 1995 - 1998 Microsoft Corp. All rights reserved.

You have a royalty-free right to use, modify, reproduce and
distribute the Sample Files (and/or any modified version) in
any way you find useful, provided that you agree that
Microsoft has no warranty obligations or liability for any
Sample Application Files which are modified.

**************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <stdarg.h>

#define DIRECTINPUT_VERSION 0x0300 /* Remain DX3-compatible */
#include <dinput.h>

#include "scrawl.h"

/****************************************************************************
*
* This is an incredibly dump app. It just lets you "draw" on
* a monochrome bitmap via DirectInput. The purpose is not to
* dazzle you with mind-altering brilliance. It's just to show
* how to use the DirectInput mouse interface.
*
****************************************************************************/

/****************************************************************************
*
* Manifest constants
*
****************************************************************************/

#define DINPUT_BUFFERSIZE 16

#define DINPUT_CXBITMAP 512
#define DINPUT_CYBITMAP 300

/****************************************************************************
*
* Global variables
*
****************************************************************************/

const char c_szAppName[] = "Scrawl"; /* My name */

HINSTANCE g_hinst; /* My instance handle */
BOOL g_fActive; /* Am I the active window? */

int g_x = DINPUT_CXBITMAP / 2; /* Virtual x-coordinate */
int g_y = DINPUT_CYBITMAP / 2; /* Virtual y-coordinate */

int g_dxFuzz; /* Leftover x-fuzz from scaling */
int g_dyFuzz; /* Leftover y-fuzz from scaling */
int g_iSensitivity; /* Mouse sensitivity */

HDC g_hdc; /* Memory DC our picture lives in */
HBITMAP g_hbm; /* Our picture */
HBITMAP g_hbmDeselect; /* Stock bitmap for deselecting */

HCURSOR g_hcurCross; /* Crosshairs */
int g_cxCross; /* Width of crosshairs cursor */
int g_cyCross; /* Height of crosshairs cursor */
int g_dxCrossHot; /* Hotspot location of crosshairs */
int g_dyCrossHot; /* Hotspot location of crosshairs */
int g_fShowCursor = 1; /* Should the cursor be shown? */

/****************************************************************************
*
* DirectInput globals
*
****************************************************************************/

LPDIRECTINPUT g_pdi;
LPDIRECTINPUTDEVICE g_pMouse;

HANDLE g_hevtMouse;

/****************************************************************************
*
* Complain
*
* Whine and moan.
*
****************************************************************************/

void CDECL
Complain(HWND hwndOwner, HRESULT hr, LPCSTR pszFormat, ...)
{
va_list ap;
char szBuf[1024];
char *pszBuf;

va_start(ap, pszFormat);

pszBuf = szBuf + wsprintf(szBuf, pszFormat, ap);

va_end(ap);

wsprintf(pszBuf, "\n\nError = %08x", hr);

MessageBox(hwndOwner, szBuf, c_szAppName, MB_OK);
}

/****************************************************************************
*
* DIInit
*
* Initialize the DirectInput variables.
*
****************************************************************************/

BOOL DIInit(HWND hwnd)
{
HRESULT hr;

/*
* Register with DirectInput and get an IDirectInput to play with.
*/
hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);

if (FAILED(hr)) {
Complain(hwnd, hr, "DirectInputCreate");
return FALSE;
}

/*
* Obtain an interface to the system mouse device.
*/
hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);

if (FAILED(hr)) {
Complain(hwnd, hr, "CreateDevice(SysMouse)");
return FALSE;
}

/*
* Set the data format to "mouse format".
*/
hr = g_pMouse->SetDataFormat(&c_dfDIMouse);

if (FAILED(hr)) {
Complain(hwnd, hr, "SetDataFormat(SysMouse, dfDIMouse)");
return FALSE;
}


/*
* Set the cooperativity level.
*/
hr = g_pMouse->SetCooperativeLevel(hwnd,
DISCL_EXCLUSIVE | DISCL_FOREGROUND);

if (FAILED(hr)) {
Complain(hwnd, hr, "SetCooperativeLevel(SysMouse)");
return FALSE;
}


/*
* Create the handle that tells us new data is available.
*/
g_hevtMouse = CreateEvent(0, 0, 0, 0);

if (g_hevtMouse == NULL) {
Complain(hwnd, GetLastError(), "CreateEvent");
return FALSE;
}

/*
* Associate the event with the device.
*/
hr = g_pMouse->SetEventNotification(g_hevtMouse);

if (FAILED(hr)) {
Complain(hwnd, hr, "SetEventNotification(SysMouse)");
return FALSE;
}

/*
* Set the buffer size to DINPUT_BUFFERSIZE elements.
* The buffer size is a DWORD property associated with the device.
*/
DIPROPDWORD dipdw =
{
{
sizeof(DIPROPDWORD), // diph.dwSize
sizeof(DIPROPHEADER), // diph.dwHeaderSize
0, // diph.dwObj
DIPH_DEVICE, // diph.dwHow
},
DINPUT_BUFFERSIZE, // dwData
};

hr = g_pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

if (FAILED(hr)) {
Complain(hwnd, hr, "Set buffer size(SysMouse)");
return FALSE;
}

return TRUE;

}

/****************************************************************************
*
* DITerm
*
* Terminate our usage of DirectInput.
*
****************************************************************************/

void DITerm(void)
{
if (g_pdi) g_pdi ->Release(), g_pdi = NULL;
if (g_pMouse) g_pMouse->Release(), g_pMouse = NULL;

if (g_hevtMouse) CloseHandle(g_hevtMouse), g_hevtMouse = NULL;
}

/****************************************************************************
*
* InvalidateCursorRect
*
* Invalidate the rectangle that contains the cursor.
*
* The coordinates are in client coordinates.
*
****************************************************************************/

void InvalidateCursorRect(HWND hwnd)
{
RECT rc = { g_x - g_dxCrossHot, g_y - g_dyCrossHot,
g_x - g_dxCrossHot + g_cxCross, g_y - g_dyCrossHot + g_cyCross };

InvalidateRect(hwnd, &rc, 0);
}

/****************************************************************************
*
* UpdateCursorPosition
*
* Move our private cursor in the requested direction, subject
* to clipping, scaling, and all that other stuff.
*
* This does not redraw the cursor. You need to do that yourself.
*
****************************************************************************/

void UpdateCursorPosition(int dx, int dy)
{

/*
* Pick up any leftover fuzz from last time. This is important
* when scaling down mouse motions. Otherwise, the user can
* drag to the right extremely slow for the length of the table
* and not get anywhere.
*/
dx += g_dxFuzz; g_dxFuzz = 0;
dy += g_dyFuzz; g_dyFuzz = 0;

switch (g_iSensitivity) {

case 1: /* High sensitivity: Magnify! */
dx *= 2;
dy *= 2;
break;

case -1: /* Low sensitivity: Scale down */
g_dxFuzz = dx % 2; /* remember the fuzz for next time */
g_dyFuzz = dy % 2;
dx /= 2;
dy /= 2;
break;

default:
case 0: /* No sensitivity */
; /* No adjustments needed */
}

g_x += dx;
g_y += dy;

/* Clip the cursor to our client area */
if (g_x < 0) g_x = 0;
if (g_x >= DINPUT_CXBITMAP) g_x = DINPUT_CXBITMAP - 1;

if (g_y < 0) g_y = 0;
if (g_y >= DINPUT_CYBITMAP) g_y = DINPUT_CYBITMAP - 1;

}

/****************************************************************************
*
* Scrawl_SyncAcquire
*
* Acquire or unacquire the devices, depending on the the g_fActive
* flag. This synchronizes the devices with our internal view of
* the world.
*
* Also repaint the cursor so that it hides/shows in sync with
* acquisition.
*
****************************************************************************/

void
Scrawl_SyncAcquire(HWND hwnd)
{
if (g_fActive) {
if (g_pMouse) g_pMouse->Acquire();
} else {
if (g_pMouse) g_pMouse->Unacquire();
}

InvalidateCursorRect(hwnd);
}

/****************************************************************************
*
* Private messages
*
* WM_SYNCACQUIRE forces us to re-synchronize our acquisition
* with the world.
*
****************************************************************************/

#define WM_SYNCACQUIRE (WM_USER + 0)

/****************************************************************************
*
* Scrawl_OnClear
*
* Wipe out the bitmap.
*
****************************************************************************/

void Scrawl_OnClear(HWND hwnd)
{
/*
* Start out all white.
*/
PatBlt(g_hdc, 0, 0, DINPUT_CXBITMAP, DINPUT_CYBITMAP, WHITENESS);

if (hwnd) {
InvalidateRect(hwnd, 0, 0);
}
}

/****************************************************************************
*
* Scrawl_OnCreate
*
* Set up the window by appending our custom commands to the System
* menu.
*
* Also disable the menu items we don't want to see.
*
****************************************************************************/

BOOL Scrawl_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
HMENU hmenu = GetSystemMenu(hwnd, FALSE);

EnableMenuItem(hmenu, SC_SIZE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
EnableMenuItem(hmenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);

AppendMenu(hmenu, MF_ENABLED | MF_STRING, IDC_CLEAR, "C&lear\tDel");
AppendMenu(hmenu, MF_ENABLED | MF_STRING, IDC_ABOUT, "&About\tF1");

AppendMenu(hmenu, MF_ENABLED | MF_STRING | MF_POPUP,
(UINT)LoadMenu(g_hinst,
MAKEINTRESOURCE(IDM_SENSITIVITY)),
"Sensitivit&y");

return 1;
}

/****************************************************************************
*
* Scrawl_OnInitMenuPopup
*
* Initialize the sensitivity item accordingly.
*
****************************************************************************/

void
Scrawl_OnInitMenuPopup(HWND hwnd, HMENU hmenu, UINT item, BOOL fSystemMenu)
{
int iSensitivity;
for (iSensitivity = -1; iSensitivity <= 1; iSensitivity++) {
if (g_iSensitivity == iSensitivity) {
CheckMenuItem(hmenu, IDC_SENSITIVITY_NORMAL + iSensitivity,
MF_BYCOMMAND | MF_CHECKED);
} else {
CheckMenuItem(hmenu, IDC_SENSITIVITY_NORMAL + iSensitivity,
MF_BYCOMMAND | MF_UNCHECKED);
}
}

}

/****************************************************************************
*
* Scrawl_OnKeyDown
*
* See if it's one of our accelerators.
*
****************************************************************************/

void Scrawl_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
switch (vk) {
case '1':
case '2':
case '3':
PostMessage(hwnd, WM_SYSCOMMAND, IDC_SENSITIVITY_NORMAL +
vk - '2', 0);
break;

case VK_DELETE:
PostMessage(hwnd, WM_SYSCOMMAND, IDC_CLEAR, 0);
break;

case VK_F1:
PostMessage(hwnd, WM_SYSCOMMAND, IDC_ABOUT, 0);
break;

}
}

/****************************************************************************
*
* Scrawl_OnPaint
*
* Blt out our bitmap and draw our cursor on top of it.
*
****************************************************************************/

void
Scrawl_OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

if (hdc) {

BitBlt(hdc,
ps.rcPaint.left,
ps.rcPaint.top,
ps.rcPaint.right - ps.rcPaint.left,
ps.rcPaint.bottom - ps.rcPaint.top,
g_hdc,
ps.rcPaint.left,
ps.rcPaint.top,
SRCCOPY);

if (g_fActive && g_fShowCursor) {
DrawIcon(hdc, g_x - g_dxCrossHot,
g_y - g_dyCrossHot, g_hcurCross);
}

EndPaint(hwnd, &ps);
}
}

/****************************************************************************
*
* Scrawl_OnButton0Down_FlushMotion
*
* Flush out any motion that we are holding.
*
****************************************************************************/

typedef struct BUTTON0INFO {

HDC hdcWindow;
BOOL fMoved;
DWORD dwSeqLastSeen;

} BUTTON0INFO, *PBUTTON0INFO;

void Scrawl_OnButton0Down_FlushMotion(PBUTTON0INFO pb0i)
{
if (pb0i->fMoved) {
pb0i->fMoved = 0;
pb0i->dwSeqLastSeen = 0;
LineTo(pb0i->hdcWindow, g_x, g_y);
LineTo(g_hdc, g_x, g_y);
}

}

/****************************************************************************
*
* Scrawl_OnButton0Down
*
* Enter draw mode.
*
* If we are drawing a curve, then read buffered data and draw
* lines from point to point. By reading buffered data, we can
* track the motion of the mouse accurately without coalescing.
*
* This function illustrates how a non-message-based program can
* process buffered data directly from a device, processing
* messages only occasionally (as required by Windows).
*
* This function also illustrates how an application can piece
* together buffered data elements based on the sequence number.
* A single mouse action (e.g., moving diagonally) is reported
* as a series of events, all with the same sequence number.
* Zero is never a valid DirectInput sequence number, so it is
* safe to use it as a sentinel value.
*
****************************************************************************/

void Scrawl_OnButton0Down(HWND hwnd)
{
BUTTON0INFO b0i;

/* Hide the cursor while scrawling */
g_fShowCursor = FALSE;
InvalidateCursorRect(hwnd);
UpdateWindow(hwnd);

/*
* For performance, draw directly onto the window's DC instead of
* invalidating and waiting for the WM_PAINT message. Of course,
* we always draw onto our bitmap, too, since that's what really
* counts.
*/

/* BUGBUG -- select a decent pen, too */
b0i.hdcWindow = GetDC(hwnd);
MoveToEx(b0i.hdcWindow, g_x, g_y, 0);
MoveToEx(g_hdc, g_x, g_y, 0);

/* BUGBUG -- save old pen */
SelectObject(b0i.hdcWindow, GetStockObject(BLACK_PEN));
SelectObject(g_hdc, GetStockObject(BLACK_PEN));

b0i.fMoved = 0;
b0i.dwSeqLastSeen = 0;

/*
* Keep reading data elements until we see a "mouse button up" event.
*/
BOOL fDone = 0;
while (!fDone) {
DIDEVICEOBJECTDATA od;

DWORD dwElements = 1;

HRESULT hr = g_pMouse->GetDeviceData(
sizeof(DIDEVICEOBJECTDATA), &od,
&dwElements, 0);

/* Unable to read data */
if (FAILED(hr)) {
break;
}

/*
* If no data available, finish the element we had been
* collecting, and then process our message queue so
* the system don't think we're hung.
*/
if (dwElements == 0) {

/* If there is a partial motion, flush it out */
Scrawl_OnButton0Down_FlushMotion(&b0i);

MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
/* If it's a quit message, we're outta here */
if (msg.message == WM_QUIT) {
fDone = TRUE;
/*
* Re-post the quit message so the
* outer loop will see it and exit.
*/
PostQuitMessage(msg.wParam);
break;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
continue;
}

/* If this is the start of a new event, flush out the old one */
if (od.dwSequence != b0i.dwSeqLastSeen) {
Scrawl_OnButton0Down_FlushMotion(&b0i);
b0i.dwSeqLastSeen = od.dwSequence;
}

/* Look at the element to see what happened */

switch (od.dwOfs) {

/* DIMOFS_X: Mouse horizontal motion */
case DIMOFS_X:
UpdateCursorPosition(od.dwData, 0);
b0i.fMoved = 1;
break;


/* DIMOFS_Y: Mouse vertical motion */
case DIMOFS_Y:
UpdateCursorPosition(0, od.dwData);
b0i.fMoved = 1;
break;

/* DIMOFS_BUTTON0: Button 0 pressed or released */
case DIMOFS_BUTTON0:

if (!(od.dwData & 0x80)) { /* Button released */
fDone = 1;
Scrawl_OnButton0Down_FlushMotion(&b0i); /* Flush out dregs */
}
break;

}
}

ReleaseDC(hwnd, b0i.hdcWindow);

/* Re-show the cursor now that scrawling is finished */
g_fShowCursor = TRUE;
InvalidateCursorRect(hwnd);
}


/****************************************************************************
*
* Scrawl_OnButton1Up
*
* Pop up a context menu.
*
****************************************************************************/

void Scrawl_OnButton1Up(HWND hwnd)
{
POINT pt = { g_x, g_y };
ClientToScreen(hwnd, &pt);

/*
* Unacquire the devices so the user can interact with the menu.
*
* Put the Windows cursor at the same location as our virtual cursor.
*
* Hide the cursor while moving it so you don't get annoying flicker.
*/

ShowCursor(FALSE);
g_fActive = FALSE;
Scrawl_SyncAcquire(hwnd);
SetCursorPos(pt.x, pt.y);
ShowCursor(TRUE);

HMENU hmenuPopup = GetSystemMenu(hwnd, FALSE);

UINT idc = TrackPopupMenuEx(hmenuPopup,
TPM_RIGHTBUTTON | TPM_RETURNCMD,
pt.x, pt.y, hwnd, 0);

PostMessage(hwnd, WM_SYSCOMMAND, idc, 0L);

}

/****************************************************************************
*
* Scrawl_OnMouseInput
*
* The mouse moved while nothing was happening. Walk the event list
* and update the mouse position for each event. If we see a button
* event, then stop pulling events and leave the elements in the
* input buffer for the drawing handler to pull.
*
*
****************************************************************************/

void
Scrawl_OnMouseInput(HWND hwnd)
{
/* Invalidate the old cursor so it will be erased */
InvalidateCursorRect(hwnd);

/*
* Attempt to read one data element. Continue as long as
* device data is available.
*/
BOOL fDone = 0;
while (!fDone) {
DIDEVICEOBJECTDATA od;

DWORD dwElements = 1;

HRESULT hr = g_pMouse->GetDeviceData(
sizeof(DIDEVICEOBJECTDATA), &od,
&dwElements, 0);

if (hr == DIERR_INPUTLOST) {
/*
* We had acquisition, but lost it. Try to reacquire it.
*
* WARNING! DO NOT ATTEMPT TO REACQUIRE IF YOU GET
* DIERR_NOTACQUIRED! Otherwise, you're extremely likely
* to get caught in an infinite loop: The acquire will fail,
* and you'll get another DIERR_NOTACQUIRED so you'll
* try to aquire again, and that'll fail, etc.
*/
PostMessage(hwnd, WM_SYNCACQUIRE, 0, 0L);
break;
}

/* Unable to read data or no data available */
if (FAILED(hr) || dwElements == 0) {
break;
}

/* Look at the element to see what happened */

switch (od.dwOfs) {

/* DIMOFS_X: Mouse horizontal motion */
case DIMOFS_X: UpdateCursorPosition(od.dwData, 0); break;


/* DIMOFS_Y: Mouse vertical motion */
case DIMOFS_Y: UpdateCursorPosition(0, od.dwData); break;

/* DIMOFS_BUTTON0: Button 0 pressed or released */
case DIMOFS_BUTTON0:

if (od.dwData & 0x80) { /* Button pressed */
fDone = 1;
Scrawl_OnButton0Down(hwnd); /* Go into button-down mode */
}
break;

/* DIMOFS_BUTTON1: Button 1 pressed or released */
case DIMOFS_BUTTON1:

if (!(od.dwData & 0x80)) { /* Button released */
fDone = 1;
Scrawl_OnButton1Up(hwnd); /* Context menu time */
}
}

}

/* Invalidate the new cursor so it will be drawn */
InvalidateCursorRect(hwnd);

}

/****************************************************************************
*
* ScrawlWndProc
*
* Application window procedure.
*
****************************************************************************/

LONG CALLBACK ScrawlWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
switch (msg) {

HANDLE_MSG(hwnd, WM_CREATE, Scrawl_OnCreate);

HANDLE_MSG(hwnd, WM_PAINT, Scrawl_OnPaint);

HANDLE_MSG(hwnd, WM_INITMENUPOPUP, Scrawl_OnInitMenuPopup);

HANDLE_MSG(hwnd, WM_KEYDOWN, Scrawl_OnKeyDown);

/*
* Reacquire the mouse and keyboard when we are the active window.
* Unacquire them when we stop being the active window.
*/
case WM_ACTIVATE:
g_fActive = wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE;
Scrawl_SyncAcquire(hwnd);
break;

/*
* Unacquire the devices if a menu appears, so that the user can
* interact with the menu in the normal manner.
*
* From Windows' point of view, we are still the active window
* when a menu appears, but we want to act like the menu deactivated
* us.
*/
case WM_ENTERMENULOOP:
case WM_ENTERSIZEMOVE:
g_fActive = FALSE;
Scrawl_SyncAcquire(hwnd);
break;

/*
* Reacquire the devices when the menu goes away.
*
* SUBTLETY 1: Windows actually sends the WM_EXITMENULOOP message
* before all the menu-related stuff is finished, so post ourselves
* a private message to reacquire after the menu has been torn
* down for real.
*
* SUBTLETY 2: Don't assume that just because the menu is going
* away that you are still the active window. You might not be.
*
* SUBTLETY 3: Don't assume that just because you're the active
* window that you are restored and ready for action. You might
* just be a taskbar button.
*/
case WM_EXITMENULOOP:
case WM_EXITSIZEMOVE:
g_fActive = GetActiveWindow() == hwnd && !IsIconic(hwnd);
PostMessage(hwnd, WM_SYNCACQUIRE, 0, 0L);
break;

case WM_SYNCACQUIRE:
Scrawl_SyncAcquire(hwnd);
break;

case WM_SYSCOMMAND:

LRESULT lRc;
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
case IDC_CLEAR:
Scrawl_OnClear(hwnd);
lRc = 0;
break;

case IDC_ABOUT:
MessageBox(hwnd, "Scrawl DirectInput Sample v1.0",
c_szAppName, MB_OK);
lRc = 0;
break;

/*
* Eat the screen-saver notification.
*/
case SC_SCREENSAVE:
lRc = 0;
break;

case IDC_SENSITIVITY_LOW:
case IDC_SENSITIVITY_NORMAL:
case IDC_SENSITIVITY_HIGH:
g_iSensitivity = (signed short)GET_WM_COMMAND_ID(wParam, lParam)
- IDC_SENSITIVITY_NORMAL;
lRc = 0;
break;

default:
lRc = DefWindowProc(hwnd, msg, wParam, lParam);
break;
}

/*
* The WM_SYSCOMMAND might've been a WM_CLOSE, in which case
* our window no longer exists. So be careful.
*/
if (IsWindow(hwnd)) {
Scrawl_SyncAcquire(hwnd);
}
return lRc;


case WM_DESTROY:
PostQuitMessage(0);
break;

}

return DefWindowProc(hwnd, msg, wParam, lParam);
}

/****************************************************************************
*
* AppInit
*
* Set up everything the application needs to get started.
*
****************************************************************************/

HWND AppInit(HINSTANCE hinst, int nCmdShow)
{

/* Save instance handle for people who care */
g_hinst = hinst;

/*
* Get our crosshairs cursor and extract the the width and
* hotspot location so we can draw it manually.
*/
g_hcurCross = LoadCursor(NULL, IDC_CROSS);

ICONINFO ii;

GetIconInfo(g_hcurCross, &ii);

BITMAP bm;

GetObject(ii.hbmMask, sizeof(BITMAP), &bm);

if (ii.hbmMask) DeleteObject(ii.hbmMask);
if (ii.hbmColor) DeleteObject(ii.hbmColor);

g_dxCrossHot = ii.xHotspot;
g_dyCrossHot = ii.yHotspot;

g_cxCross = bm.bmWidth;
g_cyCross = bm.bmHeight;

/*
* Create our scrawl bitmap and set it up.
*/
HDC hdc = GetDC(0);
g_hdc = CreateCompatibleDC(hdc);
ReleaseDC(0, hdc);

if (!g_hdc) return NULL;

g_hbm = CreateBitmap(DINPUT_CXBITMAP, DINPUT_CYBITMAP, 1, 1, 0);

if (!g_hbm) return NULL;

g_hbmDeselect = (HBITMAP) SelectObject(g_hdc, g_hbm);

Scrawl_OnClear(0);

/*
* Set up the window class.
*/
WNDCLASS wc;

wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_MAIN));
wc.lpszMenuName = NULL;
wc.lpszClassName = c_szAppName;
wc.hbrBackground = 0;
wc.hInstance = hinst;
wc.style = 0;
wc.lpfnWndProc = ScrawlWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

if (!RegisterClass(&wc)) {
return NULL;
}

DWORD dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
DWORD dwExStyle = WS_EX_APPWINDOW;

RECT rc = { 0, 0, DINPUT_CXBITMAP, DINPUT_CYBITMAP };

AdjustWindowRectEx(&rc, dwStyle, FALSE, dwExStyle);

HWND hwnd = CreateWindowEx(
dwExStyle, // ExStyle
c_szAppName, // Class name
c_szAppName, // Caption
dwStyle, // Style
CW_USEDEFAULT, CW_USEDEFAULT, // Position
rc.right - rc.left, // cx
rc.bottom - rc.top, // cy

0,                  // Parent window (no parent) 
0, // use class menu
g_hinst, // handle to module instance
0 // no params to pass on
);

if (!DIInit(hwnd)) {
DestroyWindow(hwnd);
return NULL;
}

ShowWindow(hwnd, nCmdShow);

return hwnd;
}

/****************************************************************************
*
* WinMain
*
* Application entry point.
*
* The main message loop illustrates how a message-driven program
* can use event notifications to be signalled when new data is
* available from a device.
*
****************************************************************************/

int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
MSG msg;
msg.wParam = 0; /* In case something goes horribly wrong */

HWND hwnd = AppInit(hinst, nCmdShow);

if (hwnd) {

/*
* Since we use notification handles, we need to use
* MsgWaitForMultipleObjects to wait for the event or
* a message, whichever comes first.
*/

BOOL fDone = FALSE;

while (!fDone) {

DWORD dw = MsgWaitForMultipleObjects(1, &g_hevtMouse, 0, INFINITE,
QS_ALLINPUT);

switch (dw) {

/* WAIT_OBJECT_0 + 0 means that g_hevtMouse was signalled */
case WAIT_OBJECT_0 + 0:
Scrawl_OnMouseInput(hwnd);
break;

/* WAIT_OBJECT_0 + 1 means that we have messages to process */
case WAIT_OBJECT_0 + 1:

while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

/* If it's a quit message, we're outta here */
if (msg.message == WM_QUIT) {
fDone = TRUE;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
break;
}
}
}

DITerm();

if (g_hdc) {
if (g_hbmDeselect) {
SelectObject(g_hdc, g_hbmDeselect);
}
DeleteDC(g_hdc);
}

if (g_hbm) {
DeleteObject(g_hbm);
}

return msg.wParam;

}