Добрый день. У меня, на домашнем компе, в качестве обоев установлен черный цвет, и я к нему привык, мне удобно работать с таким фоном. На моем не домашнем компе, стоят обои, довольно светлые. При смене обоев на черный цвет, через некоторое время, обои возвращаются. Поменять обои на черный цвет не трудно: ПКМ->Персонализация->итд . Но я решил менять фон рабочего стола программно. Исходный текст на GitHub'е (https://github.com/bvr63/ChangeDesktopColor). Написана на C++, в VS2026. Программа циклически меняет фон рабочего стола: Файл ->Solid Color->Файл ->Solid Color->...
Ниже опишу некоторые моменты.
Программа консольная, для windows.При запуске программы я не хочу видеть окно консоли => окно консоли надо скрыть. В программе есть 2 варианта скрыть окно.
HWND hWnd = GetConsoleWindow();ShowWindow(hWnd, SW_HIDE);
Получить хендл окна, затем скрыть окно. При этом варианте окно создается, потом скрывается, есть эффект мелькания.Использовать директивы для линкера
#pragma comment(linker, "/subsystem:windows")#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
Первая директива предписывает линкеру собрать приложение как Windows GUI и искать точку входа WinMain(). Вторая директива - скрывает консоль и предписывает линкеру искать main(). При таком решении окно консоли вообще не создается, и как следствие, нет мелькания. Эти директивы применяются для всех конфигураций, кроме Debug.
#ifndef _DEBUG //скрыть консольное окно: либо программно скрыть его сразу после запуска //ShowWindow(hWnd, SW_HIDE), либо настроить проект так, чтобы оно вообще не создавалось // /subsystem:windows #pragma comment(linker, "/subsystem:windows") #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #endif // _DEBUG
Менять фон рабочего стола можно через COM или WINAPI. В программе определена #define MyCOM для выбора используемой технологии.
//Если определена MyCOM, то обновление фона через СОМ, иначе через WINAPI
//#define MyCOM
Мне WINAPI больше нравится, я использовал его. COM оставил для информации.
В программе определено 3 конфигурации: Debug, Release, TEST. Конфигурация TEST наследована от Release. Для конфигурации TEST в свойствах проекта в разделе C/C++ -> Препроцессор -> Определения препроцессора добавлена _TEST .
Опишу TEST. Release отличается от TEST тем, что файлы обоев могут располагаться на разных дисках, в разных папках. При конфигурации TEST, файлы обоев должны располагаться в папке с программой и должны быть 0.jpg, 1.jpg, 2.jpg, 3.jpg, 4.jpg.
Если раскомментировать #define _TEST, то можно отлаживать фактически конфигурацию TEST в конфигурации Debug.
#ifdef _TEST switch (*argv[1]) { case '0': filePath = dir.wstring() + L"\\0.jpg"; fileName = L"0.jpg"; break; case '1': filePath = dir.wstring() + L"\\1.jpg"; fileName = L"1.jpg"; break; ..................................................... default: filePath = dir.wstring() + L"\\4.jpg"; fileName = L"4.jpg"; break; } #else switch (*argv[1]) { case '0': filePath = L"C:\\folder\\subfolder\\subfolder\\filename0.jpg"; fileName = L"filename0.jpg"; break; case '1': filePath = L"C:\\folder\\subfolder\\subfolder\\filename1.jpg"; fileName = L"filename1.jpg"; break; ..................................................... default: filePath = L"C:\\folder\\subfolder\\subfolder\\filename4.jpg"; fileName = L"filename4.jpg"; break; } #endif //_TEST
При запуске программы, определяем папку расположения программы. При запуске из командной строки argv[0]="", поэтому используем GetModuleFileName. Для конфигурации Release нет необходимости определять папку запуска программы, так как файлы обоев содержат полные пути к файлам. Присваиваем filePath и fileName значения, которые будут использоваться по умолчанию.
#ifdef _TEST //Получаем путь расположения программы wchar_t pathTmp[MAX_PATH] = { L'\0' }; GetModuleFileName(NULL, pathTmp, MAX_PATH); //std::filesystem::path dir = ((std::filesystem::path)argv[0]).parent_path(); //из командной строки не работает std::filesystem::path dir = ((std::filesystem::path)pathTmp).parent_path(); filePath = dir.wstring() + L"\\0.jpg"; fileName = L"0.jpg"; #else filePath = L"C:\\folder\\subfolder\\subfolder\\filename0.jpg"; fileName = L"filename0.jpg"; #endif // _TEST
Так как программа для личного использования, расположение файлов обоев прописаны в самом тексте
filePath = dir.wstring() + L"\\0.jpg"; //для конфигурации TEST
fileName = L"0.jpg"; //для конфигурации TEST
filePath = L"C:\\folder\\subfolder\\subfolder\\filename0.jpg"; //для конфигурации Release
fileName = L"filename0.jpg"; //для конфигурации Release
Проверяем, что сейчас установлено в качестве обоев
wchar_t wallFile[MAX_PATH] = { L'\0' }; SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallFile, 0); wchar_t* ptr = wcsstr(wallFile, fileName.c_str());
Если в качестве обоев используется файл, то в wallFile будет имя файла, если используется сплошной цвет, то wallFile="". В 3 строчке ищем вхождение fileName в wallFile. Если вхождение не найдено (используется сплошной цвет или другой файл), указатель ptr будет нулевым.
Кстати, строчки 1, 2 можно использовать для определения какой файл используется для обоев.
В зависимости от ptr и существует ли файл filePath устанавливаем либо файл в качестве обоев, либо сплошной цвет. И рассылаем сообщения всем окнам SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, NULL, SPIF_SENDWININICHANGE);
if (!ptr && std::filesystem::exists(filePath)) { BOOL result = SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (void*)filePath.c_str(), SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE); } else { //При этом варианте окно создается, потом скрывается, есть эффект мелькания // HWND hWnd = GetConsoleWindow(); // ShowWindow(hWnd, SW_HIDE); //Задаем цвет COLORREF blackColor = RGB(0, 0, 0); #ifdef MyCOM //Изменение фона через COM HRESULT hr = ChangeDesktopBackgroundColor(blackColor); if (SUCCEEDED(hr)) { SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, NULL, SPIF_SENDWININICHANGE); } #else //Изменение фона через WINAPI if (SetDesktopSolidColor(blackColor)) { SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, NULL, SPIF_SENDWININICHANGE); } #endif // MyCOM }
На этом собственно все о программе, ниже несколько слов о запуске и использовании шпаргалок в качестве обоев.
При компиляции в конфигурации TEST, файлы фона рабочего стола должны иметь названия 0.jpg, 1.jpg, 2.jpg, 3.jpg, 4.jpg и располагаться в папке программы. Запуск программы: ChangeDesktopColor.exe Х, где Х от 0 до ... .
Примеры запуска |
Файл для обоев |
ChangeDesktopColor.exe |
0.jpg |
ChangeDesktopColor.exe 0 |
0.jpg |
ChangeDesktopColor.exe 1 |
1.jpg |
ChangeDesktopColor.exe 2 |
2.jpg |
ChangeDesktopColor.exe 3 |
3.jpg |
ChangeDesktopColor.exe 4 |
4.jpg |
............................... |
............................... |
ChangeDesktopColor.exe 999 |
4.jpg |
ChangeDesktopColor.exe 1 2 |
0.jpg |
Исполняемый файл ChangeDesktopColor.exe скомпилированный в конфигурации TEST с образцами файлов обоев доступен по ссылке. Проверить целостность файла можно алгоритмом SHA256 (в PowerShell перейти в папку с программой и выполнить Get-FileHash ChangeDesktopColor.exe -Algorithm SHA256). Хэш файла ChangeDesktopColor.exe в таблице ниже.
Algorithm |
Hash |
SHA256 |
659323F608340C48DF42089E50D1561377A5E744A4FE7528F6F34018A57FEE54 |
Немножко про файл 0.jpg. Сейчас много мышек с большим количеством дополнительных кнопок. Используя фирменный софт, на дополнительные кнопки можно повесить комбинации клавиш, макросы, и тд и тп. Эти установки можно сохранять в разные файлы как разные профили и во время работы с разными программами/играми загружать необходимый профиль.
Я использую мышку Razer NAGA X. У нее 12 боковых кнопок + кнопка модификатор, которая еще добавляет 12 кнопок (аналог Shift на клавиатуре). Что бы они всегда были перед глазами, я сделал такой файл-шпаргалку и использую его как фон рабочего стола.
Можно наделать таких шпаргалок, и загружать их при необходимости. И еще раз про шпаргалки - файл 4.jpg - шутка, но почему бы и нет ?!
Спасибо за внимание, буду рад, если мой труд был не напрасен и кому-то окажется полезным.
Комментарии (11)

baldr
12.04.2026 17:38Мда, статья по уровню, действительно, не совсем для хабра, но даже рука не поднимается ставить минус.
Нахлынули воспоминания как я 30 лет назад делал это на bat-файлах, на Паскале и на С++, для Windows 95.. Менял не только обои, но и загрузочные картинки - писал на них всякие напоминания типа ближайших праздников и дней рождения. Ох как я стар...

bvr63 Автор
12.04.2026 17:38Да, статья конечно (с технической точки зрения) ..., , но иногда воспоминания нас делают моложе, возвращают нас во времена, когда мы были огого !!!

HemulGM
12.04.2026 17:38Держи на Паскале)
program ChangeDesktopColor; {$APPTYPE CONSOLE} // можно убрать и будет без консоли //{$DEFINE USE_COM} {$DEFINE TEST_MODE} uses Winapi.Windows, Winapi.ActiveX, System.SysUtils, System.IOUtils, Winapi.ShlObj; function ChangeDesktopBackgroundColor(Color: COLORREF): Boolean; begin {$IFDEF USE_COM} // Установка цвета через COM var HRES := CoInitialize(nil); if Succeeded(HRES) then try var Wallpaper: IDesktopWallpaper; if Succeeded(CoCreateInstance(CLSID_DesktopWallpaper, nil, CLSCTX_ALL, IDesktopWallpaper, Wallpaper)) then HRES := Wallpaper.SetBackgroundColor(Color); finally CoUninitialize; end; Result := Succeeded(HRES); {$ELSE} // Установка цвета через WinAPI Result := False; if SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, nil, SPIF_UPDATEINIFILE or SPIF_SENDCHANGE) then begin var Element: Integer := COLOR_DESKTOP; Result := SetSysColors(1, Element, Color); end; {$ENDIF} end; begin {$IFDEF TEST_MODE} var Dir := TPath.GetLibraryPath; {$ELSE} var Dir := 'C:\folder\subfolder\subfolder\'; {$ENDIF} var FilePath := TPath.Combine(Dir, '0.jpg'); var FileName := '0.jpg'; // обработка аргумента if ParamCount = 1 then begin FileName := ParamStr(1) + '.jpg'; FilePath := TPath.Combine(Dir, FileName); end; // текущие обои var WallFile: string; Setlength(WallFile, MAX_PATH); SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, PChar(WallFile), 0); // логика переключения if (WallFile <> FileName) and TFile.Exists(FilePath) then SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, PChar(FilePath), SPIF_UPDATEINIFILE or SPIF_SENDWININICHANGE) else begin if ChangeDesktopBackgroundColor(RGB(0, 0, 0)) then SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, nil, SPIF_SENDWININICHANGE); end; end.Правда я тут улучшил немного. Дублирование кода убрал. И читается раз в 15 легче

