SWITCHER.C

/**************************************************************************** 
* This is a part of the Microsoft Source Code Samples.
* Copyright (C) 1995 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
*
*****************************************************************************/

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

PROGRAM: desktop.c

PURPOSE: Allow user to switch between active desktops on the User's
WindowStation

USAGE: desktop [-t #threads]
#threads is the number of desktops and corresponding threads to create

APIS of Importance:
CreateDesktop()
SwitchDesktop()
GetUserObjectInformation()
GetThreadDesktop()
SetThreadDesktop()
CloseDesktop()
OpenDesktop()

FUNCTIONS:
WinMain
StartNewDesktop
CreateAllDesktops
LoadResources
InitApplication
ThreadInit
SaveScreen
PaintMainWnd
RunApp
GetFontHeight
TitleWindow
CreateControls
WndProc
PreviewWndProc
EditProc


COMMENTS: This application demonstrates the multiple desktop
capabilities of Windows NT 3.51, PPC release.



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

#include <windows.h> // required for all Windows applications

#include "switcher.h" // specific to this program

//
// Array of string resources
//
TCHAR SwitchStrings[LAST_STRING-FIRST_STRING + 1][MAXSTRLEN];
#define PSZ(x) SwitchStrings[x-FIRST_STRING]

//
// Structure used for thread-specific data
//
typedef struct _tdata
{
HDESK hDesk; // desktop assigned to new thread
int index; // index into deskArray
HWND hWndStatic; // "Run:" static control
HWND hWndEdit; // edit control for user input
HWND hWndBtn; // button for user input
HWND hWndNew; // button for new desktop
} ThreadData;

int gMaxIndex; // Highest index for array of desk handles
HWND gBaseWindow; // Window handle of default desktop window
HDESK gDeskArray[MAX_THREADS]; // Global array of desktop handles
HWND hWndArray[MAX_THREADS]; // Global array of window handles
HDC gHDCArray[MAX_THREADS]; // global array of memory device contexts
// these DCs store snapshots of the desktops
int gWidth, gHeight; // dimensions of desktop rectangles

//
// Keep track of how big the controls need to be to match the
// active system fixed font. These will help determine the
// minimum size of the switcher window.
//
int gStaticWidth; // Edit control label
int gEditWidth; // Edit control
int gBtnWidth; // Button to run app in edit control
int gNewWidth; // Button to create new desktop

HINSTANCE ghInst = NULL; // Global hInstance
#define DEFAULT_DESKTOP gDeskArray[0] // For easy reading
LONG APIENTRY EditProc (HWND, UINT, WPARAM, LPARAM);
TCHAR szAppName[] = TEXT("Desktop Switcher!");
TCHAR szClassName[] = TEXT("SwitcherWindow");
TCHAR szPreviewClass[]= TEXT("PreviewWindow");

/****************************************************************************
FUNCTION: StartNewDesktop

PURPOSE: Create or open a handle to a desktop and put a switcher thread
on it.

ARGUMENTS:
int nCount - Which desktop number this is

Assumes gDeskArray[nCount] == NULL

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

void StartNewDesktop (int nCount)
{
ThreadData *ptd;
TCHAR szDesk[50];
DWORD tID;
ptd = (ThreadData*)GlobalAlloc(GMEM_FIXED,sizeof(ThreadData));
if (ptd)
{
ptd->index = nCount;
//
// Give the desktop a name.
//
wsprintf (szDesk, PSZ(IDS_DESKTOPNAME), nCount+1);
//
// First, try to open an existing desktop
//
if ( !(ptd->hDesk = OpenDesktop (szDesk, 0, FALSE, GENERIC_ALL)))
{
//
// Failing an open, Create it
//
if (!(ptd->hDesk= CreateDesktop (szDesk, NULL,
NULL,0,MAXIMUM_ALLOWED,
NULL)))
{

MessageBox (NULL, PSZ(IDS_CREATEERROR),
PSZ(IDS_ERRCAPTION), MB_OK);
//
//Mark this array slot as invalid
//
gDeskArray[nCount] = NULL;

}

}
if (ptd->hDesk)
{
//
// Save the handle to the global array. Start the new thread
//
gDeskArray[ptd->index] = ptd->hDesk;
CloseHandle(CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)ThreadInit,
(LPVOID)ptd, 0, &tID));
}
}
else
{
//
// Out of memory
//
MessageBox (NULL, PSZ(IDS_CREATEERROR),
PSZ(IDS_MEMERRCAPTION), MB_OK);
//
//Mark this array slot as invalid
//
gDeskArray[nCount] = NULL;
}

}
/****************************************************************************

FUNCTION: CreateAllDesktops (cThreads)

PURPOSE: Creates desktops and assigns a switcher thread to each.
Updates the global desktop array.

ARGUMENTS:
int cThreads - Number of threads/desktops to open or create

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

void CreateAllDesktops (int cThreads)
{
ThreadData *ptdDefault;


//
// Make sure we allocate for the default desktop first
//
ptdDefault = (ThreadData *)GlobalAlloc (GMEM_FIXED, sizeof(ThreadData));
if (!ptdDefault) {
return;
}
while (cThreads)
{
StartNewDesktop (cThreads);
cThreads--;
}
//
// Main thread is one of the running threads too
//
ptdDefault->index = 0;
ptdDefault->hDesk = DEFAULT_DESKTOP;
ThreadInit((LPVOID)ptdDefault);

}

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

FUNCTION: WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

PURPOSE: Creates the threads and desktops
COMMENTS: Each thread has a separate desktop assigned to it.


****************************************************************************/
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{

int cThreads; // number of desktops

ghInst = hInstance;

if (hPrevInstance)
{ // Other instances of app running?
return (FALSE); // Exit
} else {
if (!InitApplication ()) {
return FALSE;
}
}

// parse command line to determine number of desktops
// Assume 9 threads

cThreads = 9;
lpCmdLine = GetCommandLineA();

//
// Get past .exe name
//
while (*lpCmdLine != ' ' && *lpCmdLine != 0)
lpCmdLine++;

//
// Find the parameters
//
while (*lpCmdLine != 0)
{

// Eat white space

if (*lpCmdLine == ' ')
{
lpCmdLine++;
continue;
}

//
// Do we have a dash? If not, just exit the loop
//
if (*lpCmdLine++ != '-')
break;

switch (*lpCmdLine++)
{
case 't':
case 'T':
//
// How many threads?
//

while (*lpCmdLine == ' ')
lpCmdLine++;

if (*lpCmdLine == 0 || *lpCmdLine == '-')
continue;

cThreads = 0;
while (*lpCmdLine >= '0' && *lpCmdLine <= '9')
cThreads = cThreads * 10 + (*lpCmdLine++ - 0x30);
break;

}
}

// Create the threads - if zero was specified, default to 1.
// What does 0 threads mean?
if (cThreads == 0)
cThreads = 1;
else if (cThreads > MAX_THREADS)
cThreads = MAX_THREADS;
//
// Account for the main thread - only create extras
//
cThreads--;
gMaxIndex = cThreads; // Keep track of the highest array index

//
// Assign this here, since threads reference it
//
DEFAULT_DESKTOP = GetThreadDesktop(GetCurrentThreadId());
CreateAllDesktops (cThreads);
return 0;
}

/*************************************************************
FUNCTION: LoadResources

PURPOSE: Load string table entries

ARGUMENTS: None

RETURNS: True if all strings are loaded, False otherwise

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

BOOL LoadResources (void)
{
int i;
for (i=0;i<(LAST_STRING-FIRST_STRING+1);i++)
{
if (!LoadString (ghInst, FIRST_STRING + i, SwitchStrings[i], MAXSTRLEN))
return FALSE;
}
return TRUE;
}
/*************************************************************

FUNCTION: InitApplication

PURPOSE: Register the window class and init global variables

ARGUMENTS:

RETURNS:
TRUE if window class registration and other initialization succeeds

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

BOOL InitApplication (void) {

WNDCLASS wc;
HWND hTemp;
HWINSTA hWndSta;
RECT rect;

if (!LoadResources ())
return FALSE;
//
// Initialize the gHDCArray to all NULLS
//
ZeroMemory (gHDCArray, sizeof (gHDCArray));
hTemp = GetDesktopWindow(); // Call this so User will assign us a WindowStation.
//
// Initialize gWidth and gHeight
// Get the size of the screen, make gWidth/gHeight == scrnW/scrnH
//
GetClientRect (hTemp, &rect);
gWidth = rect.right/DIVISOR;
gHeight = rect.bottom/DIVISOR;

//
// Make sure this app has a windowstation
//
hWndSta = GetProcessWindowStation();
if (!hWndSta)
{
MessageBox (NULL, PSZ(IDS_WNDSTAERROR), PSZ(IDS_ERRCAPTION), MB_OK);
return FALSE;
}
//
// Register the main window class
//
wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = ghInst;
wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject (WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = szClassName;

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

//
// Register the preview window class
//
wc.style = 0;
wc.lpfnWndProc = PreviewWndProc;
wc.lpszClassName = szPreviewClass;

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


return TRUE;
}

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

FUNCTION: ThreadInit

PURPOSE: Given a desktop handle, create a window on it to allow switching
among desktops.

ARGUMENTS:
LPVOID tData - Thread-specific data

RETURNS:
nothing
********************************************************************/

void ThreadInit(LPVOID tData)
{
MSG msg;
HWND hWnd;
HDC hTemp;
int width; // window width
USEROBJECTFLAGS uof; // To set Desktop attributes

uof.fInherit = FALSE; // If an app inherits multiple desktop handles,
// it could run on any one of those desktops
uof.fReserved = FALSE;
//
// Let other account processes hook this desktop
//
uof.dwFlags = DF_ALLOWOTHERACCOUNTHOOK;
SetUserObjectInformation (((ThreadData*)tData)->hDesk,
UOI_FLAGS,
(LPVOID)&uof,
sizeof(uof));
//
// Make sure the handle is valid
//
if (gDeskArray[((ThreadData*)tData)->index])
{
//
// Assign new desktop to this thread
//
SetThreadDesktop (((ThreadData*)tData)->hDesk);
// create the cool switcher window
if ((gMaxIndex+1) * gWidth > MINWINDOWWIDTH)
{
width = (gMaxIndex+1) * gWidth;
}
else
{
width = MINWINDOWWIDTH;
}
hWnd = CreateWindow (szClassName,
szAppName,
WS_MINIMIZEBOX|WS_OVERLAPPED|WS_VISIBLE|WS_BORDER|WS_CAPTION|WS_SYSMENU,
0, 0, width, 30+gHeight + CONTROLHEIGHT,
NULL, NULL, ghInst, tData);
if (!hWnd) // bag it
{
gDeskArray[((ThreadData*)tData)->index] = NULL;
GlobalFree (tData);
return;
}

//
//update the global window array
//
hWndArray[((ThreadData*)tData)->index] = hWnd;

}
else
{

GlobalFree (tData);
return;
}

//
// Acquire and dispatch messages until a WM_QUIT message is received.
//
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);// Translates virtual key codes
DispatchMessage(&msg); // Dispatches message to window
}
//
// Switch back to the default desktop and close the user-created one
//
SetThreadDesktop (DEFAULT_DESKTOP);
SwitchDesktop (DEFAULT_DESKTOP);
CloseDesktop (((ThreadData*)tData)->hDesk);
//
// NULL out the global array entry so other threads won't try to switch to
// this desktop
//
gDeskArray[((ThreadData*)tData)->index] = NULL;
//
// cleanup
//
hTemp = gHDCArray[((ThreadData*)tData)->index];
gHDCArray[((ThreadData*)tData)->index] = NULL;
DeleteObject (hTemp);
GlobalFree (tData);

}


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

