Jak vytvořit spořič obrazovky v C++?

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.
Klepněte pro větší obrázek

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.

Jak na to?

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)

Diskuze (12) Další článek: AMD spustilo svou kampaň proti megahertzům

Témata článku: Software, Windows, Programování, Case, Dialog, Obrazovka, HFO, Break, Jak, Spořič, Významná složka, Okno, Message


Určitě si přečtěte

Microsoft Defender je jeden z nejlepších antivirových programů, tvrdí výsledky AV-TESTu
Karel Kilián
Windows DefenderAntivirusWindows 10
Velký den pro Apple: Uvedl tři nové Macy s vlastním procesorem M1
Lukáš Václavík
PočítačeApple
Dostali jste nový počítač? Tohle s ním udělejte, než ho začnete používat

Dostali jste nový počítač? Tohle s ním udělejte, než ho začnete používat

** Každý nový počítač si zaslouží počáteční péči ** Odinstalujte bloatware a nezapomeňte na vhodné nastavení ** Poradíme, jak se o počítač s Windows 10 postarat

David Polesný, Stanislav Janů | 71

David PolesnýStanislav Janů
PočítačeNotebooky
Čím nahradit WhatsApp: Vyberte si z 10 alternativních komunikátorů

Čím nahradit WhatsApp: Vyberte si z 10 alternativních komunikátorů

** Z WhatsAppu kvůli novým podmínkám utíkají tisíce uživatelů ** Čím nahradit populární aplikaci pro zasílání zpráv? ** Vybrali jsme pro vás 10 alternativních komunikátorů

Karel Kilián | 110

Karel Kilián
KomunikaceWhatsAppInstant Messaging
Vybíráme nejlepší monitory: Od úplně levných až po displeje na rozmazlování očí

Vybíráme nejlepší monitory: Od úplně levných až po displeje na rozmazlování očí

** Vybrali jsme nejlepší monitory na práci i pořádné hraní ** Nejlevnější monitor s kvalitním panelem nestojí ani tři tisíce ** Rozlišení 4K a větší obrazovka už není nedostupný luxus

David Polesný | 30

David Polesný
Monitory
Air Bank, Fio banka a MONETA zakládají alianci pro bankovní identitu
Jakub Čížek
BankaČeskoeGovernment
Nejlepší notebooky do 10 000 korun: Co má ještě smysl kupovat. A co ne?

Nejlepší notebooky do 10 000 korun: Co má ještě smysl kupovat. A co ne?

** Notebooky s cenou do deseti tisíc korun jsou plné kompromisů ** Existuje několik modelů dobře použitelných pro nenáročné použití ** Vhodnou alternativou jsou tablety nebo repasované počítače

David Polesný | 94

David Polesný
Jak vybrat notebookNotebooky
Google vymyslel technologii superpřesného GPS. Už ji podporuje Pixel 5 a dorazí i na ostatní telefony

Google vymyslel technologii superpřesného GPS. Už ji podporuje Pixel 5 a dorazí i na ostatní telefony

** Kvalita GPS ve městech občas stojí za starou bačkoru ** Mohou za to odrazy signálu od okolních budov ** Google má jejich 3D model, a tak spolupracuje s výrobci GPS čipů

Jakub Čížek | 40

Jakub Čížek
NavigaceTechnologieGoogle

Aktuální číslo časopisu Computer

Jak prodloužit výdrž notebooku

Velké testy: gamepady a inkoustové tiskárny

Důkladný test Sony Playstation 5