Page 1 of 6

CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 12:56
by misol101
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, JPG, GIF, or PNG image as console window background.
The image will automatically stretch to whatever window size is currently used.

Where?
Here : http://www.mediafire.com/download/628nj ... cmdbkg.zip
(Open in new tab if it doesn't load properly)
The archive also contains the source code.

Version?
The current version of CmdBkg is 2.1.

Usage?
Since version 2.0, CmdBkg uses the following format:

cmdbkg ["file.[bmp|jpg|gif|png]" [/t p] [/c [bg]] [/b]]

/t Transparency with p = percentage 0..100 (default 33).
/c Center image and preserve aspect ratio. Optionally specify background color with hex value format RRGGBB (default is the color of the console buffer's upper left corner on start).
/b Borders included.

Specify no arguments to remove previous background.
Specify /? as first argument to see this help message.

Screenshots (using syntax from pre 2.0 version):

ImageImage

Re: CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 13:02
by misol101
Additional info:

No longer true since version 1.1, do NOT start cmdbkg with "start" !
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".

Re: CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 13:07
by aGerman
Thanks for the realization of my idea :D Nice work!

Steffen

Re: CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 14:18
by Squashman
+1
+1
+1
+1
etc....
Infinity!

Re: CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 19:25
by Compo
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

Re: CmdBkg - use bitmap as background to console window

Posted: 12 Sep 2016 20:38
by misol101
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:

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 06:06
by penpen
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

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 07:26
by misol101
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.

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 07:44
by Compo
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.

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 08:24
by misol101
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]]

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 10:18
by penpen
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".

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 10:36
by aGerman
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 ...

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 13:54
by misol101
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:

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 17:07
by misol101
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... :)

Re: CmdBkg - use bitmap as background to console window

Posted: 13 Sep 2016 18:02
by penpen
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