CmdBkg - use bitmap as background to console window

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
misol101
Posts: 381
Joined: 02 May 2016 18:20

CmdBkg - use bitmap as background to console window

#1 Post by misol101 » 12 Sep 2016 12:56

This idea was given to me by aGerman, who said he had been thinking about it for years but never actually tried it. So, I decided to give it a go. aGerman also helped out in testing the first version of this.

What is it?
cmdbkg.exe lets you use a BMP image as console window background.
The bitmap will automatically stretch to whatever window size is currently used.

Where?
Here : http://www.mediafire.com/download/628njye1223xw6g/cmdbkg.zip
(Open in new tab if it doesn't load properly)
The archive also contains the source code (not a pretty sight, but it does the job)

Usage?
cmdbkg "file.bmp" [transparency 0-100(default 33) [includeBorders]]
Specify no arguments to remove previous background.
Specify /? to see help.

If a third argument is given, the borders around the window also shows the bitmap.

Screenshots:

ImageImage
Last edited by misol101 on 16 Apr 2017 13:54, edited 2 times in total.

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#2 Post by misol101 » 12 Sep 2016 13:02

Additional info:

1. If run from a batch script, cmdbkg.exe will block the script. To prevent this, it has to be run with: start "" cmdbkg.exe

2. It's possible to have different background images for different console windows. New console windows do not inherit the background of its parent.

3. It's possible to tint the image quite nicely by using the "color" command, e.g. "color d0" for a shade of purple with black text.

4. Transparency can be adjusted without reloading the image by using my other tool "cmdwiz.exe" with operation "setwindowtransparency".
Last edited by misol101 on 12 Sep 2016 14:04, edited 2 times in total.

aGerman
Expert
Posts: 3706
Joined: 22 Jan 2010 18:01
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#3 Post by aGerman » 12 Sep 2016 13:07

Thanks for the realization of my idea :D Nice work!

Steffen

Squashman
Expert
Posts: 4111
Joined: 23 Dec 2011 13:59

Re: CmdBkg - use bitmap as background to console window

#4 Post by Squashman » 12 Sep 2016 14:18

+1
+1
+1
+1
etc....
Infinity!

Compo
Posts: 488
Joined: 21 Mar 2014 08:50

Re: CmdBkg - use bitmap as background to console window

#5 Post by Compo » 12 Sep 2016 19:25

What about adding something to the user autorun key.
e.g.

Code: Select all

Reg Add "HKCU\Software\Microsoft\Command Processor" /V AutoRun /D "Start \"\" \"%UserProfile%\CmdBkg.exe\" \"%UserProfile%\123.bmp\" 25"

http://imgur.com/a/hcnYB

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#6 Post by misol101 » 12 Sep 2016 20:38

Thanks for the comments, glad you all like it!

Just want to point out that there is an issue with this program which I didn't think of before. Try this:

timeout 5 & cmdbkg 123.bmp

then select any other window than the console, and wait. That's right, now that window gets the image instead :)

While this is a cool "feature" and all, it's obviously a bug. Not sure how to fix it at the moment, anybody with some Windows coding skill is invited to help out :mrgreen:

penpen
Expert
Posts: 1723
Joined: 23 Jun 2013 06:15
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#7 Post by penpen » 13 Sep 2016 06:06

Neat application!

Actually i cannot test it, but i think this line causes the bug (using the actual foreground window):

Code: Select all

   hConsoleWnd = GetForegroundWindow();

Better use the console window associated with the application instance:

Code: Select all

   hConsoleWnd = GetConsoleWindow();


penpen

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#8 Post by misol101 » 13 Sep 2016 07:26

penpen wrote:Neat application!

Actually i cannot test it, but i think this line causes the bug (using the actual foreground window):

Code: Select all

   hConsoleWnd = GetForegroundWindow();

Better use the console window associated with the application instance:

Code: Select all

   hConsoleWnd = GetConsoleWindow();



You are right, penpen, that is the cause. Unfortunately I cannot run GetConsoleWindow() because it returns NULL in this case. The reason it returns NULL is that this is a GUI application, not a console app. If it was a console app it would block the console until the program finishes (I am experimenting with creating a new process instead, with limited success).

Otherwise I need a new, reliable way to get a handle to the console window.

Compo
Posts: 488
Joined: 21 Mar 2014 08:50

Re: CmdBkg - use bitmap as background to console window

#9 Post by Compo » 13 Sep 2016 07:44

Im not sure if you're aware, but this works:

Code: Select all

start "" cmdbkg 123.bmp 33 includeborders
this doesn't:

Code: Select all

start "" cmdbkg 123.bmp includeborders
your instruction was that default 33 is applied, this appears not to be the case when followed with the includeborders option.

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#10 Post by misol101 » 13 Sep 2016 08:24

Compo wrote:Im not sure if you're aware, but this works:

Code: Select all

start "" cmdbkg 123.bmp 33 includeborders
this doesn't:

Code: Select all

start "" cmdbkg 123.bmp includeborders
your instruction was that default 33 is applied, this appears not to be the case when followed with the includeborders option.


Yeah, that's "by design" (or lack of)... I prefer it to using switches. So basically in order to specify includeBorders you have to set the transparency. It's currently even more liberal actually, it doesn't matter what your third param is as long as it's set, so you could do:

Code: Select all

start "" cmdbkg 123.bmp 33 1 
with the same result.

I guess if I want to keep this design the help should actually be: cmdbkg "file.bmp" [transparency 0-100 (default 33) [includeBorders]]

penpen
Expert
Posts: 1723
Joined: 23 Jun 2013 06:15
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#11 Post by penpen » 13 Sep 2016 10:18

misol101 wrote:Unfortunately I cannot run GetConsoleWindow() because it returns NULL in this case.
Just attach the console of the parent process (untested, but should be possible within cmd, even when using start):

Code: Select all

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   std::string filename = "filename.txt";
   std::ofstream fileout(filename.c_str());
   BOOL attached = FALSE;

   fileout << "attached          : " << ((attached) ? "TRUE" : "FALSE") << std::endl;
   fileout << "GetConsoleWindow(): " << GetConsoleWindow()              << std::endl;
   fileout << std::endl;

   if (AttachConsole(ATTACH_PARENT_PROCESS)) {
      attached = TRUE;
      fileout << "attached          : " << ((attached) ? "TRUE" : "FALSE") << std::endl;
      fileout << "GetConsoleWindow(): " << GetConsoleWindow()              << std::endl;
      FreeConsole();
   } else {
      fileout << "Needs to be executed under \"cmd.exe\"" << std::endl;
   }

   fileout.close();

   return 0;
}


