Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save cristeigabriel/9dad75f1f71c57850c31a5349fd12c33 to your computer and use it in GitHub Desktop.
Save cristeigabriel/9dad75f1f71c57850c31a5349fd12c33 to your computer and use it in GitHub Desktop.
Getting custom colored Windows taskbar progress - Hacking Explorer and StartIsBack

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 [guid ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf], respectively by it's SetProgressValue and SetProgressState 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: image
  • By explorer!CTaskListWnd::_HandlePaint+0x442, we detour into StartIsBack64, 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: image
  • 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! image
  • 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?

image

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment