Sunshine's
Desktop Symbol Utility |
What's this?
In Windows XP,
I always missed a feature concerning the desktop symbols. You have 2 choices:
First, you can choose a background color which is also used to draw the text
background (screenshot 1). But this looks quite ugly with most wallpapers. Second
possibility is the enable transparent backgrounds (under Control Panel ->
bla bla...). Unfortunately does Windows paint also a shadow under the text which
makes it difficult to read and in my opinion does not look very good.
That's the reason I decided to write a tool to solve this 'misbehaviour' (screenshot
3).
Screenshot 1 |
Screenshot 2 |
What it should be... |
Features:
- Use the color
of your choice for text and background (or transparency of course).
- When activated, you can completely close the utlity and restart later to deactivate
it.
- Dll which does the main work is just 4 KB.
- Autostart option: the utility closes itself after activation; you will notice
no difference to normal windows start!
Enjoy!
Information for Coders
Here come some
notes on developing this utility... maybe they are interesting for the coders
outside :-)
So how to
start? At first, I had to find out how the symbols are drawn. So I used Spy++
which comes with Visual Studio to find the desktop window and I figured out
following hierachie:
- "Program Manager" Progman
- "" SHELLDLL_DefView
- "FolderView" SysListView32
Pretty interesting... Our desktop window is a normal listview control, well
known from the windows common controls :-) So finding it in our code is quite
simple, following three lines will do the job:
HWND hProgman = FindWindow("Progman", 0);
HWND hDesktop = FindWindowEx(hProgman, 0, "SHELLDLL_DefView", 0);
HWND hListView = FindWindowEx(hDesktop, 0, "SysListView32", 0);
A bit diving in
the Windows API make clear that you can use the messages LVM_SETTEXTCOLOR
and LVM_SETTEXTBKCOLOR to modify the text (background) colors. And
that worked fine! With just 5 lines of code, I changed the desktop symbol text
colors... but two problems faces us:
Problem
1: When
the transparent desktop symbol mode is activated, it does NOT work.
Solution: Deactivate it ;-) Ok, more preciser: I used Regmon
from Sysinternals to find out which values are changed in registry when I (de)activate
this mode. In fact, just one key handles it: it's called ListviewShadow
and you can find it under HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced.
1 means enabled, 0 means disabled. Ok, that was easy :-)
Problem
2:
Whenever the desktop window is repainted/updated, our changes are lost. As workaround,
you could use a timer and send the two messages periodically, but that's not
a good solution cause too long periods will result in some flickering when for
a short time the original colors are seen and too short periods waste system
power.
Solution: We should try to catch
when desktop is updated and apply our colors. I did it by injecting my own dll
into the process where the listview window is belonging to (which is, in fact,
explorer.exe). The CreateRemoteThread/WriteProcessMemory works fine
(for more information about it see [1]) Cause this tool will only be useful
under WinXP, it's no problem to use API functions which are only under XP available.
After injecting, we can sublass the corresponding window procedure, catch the
WM_PAINT message and set our colors. (to make it clear: you can just subclass
a window procedure in your own process (!), so that's the only reason
we inject the dll in explorer.exe!)
I coded a first version which worked really fine. But another problem came up.
Inter-Process
Communication:
In our main application,
we choose the colors, but our dll has to set it. So we have to send data from
the utility to the dll. My first approach didn't worked: I wanted to use WM_COPYDATA
for sending the color values because by already catching WM_PAINT it would be
just a few lines of code to process the WM_COPYDATA message in our dll. But
my message never arrived in the dll file; in fact even sending the message in
the main app to the listview window failed... and I don't know why :-(
So searching for another method, I came across the 'shared data segement'-approach
in C++. This is normally used if you have more than one instance of a dll file
but want to use just one instance of a specific variable. All variables in the
shared data segment are stored in an extra section in the PE file which is (as
the name says it) shared between all instances. So I thought I can use this
method too to share variables in my app and in my dll :-)
To got it running, some things have to be looked at: the variables in the shared
data segment in the dll as to be declared again as extern in a header file which
must be included in the main app. To get it linked in MSVC, both projects must
be in the same solution and you have to define that both projects are tied together
by right-clicking the main project and check the dependancy to the project of
the dll file. (Took me some time to find that out and finally got it linked...).
But suddenly I got
some errors when starting my little app... well let's think... due to the shared
segment thing, the dll is now loaded with my app (check the import table!).
Well, that seems logic cause the dll must be in our address space to modify
the variables in the shared section. As soon as the dll is loaded to some address
space, it tries to subclass the window procedure of the desktop listview...
but that's works just if it's in the explorer process and not in our process.
That's why I inserted a new variable called bActivate which is only set to true
by my app when the dll is about to be injected in the explorer.exe and the dll
just try to subclass when bActivate is true.
Another thing to mention is the fact that I save the values of some variables
to file when closing my app without unjecting the dll. That's needed to unload
the dll later when the app is executed again; cause for unloading the dll, some
values are needed we got from the injecting function. Otherwise we had no opportunity
the unload our dll, we had always to kill the explorer process to deactivate
it.
I think that were the most important notes, the rest of code should be easy
to understand. Hope you read something new and my code will help you sometime.
Sunshine, August
2k6
References:
[1] CodeProject.com ("Three Ways to Inject Your Code into Another Process")
This site is part of Sunshine's Homepage