Click here to Skip to main content
15,885,919 members
Articles / Programming Languages / C++

Holistic Screensavers: Beginning to End

Rate me:
Please Sign up or sign in to vote.
4.91/5 (39 votes)
14 Jun 2003Public Domain23 min read 166.2K   2.7K   96  
Best practices for screensaver coding/distribution.
// Minimal Windows Screen Saver
//
// This particular screen saver takes a snapshot of the desktop upon startup,
// keeps the snapshot as a back-buffer, and draws blobs on it. There's a
// configuration option to change the size of the blobs.
//
// The basic things to change are marked TODO. They are: load/save the global
// settings, create an animation for the saver, make its Configuration dialog,
// and set its name in the Stringtable resource.
// But the source code is very short and clear, so the programmer should feel
// confident to modify all of it.
//

#pragma warning( disable: 4127 4800 4702 )
#include <string>
#include <vector>
#include <math.h>
#include <windows.h>
#include <commctrl.h>
#include <regstr.h>
#include <stdlib.h>
#include <tchar.h>
typedef std::basic_string<TCHAR> tstring;
using namespace std;



//
// TODO: set the saver's name and URL in the STRINGTABLE resource.
//



// If you want debugging output, set this to "OutputDebugString" or a filename. The second line controls the saver's "friendliness"
const tstring DebugFile=_T("OutputDebugString");
const bool SCRDEBUG = (DebugFile!=_T(""));
//
// These global variables are loaded at the start of WinMain
BOOL  MuteSound;
DWORD MouseThreshold;  // In pixels
DWORD PasswordDelay;   // In seconds. Doesn't apply to NT/XP/Win2k.
// also these, which are used by the General properties dialog
DWORD PasswordDelayIndex;  // 0=seconds, 1=minutes. Purely a visual thing.
DWORD MouseThresholdIndex; // 0=high, 1=normal, 2=low, 3=ignore. Purely visual
TCHAR Corners[5];          // "-YN-" or something similar
BOOL  HotServices;         // whether they're present or not
// and these are created when the dialog/saver starts
POINT InitCursorPos;
DWORD InitTime;        // in ms
bool  IsDialogActive;
bool  ReallyClose;     // for NT, so we know if a WM_CLOSE came from us or it.
// Some other minor global variables and prototypes
HINSTANCE hInstance=0;
HICON hiconsm=0,hiconbg=0;
HBITMAP hbmmonitor=0;  // bitmap for the monitor class
tstring SaverName;     // this is retrieved at the start of WinMain from String Resource 1
vector<RECT> monitors; // the rectangles of each monitor (smSaver) or of the preview window (smPreview)
struct TSaverWindow;
const UINT SCRM_GETMONITORAREA=WM_APP; // gets the client rectangle. 0=all, 1=topleft, 2=topright, 3=botright, 4=botleft corner
inline void Debug(const tstring s);
tstring GetLastErrorString();
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url);
void   RegSave(const tstring name,int val);
void   RegSave(const tstring name,bool val);
void   RegSave(const tstring name,tstring val);
int    RegLoad(const tstring name,int def);
bool   RegLoad(const tstring name,bool def);
tstring RegLoad(const tstring name,tstring def);

//
// IMPORTANT GLOBAL VARIABLES:
enum TScrMode {smNone,smConfig,smPassword,smPreview,smSaver,smInstall,smUninstall} ScrMode=smNone;
vector<TSaverWindow*> SaverWindow;   // the saver windows, one per monitor. In preview mode there's just one.

// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------


// These are the saver's global settings
int refcount=0;      // we use a global refcount so that settings aren't loaded more than necessary
bool BigBlobs=false; // this is our setting that we're storing in the registry

void CommonInit()
{ refcount++; if (refcount!=1) return;
  //
  // TODO: saver initialization - load settings from registry &c.
  // Some things are naturally global, and shared between configuration
  // dialog and saver-preview, or between multiple monitors when running full-screen.
  //
  BigBlobs=RegLoad(_T("bigblobs"),true);
}





// TSaverWindow: one is created for each saver window (be it preview, or the
// preview in the config dialog, or one for each monitor when running full-screen)
// For things like back-buffer that are inherently related to a particular window,
// I store them here. (But things that are shared between all saver windows on a multi
// monitor system, I leave as global instead).
//
struct TSaverWindow
{ HWND hwnd; int id;  // id=-1 for a preview, or 0..n for full-screen on the specified monitor
  HANDLE hbmBuffer;   // this is a back-buffer used by the saver
  int bw,bh;          // dimensions of the back-buffer
  //
  TSaverWindow(HWND _hwnd,int _id) : hwnd(_hwnd),id(_id)
  { CommonInit(); SetTimer(hwnd,1,100,NULL);
    // In this example we take a snapshot of the screen at startup. This trick doesn't
    // really work under NT/Win2k/XP, where savers are run on their own private desktops.
    // If we're a preview window, we snapshot the primary desktop. If we're a full-screen
    // saver, we snapshot whatever monitor we were running on.
    RECT rc; GetClientRect(hwnd,&rc); bw=rc.right; bh=rc.bottom;
    if (id==-1) {rc.left=0; rc.top=0; rc.right=GetSystemMetrics(SM_CXSCREEN); rc.bottom=GetSystemMetrics(SM_CYSCREEN);}
    else GetWindowRect(hwnd,&rc);
    //
    HDC sdc=GetDC(0), bufdc=CreateCompatibleDC(sdc);
    hbmBuffer=CreateCompatibleBitmap(sdc,bw,bh); SelectObject(bufdc,hbmBuffer);
    SetStretchBltMode(bufdc,COLORONCOLOR);
    StretchBlt(bufdc,0,0,bw,bh,sdc,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,SRCCOPY);
    DeleteDC(bufdc);
    ReleaseDC(0,sdc);
  }
  ~TSaverWindow()
  { KillTimer(hwnd,1);
    if (hbmBuffer!=0) DeleteObject(hbmBuffer); hbmBuffer=0;
  }
  void OnTimer()
  { HDC sdc=GetDC(0), bufdc=CreateCompatibleDC(sdc); ReleaseDC(0,sdc);
    SelectObject(bufdc,hbmBuffer);
    //
    // TODO: make the saver animate!
    //
    int col=rand()%20;
    int divisor=(BigBlobs?2:8);
    int cx=rand()%bw, cy=rand()%bh, w=rand()%(bw/divisor), h=rand()%(bh/divisor);    
    SelectObject(bufdc,GetSysColorBrush(col));
    Ellipse(bufdc,cx-w/2,cy-h/2,cx+w/2,cy+h/2);
    //
    DeleteDC(bufdc);
    InvalidateRect(hwnd,NULL,TRUE);
  }
  void OtherWndProc(UINT,WPARAM,LPARAM) {}

  void OnPaint(HDC hdc,const RECT &rc)
  { HDC bufdc=CreateCompatibleDC(hdc); SelectObject(bufdc,hbmBuffer);
    StretchBlt(hdc,0,0,rc.right,rc.bottom,bufdc,0,0,bw,bh,SRCCOPY);
    DeleteDC(bufdc);
  }
};








BOOL CALLBACK OptionsDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_INITDIALOG:
	  { CommonInit();
      //
      // TODO: set up the "options" dialog.
      // Also, in the dialog-editor, remember to remove the "Mute" checkbox if you don't have sound!
      //
      CheckDlgButton(hwnd,102,BigBlobs?BST_CHECKED:BST_UNCHECKED);
      //
    } return TRUE;

    case WM_COMMAND:
    { int id=LOWORD(wParam), code=HIWORD(wParam);
      if (id==102 && code==BN_CLICKED) BigBlobs =  (IsDlgButtonChecked(hwnd,102)==BST_CHECKED);
    } return TRUE;

    case WM_NOTIFY:
    { LPNMHDR nmh=(LPNMHDR)lParam; UINT code=nmh->code;
      switch (code)
      { case (PSN_APPLY):
        { //
          // TODO: save it to the registry
          //
          RegSave(_T("bigblobs"),BigBlobs);
          //
          SetWindowLong(hwnd,DWL_MSGRESULT,PSNRET_NOERROR);
        } return TRUE;
      }
    } return FALSE;
  }
  return FALSE;
}






// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------

BOOL VerifyPassword(HWND hwnd)
{ // Under NT, we return TRUE immediately. This lets the saver quit, and the system manages passwords.
  // Under '95, we call VerifyScreenSavePwd. This checks the appropriate registry key and, if necessary, pops up a verify dialog
  OSVERSIONINFO osv; osv.dwOSVersionInfoSize=sizeof(osv); GetVersionEx(&osv);
  if (osv.dwPlatformId==VER_PLATFORM_WIN32_NT) return TRUE;
  HINSTANCE hpwdcpl=::LoadLibrary(_T("PASSWORD.CPL"));
  if (hpwdcpl==NULL) {Debug(_T("Unable to load PASSWORD.CPL. Aborting"));return TRUE;}
  typedef BOOL (WINAPI *VERIFYSCREENSAVEPWD)(HWND hwnd);
  VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
  VerifyScreenSavePwd=(VERIFYSCREENSAVEPWD)GetProcAddress(hpwdcpl,"VerifyScreenSavePwd");
  if (VerifyScreenSavePwd==NULL) {Debug(_T("Unable to get VerifyPwProc address. Aborting"));FreeLibrary(hpwdcpl);return TRUE;}
  Debug(_T("About to call VerifyPwProc")); BOOL bres=VerifyScreenSavePwd(hwnd); FreeLibrary(hpwdcpl);
  return bres;
}
void ChangePassword(HWND hwnd)
{ // This only ever gets called under '95, when started with the /a option.
  HINSTANCE hmpr=::LoadLibrary(_T("MPR.DLL"));
  if (hmpr==NULL) {Debug(_T("MPR.DLL not found: cannot change password."));return;}
  typedef VOID (WINAPI *PWDCHANGEPASSWORD) (LPCSTR lpcRegkeyname,HWND hwnd,UINT uiReserved1,UINT uiReserved2);
  PWDCHANGEPASSWORD PwdChangePassword=(PWDCHANGEPASSWORD)::GetProcAddress(hmpr,"PwdChangePasswordA");
  if (PwdChangePassword==NULL) {FreeLibrary(hmpr); Debug(_T("PwdChangeProc not found: cannot change password"));return;}
  PwdChangePassword("SCRSAVE",hwnd,0,0); FreeLibrary(hmpr);
}



void ReadGeneralRegistry()
{ PasswordDelay=15; PasswordDelayIndex=0; MouseThreshold=50; IsDialogActive=false; // default values in case they're not in registry
  LONG res; HKEY skey; DWORD valtype, valsize, val;
  res=RegOpenKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_SETUP _T("\\Screen Savers"),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Password Delay"),0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) PasswordDelay=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Password Delay Index"),0,&valtype,(LPBYTE)&val,&valsize); if (res==ERROR_SUCCESS) PasswordDelayIndex=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mouse Threshold"),0,&valtype,(LPBYTE)&val,&valsize);if (res==ERROR_SUCCESS) MouseThreshold=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mouse Threshold Index"),0,&valtype,(LPBYTE)&val,&valsize);if (res==ERROR_SUCCESS) MouseThresholdIndex=val;
  valsize=sizeof(val); res=RegQueryValueEx(skey,_T("Mute Sound"),0,&valtype,(LPBYTE)&val,&valsize);     if (res==ERROR_SUCCESS) MuteSound=val;
  valsize=5*sizeof(TCHAR); res=RegQueryValueEx(skey,_T("Mouse Corners"),0,&valtype,(LPBYTE)Corners,&valsize);
  for (int i=0; i<4; i++) {if (Corners[i]!='Y' && Corners[i]!='N') Corners[i]='-';} Corners[4]=0;
  RegCloseKey(skey);
  //
  HotServices=FALSE;
  HINSTANCE sagedll=LoadLibrary(_T("sage.dll"));
	if (sagedll==0) sagedll=LoadLibrary(_T("scrhots.dll"));
  if (sagedll!=0)
  { typedef BOOL (WINAPI *SYSTEMAGENTDETECT)();
    SYSTEMAGENTDETECT detectproc=(SYSTEMAGENTDETECT)GetProcAddress(sagedll,"System_Agent_Detect");
    if (detectproc!=NULL) HotServices=detectproc();
    FreeLibrary(sagedll);
  }
}