FUNCTION: SaveScreen

PURPOSE: Save a snapshot of the desktop to its corresponding
memory DC. StretchBlt!

ARGUMENTS:
int index - Index of memory DC to save bits to

RETURNS: nothing
************************************************************************/

void SaveScreen (int index) {
HDC hdc;
int xSize, ySize;

xSize = GetSystemMetrics (SM_CXSCREEN);
ySize = GetSystemMetrics (SM_CYSCREEN);

if (hdc = CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL))
{
//
// Copy the desktop to a memory DC
//
StretchBlt (gHDCArray[index], 0, 0, gWidth*2, gHeight*2,
hdc, 0, 0, xSize, ySize, SRCCOPY);
DeleteDC (hdc);
}
}

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

FUNCTION: PaintMainWnd

PURPOSE: Draw the main window. This window has rectangles with miniature snapshots
of each desktop. The snapshots are retrieved from the gHDCArray and StretchBlt'd to
the right size.

ARGUMENTS:
HWND hWnd - Window to draw

RETURNS: nothing

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

void PaintMainWnd (HWND hWnd)
{
PAINTSTRUCT ps;
RECT rect;
HPEN hPen, hOld;
ThreadData *ptd;
TCHAR szName[4]; // short name!
int myThread;
HDC hDC; // always need a dc for drawing
int i; // my favorite loop counter

//
// get the array index of this window
//
ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
myThread = ptd->index;

//
//Draw a rectangle for each desktop
//
hDC = BeginPaint (hWnd, &ps);
if (GetClientRect (hWnd, &rect))
{
int right, left;
//
// leave space for edit control and button
//
rect.bottom -= CONTROLHEIGHT;
hPen = CreatePen (PS_SOLID | PS_INSIDEFRAME, 2, RGB(255,16,16));
hOld = SelectObject (hDC, hPen);

//
// draw each desktop rectangle
//
for (i=0;i<=gMaxIndex;i++)
{
right = gWidth * i + gWidth;
left = right - gWidth;
//
// If no snapshot is available, be boring
//
if (!gHDCArray[i])
{
Rectangle (hDC, left, rect.top, right, gHeight);
wsprintf (szName, TEXT("%d"), i+1);
TextOut (hDC, left+(gWidth/2),
gHeight/2, szName,
lstrlen (szName));
}
else
{
//
// BitBlt the snapshot into the rectangle
//
StretchBlt (hDC, left, rect.top, gWidth, gHeight,
gHDCArray[i], 0, 0, gWidth*2, gHeight*2, SRCCOPY);
//
// draw lines around the rectangle
//
MoveToEx (hDC, left, rect.top, NULL);
LineTo (hDC, left, gHeight);
MoveToEx (hDC, right, rect.top, NULL);
LineTo (hDC, right, gHeight);
//
// underline the active one
//
if (myThread == i)
{
MoveToEx (hDC, left, gHeight, NULL);
LineTo (hDC, right, gHeight);
}
}
}
//
// cleanup
//
SelectObject (hDC, hOld);
DeleteObject (hPen);
}

EndPaint (hWnd, &ps);

}

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

