Пишем простой cкринсейвер
Скринсейвер (или хранитель экрана) – это, по сути, обычная программа, в которой определены несколько специальных процедур для обработки сообщений, посылаемых ОС, при запуске и конфигурировании скринсейвера.
Программы-скринсейверы обычно хранятся в директории %WINDIR%\System32 и обладают расширением .scr. Таким образом, при переходе на страницу выбора заставки, Windows ищет в системной директории все файлы с соответствующим расширением и формирует из них список возможных скринсейверов. Также следует упомянуть несколько важных аспектов написания.
– Скринсейвер должен экспортировать функции ScreenSaverConfigureDialog, ScreenSaverProc.
– Название скринсейвера в окне настройки определяется строковым ресурсом с идентификатором IDS_DESCRIPTION, который должен быть равен 1.
– Идентификатор диалогового окна, которое будет появляться при нажатии клавиши “Параметры”, т.е. при попытке настроить скринсейвер, должен быть DLG_SCRNSAVECONFIGURE и равняться числу 2003.
– Программа также должна содержать реализацию функции RegisterDialogClasses.
В принципе, почти всё это описано в MSDN. В нашем скринсейвере будет изображена вращающаяся изометрическая проекция куба. Выглядеть результат будет следующим образом:
Экран выбора скринсейвера
Настройки скринсейвера
Результат работы
Теперь рассмотрим сам код. Для начала подключим необходимые файлы, укажем lib-файлы, необходимые при линковке, и определим несколько констант.
#undef UNICODE #include #include #include #include #include "resource.h" #pragma comment(lib, "Scrnsave") #pragma comment(lib, "comctl32") //Можно было импортировать константу из math.h, но зачем... #define M_PI 3.14159265358979323846 //Текст, который будет отображаться в превью скринсейвера #define scr_name "Spining cube" #define scr_auth "kaimi.ru"
Также неплохо было бы реализовать сохранение настроек, которые мы будем хранить в реестре.
#pragma pack(push, 1) //Структура, которая хранит цвет, используемый в скринсейвере, и скорость вращения куба static struct { union { DWORD settings; struct { BYTE r; BYTE g; BYTE b; BYTE pos; }; }; } scr_settings; #pragma pack(pop) //Функция загрузки/сохранения настроек void LoadSaveSettings(BOOL do_save) { HKEY key; DWORD disposition; DWORD type = REG_DWORD, size = sizeof(REG_DWORD); //Открываем ключ в реестре с правами на чтение/запись (если ключ не существует, то он будет создан) if(RegCreateKeyEx(HKEY_CURRENT_USER, "Software\Sample\SimpleScreenSaver", 0, NULL, 0, KEY_WRITE | KEY_READ, NULL, &key, &disposition) == ERROR_SUCCESS) { //Сохраняем/Загружаем данные в виде одного DWORD'а if(do_save) RegSetValueEx(key, "Settings", 0, type, (PBYTE)&scr_settings.settings, size); else RegQueryValueEx(key, "Settings", NULL, &type, (PBYTE)&scr_settings.settings, &size); RegCloseKey(key); } }
Перейдем к основной процедуре, которая вызывается при запуске скринсейвера.
LONG WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { //Определяем необходимые переменные static HDC hDC; static HPEN hPen; static UINT timer_id; static RECT rect; static PAINTSTRUCT ps = {0}; //Предвычисленные массивы для хранения значений sin/cos static double s[360], c[360]; //Массивы для хранения оригинальных координат линий и координат после поворота static double lO[12][2][4], lR[12][2][4]; //Размер куба static int side_size = 500; //Координаты центра куба static int xCenter = 0, yCenter = 0, zCenter = 0; //Углы поворота static int theta = 0, phi = 0; static int thetaRot = 2, phiRot = 2; //Массивы для хранения координат ребер куба static double scrX[12][2], scrY[12][2]; static int i; switch(message) { case WM_CREATE: //Загружаем настройки в структуру LoadSaveSettings(FALSE); //Задаем цвет и толщину линий куба hPen = CreatePen(PS_SOLID, 5, RGB(scr_settings.r, scr_settings.g, scr_settings.b)); //Вычисляем центры проекции куба на основе разрешения экрана xCenter = GetSystemMetrics (SM_CXSCREEN) / 2; yCenter = GetSystemMetrics (SM_CYSCREEN) / 2; zCenter = xCenter + yCenter - 4; //Задаем область, которую в дальнейшем будем перерисовывать rect.left = xCenter - side_size; rect.top = yCenter - side_size; rect.right = xCenter + side_size; rect.bottom = yCenter + side_size; //Вычисляем значения sin и cos for(i = 0; i < 360; i++) { s[i] = sin(i * (M_PI / 180)); c[i] = cos(i * (M_PI / 180)); } //Задаем координаты ребер куба lO[0][0][0] = -side_size; lO[0][0][1] = side_size; lO[0][0][2] = side_size; lO[0][1][0] = side_size; lO[0][1][1] = side_size; lO[0][1][2] = side_size; lO[1][0][0] = side_size; lO[1][0][1] = -side_size; lO[1][0][2] = side_size; lO[1][1][0] = side_size; lO[1][1][1] = side_size; lO[1][1][2] = side_size; lO[2][0][0] = side_size; lO[2][0][1] = side_size; lO[2][0][2] = -side_size; lO[2][1][0] = side_size; lO[2][1][1] = side_size; lO[2][1][2] = side_size; lO[3][0][0] = -side_size; lO[3][0][1] = -side_size; lO[3][0][2] = side_size; lO[3][1][0] = -side_size; lO[3][1][1] = side_size; lO[3][1][2] = side_size; lO[4][0][0] = -side_size; lO[4][0][1] = side_size; lO[4][0][2] = -side_size; lO[4][1][0] = -side_size; lO[4][1][1] = side_size; lO[4][1][2] = side_size; lO[5][0][0] = -side_size; lO[5][0][1] = -side_size; lO[5][0][2] = side_size; lO[5][1][0] = side_size; lO[5][1][1] = -side_size; lO[5][1][2] = side_size; lO[6][0][0] = -side_size; lO[6][0][1] = side_size; lO[6][0][2] = -side_size; lO[6][1][0] = side_size; lO[6][1][1] = side_size; lO[6][1][2] = -side_size; lO[7][0][0] = -side_size; lO[7][0][1] = -side_size; lO[7][0][2] = -side_size; lO[7][1][0] = side_size; lO[7][1][1] = -side_size; lO[7][1][2] = -side_size; lO[8][0][0] = -side_size; lO[8][0][1] = -side_size; lO[8][0][2] = -side_size; lO[8][1][0] = -side_size; lO[8][1][1] = side_size; lO[8][1][2] = -side_size; lO[9][0][0] = side_size; lO[9][0][1] = -side_size; lO[9][0][2] = -side_size; lO[9][1][0] = side_size; lO[9][1][1] = -side_size; lO[9][1][2] = side_size; lO[10][0][0] = side_size; lO[10][0][1] = -side_size; lO[10][0][2] = -side_size; lO[10][1][0] = side_size; lO[10][1][1] = side_size; lO[10][1][2] = -side_size; lO[11][0][0] = -side_size; lO[11][0][1] = -side_size; lO[11][0][2] = -side_size; lO[11][1][0] = -side_size; lO[11][1][1] = -side_size; lO[11][1][2] = side_size; //Запускаем таймер, по которому будет перерисовываться куб timer_id = SetTimer(hWnd, 1, 500 - 5 * scr_settings.pos + 1, NULL); break; case WM_DESTROY: //При закрытии окна скринсейвера останавливаем таймер и удаляем созданный объект if(timer_id) KillTimer(hWnd, timer_id); if(hPen) DeleteObject(hPen); //Сообщаем системе о том, что текущий поток сделал запрос на прекращение работы PostQuitMessage(0); break; case WM_TIMER: //Вычисляем координаты ребер куба for(i = 0; i < 12; i++) { lR[i][0][0] = -lO[i][0][0] * s[theta] + lO[i][0][1] * c[theta]; lR[i][0][1] = -lO[i][0][0] * c[theta] * s[phi] - lO[i][0][1] * s[theta] * s[phi] - lO[i][0][2] * c[phi] + 1; lR[i][0][2] = -lO[i][0][0] * c[theta] * c[phi] - lO[i][0][1] * s[theta] * c[phi] + lO[i][0][2] * s[phi]; lR[i][1][0] = -lO[i][1][0] * s[theta] + lO[i][1][1] * c[theta]; lR[i][1][1] = -lO[i][1][0] * c[theta] * s[phi] - lO[i][1][1] * s[theta] * s[phi] - lO[i][1][2] * c[phi] + 1; lR[i][1][2] = -lO[i][1][0] * c[theta] * c[phi] - lO[i][1][1] * s[theta] * c[phi] + lO[i][1][2] * s[phi]; if(lR[i][0][2] + zCenter > 0 || lR[i][0][2] + zCenter < 0) { scrX[i][0] = 256 * (lR[i][0][0] / (lR[i][0][2] + zCenter)) + xCenter; scrY[i][0] = 256 * (lR[i][0][1] / (lR[i][0][2] + zCenter)) + yCenter; } if(lR[i][1][2] + zCenter > 0 || lR[i][1][2] + zCenter < 0) { scrX[i][1] = 256 * (lR[i][1][0] / (lR[i][1][2] + zCenter)) + xCenter; scrY[i][1] = 256 * (lR[i][1][1] / (lR[i][1][2] + zCenter)) + yCenter; } } //Помечаем область, содержащую куб, как требующую перерисовки InvalidateRect(hWnd, &rect, TRUE); break; case WM_PAINT: //Вызываем функцию, подготавливающую окно к выводу графической информации hDC = BeginPaint(hWnd, &ps); //fChildPreview - глобальная переменная, показывающая смотрим мы превью или полноэкранную версию скринсейвера if(!fChildPreview) { //Выбираем перо, которое будет использоваться для рисования куба SelectObject(hDC, hPen); //Рисуем ребра куба for(i = 0; i < 12; i++) { MoveToEx(hDC, (int)scrX[i][0], (int)scrY[i][0], NULL); LineTo(hDC, (int)scrX[i][1], (int)scrY[i][1]); } theta = (theta + thetaRot) % 360; phi = (phi + phiRot) % 360; } else { //Выводим текст с описанием скринсейвера SetBkColor(hDC, RGB(0, 0, 0)); SetTextColor(hDC, RGB(0, 255, 0)); TextOut(hDC, 35, 30, scr_name, strlen(scr_name)); TextOut(hDC, 50, 50, scr_auth, strlen(scr_auth)); } //Завершаем рисование в окне EndPaint(hWnd, &ps); break; default: return DefScreenSaverProc(hWnd, message, wParam, lParam); } return 0; }
Теперь функция, которая вызывается при нажатии кнопки “Параметры”, и функция RegisterDialogClasses, которая в этом примере не используется и просто возвращает TRUE.
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: //Загружаем текущие настройки LoadSaveSettings(FALSE); //Восстанавливаем состояние элементов управления в соответствии с настройками SendDlgItemMessage(hDlg, SPIN_SLIDER, TBM_SETPOS, TRUE, scr_settings.pos); SetDlgItemInt(hDlg, R_EDIT, scr_settings.r, FALSE); SetDlgItemInt(hDlg, G_EDIT, scr_settings.g, FALSE); SetDlgItemInt(hDlg, B_EDIT, scr_settings.b, FALSE); //Задаем диапазон возможных значений для элементов Spin Control SendDlgItemMessage(hDlg, R_SPIN, UDM_SETRANGE32, 0, 255); SendDlgItemMessage(hDlg, G_SPIN, UDM_SETRANGE32, 0, 255); SendDlgItemMessage(hDlg, B_SPIN, UDM_SETRANGE32, 0, 255); //Задаем соответствие между Spin и Edit Control'ами SendDlgItemMessage(hDlg, R_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, R_EDIT), 0); SendDlgItemMessage(hDlg, G_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, G_EDIT), 0); SendDlgItemMessage(hDlg, B_SPIN, UDM_SETBUDDY, (WPARAM) GetDlgItem(hDlg, B_EDIT), 0); return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { //При нажатии клавиши "OK" case IDOK: //Получаем значения контролов с диалогового окна scr_settings.pos = (BYTE)SendDlgItemMessage(hDlg, SPIN_SLIDER, TBM_GETPOS, 0, 0); scr_settings.r = GetDlgItemInt(hDlg, R_EDIT, NULL, FALSE); scr_settings.g = GetDlgItemInt(hDlg, G_EDIT, NULL, FALSE); scr_settings.b = GetDlgItemInt(hDlg, B_EDIT, NULL, FALSE); //Сохраняем настройки LoadSaveSettings(TRUE); //Закрываем диалоговое окно EndDialog(hDlg, LOWORD(wParam)); break; //При нажатии клавиши "Отмена" case IDCANCEL: EndDialog(hDlg, LOWORD(wParam)); break; } return TRUE; } return FALSE; } BOOL WINAPI RegisterDialogClasses(HANDLE hInst) { return TRUE; }
И, наконец, экспортируем необходимые функции через .def-файл.
EXPORTS ScreenSaverConfigureDialog ScreenSaverProc
Внимательный читатель, наверное, заметил, что я не привел содержимое файла ресурсов, но это не имеет особого смысла, так как он был полностью сгенерирован визуальным редактором MSVC за исключением нескольких констант.
Исходный код и бинарник: скачать