CmdBkg - use bitmap as background to console window

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
aGerman
Expert
Posts: 4058
Joined: 22 Jan 2010 18:01
Location: Germany

Re: CmdBkg - use bitmap as background to console window

#61 Post by aGerman » 11 Nov 2020 12:01

Well, standard C is a simple language with little extend. Understanding pointers might be tricky, memory management is challenging and requires your full attention ... But here we're rather using the Windows API. It's C-style but can be overwhelming. However, programming languages are just istruments. Having a good understanding of how to discribe a problem and it's solution in terms of algorithms (even in natural language or in pseudocode) is already more than half the way. So, I guess if you're already experienced in programming the hurdle could be rather the syntax. COBOL and Basic have closing statements while you're facing curly braces in C-like languages. But that's somehow comparable with parenthesized blocks in Batch. People like you who speak assembly language deserve my full respect. That's something that my grey matter isn't able to grasp :lol:

Steffen

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

Re: CmdBkg - use bitmap as background to console window

#62 Post by misol101 » 11 Nov 2020 15:30

aGerman wrote:
09 Nov 2020 19:26
Found it :D
We have to call PostQuitMessage for WM_DESTROY.

My original assumption that WMI could be the culprit made me think about alternatives. And indeed I found a much easier way. Instead of passing the console HWND as additional argument it gets now assigned to the user data of the background window. Less dependencies, smaller code, easier processing, quick executable. It really makes a difference 8)
Well done Steffen! 8)

I replaced the archive linked in the first post, version number remains 1.2.

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

Re: CmdBkg - use bitmap as background to console window

#63 Post by aGerman » 19 Nov 2020 16:11

I swear I leave it alone for a while ...
Time is over :P :lol:

Hopefully the remaining TODOs are all addressed now and you still recognize your own code, Mikael :)
Because we can't attach files to PMs I have to post the code in this thread anyway. So I'll also explain what I did here (a little more verbous than actually sensible in a forum for Batch scripting).
1. Allow centered placement of image, no stretching?
That's the default in the new code. Option /s can be passed for stretching the image to fit.
Because some area of the background might not be covered by the image (and because we also support transparent image backgrounds) I added option /c where a custom background color can be defined as 6-digit HEX value in format RRGGBB. If /c is ommitted it will default to the background color of the console window (e.g. the color that was previously set using the COLOR command).

2. Background window visible a short time after restoring minimized console window. Minimize background if console window is minimized?
I tried to implement it but this didn't work properly because it's been interferred by SetWindowPos. However, after using SetWinEventHook this issue has been mitigated. I refrained from explicit minimizing and restoring without seeing any difference. The background window appeared a little earlier after restoring but that's rather because the console is delayed by fading in. Making the backgroud fading too would add more complexity than being benefitial IMO. Eventually I found it reasonable to setup a timer which elapses approximately at the same time the console finalizes with fading in. I think this looks sound now.

3. Inelegant polling. Use SetWinEventHook instead?
It took me some time to figure out why I couldn't bind the hook to the console process. It turned out that GetWindowThreadProcessId is a liar. Conhost.exe owns console windows instead of the shell process. Finally I decided to filter the events later on in the callback function. Instead of updating the background once in 20ms, we react now to the desired events. The background follows more smoothly if the console is dragged. And it allows to post a WM_CLOSE instead of just dropping out of the message loop while the background window is still alive. (I'm pretty sure this is still leaking in the latest release.) However, we don't get this for free. The drawback is that the events are fired for every update of the window that the OS is able to make out. This might happen thousands of times per second. In the beginning I faced almost 50% CPU utilization while moving of the window. (Coming from ~5% using the 20ms polling loop for this case.) I had to update the hook callback to process only two kinds of events. And I had to carefully refactor the message loop to ensure we only execute indispensable code. E.g. the update of the image is performed only once in a two-times redraw sequence. And I removed the WS_POPUP branch because I didn't get for what purpose this was in. FWIW I also tried to outsource the event handling into an asynchronous thread. This caused all kind of havoc though. Seems that the events have to be posted to the message queue to get them aligned.
Now it's at ~20% CPU utilization as long as the window is getting moved or resized. Of course, still 0% if you leave the window unchanged. So, this should be acceptable.

All in all I believe the first item adds the only relevant new features for the user. I mean nobody will seriously move, resize, or minimize and restore the window all along :wink:

Syntax:

Code: Select all