penpen

Edit: Corrected "cstr" to "c_str".
Last edited by penpen on 13 Sep 2016 18:04, edited 1 time in total.

aGerman
Expert
Posts: 3706
Joined: 22 Jan 2010 18:01
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#12 Post by aGerman » 13 Sep 2016 10:36

Or find the window of the parent process.

Code: Select all

/* CmdBkg (c) 2016 Mikael Sollenborn */

#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600

// Usage: cmdbkg "file.bmp" [transparency 0-100 (default 33) [includeBorders]]
// Specify no arguments to remove previous background.
// Specify /? as first argument to see this help.

// Compilation: gcc -o cmdbkg.exe cmdbkg.c -lgdi32 -ldwmapi -mwindows

// TODO/Issues:
// 1. Allow centered placement of image, no stretching?
// 2. Background window visible a short time after restoring minimized console window. Minimize background if console window is minimized?
// 3. Inelegant polling. Use SetWinEventHook instead?
// 4. Find a better way to kill the old background window than this horrible title polling hack
// 5. Program blocks if run from a batch script, need to use: start "" cmdbkg

#include <windows.h>
#include <stdio.h>
#include <dwmapi.h>
#include <tlhelp32.h>

#define POLL_INTERVAL 20
#define KILL_TITLE_MESSAGE L"Kill"

// structure to be passed to EnumWindows() and EnumWindowsCallback()
typedef struct tag_CALLBACKDATA
{
  DWORD pid;
  HWND hwnd;
} CALLBACKDATA;

// callback function that is called from EnumWindows() in order to find the main window of a process
BOOL CALLBACK EnumWindowsCallback(HWND hwnd, LPARAM lParam)
{
  CALLBACKDATA *pData = (CALLBACKDATA*)lParam;
  DWORD process_id = 0;
  GetWindowThreadProcessId(hwnd, &process_id);
  if (pData->pid == process_id && !GetWindow(hwnd, GW_OWNER))
  {
    pData->hwnd = hwnd;
    return FALSE;
  }

  return TRUE;
}

// Functions Fn_LoadBmp and f_SetConsoleTransparency by user "aGerman" at dostips.com

int Fn_LoadBmp(HWND hWnd, wchar_t *szBmpPath, long x, long y, long z, long w, long h)
{
   HDC hDc = NULL, hDcBmp = NULL;
   HBITMAP hBmp1 = NULL, hBmp2 = NULL;
   HGDIOBJ hGdiObj = NULL;
   BITMAP bmp = {0};
   int iRet = EXIT_FAILURE;

   if (hWnd)
   {
      if ((hDc = GetDC(hWnd)))
      {
         if ((hDcBmp = CreateCompatibleDC(hDc)))
         {
            if ((hBmp1 = (HBITMAP)LoadImage(NULL, szBmpPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)))
            {
               if (GetObject(hBmp1, sizeof(bmp), &bmp))
               {
                  if (w == -1) {
                     if ((w = bmp.bmWidth * z / 100.0 + 0.5) <= 0 || (h = bmp.bmHeight * z / 100.0 + 0.5) <= 0)
                     {
                        w = bmp.bmWidth;
                        h = bmp.bmHeight;
                     }
                  }
                  if ((hBmp2 = (HBITMAP)CopyImage((HANDLE)hBmp1, IMAGE_BITMAP, w, h, LR_COPYDELETEORG)))
                  {
                     if ((hGdiObj = SelectObject(hDcBmp, hBmp2)) && hGdiObj != HGDI_ERROR)
                     {
                        if (BitBlt(hDc, (int)x, (int)y, (int)w, (int)h, hDcBmp, 0, 0, SRCCOPY))
                        iRet = EXIT_SUCCESS;
                        DeleteObject(hGdiObj);
                     }
                     DeleteObject(hBmp2);
                  }
               }
               DeleteObject(hBmp1);
            }
            ReleaseDC(hWnd, hDcBmp);
         }
         ReleaseDC(hWnd, hDc);
      }
   }
   return iRet;
}

