So, one of my latest distractions was making Windows' taskbar progress custom colored. I personally already use StartIsBack, but it has no such option (by default.) So, how do we go about it? I decided to start from Explorer, as it is responsible for the taskbar in itself, and StartIsBack itself hooks into Explorer's painting code to draw everything by itself, with all the context that Explorer has gathered for it.
The following will be a recollection of how my debugging session started off, and how I eventually ended up in the right place:
- We know that taskbar progress is handled through
ITaskbarList3
[guidea1afb91-9e28-4b86-90e9-9e9f8a5eefaf
], respectively by it'sSetProgressValue
andSetProgressState
functions, so let's look for that... - Ok, we have the PDB... so let's look for "Progress"... bingo,
CTaskItem::GetProgressState
- Information available from here was exactly what I needed, but I personally lost more time debugging the flow of the whole taskbar display system by inattention. More on this later.
- The stacktrace is as follows:
- By
explorer!CTaskListWnd::_HandlePaint+0x442
, we detour intoStartIsBack64
, let's see what's there. - The first function into StartIsBack is a handler which checks if we want a custom taskbar, and if we do, we go into the custom drawing code
- Precisely where we are, what happens is that the progress state and value are stored into a variable. I may have an incorrectly reconstructed stack, but the variable(s) never seem to be referenced again. I even (just loosely however) checked the associated disassembly for something within the range of the stack where our numbers lay.
- After something similar while checking if there's an animation, we eventually get to some code that actually does something with our drawing context, associated with another context of data collected in the function, before.
- Maybe the context variable does not have a proper type, and the progress variables mentioned before are written there.
- A function inside the function becomes evident because of it's use of the HDC context, respectively the following:
- It's called multiple times in the function mentioned before. Let's examine when all of them happen.
- After placing the respective breakpoints, we release execution, and disable the ones which happen when we don't intend for them to.
- What remains should be the progress bar!
- Checking the arguments inside the function, we see that we can identify the progress bar by it's part ID (9)
- The function also provides a target rectangle, which we may use to just draw a different progress bar instead. So lets do just that!
static HRESULT __fastcall hkDrawPartStartIsBack(HTHEME hTheme,
HDC hdcTarget, int iPartId,
int iStateId,
RECT* prcTarget,
LPCRECT pClipRect, int a7) {
// Checks if the current part is the progress bar (9)
if (iPartId == kProgressbar) {
// Creates a solid brush, which is a valid HGDJOBJ, with our desired color
auto brush = CreateSolidBrush(RGB(248, 0, 248));
// Select our brush into the DC
SelectObject(hdcTarget, brush);
// Draw a rectangle, using the already calculated target rectangle
Rectangle(hdcTarget, prcTarget->left, prcTarget->top, prcTarget->right,
prcTarget->bottom);
DeleteObject(brush);
return 0;
}
// If the current part isn't the progress bar, then just do normal execution.
return pOrig(hTheme, hdcTarget, iPartId, iStateId, prcTarget, pClipRect,
a7);
}
- This function is where we make StartIsBack detour when it goes to the
DrawPart
. If the part ID is 9, we take over execution completely, otherwise we call a function which unhooks (which patches back the detour), calls to the original place again, then reapplies the detour.
So, does this work?
Looks like it!
Coming back to my first comment, I could have saved time by not debugging unrelated stuff by just... looking more closely at the stacktrace! :) I did give it a look, but clearly not enough to notice the fourth entry, which is quite embarassing.
Furthermore, you could have started your debugging here just by knowing that Explorer issues that the taskbar be drawn in WndProc, upon receiving a WM_PAINT
message. This fact is not obscure to me, it's just not a model of rendering that I debug on the daily.
This was a fun, simple exercise, and I recommend that everybody reading spends some time every now and then hacking software they use on the regular, it's fun, and can be educative. Maybe you can use it to notice bad habits (like I have), and make it serve as a reminder for your future endeavors.
Recommended reading for beginners: "Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems" by David J. Agans.