Usage: cmdbkg ["file.[bmp|jpg|gif|png]" [/t p] [/c bg] [/b] [/s]]
  /t  Transparency with p = percentage 0..100 (default 33).
  /c  Color of the background with bg = hex value format RRGGBB
      (default is the color of the console buffer's upper left corner
      at the time CmdBkg is called).
  /b  Borders included.
  /s  Stretch to fit.
Specify no arguments to remove previous background.
Specify /? as first argument to see this help message.
Due to the new command line options the syntax isn't downward compatible anymore. Probably it should be released as version 2.0 now.

Steffen
Attachments
cmdbkg_C_source9.zip
(6.95 KiB) Downloaded 29 times

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

Re: CmdBkg - use bitmap as background to console window

#64 Post by misol101 » 22 Nov 2020 10:43

:lol:

You're on a roll! Well done.

I have very limited time now, will try to make a release next week.

Interesting that using hooks is more cpu-consuming than polling, but the way you describe it makes sense.

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

Re: CmdBkg - use bitmap as background to console window

#65 Post by aGerman » 22 Nov 2020 14:54

misol101 wrote:
22 Nov 2020 10:43
Interesting that using hooks is more cpu-consuming than polling
Well there's still room for improvements. Inserting the image is just super expensive.

1) The original picture has to be scaled. The larger the picture is, the more bytes have to be processed to calculate the resulting bitmap. The performance could decrease to an extend where the message queue grows because our message loop iterates too slow. To mitigate this we could do two things.
- Check the dimension of the picture after reading the file. If both width and hight are greater then the dimensions of the screen then shrink it once in memory, and use the smaller bitmap for every scaling that needs to be performed afterwards.
- Make the bitmap insertion a thread routine. Skip any draw requests as long as we are still about to draw in the thread. This will keep the message loop iterating, and the window responsive.

2) Drawing the image isn't necessary as long as the window is only moved. There is one exception though. If the window was partially moved across the edges of the screen, the parts of the picture which have been invisible won't get restored. Fortunately we can bind this case to WM_NCPAINT.

Steffen
Attachments
cmdbkg_C_source10.zip
(7.86 KiB) Downloaded 25 times

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

Re: CmdBkg - use bitmap as background to console window

#66 Post by aGerman » 24 Nov 2020 12:23

OK, one last update because it's not yet realeased. Two improvements:
1) I found a way to get the scaled bitmap back from the DC to reuse it. That way the CPU utilization is still low if the draw request comes from WM_NCPAINT as explained above.
2) I never actually understood why the short delay after WaitForInputIdle was necessary. Now I got it. It's a matter of when we call FreeConsole. That's the trigger for WaitForInputIdle to return. After moving it to the point after the console handle was assigned to the user data of the background window, closing the previous background window works reliable without any further delay.

Steffen
Attachments
cmdbkg_C_source11.zip
(7.9 KiB) Downloaded 27 times

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

Re: CmdBkg - use bitmap as background to console window

#67 Post by misol101 » 30 Nov 2020 16:13

Archive finally updated (to 2.0). Again, good work :)

I prefer to keep the original behavior of scaling the image. The /s option was removed, instead /c [bg] is now the center option, with optional background color.

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

Re: CmdBkg - use bitmap as background to console window

#68 Post by aGerman » 30 Nov 2020 16:58

Thank you, Mikael! It was fun to work on it and - as always - I learned something new :)
misol101 wrote:
30 Nov 2020 16:13
The /s option was removed, instead /c [bg] is now the center option, with optional background color.
I love it :!: This is much more intuitive.

Steffen

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

Re: CmdBkg - use bitmap as background to console window

#69 Post by aGerman » 29 Dec 2020 06:37

Mikael,

I equipped a couple of frequently used scripts with CmdBkg to see how it behaves in the longer term. Now that I have a few days off I found the time to work on the findings as well as on things I've seen in the code review. This is my proposal for a v2.1. It addresses bugs, performance, and robustness. It doesn't add any new functionality though.
  • There was a little inaccuracy in the calculation of the window height, which resulted in a few pixel rows getting clipped at the bottom.
  • Every now and then the background has not been updated properly after resizing. Posting a WM_PAINT message from within the thread fixed it.
  • The validation of program options was somewhat broken. This regressed when you moved calling GetOpt to the synchronously executed part of the code. I absolutely understand your reason for that though. Unsynchronized printing of an error message to the console window might get interleaved with the output of the cmd process. After a refactoring of GetOpt we have two branches there. The first branch is for the synchronous first process. It fully validates the passed arguments. The second branch is for the asynchronous second process. No check is performed anymore because it's been done before.
  • Following the concept of reporting errors in the synchronous process, a readability check of the image file is performed before the second process is created. Other errors may occur once in a blue moon, if at all. I removed their reporting via message box entirely. Message boxes feel rather odd for a tool which aims to be a gadget for console processes. And the user has no chance to suppress them while anything written to the console window could get redirected to NUL.
  • GetHBitmapFromFile was leaking. Calling GdipDisposeImage is required to delete the intermediate bitmap pointer. My mistake, I didn't investigate enough when I started using GDI+ in CmdBkg.
  • During code profiling I found that CopyImage is the culprit for high CPU load. This function is necessary to update the size of the drawn image. A refactoring of the thread function ensures that CopyImage is called if and only if we have to rescale the image. And for large images it's sensible to spend a few more lines of code in GetHBitmapFromFile to initially downsize it as much as reasonable, depending on whether option /c is passed. This makes repeated rescaling in the thread faster and avoids looking too jolty.
  • A deceleration of only 1 ms in the thread iterations saves ~25% of the CPU usage that we would have without this little delay. Too short to have any visual impact. So it's definitely worth it.
  • Invoking the callback function for events of all windows causes up to ~0.5% additional CPU usage on my machine. E.g. for mouse hovering on a foreign window. Binding the event handler to the conhost.exe process (which owns the console window) is tricky due to the fact that Microsoft doesn't provide an API function to get its PID from the belonging window handle. However, at least on Win 10 (where conhost.exe is a child of the console process) we're now listening for the console events only. There is a little more effort in the beginning. But that's only once, and better than wasting resources for the lifetime of the process.
  • It's no longer necessary to update the command line. We use the original command line but specify a custom AppUserModelID (AUMID) along with flag STARTF_TITLEISAPPID in the STARTUPINFOW structure to tag the second process. It has no influence on the appearance of CmdBkg because it isn't a registered AUMID. However, it makes both the distinction between the two processes and the command line parsing easier and more reliable. And it avoids the insertion of an additional command line argument which required memory allocation and string copying.
  • It's been kinda scary to create a new thread for every update of the background window. Waiting for an event to repeat the tasks in a single thread is more appropriate after thinking about it.
  • Floating point operations to calculate a rounding are unnecessarily expensive. Integer operations are sufficient for that, and they perform better. Especially because shift operations can be used for this purpose.
  • A couple of minor updates ...
Steffen
Attachments
cmdbkg_C_source12.zip
(8.63 KiB) Downloaded 13 times

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

Re: CmdBkg - use bitmap as background to console window

#70 Post by misol101 » 30 Dec 2020 05:04

Steffen,
I trust you have done an excellent job as always 8)

I will give it a few days before I update the archive though, just in case we should see any regression.

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

Re: CmdBkg - use bitmap as background to console window

#71 Post by aGerman » 30 Dec 2020 05:35

Thanks Mikael!
I've already tested extensively. However, Win 10 is the only OS I'm able to perform those tests. If you still have access to a machine (or a VM) running Win 7, it would be good to know if we kept downward compatibility.

Steffen

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

Re: CmdBkg - use bitmap as background to console window

#72 Post by aGerman » 30 Dec 2020 07:29

I guess I should have explained it better :roll:
I took care of using functions and flags that have already been available on XP. So I don't expect that the tool isn't running on Win 7 anymore. It's merely about my first two bullet points.
- I recall discussing with you about the "weird" extended window boundary on Win 10. Maybe the calculation of the hight was absolutely correct for windows on Win 7.
- In the v1 code you used an "initial redraw" countdown. After quite some code updating I thought it's not necessary anymore, and I removed it in v2.0. However, I'm sure it was there for a reason. And after all I was facing random problems which led me to the additional posting of WM_PAINT now.

Steffen

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

Re: CmdBkg - use bitmap as background to console window

#73 Post by misol101 » 30 Dec 2020 17:45

I actually ran this on my terribly worn down Win7 machine with the noisiest fan known to mankind, but didn't notice any problems.

Therefore, I have now updated the archive to 2.1

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

Re: CmdBkg - use bitmap as background to console window

#74 Post by aGerman » 30 Dec 2020 18:49

misol101 wrote:
30 Dec 2020 17:45
I actually ran this on my terribly worn down Win7 machine with the noisiest fan known to mankind
Haha, sorry for causing those inconveniences :lol: I was almost about to setup Win 7 on VirtualBox. But out of my experiences the appearence and behavior in VirtualBox is pretty different. Even an outworn Win 7 machine gives more reliable results :)

Steffen

Post Reply