quinta-feira, janeiro 29, 2009

Screen Savers - Exemplos

O código fonte e executável dos exemplos podem ser baixados daqui.

ScrBasico é um exemplo de screen saver feito utilizando diretamente a API do Windows. Não é um screen saver muito sofisticado: simplesmente mostra um texto no meio da tela, com a cor variando continuamente entre branco e preto.

O programa principal é simples:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrecInst,
LPSTR lpCmdLine, int nShowCmd)
{
hInst = hInstance;
AnalisaCmd (lpCmdLine);
switch (modo)
{
case smConfig:
DialogBox (hInst, MAKEINTRESOURCE (IDD_CONFIG), hMainWnd,
ConfigDlgProc);
break;

case smPassword:
ChangePassword ();
break;

case smPreview:
case smSaver:
DoScreenSaver ();
break;
}

return 0;
}
A rotina AnalisaCmd trata os parâmetros na linha de comando, conforme o que foi visto na segunda parte desta série:
static void AnalisaCmd (char *szCmd)
{
// valores default
hMainWnd = NULL;
modo = smConfig;

// por seguranca
if (szCmd == NULL)
return;

// pula delimitadores inicias
while ((*szCmd == ' ') || (*szCmd == 0x09))
szCmd++;

// testa comando vazio
if (*szCmd == 0)
return;

// testa e trata flags
if ((*szCmd == '-') || (*szCmd == '/'))
{
szCmd++;
switch (*szCmd)
{
// config
case 'c':
case 'C':
modo = smConfig;
hMainWnd = PegaHandle (szCmd+1);
return;

// screen saver
case 's':
case 'S':
modo = smSaver;
return;

// preview
case 'p':
case 'P':
case 'l':
case 'L':
modo = smPreview;
hMainWnd = PegaHandle (szCmd+1);
return;

// alteração de senha
case 'a':
case 'A':
modo = smPassword;
hMainWnd = PegaHandle (szCmd+1);
return;
}
}

// algo inesperado
modo = smNone;
}
O diálogo de configuração é simples, com um editbox para entrar a mensagem a ser apresentada. Esta mensagem é salva no registry.

A rotina de alteração de senha (usada somente pelos arqueólogos brincando com Windows 9x) apela para uma rotina não documentada do Windows, linkada dinamicamente da MPR.DLL:
static void ChangePassword()
{
HINSTANCE hmpr;


hmpr = LoadLibrary("MPR.DLL");
if (hmpr != NULL)
{
PWDCHANGEPASSWORD PwdChangePassword = (PWDCHANGEPASSWORD)
GetProcAddress (hmpr,"PwdChangePasswordA");
if (PwdChangePassword != NULL)
PwdChangePassword("SCRSAVE", hMainWnd, 0, 0);
FreeLibrary(hmpr);
}
}
A rotina DoScreenSaver funciona como um programa principal típico de aplicações Windows API: registra uma classe de janela, cria a janela e fica em um laço de tratamento de mensagens:
static void DoScreenSaver ()
{
WNDCLASS wc;
int cx, cy;
UINT oldval;
MSG msg;

// Registra a nossa classe de janela
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = ScreenSaverProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = szWndClass;
RegisterClass (&wc);

// Cria a janela
if (modo == smPreview)
{
RECT rc;

GetWindowRect (hMainWnd, &rc);
cx = rc.right - rc.left;
cy = rc.bottom - rc.top;
hSSWnd = CreateWindowEx (0, szWndClass, "Preview", WS_CHILD | WS_VISIBLE,
0, 0, cx, cy, hMainWnd, NULL, hInst, NULL);
}
else
{
cx = GetSystemMetrics (SM_CXSCREEN);
cy = GetSystemMetrics (SM_CYSCREEN);
hSSWnd = CreateWindowEx (WS_EX_TOPMOST, szWndClass, "ScreenSaver",
WS_POPUP | WS_VISIBLE,
0, 0, cx, cy, NULL, NULL, hInst, NULL);
}
if (hSSWnd == NULL)
return;

// avisa screen saver rodando (desativa Ctrl Alt Del)
if (modo == smSaver)
SystemParametersInfo(SPI_SCREENSAVERRUNNING,1,&oldval,0);

// Loop de tratamento de mensagens
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

// avisa fim do screen saver (reativa Ctrl Alt Del)
if (modo == smSaver)
SystemParametersInfo(SPI_SCREENSAVERRUNNING,0,&oldval,0);
}
A rotina de janela trata as mensagens relevantes:
LRESULT WINAPI ScreenSaverProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static POINT InitCursorPos; // posicao inicial do mouse
static UINT uTimer; // identificador do timer
static int iCor; // cor da mensagem
static int iDelta; // variacao da cor
static BOOL bFecha = FALSE; // TRUE se geramos WM_CLOSE
static DWORD dwInicio; // inicio da execucao do screen saver