BOOL f_SetConsoleTransparency(HWND hConsoleWnd, long percentage)
{
   BYTE bAlpha = 0;
   LONG lNewLong = 0;
   if (hConsoleWnd && percentage > -1 && percentage < 101)
   {
      bAlpha = (BYTE)(2.55 * (100 - percentage) + 0.5);
      lNewLong = GetWindowLong(hConsoleWnd, GWL_EXSTYLE) | WS_EX_LAYERED;
      if (!SetWindowLong(hConsoleWnd, GWL_EXSTYLE, lNewLong)) return FALSE;
      return SetLayeredWindowAttributes(hConsoleWnd, 0, bAlpha, LWA_ALPHA);
   }
   return FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   int bx, by, bw, bh, ret;
   int nbx, nby, nbw, nbh;
   int fw = 0, fh = 0, fth = 0;
   HWND hConsoleWnd, hBkgWnd, currFgWnd, tempWnd;
   RECT bounds, extendBounds;
   LPWSTR *szArgList;
   int argCount, initialRedraw = 10, compTitle = 1, transparency = 33;
   wchar_t titleBuffer[1024];
   WNDCLASS wc={0};
   UINT_PTR timerId;
   BOOL bIsWeirdoBorders = FALSE;
   BOOL res;
   MSG msg;

   // hConsoleWnd = GetForegroundWindow();

//////////////////////////
  HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 procentry = {0};
  procentry.dwSize = sizeof(PROCESSENTRY32);
  DWORD pid = GetCurrentProcessId(), ppid = 0;


  if (Process32First(hProcessSnap, &procentry))
  {
    do
    {
      if (procentry.th32ProcessID == pid)
        ppid = procentry.th32ParentProcessID;
    } while (ppid == 0 && Process32Next(hProcessSnap, &procentry));
  }

  if (hProcessSnap != INVALID_HANDLE_VALUE)
    CloseHandle(hProcessSnap);

  if (ppid == 0)
  {
      MessageBox(NULL, L"Unable to find cmd.exe process.", L"Error", MB_OK);
      return 1;
  }

  CALLBACKDATA data = {ppid, NULL};
  EnumWindows(EnumWindowsCallback, (LPARAM)&data);

  hConsoleWnd = data.hwnd;
//////////////////////////

  if (hConsoleWnd) {
      GetWindowRect(hConsoleWnd, &bounds);
      bx = bounds.left;
      by = bounds.top;
      bw = bounds.right-bounds.left;
      bh = bounds.bottom-bounds.top;
   } else {
      MessageBox(NULL, L"Unable to prepare window creation", L"Error", MB_OK);
      return 1;
   }

   DwmGetWindowAttribute(hConsoleWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &extendBounds, sizeof(extendBounds));
   if (extendBounds.left != bounds.left) bIsWeirdoBorders = TRUE;

   szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
   if (szArgList == NULL)
   {
      MessageBox(NULL, L"Unable to get arguments", L"Error", MB_OK);
      return 1;
   }

   if (argCount == 2 && wcscmp(szArgList[1], L"/?") == 0) {
      MessageBox(NULL, L"Usage: cmdbkg \"file.bmp\" [transparency 0-100 (default 33) [includeBorders]]\n\nSpecify no arguments to remove previous background.\n\n(C)opyright 2016 Mikael Sollenborn", L"CmdBkg Arguments", MB_OK);
      return 0;
   }

   f_SetConsoleTransparency(hConsoleWnd, 0);

   GetWindowText(hConsoleWnd, titleBuffer, 1023);
   SetWindowText(hConsoleWnd, KILL_TITLE_MESSAGE);
   Sleep(500);
   SetWindowText(hConsoleWnd, titleBuffer);

   if (argCount < 2) {
      f_SetConsoleTransparency(hConsoleWnd, 0);
      return 0;
   }

   if (argCount > 2) {
      transparency = wcstol (szArgList[2], NULL, 10);
   }
   f_SetConsoleTransparency(hConsoleWnd, transparency);

   if (argCount <= 3 || (argCount > 3 && bIsWeirdoBorders)) {
      fw = GetSystemMetrics(SM_CXSIZEFRAME);
      if(argCount <= 3) fth = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION);
      fh = fth + GetSystemMetrics(SM_CYSIZEFRAME);
   }

   wc.lpszClassName=L"CmdBkgWindowBackground";
   wc.lpfnWndProc=DefWindowProc; // Use default Window proc
   wc.hInstance=hInstance;
   wc.hbrBackground=(HBRUSH)(COLOR_3DFACE+1);
   wc.hCursor=LoadCursor(NULL,IDC_ARROW);
   if (!RegisterClass(&wc)) { MessageBox(NULL, L"Unable to prepare window creation", L"Error", MB_OK); LocalFree(szArgList); return 1; }
   hBkgWnd=CreateWindowEx(0,wc.lpszClassName,0,WS_POPUP|WS_VISIBLE,bx+fw,by+fth,bw-fw*2,bh-fh,0,0,0,0);
   if (!hBkgWnd) { MessageBox(NULL, L"Unable to create background window", L"Error", MB_OK); LocalFree(szArgList); return 1; }

   ret = Fn_LoadBmp(hBkgWnd, szArgList[1], 0, 0, 100, bw-fw*2, bh-fth);
   if (ret == EXIT_FAILURE) { MessageBox(NULL, L"Unable to load bitmap", L"Error", MB_OK); LocalFree(szArgList); return 1; }

   ShowWindow(hBkgWnd, SW_HIDE);
   SetWindowLongPtr(hBkgWnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
   ShowWindow(hBkgWnd, SW_SHOW);

   SetForegroundWindow(hConsoleWnd);

   currFgWnd = GetForegroundWindow();

   while((IsWindow(hConsoleWnd) || IsIconic(hConsoleWnd)) && ret == EXIT_SUCCESS && compTitle != 0) {
      timerId = SetTimer(NULL, 0, POLL_INTERVAL, NULL);
      res = GetMessage(&msg,0,0,0);
      KillTimer(NULL, timerId);

      if (!(res && msg.message == WM_TIMER && msg.hwnd == NULL && msg.wParam == timerId))
         DispatchMessage(&msg);

      GetWindowText(hConsoleWnd, titleBuffer, 1023);
      compTitle = wcscmp(titleBuffer, KILL_TITLE_MESSAGE);

      GetWindowRect(hConsoleWnd, &bounds);
      nbx = bounds.left;
      nby = bounds.top;
      nbw = bounds.right-bounds.left;
      nbh = bounds.bottom-bounds.top;

      tempWnd = GetForegroundWindow();
      if (currFgWnd != tempWnd) {
         if (initialRedraw < 5) initialRedraw = 5;
         currFgWnd = tempWnd;
      }

      SetWindowPos(hBkgWnd, hConsoleWnd, nbx+fw, nby+fth, nbw-fw*2, nbh-fh, SWP_SHOWWINDOW);
      if (initialRedraw > 0 || nbx != bx || nby != by || nbw != bw || nbh != bh) {
         int w = nbw-fw*2, h = nbh-fth;
         if (w < 1) w = 1;
         if (h < 1) h = 1;
         ret = Fn_LoadBmp(hBkgWnd, szArgList[1], 0, 0, 100, w, h);
         bx = nbx; by = nby; bw = nbw; bh = nbh;
         if (initialRedraw > 0) initialRedraw--;
      }
   }

   LocalFree(szArgList);
   return 0;
}

