Скринсейвер (или хранитель экрана) - это, по сути, обычная программа, в которой определены несколько специальных процедур для обработки сообщений, посылаемых ОС, при запуске и конфигурировании скринсейвера.
Программы-скринсейверы обычно хранятся в директории %WINDIR%\System32 и обладают расширением .scr. Таким образом, при переходе на страницу выбора заставки, Windows ищет в системной директории все файлы с соответствующим расширением и формирует из них список возможных скринсейверов. Также следует упомянуть несколько важных аспектов написания.
– Скринсейвер должен экспортировать функции ScreenSaverConfigureDialog, ScreenSaverProc.
– Название скринсейвера в окне настройки определяется строковым ресурсом с идентификатором IDS_DESCRIPTION, который должен быть равен 1.
– Идентификатор диалогового окна, которое будет появляться при нажатии клавиши "Параметры", т.е. при попытке настроить скринсейвер, должен быть DLG_SCRNSAVECONFIGURE и равняться числу 2003.
– Программа также должна содержать реализацию функции RegisterDialogClasses.
В принципе, почти всё это описано в MSDN. В нашем скринсейвере будет изображена вращающаяся изометрическая проекция куба. Выглядеть результат будет следующим образом:
Экран выбора скринсейвера
Настройки скринсейвера
Результат работы
Теперь рассмотрим сам код. Для начала подключим необходимые файлы, укажем lib-файлы, необходимые при линковке, и определим несколько констант.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#undef UNICODE #include <Windows.h> #include <ScrnSave.h> #include <CommCtrl.h> #include <math.h> #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.io" |
Также неплохо было бы реализовать сохранение настроек, которые мы будем хранить в реестре.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#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); } } |
Перейдем к основной процедуре, которая вызывается при запуске скринсейвера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
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-файл.
1 2 3 |
EXPORTS ScreenSaverConfigureDialog ScreenSaverProc |
Внимательный читатель, наверное, заметил, что я не привел содержимое файла ресурсов, но это не имеет особого смысла, так как он был полностью сгенерирован визуальным редактором MSVC за исключением нескольких констант.
Исходный код и бинарник: скачать
Наконец-то ежедневный рефреш этого блога принес какие-то плоды.
Советую подписать на RSS ленту ;)
Я не ищу легких путей
Просто Крякер петушок
Слушай напиши мне на почту Kaimi
есть предложение.
Я не пользуюсь почтой, если что-то надо - пиши в icq.
каими, а почему не заюзал opengl?
Я сначала думал его использовать, но потом передумал. Нет конкретной причины.
странное дело, на 2х мониторах не работает.
Т.е. просто включается чёрный экран и всё. Шевелишь мышкой - выключается. Такой вот глюк.
btw, win7 pro x64
в настройках цвет RGB хотя бы больше 40 выстави.
Не глюк - а фича получается))
У меня тоже не работает... RGB выставил
Не везет. На XP SP3 x86, Win 7 SP1 x64 с одним монитором проблем не наблюдается.
На Vista x64 с двумя мониторами тоже.
Win7 x64 просто черный екран
А у миня норм
На чем написано? Нашуя бинарник aps?
По коду не видно? Где aps то? scr в архиве