FUNCTION: RunApp (HWND)

PURPOSE: Create a process, using contents of HWND's edit control
as the command line.

ARGUMENTS:
HWND hWnd - Handle of active switcher window

RETURNS: nothing

COMMENTS: Make sure proper desktop is passed in STARTUPINFO

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

void RunApp (HWND hWnd)
{
TCHAR szDesk[100]; // data holder
TCHAR szExec[100]; // Command line
STARTUPINFO sui; // Process startup info
PROCESS_INFORMATION pi; // info returned from CreateProcess
ThreadData *ptd;

ptd = (ThreadData*)GetWindowLong (hWnd, GWL_USERDATA);
//
// Most sui members will be 0
//
ZeroMemory ((PVOID)&sui, sizeof(sui));
//
//Get the command line to execute
//
GetDlgItemText (hWnd, IDC_RUNME, szExec, 100*sizeof(TCHAR));
//
//Get the current desktop name
//
GetUserObjectInformation (GetThreadDesktop (GetCurrentThreadId()),
UOI_NAME,
szDesk,
100*sizeof(TCHAR),
NULL);
sui.cb = sizeof (sui);
//
// Need the lpDesktop member so the new process runs on this desktop
// The lpDesktop member was reserved in previous versions of NT
//
sui.lpDesktop = szDesk;
CreateProcess (NULL, // image name
szExec, // command line
NULL, // process security attributes
NULL, // thread security attributes
TRUE, // inherit handles
CREATE_DEFAULT_ERROR_MODE|CREATE_SEPARATE_WOW_VDM,
NULL, // environment block
NULL, // current directory
&sui, // STARTUPINFO
&pi); // PROCESS_INFORMATION

}
/****************************************************************************
FUNCTION: GetFontHeight

PURPOSE: Set up widths for controls based on size of the system
font.

ARGUMENTS:
HWND hWnd - Window whose DC to use.

RETURNS:
Return the height of the system fixed font to use for
the controls.

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

LONG GetFontHeight (HWND hWnd)
{
HDC hdc;
TEXTMETRIC tm;
SIZE size;
#define MARGIN 7 // extra space on the button around the text

hdc = GetDC (hWnd);
if (!GetTextMetrics (hdc, &tm))
{
//
// Use defaults
//
gStaticWidth = STATICWIDTH;
gBtnWidth = BTNWIDTH;
gEditWidth = EDITWIDTH;
gNewWidth = BTNWIDTH + 25;
return CONTROLHEIGHT;
}

//
// GetTextExtentPoint32 fills in size with the width and height of
// a string.
//
GetTextExtentPoint32 (hdc, PSZ(IDS_RUNLABEL), lstrlen(PSZ(IDS_RUNLABEL)), &size);
gStaticWidth = size.cx + MARGIN;
gEditWidth = EDITWIDTH;
GetTextExtentPoint32 (hdc, PSZ(IDS_BTNLABEL), lstrlen(PSZ(IDS_BTNLABEL)), &size);
gBtnWidth = size.cx + MARGIN;
GetTextExtentPoint32 (hdc, PSZ(IDS_NEWLABEL), lstrlen(PSZ(IDS_NEWLABEL)), &size);
gNewWidth = size.cx + MARGIN;
ReleaseDC (hWnd, hdc);
return tm.tmHeight + 2;

}

/****************************************************************************
FUNCTION: TitleWindow

PURPOSE: Give a switcher window an appropriate title, using its
desktop name.

ARGUMENTS:
HWND hWnd - Window to title

RETURNS: nothing

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

void TitleWindow (HWND hWnd)
{
TCHAR *szTitle, *szName;
UINT nBytes = 0;

//
// How long is the desktop name?
//
GetUserObjectInformation (GetThreadDesktop(GetCurrentThreadId()),
UOI_NAME,
(LPVOID)&nBytes, // not used since cbInfo is 0
0,
&nBytes);
szName = (LPTSTR)GlobalAlloc (GPTR, nBytes);
if (!szName)
{
return;
}
//
// Now get the desktop name
//
GetUserObjectInformation (GetThreadDesktop(GetCurrentThreadId()),
UOI_NAME,
(LPVOID)szName,
nBytes,
&nBytes);
//
// Now make the window title
//
szTitle = (LPTSTR)GlobalAlloc (
GPTR,
(lstrlen(szAppName)+lstrlen(TEXT(" - "))) * sizeof(TCHAR) + nBytes);

if (!szTitle)
{
GlobalFree (szName);
return;
}
wsprintf (szTitle, TEXT("%s - %s"), szAppName, szName);
SetWindowText (hWnd, szTitle);
//
// Cleanup
//
GlobalFree (szName);
GlobalFree (szTitle);
}

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

FUNCTION: CreateControls (ThreadData *, HWND)

PURPOSE: Creates UI controls on a switcher window

ARGUMENTS:
ThreadData *ptd - Thread specific data to use/init
HWND hWnd - Parent window

RETURNS:
nothing


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

void CreateControls (ThreadData *ptd, HWND hWnd)
{
LONG oldproc;

//
// Create the edit control label
//
ptd->hWndStatic = CreateWindow (TEXT("static"), PSZ(IDS_RUNLABELHOT),
WS_CHILD | WS_VISIBLE,
0,0,0,0, hWnd, (HMENU)IDC_STATIC,
ghInst, NULL);
//
// Create the edit control
//
ptd->hWndEdit = CreateWindow (TEXT("Edit"), TEXT(""),
WS_BORDER | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,
0,0,0,0, hWnd, (HMENU)IDC_RUNME,
ghInst, NULL);

//
// set the edit control proc and save the default one
//
oldproc = GetWindowLong (ptd->hWndEdit, GWL_WNDPROC);
SetWindowLong (ptd->hWndEdit, GWL_WNDPROC, (LONG)EditProc);
SetWindowLong (ptd->hWndEdit, GWL_USERDATA, oldproc);

//
// Create the execution button
//
ptd->hWndBtn = CreateWindow (TEXT("Button"), PSZ(IDS_BTNLABEL),
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
0,0,0,0, hWnd, (HMENU)IDC_RUNMEBTN,
ghInst, NULL);
//
// Create a button for creating new desktops
//
ptd->hWndNew = CreateWindow (TEXT("button"), PSZ(IDS_NEWLABELHOT),
WS_CHILD | WS_VISIBLE,
0,0,0,0, hWnd, (HMENU)IDC_NEWDSKBTN,
ghInst, NULL);

}
/****************************************************************************

FUNCTION: WndProc(UINT, WPARAM, LPARAM)

PURPOSE: Processes messages to the Switcher window

MESSAGES:
WM_RBUTTONDOWN - Switch to desktop whose rectangle is under the mouse
WM_CLOSE - Send WM_CLOSE to all windows in hWndArray
WM_LBUTTONDOWN - Create a preview window to display a larger view of
a desktop until WM_LBUTTONUP
WM_COMMAND - Respond to button pushes or edit control entry
WM_SYSCHAR - ALT+R sets focus to the edit control
ALT+N creates a new desktop
WM_CHAR - Carriage return executes command line from
the edit control
WM_HOTKEY
WM_KEYDOWN - Respond to function keys by switching to
the appropriate desktop. Example, F2 means switch
to Desktop2. Ctrl-F# allows switching without
giving focus to the switcher window
WM_LBUTTONUP - Close the active preview window
WM_CREATE - Initialize controls and global array entries
WM_SIZE - Size the child controls correctly


COMMENTS:

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

LONG APIENTRY WndProc(
HWND hWnd,
UINT message, // type of message
WPARAM wParam, // additional information
LPARAM lParam) // additional information
{

int newThread; // Thread index to switch to
int i;
ThreadData *ptd;
static HWND hShowing = NULL; // which preview window is being shown
static LONG fntHeight = CONTROLHEIGHT; // height for the edit control
switch (message)
{
case WM_CREATE:
{
HDC hDC;
HBITMAP hBmp;

// Create edit control, button, and label at the bottom of the window
// This will allow the user to input a program to run

SetWindowLong (hWnd, GWL_USERDATA,
(LONG)((CREATESTRUCT *)lParam)->lpCreateParams);
ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
CreateControls (ptd, hWnd);
fntHeight = GetFontHeight (hWnd);
//
// initialize the DC array entry
//
hDC = CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL);
gHDCArray[ptd->index] = CreateCompatibleDC (hDC);
//
// Halftone is the best stretching algorithm
//
SetStretchBltMode (gHDCArray[ptd->index], HALFTONE);
SetBrushOrgEx (gHDCArray[ptd->index], 0, 0, NULL);
//
// Use a bitmap the same size as the desktop preview rectangles
//
hBmp = CreateCompatibleBitmap (hDC, gWidth*2, gHeight*2);
SelectObject (gHDCArray[ptd->index], hBmp);
DeleteDC (hDC);
SaveScreen (ptd->index);
TitleWindow (hWnd);
//
// Register hot keys
//
for (i=0;i<10;i++)
{
RegisterHotKey (hWnd, VK_F1+i, MOD_CONTROL, VK_F1+i);
}
return 0;
}

case WM_SIZE:
{
//
// Put the child controls at the right places
//
#define PADDING 5

RECT rect;
ThreadData *ptd;
if (GetClientRect (hWnd, &rect))
{
ptd = (ThreadData *)GetWindowLong (hWnd, GWL_USERDATA);
MoveWindow (ptd->hWndStatic, 0, rect.bottom - CONTROLHEIGHT,
gStaticWidth, fntHeight + PADDING, TRUE);

MoveWindow (ptd->hWndEdit, gStaticWidth + 5,
rect.bottom - fntHeight - PADDING,
gEditWidth, fntHeight+PADDING, TRUE);

MoveWindow (ptd->hWndBtn, gStaticWidth + gEditWidth + 10,
rect.bottom - fntHeight - PADDING,

gBtnWidth, fntHeight+PADDING, TRUE); 

MoveWindow (ptd->hWndNew, gStaticWidth+gEditWidth+gBtnWidth+15,
rect.bottom - fntHeight- PADDING,
gNewWidth, fntHeight+PADDING, TRUE);


}
return 0;
}
case WM_PAINT:
PaintMainWnd (hWnd);
return 0;

case WM_RBUTTONDOWN:
{
//
// Find the rectangle in which the button was pressed
//
POINTS pts;
ThreadData *ptd;
ptd = (ThreadData *)GetWindowLong(hWnd, GWL_USERDATA);
pts = MAKEPOINTS (lParam);
if (pts.y > gHeight)
{
return 1;
}
newThread = pts.x/gWidth;

//
// Get a snapshot of the current desktop
//
SaveScreen (ptd->index);

//
// Switch to the selected desktop
//
if (!gDeskArray[newThread])
{
StartNewDesktop (newThread);
}
if (!SwitchDesktop (gDeskArray[newThread]))
MessageBox (hWnd,
PSZ(IDS_BADDESKTOP),
PSZ(IDS_ERRCAPTION), MB_OK);

return 0;
}

case WM_LBUTTONDOWN:
//
// show the preview window
//
{
POINTS pts;
POINT ptl;
int *index;

pts = MAKEPOINTS (lParam);
if (pts.y > gHeight)
{
return 1;
}
newThread = pts.x/gWidth;
index = GlobalAlloc (GMEM_FIXED, sizeof(int));
if (!index)
{
return 1;
}
*index = newThread;
//
// Want to show the preview window where the button was clicked.
// Map the given points to screen coords.
// ClientToScreen is expecting a POINT structure, not a POINTS
//
ptl.x = (LONG)pts.x;
ptl.y = (LONG)pts.y;
ClientToScreen (hWnd, &ptl);
hShowing = CreateWindow (szPreviewClass, TEXT(""),
WS_POPUP | WS_VISIBLE | WS_BORDER,
ptl.x+3,
ptl.y+3,
gWidth*2,
gHeight*2,
hWnd,
(HMENU)0, ghInst, (LPVOID)index);
return 0;
}

case WM_CHAR:
if (wParam == VK_RETURN)
{
PostMessage (hWnd, WM_COMMAND, (WPARAM)IDC_RUNMEBTN, 0);
}
return 0;

case WM_SYSCHAR:
{
ThreadData *ptd;
ptd = (ThreadData *)GetWindowLong(hWnd, GWL_USERDATA);
switch (wParam)
{
// alt+r == focus on the edit control
case TEXT('r'):
case TEXT('R'):
if (GetKeyState (VK_MENU))
{
SetFocus (ptd->hWndEdit);
}
return 0;
// alt+n = create a new desktop
case TEXT('n'):
case TEXT('N'):
if (GetKeyState (VK_MENU))
{
PostMessage (hWnd, WM_COMMAND, (WPARAM)IDC_NEWDSKBTN, 0);
}
}
return 0;
}
case WM_HOTKEY:
case WM_KEYDOWN:
//
// F1-F9 switches to corresponding desktop
//

if ((wParam >= VK_F1 && wParam <= VK_F10)
&& (wParam - VK_F1 <= (UINT)gMaxIndex))
{
LONG x, y;
x = (wParam - VK_F1) * gWidth + 2;
y = gHeight - 4;
PostMessage (hWnd, WM_RBUTTONDOWN, 0, MAKELPARAM (x, y));
}
return 0;

case WM_SETFOCUS:
case WM_NCLBUTTONUP:
case WM_LBUTTONUP:

//
// destroy the preview window
//
if (hShowing)
{
DestroyWindow (hShowing);
hShowing = NULL;
}
return 0;



case WM_CLOSE:
//
// to be safe, check for a preview window
//
if (hShowing)
{
DestroyWindow (hShowing);
hShowing = NULL;
}
//
// go to the default desktop so the DestroyWindow calls all succeed
//
SwitchDesktop (DEFAULT_DESKTOP);
//
// kill the window on this desktop
// all the windows will be destroyed if this is the default desktop
//
for (i=gMaxIndex;i>=0;i--)
{
DestroyWindow (hWndArray[i]);
}
//
// Unregister the hot keys
//
for (i=0;i<10;i++)
{
UnregisterHotKey (hWnd,VK_F1+i);
}
return 0;

case WM_DESTROY: // message: window being destroyed

PostQuitMessage(0);
return 0;


case WM_COMMAND:
{

switch (LOWORD(wParam))
{
case IDC_RUNMEBTN:
{
RunApp (hWnd);
return 0;
}

case IDC_NEWDSKBTN:
//
// Create a new desktop and resize the windows to show it.
//
{
RECT rect;
int i;
if (gMaxIndex + 1 < MAX_THREADS)
{
gMaxIndex++;
StartNewDesktop (gMaxIndex);
GetWindowRect (hWnd,&rect);
for (i=0;i<gMaxIndex;i++)
{
MoveWindow (hWndArray[i],
rect.left, rect.top,
rect.right + gWidth, rect.bottom-rect.top,
TRUE);

}
}
return 0;

}

default:
return DefWindowProc (hWnd, message, wParam, lParam);
}
}

default: // Passes it on if unprocessed
return (DefWindowProc (hWnd, message, wParam, lParam));
}

}

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

FUNCTION: PreviewWndProc

PURPOSE: Displays an enlarged view of the last snapshot of a desktop

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

LONG APIENTRY PreviewWndProc (HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{

int *index;
switch (msg)
{
case WM_CREATE:
//
// save the index
//
SetWindowLong (hWnd, GWL_USERDATA,
(LONG)((CREATESTRUCT *)lParam)->lpCreateParams);

return 0;

case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
index = (int *)GetWindowLong (hWnd, GWL_USERDATA);
hdc = BeginPaint (hWnd, &ps);
//
// slap in the desktop picture
//
BitBlt (hdc, 0, 0, gWidth*2, gHeight*2,
gHDCArray[*index], 0, 0, SRCCOPY);
EndPaint (hWnd, &ps);
return 0;
}
case WM_LBUTTONUP:
{
//
// In case the button is released in my client area
//
HWND hp;
hp = GetWindow (hWnd, GW_OWNER);
PostMessage (hp, msg, wParam, lParam);
return 0;
}
case WM_CLOSE:
{
//
// cleanup the index pointer
//
index = (int *)GetWindowLong (hWnd, GWL_USERDATA);
GlobalFree (index);

return DefWindowProc (hWnd, msg, wParam, lParam);
}
default:
return DefWindowProc (hWnd, msg, wParam, lParam);

}
}

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

FUNCTION: EditProc

PURPOSE: subclass the edit control to handle carriage returns

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

LONG APIENTRY EditProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (WM_CHAR == msg && (TCHAR)wParam == 0xD)
{
PostMessage (GetParent (hWnd), WM_COMMAND, (WPARAM)IDC_RUNMEBTN, 0);
return 0;
}
//
// call the default edit control procedure
//
return CallWindowProc ( (WNDPROC)GetWindowLong (hWnd, GWL_USERDATA),
hWnd, msg, wParam, lParam);
}