bvr63 Автор
12.04.2026 17:38За код спасибо.
При запуске ChangeDesktopColor.exe 999 будет черный цвет, не будет файла обоев по умолчанию. FileName будет ="999.jpg", такого файла нет. На мой взгляд надо писать более хитрый if или использовать case.
такой ifif ParamCount = 1 then begin FileName := ParamStr(1) + '.jpg'; FilePath := TPath.Combine(Dir, FileName); end;будет создавать FileName=ParamStr(1).jpg, в ParamStr(1) может быть (в общем случае) любое выражение "ф", "вася","море", и тд и тп.
И еще 1 момент, файлы 1.jpg ... 4.jpg для конфигурации TEST лежат в папке программы. Файлы обоев с именами filename0.jpg ... filename4.jpg (например) могут находится по разным путям и не обязательно путь к файлу, например, filename3.jpg будет совпадать с путем в Dir. Но это моя оплошность. Выражение C:\\folder\\subfolder\\subfolder\\ можно было понять как один путь к папке, я имел ввиду, что это разные пути. Надо было писать C:\\folder1\\subfolder1\\subfolder1\\, C:\\folder2\\subfolder2\\subfolder2\\

HemulGM
12.04.2026 17:38Про любое значение - это специально, так же удобнее. А если при указании несуществующего файла нужен не черный цвет, то достаточно добавить просто ещё одно условие.