switch (uMsg)
{
case WM_CREATE: // janela foi criada
LeCfg (szMsg, sizeof (szMsg));
GetCursorPos (&InitCursorPos); // salva posicao do mouse
uTimer = SetTimer(hWnd, 1, 150, NULL); // liga o timer
dwInicio = GetTickCount (); // guarda horario inicial
iCor = 0;
iDelta = 4;
break;

case WM_TIMER: // mensagem periodica
Desenha (hWnd, szMsg, iCor);
iCor += iDelta;
if (iCor < icor =" 0;"> 255)
iCor = 255;
if ((iCor == 0) || (iCor == 255))
iDelta = -iDelta;
break;

case WM_ACTIVATE: case WM_ACTIVATEAPP: case WM_NCACTIVATE:
if ((modo == smSaver) && !bDialogo && (LOWORD(wParam) == WA_INACTIVE))
{
// Desativar screen saver
bFecha = TRUE;
PostMessage (hWnd, WM_CLOSE, 0, 0);
}
break;

case WM_SETCURSOR:
if ((modo == smSaver) && !bDialogo)
SetCursor (NULL); // apaga o cursor do mouse
else
SetCursor (LoadCursor (NULL,IDC_ARROW));
break;

case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN:
if ((modo == smSaver) && !bDialogo)
{
// apertou botao ou tecla, fechar screen saver
bFecha = TRUE;
PostMessage (hWnd, WM_CLOSE, 0, 0);
}
break;

case WM_MOUSEMOVE:
if ((modo == smSaver) && !bDialogo)
{
POINT pt;
int dx, dy;

// Verifica se moveu o suficente para desativar
GetCursorPos(&pt);
dx = pt.x - InitCursorPos.x;
if (dx < dx =" -dx;" dy =" pt.y" dy =" -dy;"> MouseThreshold) || (dy > MouseThreshold))
{
bFecha = TRUE;
PostMessage (hWnd, WM_CLOSE, 0, 0);
}
}
break;

case WM_SYSCOMMAND:
if (modo == smSaver)
{
// Ignora ativacao de screen saver e close
if ((wParam == SC_SCREENSAVE) || (wParam == SC_CLOSE))
return FALSE;
}
break;

case WM_CLOSE:
if ((modo == smSaver) && !bDialogo && bFecha)
{
BOOL bOk = TRUE;

// Estamos tentando fechar
if (GetTickCount() > 5000) // Pode interromper sem senha se for rapido
{
bOk = VerifyPassword (hWnd);
GetCursorPos (&InitCursorPos); // salva posicao do mouse
}
if (bOk)
DestroyWindow (hWnd); // encerra screen saver
}
bFecha = FALSE;
if (modo == smSaver)
return FALSE; // ignora WM_CLOSE
break;

case WM_DESTROY:
if (uTimer)
KillTimer(hWnd, uTimer);
PostQuitMessage(0);
break;
}

return DefWindowProc (hWnd, uMsg, wParam, lParam);
}
Um timer é usado para re-escrever a mensagem em uma cor diferente a cada 150 milisegundos.

Um último ponto de interesse é a rotina VerifyPassword:
static BOOL VerifyPassword (HWND hwnd)
{
OSVERSIONINFO osv;
HINSTANCE hpwdcpl;
VERIFYSCREENSAVEPWD VerifyScreenSavePwd;
BOOL bResult;

// No NT e derivados, o screen saver deve terminar, o sistema solicita a senha
// e re-executa o screen saver se senha incorreta
osv.dwOSVersionInfoSize = sizeof(osv);
GetVersionEx (&osv);
if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT)
return TRUE;

// No 95/98/Me, usa funcao nao documentada
hpwdcpl = LoadLibrary ("PASSWORD.CPL");
if (hpwdcpl == NULL)
return TRUE;
VerifyScreenSavePwd = (VERIFYSCREENSAVEPWD)
GetProcAddress (hpwdcpl,"VerifyScreenSavePwd");
if (VerifyScreenSavePwd == NULL)
{
FreeLibrary(hpwdcpl);
return TRUE;
}
bDialogo = TRUE;
SendMessage (hwnd, WM_SETCURSOR, 0, 0); // mostra cursor
bResult = VerifyScreenSavePwd (hwnd);
bDialogo = FALSE;
SendMessage (hwnd, WM_SETCURSOR, 0, 0); // esconde cursor
FreeLibrary (hpwdcpl);
return bResult;
}
Reparar que no Windows 9x usamos uma segunda função não documentada; nos Windows mais recentes não precisamos fazer nada.

O mesmo screen saver, feito com a Screen Saver Library está em LibScrSaver. Neste caso a biblioteca cuida de quase tudo, o que interessa é somente a rotina ScreenSaverProc:
LRESULT WINAPI ScreenSaverProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static UINT uTimer;
static int iCor;
static int iDelta;

// Trata mensagens
switch (uMsg)
{
// Iniciacao: le config e cria timer
case WM_CREATE:
LeCfg (szMsg, sizeof (szMsg));
uTimer = SetTimer(hWnd, 1, 150, NULL);
iCor = 0;
iDelta = 4;
break;

// Chamada periodica, faz a animacao
case WM_TIMER:
Desenha (hWnd, szMsg, iCor);
iCor += iDelta;
if (iCor < icor =" 0;"> 255)
iCor = 255;
if ((iCor == 0) || (iCor == 255))
iDelta = -iDelta;
break;

// Termino: destroi o timer
case WM_DESTROY:
if (uTimer)
KillTimer(hWnd, uTimer);
break;
}

// Executa tratamento default
return DefScreenSaverProc(hWnd, uMsg, wParam, lParam);
}
Percebe-se que com a biblioteca podemos nos concentrar na parte que interessa: atualizar a tela.

Nenhum comentário: