V tomto článku si ukážeme jak vytvořit spořič obrazovky pro Windows. Nepůjde zde samozřejmě o programování grafických efektů nebo něčeho podobného, ale o vytvoření kostry spořiče a pochopení základních principů spořiče obrazovky.
Spořič obrazovky je v podstatě spustitelná aplikace jako každá jiná. Rozdíl je ve způsobu spouštění. Spořiče obrazovky (resp. spustitelné soubory) mají příponu .scr a jsou standardně umístěny v systémové složce Windows. Konkrétně ve Windows 2000 je to .\winnt\system32, ve Windows Me pak .\windows\system. V těchto složkách také systém hledá "nainstalované" spořiče, které pak zobrazí ve známém dialogu nastavení vlastnosti obrazovky. Takže když vytvoříme vlastní spořič obrazovky, stačí výsledný xxx.exe přejmenovat na xxx.scr (ti zkušenější si to mohou nastavit přímo v nastavení linkeru) a zkopírovat tento soubor do výše uvedené složky.
Dále je důležité vědět, jak systém Windows spořič spouští. Podle toho, v jakém režimu má spořič běžet, systém ho spustí s jedním z následujících parametrů:
- /c popř. -c ... konfigurace, pokud uživatel v dialogu vlastností obrazovky zvolí volbu „nastavení“ u spořiče obrazovky
- /p popř. -p ... preview, tedy náhled – spořič může „běžet“ také v malém okně v dialogu nastavení vlastností obrazovky.
- /s popř. -s ... standardní spuštění – po nastaveném čase nečinnosti uživatele se spustí spořič v běžném režimu
V režimu "náhled" dále platí, že pokud se má spořič zobrazit v tom malém okně ("obrazovka") dialogu vlastností, jak vidíte na obrázku, je dalším parametrem handle okna, které má být jeho rodičem, tedy handle té "obrazovky".
Další specifickou vlastností spořiče je možnost jeho ochrany heslem. Toto se liší podle verze operačního systému. Ve Windows řady NT/2000 při nastavení této volby prostě systém zamkne počítač. Ve Windows řady 95/98/Me se musíme o tuto ochranu postarat v kódu. Jak ale uvidíte, nemusíme samozřejmě psát nějaké speciální funkce, prostě zavoláme jistou "systémovou funkci", která "zařídí zbytek". Vše si ukážeme dále v článku.
Ukážeme si příklad vytvoření spořiče obrazovky ve Visual C++. Možný postup je tento: Vytvoříme aplikaci typu dokument/pohled, odstraníme třídy dokumentu, pohledu i FrameWindow. Přidáme si vlastní okno odvozené od CWnd, nazvané třeba CRunWindow, dále si přidáme dialog (CConfigDialog), který bude vyvolán v režimu konfigurace.
Do třídy aplikace si přidáme pomocnou funkci:
TCHAR CUkazkaApp::GetSaverMode()
{
LPTSTR lpCmdLine = CharLower(AfxGetApp()->m_lpCmdLine);
if ( *lpCmdLine == `\0` )
return ` `;
if ( lpCmdLine++;
return *lpCmdLine;
}
která nám vrátí jako 1 znak režim spuštění spořiče. Pro případ režimu "náhled" v malém okně budeme ještě potřebovat z druhého parametru získat handle rodičovského okna. To můžeme vyřešit třeba takovouto funkcí:
HWND CUkazkaApp::GetParentWindow()
{
HWND hwnd;
if ( __argc < 3 )
return NULL;
hwnd = (HWND)atol(__argv[2]);
if ( !IsWindow(hwnd) )
return NULL;
return hwnd;
}
Nyní již můžeme upravit metodu InitInstance, ve které provádíme rozhodovací kroky při spuštění spořiče:
BOOL CUkazkaApp::InitInstance()
{
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
// ve Windows 98 a 2000 takto zjistíme zda běží jakýkoli spořič
BOOL bRunning;
SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &bRunning, 0);
if ( bRunning )
return FALSE;
// testujeme, zda tento spořič již běží, nebudeme ho spouštět dvakrát
m_hMutex = CreateMutex(NULL, TRUE, "MujSporicObrazovky");
if (GetLastError() == ERROR_ALREADY_EXISTS )
{
if (m_hMutex)
CloseHandle(m_hMutex);
// sporic jiz bezi
MessageBeep(MB_ICONQUESTION);
return FALSE;
}
TCHAR ch = GetSaverMode();
switch ( ch )
{
case `c`: // config - nastavení
m_ConfigDialog = new CConfigDialog();
m_ConfigDialog->DoModal();
delete m_ConfigDialog;
return FALSE;
case `p`: // preview - náhled spořiče
RunPreview();
break;
case `s`: // spustit spořič
RunSaver();
break;
default:
RunSaver();
break;
}
return TRUE;
}
Jak vidíte, nejdříve zkusíme otestovat pomocí funkce SystemParametersInfo, zda již běží jakýkoli spořič obrazovku. Toto však funguje pouze ve Windows 98 a 2000 a navíc v praxi asi těžko nastane že by se za běhu jiného spořiče spustil ten náš. Pouze kdyby si uživatel "hrál" a vytvořil nějaký "automat" na výměnu aktuálního spořiče. Proto dále použijeme mutex, pomocí kterého zjistíme, zda je spuštěn tento náš spořič. Pokud tedy zjistíme, že žádný spořič neběží, z parametrů příkazové řádky zjistíme požadovaný režim a zavoláme jednu z připravených funkcí, popřípadě vyvoláme dialog nastavení.
Nyní k vytvoření okna pro běh spořiče ve standardním režimu. Toto okno by mělo být roztažené na celou obrazovku a být ve stavu "na vrchu". Pokud použijeme třídu odvozenou od CWnd, může funkce pro vytvoření okna a spuštění spořiče vypadat takto:
BOOL CUkazkaApp::RunSaver()
{
m_RunWindow = new CRunWindow();
m_RunWindow->CreateEx(WS_EX_TOPMOST,
AfxRegisterWndClass(0, NULL,
(HBRUSH)CreatePatternBrush(LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BACKGROUND)))),
"run saver",
WS_POPUP | WS_VISIBLE,
0, 0,
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
(HWND)NULL,
(HMENU)NULL,
NULL);
m_pMainWnd = m_RunWindow;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
ShowCursor(FALSE);
UINT ui;
SystemParametersInfo(SPI_SCREENSAVERRUNNING, 1, &ui, 0);
return TRUE;
}
Kromě vytvoření okna v této funkci ještě skryjeme kurzor pomocí funkce ShowCursor a v případě, že jsme ve Windows 95/98/Me zakážeme klávesové kombinace typu <Alt><TAB>, <Ctrl><Alt><Del> apod., neboť v případě, že bude požadována ochrana heslem, bylo by možné toto obejít například přepnutím do jiné aplikace. Samozřejmě že ve Windows 98 tato ochrana spořiče heslem není nijak silná, ale to už je jiná kapitola a návodů jak toto nabourat, je na Internetu spousta. K tomuto zakázání použijeme funkci SystemParametersInfo s parametrem SPI_SCREENSAVERRUNNING. Pokud jako parametr uiParam uvedeme 1, klávesové zkratky zakážeme, uvedením hodnoty 0 je opět povolíme (což uděláme na "konci" aplikace).
Pokud má spořič běžet v režimu náhled, zjistíme, zda jde o zmíněný „malý náhled“ a použijeme získaný handle okna jako rodičovské okno našeho spořiče. Dále si zjistíme, zda tímto rodičem je opravdu to malé okno, nebo celý desktop, což může nastat, když uživatel vyvolá náhled příslušným tlačítkem v dialogu „vlastnosti zobrazení“. Výsledek uložíme do členské proměnné třídy CRunWindow, kterou pak použijeme pro větvení kreslící funkce.
BOOL CUkazkaApp::RunPreview()
{
RECT rectParent;
m_RunWindow = new CRunWindow();
HWND hwndParent = GetParentWindow();
if ( IsWindow(hwndParent) )
{
GetClientRect(hwndParent, &rectParent);
if ( rectParent.right < 200 )
m_RunWindow->m_SmallPreview = TRUE;
m_RunWindow->CreateEx(WS_EX_TOPMOST,
AfxRegisterWndClass(0, NULL,
(HBRUSH)CreatePatternBrush(LoadBitmap(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDB_BACKGROUND)))),
"saver",
WS_VISIBLE | WS_CHILD,
0, 0,
rectParent.right, rectParent.bottom,
(HWND)hwndParent,
(HMENU)NULL,
NULL);
}
else
{
m_RunWindow->CreateEx(WS_EX_TOPMOST,
AfxRegisterWndClass(0, NULL,
(HBRUSH)CreatePatternBrush(LoadBitmap(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDB_BACKGROUND)))),
"saver",
WS_POPUP | WS_VISIBLE,
0, 0,
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
(HWND)NULL,
(HMENU)NULL,
NULL);
ShowCursor(FALSE);
UINT ui;
SystemParametersInfo(SPI_SCREENSAVERRUNNING, 1, &ui, 0);
}
m_pMainWnd = m_RunWindow;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
Nyní se podívejme na specifika běhu spořiče. Musíme nějakým vhodným způsobem zajistit jeho včasné ukončení. Není to samozřejmě podmínkou, ale je "dobrým zvykem" ukončit spořič na stisknutí jakékoli klávesy, tlačítka myši a také na "významný" pohyb myši. Zde stojí za úvahu co považovat za významný pohyb. Posun o jeden pixel může totiž snadno nastat po nějakém nechtěném otřesu stolu. Považoval bych proto za rozumné stanovit nějaký práh, řekněme 6 pixelů, o který se musí myš přemístit aby to bylo považováno za podnět k ukončeni spořiče. Podívejme se, jak může vypadat procedura okna, která toto řeší:
LRESULT CRunWindow::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
POINTS points;
if ( m_SmallPreview ) return CWnd::WindowProc(message, wParam, lParam); switch ( message )
{
case WM_CREATE:
GetCursorPos(&m_oldPos);
break;
case WM_SYSCOMMAND:
if ( wParam == SC_CLOSE )
{
TryCloseSaver();
break;
}
if ( wParam == SC_SCREENSAVE )
return 0;
break;
case WM_MOUSEMOVE:
points = MAKEPOINTS(lParam);
if ( abs(points.x - m_oldPos.x) > _MouseThreshold )
TryCloseSaver();
if ( abs(points.y - m_oldPos.y) > _MouseThreshold )
TryCloseSaver();
break;
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONDOWN:
#if _WIN32_WINNT >= 0x0500
case WM_XBUTTONDOWN: // tato zpráva je pouze ve Windows 2000 a vyšší
#endif // _WIN32_WINNT >= 0x0500
case WM_KEYDOWN:
case WM_MOUSEWHEEL:
TryCloseSaver();
break;
}
return CWnd::WindowProc(message, wParam, lParam);
}
Při vytvoření okna (WM_CREATE) si uložíme počáteční pozici kurzoru, při pohybu myší porovnáváme rozdíl souřadnic s definovanou hodnotou
#define _MouseThreshold 6
Pokud jsme v „malém náhledu“ nebudeme spořič ukončovat aktivitou uživatele, proto obskočíme všechny handlery procedury okna.
V případě požadavku na ukončení spořiče voláme vlastní funkci:
void CRunWindow::TryCloseSaver()
{
if ( VerifyPassword() )
{
DestroyWindow();
PostQuitMessage(0);
}
}
V případě "úspěšné" kontroly hesla musíme odstranit okno spořiče a ukončit celou aplikaci použitím funkce PostQuitMessage, které pošle do smyčky zpráv zprávu WM_QUIT, čímž způsobí její ukončení. Podívejme se na funkci ověřující heslo:
BOOL CRunWindow::VerifyPassword()
{
OSVERSIONINFO osv;
ZeroMemory(&osv, sizeof(OSVERSIONINFO));
osv.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if ( !GetVersionEx(&osv) )
return TRUE;
if ( osv.dwPlatformId == VER_PLATFORM_WIN32_NT )
return TRUE;
HINSTANCE hPsw = LoadLibrary("password.cpl");
if ( hPsw == NULL )
return TRUE;
typedef BOOL (WINAPI *VERIFYSCREENSAVEPWD)(HWND hwnd);
VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
VerifyScreenSavePwd = (VERIFYSCREENSAVEPWD)
GetProcAddress(hPsw,"VerifyScreenSavePwd");
if ( VerifyScreenSavePwd == NULL )
{
FreeLibrary(hPsw);
return TRUE;
}
BOOL bResult = VerifyScreenSavePwd(m_hWnd);
FreeLibrary(hPsw);
return bResult;
}
V této funkci nejdříve zjistíme platformu Windows. Pokud jsme ve Windows větve NT/2000, přenecháme "starost" operačnímu systému. V případě Windows řady 95 použijeme funkci "VerifyScreenSavePwd", která je v systémové dynamické knihovně "password.cpl". Tato funkce vyvolá onen známý dialog zadání hesla, a pokud je heslo ověřené, vrátí nám TRUE, a my povolíme ukončení spořiče.
Jak jsem se zmínil, musíme ještě před ukončením provést „úklid“ a uvedení systému do původního stavu. K tomu nám v MFC aplikaci slouží metoda třídy CWinApp
int CUkazkaApp::ExitInstance()
{
ReleaseMutex(m_hMutex);
CloseHandle(m_hMutex);
if ( m_RunWindow != NULL )
delete m_RunWindow;
ShowCursor(TRUE);
UINT ui;
SystemParametersInfo(SPI_SCREENSAVERRUNNING, 0, &ui, 0);
return CWinApp::ExitInstance();
}
Tím jsme si ukázali asi vše potřebné, k vytvoření kostry spořiče obrazovky pro Windows. Programování vlastní „akce“ je již jiná kapitola a prostor pro tvůrčí kreativitu programátora. V této ukázce je vše řešeno v handlenu zprávy WM_PAINT:
void CRunWindow::OnPaint()
{
CPaintDC dc(this); // device context for painting
RECT rect;
GetClientRect(&rect);
DrawIcon(dc.m_hDC, 2,2,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINICON)));
DrawIcon(dc.m_hDC,
rect.right - GetSystemMetrics(SM_CXICON) - 2, 2,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINICON)));
LOGFONT logfont;
HFONT hfont;
TCHAR chText[300];
dc.SetBkMode(TRANSPARENT);
if ( m_SmallPreview )
{
rect.top += GetSystemMetrics(SM_CYICON) + 5,
dc.SetTextColor(0x000000A0);
DrawText(dc.m_hDC, "náhled\nspořič obrazovky", -1, &rect,
DT_CENTER);
dc.SetTextColor(0x00A00000);
DrawText(dc.m_hDC, "www.rplusj.cz", -1, &rect,
DT_SINGLELINE | DT_CENTER |DT_BOTTOM);
}
else
{
DrawIcon(dc.m_hDC,
rect.right - GetSystemMetrics(SM_CXICON) - 2,
rect.bottom - GetSystemMetrics(SM_CYICON) - 2,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINICON)));
DrawIcon(dc.m_hDC,
2, rect.bottom - GetSystemMetrics(SM_CYICON) - 2,
LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_MAINICON)));
GetObject(GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &logfont);
InflateRect(&rect, -100, -100);
strcpy(logfont.lfFaceName, "Comic Sans MS");
logfont.lfHeight = 52;
hfont = CreateFontIndirect(&logfont);
::SelectObject(dc.m_hDC, hfont);
dc.SetTextColor(0x000000A0);
lstrcpy(chText, "A nyní spořič obrazovky běží\n");
lstrcat(chText, "...sice nic nedělá...\n");
lstrcat(chText, "...ale to už je jiná kapitola...\n");
lstrcat(chText, "...života programátora.\n");
DrawText(dc.m_hDC, chText, -1, &rect,
DT_CENTER);
dc.SetTextColor(0x00A00000);
lstrcpy(chText, "(C) Radek Chalupa - www.rplusj.cz");
DrawText(dc.m_hDC, chText, -1, &rect,
DT_SINGLELINE | DT_CENTER |DT_BOTTOM);
}
}
Ukázkový projekt si můžete stáhnout zde screen_saver.zip (55,2 kB)