void WriteGeneralRegistry()
{ LONG res; HKEY skey; DWORD val;
  res=RegCreateKeyEx(HKEY_CURRENT_USER,REGSTR_PATH_SETUP _T("\\Screen Savers"),0,0,0,KEY_ALL_ACCESS,0,&skey,0);
  if (res!=ERROR_SUCCESS) return;
  val=PasswordDelay; RegSetValueEx(skey,_T("Password Delay"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=PasswordDelayIndex; RegSetValueEx(skey,_T("Password Delay Index"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MouseThreshold; RegSetValueEx(skey,_T("Mouse Threshold"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MouseThresholdIndex; RegSetValueEx(skey,_T("Mouse Threshold Index"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  val=MuteSound?1:0; RegSetValueEx(skey,_T("Mute Sound"),0,REG_DWORD,(const BYTE*)&val,sizeof(val));
  RegSetValueEx(skey,_T("Mouse Corners"),0,REG_SZ,(const BYTE*)Corners,5*sizeof(TCHAR));
  RegCloseKey(skey);
  //
  HINSTANCE sagedll=LoadLibrary(_T("sage.dll"));
	if (sagedll==0) sagedll=LoadLibrary(_T("scrhots.dll"));
  if (sagedll!=0)
  { typedef VOID (WINAPI *SCREENSAVERCHANGED)();
    SCREENSAVERCHANGED changedproc=(SCREENSAVERCHANGED)GetProcAddress(sagedll,"Screen_Saver_Changed");
    if (changedproc!=NULL) changedproc();
    FreeLibrary(sagedll);
  }
}


  
LRESULT CALLBACK SaverWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ TSaverWindow *sav; int id; HWND hmain;
  if (msg==WM_CREATE)
  { CREATESTRUCT *cs=(CREATESTRUCT*)lParam; id=*(int*)cs->lpCreateParams; SetWindowLong(hwnd,0,id);
    sav = new TSaverWindow(hwnd,id); SetWindowLong(hwnd,GWL_USERDATA,(LONG)sav);
    SaverWindow.push_back(sav);
  }
  else
  { sav=(TSaverWindow*)GetWindowLong(hwnd,GWL_USERDATA);
    id=GetWindowLong(hwnd,0);
  }
  if (id<=0) hmain=hwnd; else hmain=SaverWindow[0]->hwnd;
  //
  if (msg==WM_TIMER) sav->OnTimer();
  else if (msg==WM_PAINT) {PAINTSTRUCT ps; BeginPaint(hwnd,&ps); RECT rc; GetClientRect(hwnd,&rc); if (sav!=0) sav->OnPaint(ps.hdc,rc); EndPaint(hwnd,&ps);}
  else if (sav!=0) sav->OtherWndProc(msg,wParam,lParam);
  //
  switch (msg)
  { case WM_ACTIVATE: case WM_ACTIVATEAPP: case WM_NCACTIVATE:
    { TCHAR pn[100]; GetClassName((HWND)lParam,pn,100); 
      bool ispeer = (_tcsncmp(pn,_T("ScrClass"),8)==0);
      if (ScrMode==smSaver && !IsDialogActive && LOWORD(wParam)==WA_INACTIVE && !ispeer && !SCRDEBUG)
      { Debug(_T("WM_ACTIVATE: about to inactive window, so sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0); return 0;
      }
    } break;
    case WM_SETCURSOR:
    { if (ScrMode==smSaver && !IsDialogActive && !SCRDEBUG) SetCursor(NULL);
      else SetCursor(LoadCursor(NULL,IDC_ARROW));
    } return 0;
    case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN:
    { if (ScrMode==smSaver && !IsDialogActive) {Debug(_T("WM_BUTTONDOWN: sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0); return 0;}
    } break;
    case WM_MOUSEMOVE:
    { if (ScrMode==smSaver && !IsDialogActive && !SCRDEBUG)
      { POINT pt; GetCursorPos(&pt);
        int dx=pt.x-InitCursorPos.x; if (dx<0) dx=-dx; int dy=pt.y-InitCursorPos.y; if (dy<0) dy=-dy;
        if (dx>(int)MouseThreshold || dy>(int)MouseThreshold)
        { Debug(_T("WM_MOUSEMOVE: gone beyond threshold, sending close")); ReallyClose=true; PostMessage(hmain,WM_CLOSE,0,0);
        }
      }
    } return 0;
    case (WM_SYSCOMMAND):
    { if (ScrMode==smSaver)
      { if (wParam==SC_SCREENSAVE) {Debug(_T("WM_SYSCOMMAND: gobbling up a SC_SCREENSAVE to stop a new saver from running."));return 0;}
        if (wParam==SC_CLOSE && !SCRDEBUG) {Debug(_T("WM_SYSCOMMAND: gobbling up a SC_CLOSE"));return 0;}
      }
    } break;
    case (WM_CLOSE):
    { if (id>0) return 0; // secondary windows ignore this message
      if (id==-1) {DestroyWindow(hwnd); return 0;} // preview windows close obediently
      if (ReallyClose && !IsDialogActive)
      { Debug(_T("WM_CLOSE: maybe we need a password"));
        BOOL CanClose=TRUE;
        if (GetTickCount()-InitTime > 1000*PasswordDelay)
        { IsDialogActive=true; SendMessage(hwnd,WM_SETCURSOR,0,0);
          CanClose=VerifyPassword(hwnd);
          IsDialogActive=false; SendMessage(hwnd,WM_SETCURSOR,0,0); GetCursorPos(&InitCursorPos);
        }
        // note: all secondary monitors are owned by the primary. And we're the primary (id==0)
        // therefore, when we destroy ourself, windows will destroy the others first
        if (CanClose) {Debug(_T("WM_CLOSE: doing a DestroyWindow")); DestroyWindow(hwnd);}
        else {Debug(_T("WM_CLOSE: but failed password, so doing nothing"));}
      }
    } return 0;
    case (WM_DESTROY):
    { Debug(_T("WM_DESTROY."));
      SetWindowLong(hwnd,0,0); SetWindowLong(hwnd,GWL_USERDATA,0);
      for (vector<TSaverWindow*>::iterator i=SaverWindow.begin(); i!=SaverWindow.end(); i++) {if (sav==*i) *i=0;}
      delete sav;
      if ((id==0 && ScrMode==smSaver) || ScrMode==smPreview) PostQuitMessage(0);
    } break;
  }
  return DefWindowProc(hwnd,msg,wParam,lParam);
}






// ENUM-MONITOR-CALLBACK is part of DoSaver. Its stuff is in windef.h but
// requires WINVER>=0x0500. Well, we're doing LoadLibrary, so we know it's
// safe...
#ifndef SM_CMONITORS
DECLARE_HANDLE(HMONITOR);
#endif
//
BOOL CALLBACK EnumMonitorCallback(HMONITOR,HDC,LPRECT rc,LPARAM)
{ if (rc->left==0 && rc->top==0) monitors.insert(monitors.begin(),*rc);
  else monitors.push_back(*rc);
  return TRUE;
}

void DoSaver(HWND hparwnd, bool fakemulti)
{ if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc); monitors.push_back(rc);
  }
  else if (fakemulti)
  { int w=GetSystemMetrics(SM_CXSCREEN), x1=w/4, x2=w*2/3, h=x2-x1; RECT rc;
    rc.left=x1; rc.top=x1; rc.right=x1+h; rc.bottom=x1+h; monitors.push_back(rc);
    rc.left=0; rc.top=x1; rc.right=x1; rc.bottom=x1+x1; monitors.push_back(rc);
    rc.left=x2; rc.top=x1+h+x2-w; rc.right=w; rc.bottom=x1+h; monitors.push_back(rc);
  }
  else
  { int num_monitors=GetSystemMetrics(80); // 80=SM_CMONITORS
    if (num_monitors>1)
    { typedef BOOL (CALLBACK *LUMONITORENUMPROC)(HMONITOR,HDC,LPRECT,LPARAM);
      typedef BOOL (WINAPI *LUENUMDISPLAYMONITORS)(HDC,LPCRECT,LUMONITORENUMPROC,LPARAM);
      HINSTANCE husr=LoadLibrary(_T("user32.dll"));
      LUENUMDISPLAYMONITORS pEnumDisplayMonitors=0;
      if (husr!=NULL) pEnumDisplayMonitors=(LUENUMDISPLAYMONITORS)GetProcAddress(husr,"EnumDisplayMonitors");
      if (pEnumDisplayMonitors!=NULL) (*pEnumDisplayMonitors)(NULL,NULL,EnumMonitorCallback,NULL);
      if (husr!=NULL) FreeLibrary(husr);
    }
    if (monitors.size()==0)
    { RECT rc; rc.left=0; rc.top=0; rc.right=GetSystemMetrics(SM_CXSCREEN); rc.bottom=GetSystemMetrics(SM_CYSCREEN);
      monitors.push_back(rc);
    }
  }
  //
  HWND hwnd=0;
  if (ScrMode==smPreview)
  { RECT rc; GetWindowRect(hparwnd,&rc); int w=rc.right-rc.left, h=rc.bottom-rc.top;  
    int id=-1; hwnd=CreateWindowEx(0,_T("ScrClass"),_T(""),WS_CHILD|WS_VISIBLE,0,0,w,h,hparwnd,NULL,hInstance,&id);
  }
  else
  { GetCursorPos(&InitCursorPos); InitTime=GetTickCount();
    for (int i=0; i<(int)monitors.size(); i++)
    { const RECT &rc = monitors[i];
      DWORD exstyle=WS_EX_TOPMOST; if (SCRDEBUG) exstyle=0;
      HWND hthis = CreateWindowEx(exstyle,_T("ScrClass"),_T(""),WS_POPUP|WS_VISIBLE,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hwnd,NULL,hInstance,&i);
      if (i==0) hwnd=hthis;
    }
  }
  if (hwnd!=NULL)
  { UINT oldval;
    if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,1,&oldval,0);
    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    { TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    if (ScrMode==smSaver) SystemParametersInfo(SPI_SETSCREENSAVERRUNNING,0,&oldval,0);
  }
  //
  SaverWindow.clear();
  return;
}




BOOL CALLBACK GeneralDlgProc(HWND hwnd,UINT msg,WPARAM,LPARAM lParam)
{ switch (msg)
  { case (WM_INITDIALOG):
	  { ShowWindow(GetDlgItem(hwnd,HotServices?102:101),SW_HIDE);
      SetDlgItemText(hwnd,112,Corners);
      SendDlgItemMessage(hwnd,109,CB_ADDSTRING,0,(LPARAM)_T("seconds"));
      SendDlgItemMessage(hwnd,109,CB_ADDSTRING,0,(LPARAM)_T("minutes"));
      SendDlgItemMessage(hwnd,109,CB_SETCURSEL,PasswordDelayIndex,0);
      int n=PasswordDelay; if (PasswordDelayIndex==1) n/=60;
      TCHAR c[16]; wsprintf(c,_T("%i"),n); SetDlgItemText(hwnd,107,c);
      SendDlgItemMessage(hwnd,108,UDM_SETRANGE,0,MAKELONG(99,0));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("High"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Normal"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Low"));
      SendDlgItemMessage(hwnd,105,CB_ADDSTRING,0,(LPARAM)_T("Keyboard only (ignore mouse movement)"));
      SendDlgItemMessage(hwnd,105,CB_SETCURSEL,MouseThresholdIndex,0);
      if (MuteSound) CheckDlgButton(hwnd,113,BST_CHECKED);
      OSVERSIONINFO ver; ZeroMemory(&ver,sizeof(ver)); ver.dwOSVersionInfoSize=sizeof(ver); GetVersionEx(&ver);
      for (int i=106; i<111 && ver.dwPlatformId!=VER_PLATFORM_WIN32_WINDOWS; i++) ShowWindow(GetDlgItem(hwnd,i),SW_HIDE); 
    } return TRUE;
    case (WM_NOTIFY):
    { LPNMHDR nmh=(LPNMHDR)lParam; UINT code=nmh->code;
      switch (code)
      { case (PSN_APPLY):
        { GetDlgItemText(hwnd,112,Corners,5);                   
          PasswordDelayIndex=SendDlgItemMessage(hwnd,109,CB_GETCURSEL,0,0);
          TCHAR c[16]; GetDlgItemText(hwnd,107,c,16); int n=_ttoi(c); if (PasswordDelayIndex==1) n*=60; PasswordDelay=n;
          MouseThresholdIndex=SendDlgItemMessage(hwnd,105,CB_GETCURSEL,0,0);
          if (MouseThresholdIndex==0) MouseThreshold=0;
          else if (MouseThresholdIndex==1) MouseThreshold=200;
          else if (MouseThresholdIndex==2) MouseThreshold=400;
          else MouseThreshold=999999;
          MuteSound = (IsDlgButtonChecked(hwnd,113)==BST_CHECKED);
          WriteGeneralRegistry();
          SetWindowLong(hwnd,DWL_MSGRESULT,PSNRET_NOERROR);
        } return TRUE;
      }
    } return FALSE;
  }
  return FALSE;
}



//
// MONITOR CONTROL -- either corners or a preview
//

LRESULT CALLBACK MonitorWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ switch (msg)
  { case WM_CREATE:
    { TCHAR c[5]; GetWindowText(hwnd,c,5); if (*c!=0) return 0;
      int id=-1; RECT rc; SendMessage(hwnd,SCRM_GETMONITORAREA,0,(LPARAM)&rc);
      CreateWindow(_T("ScrClass"),_T(""),WS_CHILD|WS_VISIBLE,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,hwnd,NULL,hInstance,&id);
    } return 0;
    case WM_PAINT:
    { if (hbmmonitor==0) hbmmonitor=LoadBitmap(hInstance,_T("Monitor"));
      RECT rc; GetClientRect(hwnd,&rc);
      //
      PAINTSTRUCT ps; BeginPaint(hwnd,&ps);
      HBITMAP hback = (HBITMAP)GetWindowLong(hwnd,GWL_USERDATA);
      if (hback!=0)
      { BITMAP bmp; GetObject(hback,sizeof(bmp),&bmp);
        if (bmp.bmWidth!=rc.right || bmp.bmHeight!=rc.bottom) {DeleteObject(hback); hback=0;}
      }
      if (hback==0) {hback=CreateCompatibleBitmap(ps.hdc,rc.right,rc.bottom); SetWindowLong(hwnd,GWL_USERDATA,(LONG)hback);}
      HDC backdc=CreateCompatibleDC(ps.hdc);
      HGDIOBJ holdback=SelectObject(backdc,hback);
      BitBlt(backdc,0,0,rc.right,rc.bottom,ps.hdc,0,0,SRCCOPY);
      //
      TCHAR corners[5]; GetWindowText(hwnd,corners,5);
      HDC hdc=CreateCompatibleDC(ps.hdc);
      HGDIOBJ hold=SelectObject(hdc,hbmmonitor);
      StretchBlt(backdc,0,0,rc.right,rc.bottom,hdc,0,0,184,170,SRCAND);
      StretchBlt(backdc,0,0,rc.right,rc.bottom,hdc,184,0,184,170,SRCINVERT);
      RECT crc; SendMessage(hwnd,SCRM_GETMONITORAREA,0,(LPARAM)&crc);
      //
      if (*corners!=0) FillRect(backdc,&crc,GetSysColorBrush(COLOR_DESKTOP));
      for (int i=0; i<4 && *corners!=0; i++)
      { RECT crc; SendMessage(hwnd,SCRM_GETMONITORAREA,i+1,(LPARAM)&crc);
        int y=0; if (corners[i]=='Y') y=22; else if (corners[i]=='N') y=44;
        BitBlt(backdc,crc.left,crc.top,crc.right-crc.left,crc.bottom-crc.top,hdc,368,y,SRCCOPY);
        if (!HotServices) 
        { DWORD col=GetSysColor(COLOR_DESKTOP);
          for (int y=crc.top; y<crc.bottom; y++)
          { for (int x=crc.left+(y&1); x<crc.right; x+=2) SetPixel(backdc,x,y,col);
          }
        }
      }
      SelectObject(hdc,hold);
      DeleteDC(hdc);
      //
      BitBlt(ps.hdc,0,0,rc.right,rc.bottom,backdc,0,0,SRCCOPY);
      SelectObject(backdc,holdback);
      DeleteDC(backdc);
      EndPaint(hwnd,&ps);
    } return 0;
    case SCRM_GETMONITORAREA:
    { RECT *prc=(RECT*)lParam;
      if (hbmmonitor==0) hbmmonitor=LoadBitmap(hInstance,_T("Monitor"));
      // those are the client coordinates unscalled
      RECT wrc; GetClientRect(hwnd,&wrc); int ww=wrc.right, wh=wrc.bottom;
      RECT rc; rc.left=16*ww/184; rc.right=168*ww/184; rc.top=17*wh/170; rc.bottom=130*wh/170;
      *prc=rc; if (wParam==0) return 0;
      if (wParam==1) {prc->right=rc.left+24; prc->bottom=rc.top+22;}
      else if (wParam==2) {prc->left=rc.right-24; prc->bottom=rc.top+22;}
      else if (wParam==3) {prc->left=rc.right-24; prc->top=rc.bottom-22;}
      else if (wParam==4) {prc->right=rc.left+24; prc->top=rc.bottom-22;}
    } return 0;
    case WM_LBUTTONDOWN:
    { if (!HotServices) return 0;
      int x=LOWORD(lParam), y=HIWORD(lParam);
      TCHAR corners[5]; GetWindowText(hwnd,corners,5);
      if (corners[0]==0) return 0;
      int click=-1; for (int i=0; i<4; i++)
      { RECT rc; SendMessage(hwnd,SCRM_GETMONITORAREA,i+1,(LPARAM)&rc);
        if (x>=rc.left && y>=rc.top && x<rc.right && y<rc.bottom) {click=i; break;}
      }
      if (click==-1) return 0;
      for (int j=0; j<4; j++)
      { if (corners[j]!='-' && corners[j]!='Y' && corners[j]!='N') corners[j]='-';
      }
      corners[4]=0;
      //
      HMENU hmenu=CreatePopupMenu();
      MENUITEMINFO mi; ZeroMemory(&mi,sizeof(mi)); mi.cbSize=sizeof(MENUITEMINFO);
      mi.fMask=MIIM_TYPE|MIIM_ID|MIIM_STATE|MIIM_DATA;
      mi.fType=MFT_STRING|MFT_RADIOCHECK;
      mi.wID='N'; mi.fState=MFS_ENABLED; if (corners[click]=='N') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Never"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      mi.wID='Y'; mi.fState=MFS_ENABLED; if (corners[click]=='Y') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Now"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      mi.wID='-'; mi.fState=MFS_ENABLED; if (corners[click]!='Y' && corners[click]!='N') mi.fState|=MFS_CHECKED;
      mi.dwTypeData=_T("Default"); mi.cch=sizeof(TCHAR)*_tcslen(mi.dwTypeData);
      InsertMenuItem(hmenu,0,TRUE,&mi);
      POINT pt; pt.x=x; pt.y=y; ClientToScreen(hwnd,&pt);
      int cmd = TrackPopupMenuEx(hmenu,TPM_RETURNCMD|TPM_RIGHTBUTTON,pt.x,pt.y,hwnd,NULL);
      if (cmd!=0) corners[click]=(char)cmd;
      corners[4]=0; SetWindowText(hwnd,corners);
      InvalidateRect(hwnd,NULL,FALSE);
    } return 0;
    case WM_DESTROY:
    { HBITMAP hback = (HBITMAP)SetWindowLong(hwnd,GWL_USERDATA,0);
      if (hback!=0) DeleteObject(hback);
    } return 0;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}






BOOL CALLBACK AboutDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM)
{ if (msg==WM_INITDIALOG)
  { SetDlgItemText(hdlg,101,SaverName.c_str());
    SetDlgItemUrl(hdlg,102,_T("http://www.wischik.com/lu/scr/"));
    SetDlgItemText(hdlg,102,_T("www.wischik.com/scr"));
    return TRUE;
  }
  else if (msg==WM_COMMAND)
  { int id=LOWORD(wParam);
    if (id==IDOK || id==IDCANCEL) EndDialog(hdlg,id);
    return TRUE;
  } 
  else return FALSE;
}


//
// PROPERTY SHEET SUBCLASSING -- this is to stick an "About" option on the sysmenu.
//
WNDPROC OldSubclassProc=0;
LRESULT CALLBACK PropertysheetSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ if (msg==WM_SYSCOMMAND && wParam==3500)
  { DialogBox(hInstance,_T("DLG_ABOUT"),hwnd,AboutDlgProc);
    return 0;
  } 
  if (OldSubclassProc!=NULL) return CallWindowProc(OldSubclassProc,hwnd,msg,wParam,lParam);
  else return DefWindowProc(hwnd,msg,wParam,lParam);
}

int CALLBACK PropertysheetCallback(HWND hwnd,UINT msg,LPARAM)
{ if (msg!=PSCB_INITIALIZED) return 0;
  HMENU hsysmenu=GetSystemMenu(hwnd,FALSE);
  AppendMenu(hsysmenu,MF_SEPARATOR,1,_T("-"));
  AppendMenu(hsysmenu,MF_STRING,3500,_T("About..."));
  OldSubclassProc=(WNDPROC)SetWindowLong(hwnd,GWL_WNDPROC,(LONG)PropertysheetSubclassProc);
  return 0;
}


void DoConfig(HWND hpar)
{ hiconbg = (HICON)LoadImage(hInstance,MAKEINTRESOURCE(1),IMAGE_ICON,GetSystemMetrics(SM_CXICON),GetSystemMetrics(SM_CYICON),0);
  hiconsm = (HICON)LoadImage(hInstance,MAKEINTRESOURCE(1),IMAGE_ICON,GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),0);
  //  
  PROPSHEETHEADER psh; ZeroMemory(&psh,sizeof(psh));
  PROPSHEETPAGE psp[2]; ZeroMemory(psp,2*sizeof(PROPSHEETPAGE));
  psp[0].dwSize=sizeof(psp[0]);
  psp[0].dwFlags=PSP_DEFAULT;
  psp[0].hInstance=hInstance; 
  psp[0].pszTemplate=_T("DLG_GENERAL");
  psp[0].pfnDlgProc=GeneralDlgProc;
  psp[1].dwSize=sizeof(psp[1]);
  psp[1].dwFlags=PSP_DEFAULT;
  psp[1].hInstance=hInstance;  
  psp[1].pszTemplate=_T("DLG_OPTIONS");
  psp[1].pfnDlgProc=OptionsDlgProc;
  psh.dwSize=sizeof(psh);
  psh.dwFlags=PSH_NOAPPLYNOW | PSH_PROPSHEETPAGE | PSH_USEHICON | PSH_USECALLBACK;
  psh.hwndParent=hpar;
  psh.hInstance=hInstance;
  psh.hIcon=hiconsm;
  tstring cap=_T("Options for ")+SaverName; psh.pszCaption=cap.c_str();
  psh.nPages=2;
  psh.nStartPage=1;
  psh.ppsp=psp;
  psh.pfnCallback=PropertysheetCallback;
  Debug(_T("Config..."));
  PropertySheet(&psh);
  Debug(_T("Config done."));
  if (hiconbg!=0) DestroyIcon(hiconbg); hiconbg=0;
  if (hiconsm!=0) DestroyIcon(hiconsm); hiconsm=0;
  if (hbmmonitor!=0) DeleteObject(hbmmonitor); hbmmonitor=0;
}


// This routine is for using ScrPrev. It's so that you can start the saver
// with the command line /p scrprev and it runs itself in a preview window.
// You must first copy ScrPrev somewhere in your search path
HWND CheckForScrprev()
{ HWND hwnd=FindWindow(_T("Scrprev"),NULL); // looks for the Scrprev class
  if (hwnd==NULL) // try to load it
  { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&si,sizeof(si)); ZeroMemory(&pi,sizeof(pi));
    si.cb=sizeof(si);
    TCHAR cmd[MAX_PATH]; _tcscpy(cmd,_T("Scrprev")); // unicode CreateProcess requires it writeable
    BOOL cres=CreateProcess(NULL,cmd,0,0,FALSE,CREATE_NEW_PROCESS_GROUP | CREATE_DEFAULT_ERROR_MODE,
                            0,0,&si,&pi);
    if (!cres) {Debug(_T("Error creating scrprev process")); return NULL;}
    DWORD wres=WaitForInputIdle(pi.hProcess,2000);
    if (wres==WAIT_TIMEOUT) {Debug(_T("Scrprev never becomes idle")); return NULL;}
    if (wres==0xFFFFFFFF) {Debug(_T("ScrPrev, misc error after ScrPrev execution"));return NULL;}
    hwnd=FindWindow(_T("Scrprev"),NULL);
  }
  if (hwnd==NULL) {Debug(_T("Unable to find Scrprev window")); return NULL;}
  ::SetForegroundWindow(hwnd);
  hwnd=GetWindow(hwnd,GW_CHILD);
  if (hwnd==NULL) {Debug(_T("Couldn't find Scrprev child")); return NULL;}
  return hwnd;
}


void DoInstall()
{ TCHAR windir[MAX_PATH]; GetWindowsDirectory(windir,MAX_PATH);
  TCHAR tfn[MAX_PATH]; UINT ures=GetTempFileName(windir,_T("pst"),0,tfn);
  if (ures==0) {MessageBox(NULL,_T("You must be logged on as system administrator to install screen savers"),_T("Saver Install"),MB_ICONINFORMATION|MB_OK); return;}
  DeleteFile(tfn);
  tstring fn=tstring(windir)+_T("\\")+SaverName+_T(".scr");
  DWORD attr = GetFileAttributes(fn.c_str());
  bool exists = (attr!=0xFFFFFFFF);
  tstring msg=_T("Do you want to install '")+SaverName+_T("' ?");
  if (exists) msg+=_T("\r\n\r\n(This will replace the version that you have currently)");
  int res=MessageBox(NULL,msg.c_str(),_T("Saver Install"),MB_YESNOCANCEL);
  if (res!=IDYES) return;
  TCHAR cfn[MAX_PATH]; GetModuleFileName(hInstance,cfn,MAX_PATH);
  SetCursor(LoadCursor(NULL,IDC_WAIT));
  BOOL bres = CopyFile(cfn,fn.c_str(),FALSE);
  if (!bres)
  { tstring msg = _T("There was an error installing the saver.\r\n\r\n\"")+GetLastErrorString()+_T("\"");
    MessageBox(NULL,msg.c_str(),_T("Saver Install"),MB_ICONERROR|MB_OK);
    SetCursor(LoadCursor(NULL,IDC_ARROW));
    return;
  }
  LONG lres; HKEY skey; DWORD disp; tstring val;
  tstring key=REGSTR_PATH_UNINSTALL _T("\\")+SaverName;
  lres=RegCreateKeyEx(HKEY_LOCAL_MACHINE,key.c_str(),0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&skey,&disp);
  if (lres==ERROR_SUCCESS)
  { val=SaverName+_T(" saver"); RegSetValueEx(skey,_T("DisplayName"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=_T("\"")+fn+_T("\" /u"); RegSetValueEx(skey,_T("UninstallString"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    RegSetValueEx(skey,_T("UninstallPath"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=_T("\"")+fn+_T("\""); RegSetValueEx(skey,_T("ModifyPath"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    val=fn; RegSetValueEx(skey,_T("DisplayIcon"),0,REG_SZ,(const BYTE*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
    TCHAR url[1024]; int ures=LoadString(hInstance,2,url,1024); if (ures!=0) RegSetValueEx(skey,_T("HelpLink"),0,REG_SZ,(const BYTE*)url,sizeof(TCHAR)*(_tcslen(url)+1));
    RegCloseKey(skey);
  }
  SHELLEXECUTEINFO sex; ZeroMemory(&sex,sizeof(sex)); sex.cbSize=sizeof(sex);
  sex.fMask=SEE_MASK_NOCLOSEPROCESS;
  sex.lpVerb=_T("install");
  sex.lpFile=fn.c_str();
  sex.nShow=SW_SHOWNORMAL;
  bres = ShellExecuteEx(&sex);
  if (!bres) {SetCursor(LoadCursor(NULL,IDC_ARROW)); MessageBox(NULL,_T("The saver has been installed"),SaverName.c_str(),MB_OK); return;}
  WaitForInputIdle(sex.hProcess,2000);
  CloseHandle(sex.hProcess);
  SetCursor(LoadCursor(NULL,IDC_ARROW));
}


void DoUninstall()
{ tstring key=REGSTR_PATH_UNINSTALL _T("\\")+SaverName;
  RegDeleteKey(HKEY_LOCAL_MACHINE,key.c_str());
  TCHAR fn[MAX_PATH]; GetModuleFileName(hInstance,fn,MAX_PATH);
  SetFileAttributes(fn,FILE_ATTRIBUTE_NORMAL); // remove readonly if necessary
  BOOL res = MoveFileEx(fn,NULL,MOVEFILE_DELAY_UNTIL_REBOOT);
  //
  const TCHAR *c=fn, *lastslash=c;
  while (*c!=0) {if (*c=='\\' || *c=='/') lastslash=c+1; c++;}
  tstring cap=SaverName+_T(" uninstaller");
  tstring msg;
  if (res) msg=_T("Uninstall completed. The saver will be removed next time you reboot.");
  else msg=_T("There was a problem uninstalling.\r\n")
           _T("To complete the uninstall manually, you should go into your Windows ")
           _T("directory and delete the file '")+tstring(lastslash)+_T("'");
  MessageBox(NULL,msg.c_str(),cap.c_str(),MB_OK);
}




// --------------------------------------------------------------------------------
// SetDlgItemUrl(hwnd,IDC_MYSTATIC,"http://www.wischik.com/lu");
//   This routine turns a dialog's static text control into an underlined hyperlink.
//   You can call it in your WM_INITDIALOG, or anywhere.
//   It will also set the text of the control... if you want to change the text
//   back, you can just call SetDlgItemText() afterwards.
// --------------------------------------------------------------------------------
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url); 

// Implementation notes:
// We have to subclass both the static control (to set its cursor, to respond to click)
// and the dialog procedure (to set the font of the static control). Here are the two
// subclasses:
LRESULT CALLBACK UrlCtlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK UrlDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam);
// When the user calls SetDlgItemUrl, then the static-control-subclass is added
// if it wasn't already there, and the dialog-subclass is added if it wasn't
// already there. Both subclasses are removed in response to their respective
// WM_DESTROY messages. Also, each subclass stores a property in its window,
// which is a HGLOBAL handle to a GlobalAlloc'd structure:
typedef struct {TCHAR *url; WNDPROC oldproc; HFONT hf; HBRUSH hb;} TUrlData;
// I'm a miser and only defined a single structure, which is used by both
// the control-subclass and the dialog-subclass. Both of them use 'oldproc' of course.
// The control-subclass only uses 'url' (in response to WM_LBUTTONDOWN),
// and the dialog-subclass only uses 'hf' and 'hb' (in response to WM_CTLCOLORSTATIC)
// There is one sneaky thing to note. We create our underlined font *lazily*.
// Initially, hf is just NULL. But the first time the subclassed dialog received
// WM_CTLCOLORSTATIC, we sneak a peak at the font that was going to be used for
// the control, and we create our own copy of it but including the underline style.
// This way our code works fine on dialogs of any font.

// SetDlgItemUrl: this is the routine that sets up the subclassing.
void SetDlgItemUrl(HWND hdlg,int id,const TCHAR *url) 
{ // nb. vc7 has crummy warnings about 32/64bit. My code's perfect! That's why I hide the warnings.
  #pragma warning( push )
  #pragma warning( disable: 4312 4244 )
  // First we'll subclass the edit control
  HWND hctl = GetDlgItem(hdlg,id);
  SetWindowText(hctl,url);
  HGLOBAL hold = (HGLOBAL)GetProp(hctl,_T("href_dat"));
  if (hold!=NULL) // if it had been subclassed before, we merely need to tell it the new url
  { TUrlData *ud = (TUrlData*)GlobalLock(hold);
    delete[] ud->url;
    ud->url=new TCHAR[_tcslen(url)+1]; _tcscpy(ud->url,url);
  }
  else
  { HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE,sizeof(TUrlData));
    TUrlData *ud = (TUrlData*)GlobalLock(hglob);
    ud->oldproc = (WNDPROC)GetWindowLong(hctl,GWL_WNDPROC);
    ud->url=new TCHAR[_tcslen(url)+1]; _tcscpy(ud->url,url);
    ud->hf=0; ud->hb=0;
    GlobalUnlock(hglob);
    SetProp(hctl,_T("href_dat"),hglob);
    SetWindowLong(hctl,GWL_WNDPROC,(LONG)UrlCtlProc);
  }
  //
  // Second we subclass the dialog
  hold = (HGLOBAL)GetProp(hdlg,_T("href_dlg"));
  if (hold==NULL)
  { HGLOBAL hglob = GlobalAlloc(GMEM_MOVEABLE,sizeof(TUrlData));
    TUrlData *ud = (TUrlData*)GlobalLock(hglob);
    ud->url=0;
    ud->oldproc = (WNDPROC)GetWindowLong(hdlg,GWL_WNDPROC);
    ud->hb=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
    ud->hf=0; // the font will be created lazilly, the first time WM_CTLCOLORSTATIC gets called
    GlobalUnlock(hglob);
    SetProp(hdlg,_T("href_dlg"),hglob);
    SetWindowLong(hdlg,GWL_WNDPROC,(LONG)UrlDlgProc);
  }
  #pragma warning( pop )
}

// UrlCtlProc: this is the subclass procedure for the static control
LRESULT CALLBACK UrlCtlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ HGLOBAL hglob = (HGLOBAL)GetProp(hwnd,_T("href_dat"));
  if (hglob==NULL) return DefWindowProc(hwnd,msg,wParam,lParam);
  TUrlData *oud=(TUrlData*)GlobalLock(hglob); TUrlData ud=*oud;
  GlobalUnlock(hglob); // I made a copy of the structure just so I could GlobalUnlock it now, to be more local in my code
  switch (msg)
  { case WM_DESTROY:
    { RemoveProp(hwnd,_T("href_dat")); GlobalFree(hglob);
      if (ud.url!=0) delete[] ud.url;
      // nb. remember that ud.url is just a pointer to a memory block. It might look weird
      // for us to delete ud.url instead of oud->url, but they're both equivalent.
    } break;
    case WM_LBUTTONDOWN:
    { HWND hdlg=GetParent(hwnd); if (hdlg==0) hdlg=hwnd;
      ShellExecute(hdlg,_T("open"),ud.url,NULL,NULL,SW_SHOWNORMAL);
    } break;
    case WM_SETCURSOR:
    { HCURSOR hc=LoadCursor(NULL,MAKEINTRESOURCE(32649)); // =IDC_HAND
      if (hc==0) hc=LoadCursor(NULL,IDC_ARROW); // systems before Win2k didn't have the hand
      SetCursor(hc);
      return TRUE;
    } 
    case WM_NCHITTEST:
    { return HTCLIENT; // because normally a static returns HTTRANSPARENT, so disabling WM_SETCURSOR
    } 
  }
  return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
}
  
// UrlDlgProc: this is the subclass procedure for the dialog
LRESULT CALLBACK UrlDlgProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{ HGLOBAL hglob = (HGLOBAL)GetProp(hwnd,_T("href_dlg"));
  if (hglob==NULL) return DefWindowProc(hwnd,msg,wParam,lParam);
  TUrlData *oud=(TUrlData*)GlobalLock(hglob); TUrlData ud=*oud;
  GlobalUnlock(hglob);
  switch (msg)
  { case WM_DESTROY:
    { RemoveProp(hwnd,_T("href_dlg")); GlobalFree(hglob);
      if (ud.hb!=0) DeleteObject(ud.hb);
      if (ud.hf!=0) DeleteObject(ud.hf);
    } break;
    case WM_CTLCOLORSTATIC:
    { HDC hdc=(HDC)wParam; HWND hctl=(HWND)lParam;
      // To check whether to handle this control, we look for its subclassed property!
      HANDLE hprop=GetProp(hctl,_T("href_dat")); if (hprop==NULL) return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
      // There has been a lot of faulty discussion in the newsgroups about how to change
      // the text colour of a static control. Lots of people mess around with the
      // TRANSPARENT text mode. That is incorrect. The correct solution is here:
      // (1) Leave the text opaque. This will allow us to re-SetDlgItemText without it looking wrong.
      // (2) SetBkColor. This background colour will be used underneath each character cell.
      // (3) return HBRUSH. This background colour will be used where there's no text.
      SetTextColor(hdc,RGB(0,0,255));
      SetBkColor(hdc,GetSysColor(COLOR_BTNFACE));
      if (ud.hf==0)
      { // we use lazy creation of the font. That's so we can see font was currently being used.
        TEXTMETRIC tm; GetTextMetrics(hdc,&tm);
        LOGFONT lf;
        lf.lfHeight=tm.tmHeight;
        lf.lfWidth=0;
        lf.lfEscapement=0;
        lf.lfOrientation=0;
        lf.lfWeight=tm.tmWeight;
        lf.lfItalic=tm.tmItalic;
        lf.lfUnderline=TRUE;
        lf.lfStrikeOut=tm.tmStruckOut;
        lf.lfCharSet=tm.tmCharSet;
        lf.lfOutPrecision=OUT_DEFAULT_PRECIS;
        lf.lfClipPrecision=CLIP_DEFAULT_PRECIS;
        lf.lfQuality=DEFAULT_QUALITY;
        lf.lfPitchAndFamily=tm.tmPitchAndFamily;
        GetTextFace(hdc,LF_FACESIZE,lf.lfFaceName);
        ud.hf=CreateFontIndirect(&lf);
        TUrlData *oud = (TUrlData*)GlobalLock(hglob); oud->hf=ud.hf; GlobalUnlock(hglob);
      }
      SelectObject(hdc,ud.hf);
      // Note: the win32 docs say to return an HBRUSH, typecast as a BOOL. But they
      // fail to explain how this will work in 64bit windows where an HBRUSH is 64bit.
      // I have supressed the warnings for now, because I hate them...
      #pragma warning( push )
      #pragma warning( disable: 4311 )
      return (BOOL)ud.hb;
      #pragma warning( pop )
    }  
  }
  return CallWindowProc(ud.oldproc,hwnd,msg,wParam,lParam);
}



inline void Debug(const tstring s)
{ if (DebugFile==_T("")) return;
  if (DebugFile==_T("OutputDebugString")) {tstring err=s+_T("\r\n"); OutputDebugString(err.c_str());}
  else {FILE *f = _tfopen(DebugFile.c_str(),_T("a+t")); _ftprintf(f,_T("%s\n"),s.c_str()); fclose(f);}
}

tstring GetLastErrorString()
{ LPVOID lpMsgBuf;
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,NULL,
    GetLastError(),MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
  tstring s((TCHAR*)lpMsgBuf);
  LocalFree( lpMsgBuf );
  return s;
}


void RegSave(const tstring name,DWORD type,void*buf,int size)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegCreateKeyEx(HKEY_CURRENT_USER,path.c_str(),0,0,0,KEY_ALL_ACCESS,0,&skey,0);
  if (res!=ERROR_SUCCESS) return;
  RegSetValueEx(skey,name.c_str(),0,type,(const BYTE*)buf,size);
  RegCloseKey(skey);
}
bool RegLoadDword(const tstring name,DWORD *buf)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegOpenKeyEx(HKEY_CURRENT_USER,path.c_str(),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return false;
  DWORD size=sizeof(DWORD);
  res=RegQueryValueEx(skey,name.c_str(),0,0,(LPBYTE)buf,&size);
  RegCloseKey(skey);
  return (res==ERROR_SUCCESS);
}

void RegSave(const tstring name,int val)
{ DWORD v=val; RegSave(name,REG_DWORD,&v,sizeof(v));
}
void RegSave(const tstring name,bool val)
{ RegSave(name,val?1:0);
}
void RegSave(const tstring name,tstring val)
{ RegSave(name,REG_SZ,(void*)val.c_str(),sizeof(TCHAR)*(val.length()+1));
}
int RegLoad(const tstring name,int def)
{ DWORD val; bool res=RegLoadDword(name,&val);
  return res?val:def;
}
bool RegLoad(const tstring name,bool def)
{ int b=RegLoad(name,def?1:0); return (b!=0);
}
tstring RegLoad(const tstring name,tstring def)
{ tstring path = _T("Software\\Scrplus\\")+SaverName;
  HKEY skey; LONG res=RegOpenKeyEx(HKEY_CURRENT_USER,path.c_str(),0,KEY_READ,&skey);
  if (res!=ERROR_SUCCESS) return def;
  DWORD size=0; res=RegQueryValueEx(skey,name.c_str(),0,0,0,&size);
  if (res!=ERROR_SUCCESS) {RegCloseKey(skey); return def;}
  TCHAR *buf = new TCHAR[size];
  RegQueryValueEx(skey,name.c_str(),0,0,(LPBYTE)buf,&size);
  tstring s(buf); delete[] buf;
  RegCloseKey(skey);
  return s;
}



int WINAPI WinMain(HINSTANCE h,HINSTANCE,LPSTR,int)
{ hInstance=h;
  TCHAR name[MAX_PATH]; int sres=LoadString(hInstance,1,name,MAX_PATH);
  if (sres==0) {MessageBox(NULL,_T("Must store saver name as String Resource 1"),_T("Saver"),MB_ICONERROR|MB_OK);return 0;}
  SaverName=name;
  //
  TCHAR mod[MAX_PATH]; GetModuleFileName(hInstance,mod,MAX_PATH); tstring smod(mod);
  bool isexe = (smod.find(_T(".exe"))!=tstring::npos || smod.find(_T(".EXE"))!=tstring::npos);
  //
  TCHAR *c=GetCommandLine();
  if (*c=='\"') {c++; while (*c!=0 && *c!='\"') c++; if (*c=='\"') c++;} else {while (*c!=0 && *c!=' ') c++;}
  while (*c==' ') c++;
  HWND hwnd=NULL; bool fakemulti=false;
  if (*c==0) {if (isexe) ScrMode=smInstall; else ScrMode=smConfig; hwnd=NULL;}
  else
  { if (*c=='-' || *c=='/') c++;
    if (*c=='u' || *c=='U') ScrMode=smUninstall;
    if (*c=='p' || *c=='P' || *c=='l' || *c=='L')
    { c++; while (*c==' ' || *c==':') c++;
      if (_tcsicmp(c,_T("scrprev"))==0) hwnd=CheckForScrprev(); else hwnd=(HWND)_ttoi(c); 
      ScrMode=smPreview;
    }
    else if (*c=='s' || *c=='S') {ScrMode=smSaver; fakemulti=(c[1]=='m'||c[1]=='M');}
    else if (*c=='c' || *c=='C') {c++; while (*c==' ' || *c==':') c++; if (*c==0) hwnd=GetForegroundWindow(); else hwnd=(HWND)_ttoi(c); ScrMode=smConfig;}
    else if (*c=='a' || *c=='A') {c++; while (*c==' ' || *c==':') c++; hwnd=(HWND)_ttoi(c); ScrMode=smPassword;}
  }
  //
  if (ScrMode==smInstall) {DoInstall(); return 0;}
  if (ScrMode==smUninstall) {DoUninstall(); return 0;}
  if (ScrMode==smPassword) {ChangePassword(hwnd); return 0;}
  //
  ReadGeneralRegistry();
  //
  INITCOMMONCONTROLSEX icx; ZeroMemory(&icx,sizeof(icx));
  icx.dwSize=sizeof(icx);
  icx.dwICC=ICC_UPDOWN_CLASS;
  InitCommonControlsEx(&icx);
  //
  WNDCLASS wc; ZeroMemory(&wc,sizeof(wc));
  wc.hInstance=hInstance;
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
  wc.lpszClassName=_T("ScrMonitor");
  wc.lpfnWndProc=MonitorWndProc;
  RegisterClass(&wc);
  //
  wc.lpfnWndProc=SaverWndProc;
  wc.cbWndExtra=8;
  wc.lpszClassName=_T("ScrClass");
  RegisterClass(&wc);
  //
  if (ScrMode==smConfig) DoConfig(hwnd);
  else if (ScrMode==smSaver || ScrMode==smPreview) DoSaver(hwnd,fakemulti);
  //
  return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Technical Lead
United States United States
Lucian studied theoretical computer science in Cambridge and Bologna, and then moved into the computer industry. Since 2004 he's been paid to do what he loves -- designing and implementing programming languages! The articles he writes on CodeProject are entirely his own personal hobby work, and do not represent the position or guidance of the company he works for. (He's on the VB/C# language team at Microsoft).

Comments and Discussions