 /*

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/****************************************************************************
 28/9/99 - mirul
  - v1.0 creation
 12/10/99 - mirul
  - v 1.1
  - Fixed RemoveBangCommand for !WinHome
  - Fixed shaded window, when manually extended down, it is considered unshaded.
  - Changed shade/unshade to work on maximized windows.
  - Added !WinSendToBottom.
  - Added !WinUnshadeAll
  - Added !WinMove <x y or centered>
  - Changed !WinHome to be equivalent to !WinMove
  - Added !WinMaximize, !WinMinimize, !WinRestore
  - Added WinDialogsPosition <x y or centered>
 20/10/99 - mirul
  - v 1.1a
  - Fixed recycle crash, where the first loaded module will crash, then winctrl.
 26/10/99 - mirul
  - v 1.2
  - Added !WinAlwaysOnTop <on/off>
    - Outlook refuses to comply.
  - Changed method of tracking shaded windows.
    - used API SetProp, GetProp & RemoveProp for storage/retrieval
      of window height. Removed localWinlist. Shaded windows is determined
      by GetProp != NULL.
    - WinUnshadeAll broken.
  - Changed !WinDeltaY to work on shaded windows.
  - Changed !WinSendToBottom so that after sending, the top app window (if any) on
    the current desktop is activated.
  - Changed !WinShade so that when no app is active, nothing happens.
 18/4/00 - mirul
  - v 1.3
  - Fixed so that we work in the new OO architecture of ls. Register and handle
    revised shell messages LM_WINDOWCREATED and LM_WINDOWACTIVATED.
 17/9/00 - mirul
  - v 1.4
  - Added var WinctrlHotkeyOnly, which makes it use GetForegroundWindow instead of
    relying on LM_WINDOWACTIVATED msgs.
****************************************************************************/

#define __WIN32_LEAN_AND_MEAN__

#include <windows.h>
#include <time.h>
#include <stdio.h>
#include <mmsystem.h>
#include "winctrl.h"
#include "lsapi.h"
#include "litestep.h"
#include "windows98.h"
#include "lsapi2k.h"

const char rcsRevision[] = "$Revision: 1.4 $"; // Our Version
const char rcsId[] = "$Id: winctrl.c,v 1.4 2000/9/17 01:20:00 mirul Exp $"; // The Full RCS ID.

char szAppName[] = "winctrlWindow";

char WinShadeSound[256];
BOOL bShadeSoundSet;
char WinUnshadeSound[256];
BOOL bUnshadeSoundSet;
char WinGrowDownSound[256];
BOOL bGrowDownSoundSet;
char WinDialogsPosition[256];

BOOL HotkeyOnly;

HWND hMainWnd = NULL; // main window handle
HWND hActiveWin = NULL;
HWND parent = NULL;
HWND deskTop = NULL;
HINSTANCE dll = NULL;
LONG DesktopOldWndProc;

COLORREF DefaultColors[25];
/*
// Debug
int debugheight;
// End Debug
*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK DesktopWndHookProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

// -------------------------------------------------------------------------------------------------------
// DEBUG STUFF
/*
void Report(LPCSTR strRep)
{
	char dt[9], tm[9];
	char ln[1024];
	DWORD dwBytes = 0;

	HANDLE hFile = CreateFile("C:\\LiteStep\\error.log", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile) {
		_strdate(dt);
		_strtime(tm);
		wsprintf(ln, "%s - %s : %s\n", dt, tm, strRep);
		SetFilePointer(hFile, 0, 0, FILE_END);
		WriteFile(hFile, ln, strlen(ln), &dwBytes, NULL);
		CloseHandle(hFile);
	}
}
*/
HWND ActiveWindow(void)
{
	if (HotkeyOnly)
		return GetForegroundWindow();
	else
		return hActiveWin;
}

// -------------------------------------------------------------------------------------------------------
// Install a hook proc to the desktop
// -------------------------------------------------------------------------------------------------------
void AddDesktopHook(void)
{
  DesktopOldWndProc = GetWindowLong(deskTop, GWL_WNDPROC);
  SetWindowLong(deskTop, GWL_WNDPROC, (LONG) DesktopWndHookProc);
}

// -------------------------------------------------------------------------------------------------------
// Remove the desktop hook proc
// -------------------------------------------------------------------------------------------------------
void DeleteDesktopHook(void)
{
  SetWindowLong(deskTop, GWL_WNDPROC, DesktopOldWndProc);
}

// -------------------------------------------------------------------------------------------------------
// Returns true if a window is on current desktop
// -------------------------------------------------------------------------------------------------------
BOOL IsOnCurrentDesktop(HWND hWnd, HRGN desktopRgn)
{
  RECT r;

	GetWindowRect(hWnd, &r);
  return (RectInRegion(desktopRgn,&r));
}

BOOL IsAppWindow(HWND hWNd)
{
  BOOL bResult = TRUE;
  LONG nStyle;
  HWND hOwner;
  HWND hParent;
  /*if it is a WS_CHILD or not WS_VISIBLE, fail it*/
  nStyle = GetWindowLong(hWNd, GWL_STYLE);
  if((nStyle & WS_CHILD) || !(nStyle & WS_VISIBLE))
  bResult = FALSE;
  /*if the window is a WS_EX_TOOLWINDOW fail it*/
  nStyle = GetWindowLong(hWNd, GWL_EXSTYLE);
  if(nStyle & WS_EX_TOOLWINDOW)   bResult = FALSE;
  /*If this has an owner, then only accept this window if the partent is not accepted*/
  hOwner = GetWindow(hWNd, GW_OWNER);
  if(hOwner && bResult)
  bResult = !IsAppWindow(hOwner);
  /*if it has a parent, fail*/
  hParent = GetParent(hWNd);
  if(hParent) bResult = FALSE;
  if(GetWindowLong(hWNd, GWL_USERDATA) == 0x49474541) bResult = FALSE;
  return bResult;
}

// -------------------------------------------------------------------------------------------------------
// Returns true if a window has our "WCNormalHeight" property.
// -------------------------------------------------------------------------------------------------------
int IsFlaggedShaded(HWND hwnd)
{
  return (GetProp(hwnd,"WCNormalHeight") != NULL);
}

// -------------------------------------------------------------------------------------------------------
// NextToken
// -------------------------------------------------------------------------------------------------------
LPCTSTR NextToken( LPCTSTR s, LPTSTR d, UINT m )
{
	while( *s && *s <= 32 )
		s++;

	if( *s == 34 )
	{
		s++;

		while( *s && *s != 34 && --m )
			*d++ = *s++;

		s++;
	}
	else
	{
		while( *s && *s > 32 && --m )
			*d++ = *s++;
	}

	*d = 0;
	return s;
}

// -------------------------------------------------------------------------------------------------------
// ParseInteger
// -------------------------------------------------------------------------------------------------------
int ParseInteger( LPCTSTR pszSource )
{
	int nValue = 0;
	BOOL fNegative = FALSE;

	while( *pszSource && *pszSource <= 32 )
		pszSource++;

	if( *pszSource && *pszSource == '-' )
	{
		fNegative = TRUE;
		pszSource++;
	}

	while( *pszSource && *pszSource >= '0' && *pszSource <= '9' )
		nValue = (nValue * 10) + (*pszSource++ - '0');

	return fNegative ? -nValue : nValue;
}

// -------------------------------------------------------------------------------------------------------
// Test to determine whether window is exluded
// -------------------------------------------------------------------------------------------------------
BOOL IsExcluded(HWND hTest)
{
	if (IsIconic(hTest) || IsZoomed(hTest) || (hTest == NULL)  || (hTest == deskTop))
		return TRUE;
	else
		return FALSE;
}

// -------------------------------------------------------------------------------------------------------
// Return integer
// -------------------------------------------------------------------------------------------------------
int BangOption(char * pszArgs, char * pszOpt1, char * pszOpt2)
{
  if ((pszArgs == NULL)||(pszArgs == '\0')) return 0;

	if (strstr(strlwr(pszArgs),pszOpt1))
		return 1;
	else if (strstr(strlwr(pszArgs),pszOpt2))
		return 2;
	else
		return 0;
}

// -------------------------------------------------------------------------------------------------------
// Shrink window to titlebar, remembering original height
// -------------------------------------------------------------------------------------------------------
void WinShade(HWND MoveHwnd, int ListIndex, BOOL Maximized)
{
	int i = ListIndex;
	RECT r;

	SetForegroundWindow(MoveHwnd);

  if (i != -1) return;

	GetWindowRect(MoveHwnd, &r);
  if (!Maximized) {
    SetProp(MoveHwnd,"WCNormalHeight",(HANDLE)(r.bottom - r.top));
  }

	SetWindowPos(MoveHwnd, HWND_TOP,
                r.left, r.top,
                r.right - r.left,
                GetSystemMetrics(SM_CYMIN),
                SWP_NOOWNERZORDER | SWP_NOSENDCHANGING );
	if (bShadeSoundSet) sndPlaySound(WinShadeSound, SND_SYNC);

/*
	// Debug
	debugheight = height;
  // End Debug
*/
	return;
}