bvr63 Автор
12.04.2026 17:38Да, про это я и написал. Вариантов реализации и логики может быть много. например после
FileName := ParamStr(1) + '.jpg'; FilePath := TPath.Combine(Dir, FileName); проверяем, существует ли файл FilePath. Если существует, оставляем все как есть, если не существует - присваиваем FileName и FilePath то что нам надо.
При такой логике, функционал программы немного расширится )))
randomsimplenumber
12.04.2026 17:38Неявное преобразование параметра в имя файла путем дописывания .jpg - ужасно. Если ожидается файл - нужно выдавать ошипку если файл не читается. И никакой магии.

bvr63 Автор
12.04.2026 17:38В параметрах файл не ожидается (по моей логике). Пути к файлам прописаны в самой программе. EXE файл будет содержать пути к файлам..
ЗЫ: я исхожу из того, что программа строго персональная, и тот, кто ей будет (теоретически) пользоваться изменит пути к своим проверенным файлам.
danilasar
Отличный школьный проект (я же угадал?), неплохое начало. На будущее, чтобы хабровчане совсем уж не захейтили, делай хотя бы дисклеймер "Я не волшебник, но только учусь")
По поводу утилиты, в винде (по крайней мере по семёрку включительно, плохо знаком с актуальными версиями) есть нативная поддержка одноцветного фона рабочего стола, картинки для этого не нужны. Попробуй разобраться, как этим функционалом манипулировать программно, и используй его.
bvr63 Автор
Спасибо за подсказку