Steffen

//EDIT slightly changed ...

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#13 Post by misol101 » 13 Sep 2016 13:54

Archive updated.

Should be more stable now, and can be run from a minimized script without issues.

In the end I used a similar approach, but it is essentially the same as aGerman's method:

Code: Select all

DWORD getParentPID(DWORD pid)
{
   HANDLE h = NULL;
   PROCESSENTRY32 pe = { 0 };
   DWORD ppid = 0;
   pe.dwSize = sizeof(PROCESSENTRY32);
   h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   if( Process32First(h, &pe))
   {
      do
      {
         if (pe.th32ProcessID == pid)
         {
            ppid = pe.th32ParentProcessID;
            break;
         }
      } while( Process32Next(h, &pe));
   }
   CloseHandle(h);
   return (ppid);
}

HWND FindTopMostWindow(DWORD dwProcID)
{
   HWND hWnd = GetTopWindow(GetDesktopWindow());
   while(hWnd)
   {
      DWORD dwWndProcID = 0;
      GetWindowThreadProcessId(hWnd, &dwWndProcID);
      if(dwWndProcID == dwProcID)
         return hWnd;           
      hWnd = GetNextWindow(hWnd, GW_HWNDNEXT);
   }
   return NULL;
}

...

   DWORD parentPID = getParentPID( GetCurrentProcessId());
   HWND hConsoleWnd = FindTopMostWindow(parentPID);


No more background images for notepad, task manager and Explorer now though... maybe for another tool :mrgreen:

misol101
Posts: 381
Joined: 02 May 2016 18:20

Re: CmdBkg - use bitmap as background to console window

#14 Post by misol101 » 13 Sep 2016 17:07

penpen: There was a lof of C++ "fluff" in your answer so I didn't understand it at first, but actually it's the better and easier solution. This works just fine:

Code: Select all

AttachConsole(ATTACH_PARENT_PROCESS);
hConsoleWnd = GetConsoleWindow();


For next release... :)

penpen
Expert
Posts: 1723
Joined: 23 Jun 2013 06:15
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#15 Post by penpen » 13 Sep 2016 18:02

I wasn't sure if it would work, so (above) i've written a fast test; i assume you only need:

Code: Select all

   HWND  hConsoleWnd = NULL;
   if (AttachConsole(ATTACH_PARENT_PROCESS)) {
      hConsoleWnd = GetConsoleWindow();
      FreeConsole();
   } else {
      return 1;
   }
If you don't use "FreeConsole()", then this could result in a console that doesn't close, because there is one process (== "this one") attached to it.


penpen

Post Reply