Добрый день. У меня, на домашнем компе, в качестве обоев установлен черный цвет, и я к нему привык, мне удобно работать с таким фоном. На моем не домашнем компе, стоят обои, довольно светлые. При смене обоев на черный цвет, через некоторое время, обои возвращаются. Поменять обои на черный цвет не трудно: ПКМ->Персонализация->итд . Но я решил менять фон рабочего стола программно. Исходный текст на GitHub'е (https://github.com/bvr63/ChangeDesktopColor). Написана на C++, в VS2026. Программа циклически меняет фон рабочего стола: Файл ->Solid Color->Файл ->Solid Color->...

Ниже опишу некоторые моменты.
Программа консольная, для windows.При запуске программы я не хочу видеть окно консоли => окно консоли надо скрыть. В программе есть 2 варианта скрыть окно.

  1. HWND hWnd = GetConsoleWindow();
    ShowWindow(hWnd, SW_HIDE);
    Получить хендл окна, затем скрыть окно. При этом варианте окно создается, потом скрывается, есть эффект мелькания.

  2. Использовать директивы для линкера
    #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
Добавление _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)


  1. danilasar
    12.04.2026 17:38

    Отличный школьный проект (я же угадал?), неплохое начало. На будущее, чтобы хабровчане совсем уж не захейтили, делай хотя бы дисклеймер "Я не волшебник, но только учусь")

    По поводу утилиты, в винде (по крайней мере по семёрку включительно, плохо знаком с актуальными версиями) есть нативная поддержка одноцветного фона рабочего стола, картинки для этого не нужны. Попробуй разобраться, как этим функционалом манипулировать программно, и используй его.


    1. bvr63 Автор
      12.04.2026 17:38

      Спасибо за подсказку


  1. baldr
    12.04.2026 17:38

    Мда, статья по уровню, действительно, не совсем для хабра, но даже рука не поднимается ставить минус.

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


    1. bvr63 Автор
      12.04.2026 17:38

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


    1. 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 легче


      1. bvr63 Автор
        12.04.2026 17:38

        За код спасибо.
        При запуске ChangeDesktopColor.exe 999 будет черный цвет, не будет файла обоев по умолчанию. FileName будет ="999.jpg", такого файла нет. На мой взгляд надо писать более хитрый if или использовать case.
        такой if

          if 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\\


        1. HemulGM
          12.04.2026 17:38

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


          1. bvr63 Автор
            12.04.2026 17:38

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


            1. randomsimplenumber
              12.04.2026 17:38

              Неявное преобразование параметра в имя файла путем дописывания .jpg - ужасно. Если ожидается файл - нужно выдавать ошипку если файл не читается. И никакой магии.


              1. bvr63 Автор
                12.04.2026 17:38

                В параметрах файл не ожидается (по моей логике). Пути к файлам прописаны в самой программе. EXE файл будет содержать пути к файлам..
                ЗЫ: я исхожу из того, что программа строго персональная, и тот, кто ей будет (теоретически) пользоваться изменит пути к своим проверенным файлам.


    1. wintermute2025
      12.04.2026 17:38

      Я стар, я стар, я ... Superstar! ;)