// -------------------------------------------------------------------------------------------------------
// Restore window to original height
// -------------------------------------------------------------------------------------------------------
void WinUnshade(HWND MoveHwnd, int ListIndex, BOOL Maximized)
{
	int i = ListIndex;
	RECT r;
	int height;

	SetForegroundWindow(MoveHwnd);

  if (i == -1 && !Maximized) return;

	if (Maximized) {
    // We are dealing with a maximized window
    RECT WorkArea;
    SystemParametersInfo(SPI_GETWORKAREA,0,(PVOID)&WorkArea,SPIF_SENDCHANGE);
    height = WorkArea.bottom - WorkArea.top;
	} else {
    height = (long)GetProp(MoveHwnd,"WCNormalHeight");
    RemoveProp(MoveHwnd,"WCNormalHeight");
  }

	GetWindowRect(MoveHwnd, &r);
	MoveWindow(MoveHwnd, r.left, r.top, r.right-r.left,
	           height, TRUE);
	InvalidateRect(MoveHwnd,NULL,FALSE);
	if (bUnshadeSoundSet) sndPlaySound(WinUnshadeSound, SND_SYNC);

}

// -------------------------------------------------------------------------------------------------------
// Set system parameters
// -------------------------------------------------------------------------------------------------------
void SetSystemParameters(UINT uiActionGET, UINT uiActionSET, char * pszArgs)
{
	BOOL bTemp;
	BOOL pbTemp;

  switch (BangOption( pszArgs, "on", "off"))
  {
    case 0:
  	default:
  	{
  		SystemParametersInfo(uiActionGET, (UINT)NULL, &pbTemp, (UINT)NULL);
  		bTemp = !pbTemp;
  		break;
  	}
  	case 1:
  		bTemp = TRUE;
  		break;
  	case 2:
  		bTemp = FALSE;
  }

	// Don't ask, but this way it works. Ignore the warnings.
	SystemParametersInfo(uiActionSET, (UINT)NULL , (void *)bTemp, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}

// -------------------------------------------------------------------------------------------------------
// Grow window to maximum height or just downwards
// -------------------------------------------------------------------------------------------------------
BOOL WinGrowMaxHeight(HWND MoveHwnd, BOOL bMax)
{
//	int i;
	RECT r, WorkArea;

	if (IsExcluded(MoveHwnd)) return FALSE;

	GetWindowRect(MoveHwnd, &r);
	SystemParametersInfo(SPI_GETWORKAREA,0,(PVOID)&WorkArea,SPIF_SENDCHANGE);
	MoveWindow
		(MoveHwnd,
		 r.left,
		 bMax ? 0 : r.top,
		 r.right-r.left,
		 bMax ? WorkArea.bottom-WorkArea.top : WorkArea.bottom-r.top,
		 TRUE);
	InvalidateRect(MoveHwnd,NULL,FALSE);
	SetForegroundWindow(MoveHwnd);

	return TRUE;
}

// -------------------------------------------------------------------------------------------------------
// Bang: Grow window to maximum height
// -------------------------------------------------------------------------------------------------------
void BangWinMaxHeight(void)
{
	WinGrowMaxHeight(ActiveWindow(), TRUE);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Grow bottom of window to bottom of screen
// -------------------------------------------------------------------------------------------------------
void BangWinGrowDown(void)
{
	if (WinGrowMaxHeight(ActiveWindow(), FALSE))
		if (bGrowDownSoundSet) sndPlaySound(WinGrowDownSound, SND_SYNC);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Move window to position in pszArgs
// -------------------------------------------------------------------------------------------------------
void BangWinMove(HWND hCaller, char * pszArgs)
{
	HWND MoveHwnd = ActiveWindow();
	RECT r, WorkArea;
	LPCTSTR p = pszArgs;
	TCHAR szValue[16];
	int nY, nX;

	if (IsExcluded(MoveHwnd)) return;

	GetWindowRect(MoveHwnd, &r);
	if ((pszArgs == NULL) || (*pszArgs == '\0')) {
    PostMessage( MoveHwnd, WM_SYSCOMMAND, SC_MOVE, 0 );
    return;
	} else if (strstr(strlwr(pszArgs),"centered")) { // center window on desktop
    SystemParametersInfo(SPI_GETWORKAREA,0,(PVOID)&WorkArea,SPIF_SENDCHANGE);
    nX = (WorkArea.right - WorkArea.left - r.right + r.left)/2;
    nY = (WorkArea.bottom - WorkArea.top - r.bottom + r.top)/2;
  } else { // move window to predefined position
  	p = NextToken( p, szValue, 16 );
  	nX = ParseInteger( szValue );
  	nX = (nX < 0) ? (GetSystemMetrics(SM_CXSCREEN) + nX + 1) : nX;

  	p = NextToken( p, szValue, 16 );
  	nY = ParseInteger( szValue );
  	nY = (nY < 0) ? (GetSystemMetrics(SM_CYSCREEN) + nY + 1) : nY;
  }
	MoveWindow(MoveHwnd, nX, nY, r.right-r.left, r.bottom-r.top, TRUE);
	SetForegroundWindow(MoveHwnd);
	return;
}

// -------------------------------------------------------------------------------------------------------
// Bang: Grow window to maximum width
// -------------------------------------------------------------------------------------------------------
void BangWinMaxWidth(void)
{
	HWND MoveHwnd = ActiveWindow();
	RECT r, WorkArea;

	if (IsExcluded(MoveHwnd)) return;

	GetWindowRect(MoveHwnd, &r);
	SystemParametersInfo(SPI_GETWORKAREA,0,(PVOID)&WorkArea,SPIF_SENDCHANGE);
	MoveWindow(MoveHwnd, 0, r.top, WorkArea.right-WorkArea.left, r.bottom-r.top, TRUE);
	GetWindowRect(MoveHwnd, &r);
	SetForegroundWindow(MoveHwnd);
	return;
}

// -------------------------------------------------------------------------------------------------------
// Bang: Grow window to the right by value in pszArgs
// -------------------------------------------------------------------------------------------------------
void BangWinDeltaX(HWND hCaller, LPCTSTR pszArgs)
{
	HWND MoveHwnd = ActiveWindow();
	RECT r;
	LPCTSTR p = pszArgs;
	TCHAR szValue[16];
	int nX;

	if (IsExcluded(MoveHwnd)) return;

	if ( pszArgs != NULL ) {
    p = NextToken( p, szValue, 16 );
    nX = ParseInteger( szValue );
    nX = (nX == 0) ? 1 : nX;
  } else
    nX = 1;

	GetWindowRect(MoveHwnd, &r);
	MoveWindow(MoveHwnd, r.left, r.top, r.right-r.left+nX, r.bottom-r.top, TRUE);
	InvalidateRect(MoveHwnd,NULL,FALSE);
	SetForegroundWindow(MoveHwnd);
	return;
}

// -------------------------------------------------------------------------------------------------------
// Bang: Grow window to the bottom by value in pszArgs
// -------------------------------------------------------------------------------------------------------
void BangWinDeltaY(HWND hCaller, LPCTSTR pszArgs)
{
//	int i;
	HWND MoveHwnd = ActiveWindow();
	RECT r;
	LPCTSTR p = pszArgs;
	TCHAR szValue[16];
	int nY;
//	BOOL IsManuallyUnshaded;

	if (IsExcluded(MoveHwnd)) return;
/*
  GetWindowRect(MoveHwnd, &r);
  IsManuallyUnshaded =	(r.bottom - r.top > GetSystemMetrics(SM_CYMIN));
//	i = IsFlaggedShaded(MoveHwnd);
	if (IsFlaggedShaded(MoveHwnd) & !IsManuallyUnshaded) {
		SetForegroundWindow(MoveHwnd);
		return;
	}
*/
	if ( pszArgs != NULL ) {
    p = NextToken( p, szValue, 16 );
    nY = ParseInteger( szValue );
    nY = (nY == 0) ? 1 : nY;
  } else
    nY = 1;

	GetWindowRect(MoveHwnd, &r);
	MoveWindow(MoveHwnd, r.left, r.top, r.right-r.left, r.bottom-r.top+nY, TRUE);
	InvalidateRect(MoveHwnd,NULL,FALSE);
	SetForegroundWindow(MoveHwnd);
	return;
}

// -------------------------------------------------------------------------------------------------------
// Bang: Drag full window "on", "off" or toggle
// -------------------------------------------------------------------------------------------------------
void BangWinFullDrag(HWND hCaller, char * pszArgs)
{
	BOOL bTemp;

  switch (BangOption( pszArgs, "on", "off" ))
  {
  	case 0:
  	default:
    {
  		SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, (UINT)NULL, &bTemp, (UINT)NULL);
  		bTemp = !bTemp;
  		break;
  	}
  	case 1:
  		bTemp = TRUE;
  		break;
  	case 2:
  		bTemp = FALSE;
	}

	SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, bTemp, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Autoraise tracked window "on" or "off"
// -------------------------------------------------------------------------------------------------------
void BangWinAutoRaise(HWND hCaller, char * pszArgs)
{
  switch (BangOption( pszArgs, "on", "off" ))
  {
    case 1:
    case 2:
    {
      SetSystemParameters(SPI_GETACTIVEWINDOWTRACKING, SPI_SETACTIVEWINDOWTRACKING, pszArgs);
      SetSystemParameters(SPI_GETACTIVEWNDTRKZORDER, SPI_SETACTIVEWNDTRKZORDER, pszArgs);
    }
  }
}

// -------------------------------------------------------------------------------------------------------
// Bang: Animate combobox "on", "off" or toggle
// -------------------------------------------------------------------------------------------------------
void BangWinComboboxAnimate(HWND hCaller, char * pszArgs)
{
	SetSystemParameters(SPI_GETCOMBOBOXANIMATION, SPI_SETCOMBOBOXANIMATION, pszArgs);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Animate menu "on", "off" or toggle
// -------------------------------------------------------------------------------------------------------
void BangWinMenuAnimate(HWND hCaller, char * pszArgs)
{
	SetSystemParameters(SPI_GETMENUANIMATION, SPI_SETMENUANIMATION, pszArgs);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Active window tracking "on", "off" or "toggle"
// -------------------------------------------------------------------------------------------------------
void BangWinActiveTrack(HWND hCaller, char * pszArgs)
{
	SetSystemParameters(SPI_GETACTIVEWINDOWTRACKING, SPI_SETACTIVEWINDOWTRACKING, pszArgs);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Window shade "on", "off" or toggle
// -------------------------------------------------------------------------------------------------------
void BangWinShade(HWND hCaller, char * pszArgs)
{
//  char msg[MAX_PATH];
	HWND hTarget = ActiveWindow();
	int i, TitleBarHeight;
	RECT r;
	BOOL IsUnshaded;
	BOOL IsMaximized = IsZoomed(hTarget);

	if (IsExcluded(hTarget) && !IsMaximized) {
    return;
  }

  TitleBarHeight = GetSystemMetrics(SM_CYMIN);
  GetWindowRect(hTarget, &r);
  IsUnshaded =	(r.bottom - r.top > TitleBarHeight);

	i = IsFlaggedShaded(hTarget);

  switch (BangOption( pszArgs, "on", "off" ))
  {
    case 0:
    default:  // no argument supplied
    {
      if (IsMaximized) {
        if (IsUnshaded)
          WinShade(hTarget, -1, IsMaximized);
        else
          WinUnshade(hTarget, -1, IsMaximized);
      } else {
  		  if (i != -1) {  // found in our list
    			if (IsUnshaded) {  // window has been unshaded, so do shade
            RemoveProp(hTarget,"WCNormalHeight");
            WinShade(hTarget, -1, IsMaximized);
          } else {  // unshade the window
            WinUnshade(hTarget, i, IsMaximized);
          }
  		  } else {
  		      WinShade(hTarget, i, IsMaximized);
        }
      }
      break;
  	}

  	case 1:  // !WinShade on
  	{
      if (IsMaximized) {
        if (IsUnshaded)
          WinShade(hTarget, -1, IsMaximized);
      } else {
    		if (i == -1) {}
    		else {  // found in our list
    			if (IsUnshaded) {  // window has been unshaded, so do shade
            RemoveProp(hTarget,"WCNormalHeight");
            i = -1;
          } else break;
        }
        WinShade(hTarget, i, IsMaximized);
      }
      break;
  	}

    case 2: // !WinShade off
    {
      if (IsMaximized) {
        if (!IsUnshaded)
          WinUnshade(hTarget, -1, IsMaximized);
      } else {
    		if (i != -1) {  // found in our list
    			if (IsUnshaded)   // window has been unshaded, so remove window prop
            RemoveProp(hTarget,"WCNormalHeight");
          else   // do unshade
            WinUnshade(hTarget, i, IsMaximized);
    		}
      }
  	}

  } // End switch
}

// -------------------------------------------------------------------------------------------------------
// Bang: Window unshade all
// -------------------------------------------------------------------------------------------------------
void BangWinUnshadeAll(HWND hCaller, char * pszArgs)
{
  HWND hwndT1, hwndT2;

	hwndT1 = GetTopWindow(NULL);
	while (hwndT1 != NULL)
  {
    hwndT2 = GetNextWindow(hwndT1,GW_HWNDNEXT);
    if ( IsAppWindow(hwndT1) )
      BangWinShade(NULL,"off");
    hwndT1 = hwndT2;
  }
}
/*
// -------------------------------------------------------------------------------------------------------
// Bang: Revert to default window colors
// -------------------------------------------------------------------------------------------------------
void BangWinDefaultColors(HWND hCaller, char * pszArgs)
{
  const int lpaElements[25] = {0,1,2,3,4,5,6,7,8,9,
                               10,11,12,13,14,15,16,17,18,19,
                               20,21,22,23,24};
  SetSysColors( 25, lpaElements, DefaultColors );
}
*/
// -------------------------------------------------------------------------------------------------------
// Bang: Send to bottom
// -------------------------------------------------------------------------------------------------------
void BangWinSendToBottom(HWND hCaller, char * pszArgs)
{
	HWND hTarget = ActiveWindow();
	HWND hwndBelow;
	HRGN desktopRgn;

	if (IsExcluded(hTarget) && !IsZoomed(hTarget)) return;

	desktopRgn = CreateRectRgn(0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
	hwndBelow = GetNextWindow(hTarget,GW_HWNDNEXT);
	while (hwndBelow != NULL)
	{
    	if ( IsAppWindow(hwndBelow) && IsOnCurrentDesktop(hwndBelow,desktopRgn) )
    		break;
    	hwndBelow = GetNextWindow(hwndBelow,GW_HWNDNEXT);
	}
	DeleteObject(desktopRgn);

	// Send current window to bottom
	SetWindowPos( hTarget, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
	SetForegroundWindow( deskTop );
	// Activate window below, if any
	if (hwndBelow != NULL)
    SetForegroundWindow(hwndBelow);
}

// -------------------------------------------------------------------------------------------------------
// Bang: Always on top
// -------------------------------------------------------------------------------------------------------
void BangWinAlwaysOnTop(HWND hCaller, char * pszArgs)
{
	HWND hTarget = ActiveWindow();
	BOOL bActivate;

	if (IsExcluded(hTarget) && !IsZoomed(hTarget)) return;
	switch (BangOption( pszArgs, "on", "off" )) {
  	case 0:
  	default:
    {
  		bActivate = !((GetWindowLong(hTarget, GWL_EXSTYLE) & WS_EX_TOPMOST) == WS_EX_TOPMOST );
  		break;
  	}
    case 1: // on
     	bActivate = TRUE;
      	break;
    case 2: // off
    	bActivate = FALSE;
  }
  SetWindowPos( hTarget, bActivate ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

// -------------------------------------------------------------------------------------------------------
// Bang: minimize window
// -------------------------------------------------------------------------------------------------------
void BangWinMinimize(HWND hCaller, char * pszArgs)
{
	HWND hTarget = ActiveWindow();

	if (IsExcluded(hTarget) && !IsZoomed(hTarget)) return;
	PostMessage( hTarget, WM_SYSCOMMAND, SC_MINIMIZE, 0 );
}

// -------------------------------------------------------------------------------------------------------
// Bang: maximize window
// -------------------------------------------------------------------------------------------------------
void BangWinMaximize(HWND hCaller, char * pszArgs)
{
	HWND hTarget = ActiveWindow();

	if (IsExcluded(hTarget)) return;
	PostMessage( hTarget, WM_SYSCOMMAND, SC_MAXIMIZE, 0 );
}

// -------------------------------------------------------------------------------------------------------
// Bang: restore window
// -------------------------------------------------------------------------------------------------------
void BangWinRestore(HWND hCaller, char * pszArgs)
{
	HWND hTarget = ActiveWindow();

	if (IsExcluded(hTarget) && !IsZoomed(hTarget)) return;
	PostMessage( hTarget, WM_SYSCOMMAND, SC_RESTORE, 0 );
}

// -------------------------------------------------------------------------------------------------------
// Register bang commands
// -------------------------------------------------------------------------------------------------------
void RegisterBangCommands(void)
{

	AddBangCommand("!WinMove", BangWinMove);
	AddBangCommand("!WinMaximize", BangWinMaximize);
	AddBangCommand("!WinMinimize", BangWinMinimize);
	AddBangCommand("!WinRestore", BangWinRestore);
	AddBangCommand("!WinHome", BangWinMove);
	AddBangCommand("!WinMaxHeight", BangWinMaxHeight);
	AddBangCommand("!WinMaxWidth", BangWinMaxWidth);
	AddBangCommand("!WinGrowDown", BangWinGrowDown);
	AddBangCommand("!WinFullDrag", BangWinFullDrag);
	AddBangCommand("!WinComboboxAnimate", BangWinComboboxAnimate);
	AddBangCommand("!WinMenuAnimate", BangWinMenuAnimate);
	AddBangCommand("!WinActiveTrack", BangWinActiveTrack);
	AddBangCommand("!WinAutoRaise", BangWinAutoRaise);

	AddBangCommand("!WinDeltaX", BangWinDeltaX);
	AddBangCommand("!WinDeltaY", BangWinDeltaY);
	AddBangCommand("!WinShade", BangWinShade);
	AddBangCommand("!WinUnshadeAll", BangWinUnshadeAll);
	AddBangCommand("!WinSendToBottom", BangWinSendToBottom);
	AddBangCommand("!WinAlwaysOnTop", BangWinAlwaysOnTop);
}

// -------------------------------------------------------------------------------------------------------
// Unregister bang commands
// -------------------------------------------------------------------------------------------------------
void UnregisterBangCommands(void)
{

	RemoveBangCommand("!WinMove");
	RemoveBangCommand("!WinMaximize");
	RemoveBangCommand("!WinMinimize");
	RemoveBangCommand("!WinRestore");
	RemoveBangCommand("!WinHome");
	RemoveBangCommand("!WinMaxHeight");
	RemoveBangCommand("!WinMaxWidth");
	RemoveBangCommand("!WinGrowDown");
	RemoveBangCommand("!WinFullDrag");
	RemoveBangCommand("!WinComboboxAnimate");
	RemoveBangCommand("!WinMenuAnimate");
	RemoveBangCommand("!WinActiveTrack");
	RemoveBangCommand("!WinAutoRaise");

	RemoveBangCommand("!WinDeltaX");
	RemoveBangCommand("!WinDeltaY");
	RemoveBangCommand("!WinShade");
	RemoveBangCommand("!WinUnshadeAll");
	RemoveBangCommand("!WinSendToBottom");
	RemoveBangCommand("!WinAlwaysOnTop");
}

// -------------------------------------------------------------------------------------------------------
// Standard LS module initialisation & quit routines
// -------------------------------------------------------------------------------------------------------
int initModule(HWND ParentWnd, HINSTANCE dllInst, wharfDataType* wd)
{
	return initModuleEx (ParentWnd, dllInst, wd->lsPath);
}

int initModuleEx (HWND ParentWnd, HINSTANCE dllInst, LPCSTR szPath)
{
	int Msgs[10];

  // Initialize variables
	dll = dllInst;
	parent = ParentWnd;

  // Register window class
	deskTop = FindWindow("DesktopBackgroundClass", NULL);
	if (!deskTop)
		deskTop = GetDesktopWindow();
    {
		WNDCLASS wc;

		memset(&wc, 0, sizeof(wc));
		wc.lpfnWndProc = WndProc;
		wc.hInstance = dllInst;
		wc.lpszClassName = szAppName;
		wc.style = 0;

		if (!RegisterClass(&wc))
		{
			MessageBox(parent, "Error registering window class", szAppName, MB_OK);
			PostQuitMessage(0);
			return 1;
		}
	}

  // Create window
	hMainWnd = CreateWindowEx(
		WS_EX_TOOLWINDOW,
		szAppName,
		"sweep",
		WS_CHILD,
		0,
		0,
		0,
		0,
		parent,
		NULL,
		dllInst,
		NULL);

	if (!hMainWnd)
	{
		MessageBox(NULL, "Error creating window", szAppName, MB_OK);
		PostQuitMessage(0);
		return 1;
	}

	// See whether to use API or LM_WINDOWACTIVATED
	HotkeyOnly = GetRCBool("WinctrlHotkeyOnly",TRUE);

	// Setup sound preferences
	bShadeSoundSet = GetRCString("WinShadeSound", WinShadeSound, "Minimize", 256);
	bUnshadeSoundSet = GetRCString("WinUnshadeSound", WinUnshadeSound, "RestoreDown", 256);
	bGrowDownSoundSet = GetRCString("WinGrowDownSound", WinGrowDownSound, "RestoreDown", 256);

	// Setup dialog positions
	GetRCString("WinDialogsPosition", WinDialogsPosition, "\0", 256);

  // Setup color preferences from step.rc
  {
    COLORREF Color;
    int ElementID[25];
    COLORREF ElementColor[25];
    int ColorCount = 0;

    Color = GetRCColor("WinMDIBackgroundColor",0xFF00FF);
    if ( Color != 0xFF00FF )
    {
      ElementID[ColorCount] = COLOR_APPWORKSPACE;
      ElementColor[ColorCount] = Color;
      ColorCount++;
    }
    Color = GetRCColor("WinScrollbarBackgroundColor",0xFF00FF);
    if ( Color != 0xFF00FF )
    {
      ElementID[ColorCount] = COLOR_SCROLLBAR;
      ElementColor[ColorCount] = Color;
      ColorCount++;
    }
    Color = GetRCColor("WinTooltipBackgroundColor",0xFF00FF);
    if ( Color != 0xFF00FF )
    {
      ElementID[ColorCount] = COLOR_INFOBK;
      ElementColor[ColorCount] = Color;
      ColorCount++;
    }
    Color = GetRCColor("WinTooltipTextColor",0xFF00FF);
    if ( Color != 0xFF00FF )
    {
      ElementID[ColorCount] = COLOR_INFOTEXT;
      ElementColor[ColorCount] = Color;
      ColorCount++;
    }
    if ( ColorCount != 0 )
      SetSysColors( ColorCount, ElementID, ElementColor );
  }

  // Register messages
	Msgs[0] = LM_GETREVID;
	Msgs[1] = LM_WINDOWCREATED;
	Msgs[2] = LM_WINDOWACTIVATED;
	Msgs[3] = 0;
	SendMessage(parent, LM_REGISTERMESSAGE, (WPARAM) hMainWnd, (LPARAM) Msgs);

	// Register bangs
	RegisterBangCommands();

	// Hook the desktop message loop.
	// We're hoping to receive LM_ACTIVEWIN and LM_ADDWINDOW.
	if (!HotkeyOnly)
		AddDesktopHook();

	return 0;
}

void quitModule(HINSTANCE dllInst)
{
	int Msgs[10];

	Msgs[0] = LM_GETREVID;
	Msgs[1] = LM_WINDOWCREATED;
	Msgs[2] = LM_WINDOWACTIVATED;
	Msgs[3] = 0;
	SendMessage(parent, LM_UNREGISTERMESSAGE, (WPARAM) hMainWnd, (LPARAM) Msgs);
	UnregisterBangCommands();
	DestroyWindow(hMainWnd);
	if (!HotkeyOnly)
		DeleteDesktopHook();                       // Unhook from desktop dll
	UnregisterClass(szAppName, dllInst);
}

// -------------------------------------------------------------------------------------------------------
// Window message handler
// -------------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case LM_GETREVID:
		{
			char *buf = (char *) lParam;

			if (wParam == 0)
			{
				strcpy(buf, "winctrl.dll: ");
				strcat(buf, &rcsRevision[11]);
				buf[strlen(buf)-1] = '\0';
			}
			else if (wParam == 1)
			{
				strcpy(buf, &rcsId[1]);
				buf[strlen(buf)-1] = '\0';
			} else
			{
				strcpy(buf, "");
			}
			return strlen(buf);
		}
	case LM_WINDOWCREATED:
		{
			if (WinDialogsPosition[0] == '\0') break;
			if ( GetClassLong( (HWND)wParam, GCW_ATOM ) == 0x8002 )
			{
				hActiveWin = (HWND)wParam;
				BangWinMove(NULL, WinDialogsPosition);
			}
			break;
		}
	case LM_WINDOWACTIVATED:
		{
			hActiveWin = (HWND)wParam;
			break;
		}
	}
	return DefWindowProc(hwnd,message,wParam,lParam);
}

// -------------------------------------------------------------------------------------------------------
// Desktop hook message handler
// -------------------------------------------------------------------------------------------------------
LRESULT CALLBACK DesktopWndHookProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case LM_ADDWINDOW:
		{
			if (WinDialogsPosition[0] == '\0') break;
			if ( GetClassLong( (HWND)wParam, GCW_ATOM ) == 0x8002 )
			{
				hActiveWin = (HWND)wParam;
				BangWinMove(NULL, WinDialogsPosition);
			}
			break;
		}
	case LM_ACTIVEWIN:
		{
			hActiveWin = (HWND)wParam;
			break;
		}
	case WM_MOUSEACTIVATE:
	case WM_ACTIVATE:
		{
			hActiveWin = NULL;
			break;
		}
  }
  return CallWindowProc((WNDPROC) DesktopOldWndProc, hwnd, message, wParam, lParam);
}

// -------------------------------------------------------------------------------------------------------
// Dll entry point
// -------------------------------------------------------------------------------------------------------
extern int APIENTRY DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID pvReserved )
{
	return 1;
}
