First let me go on record as saying that I think using a ‘z’ to terminate words is utterly moronic, like using the number 2 instead of the word ‘to’.
Anyway, on 2 my skillz….
Last week I posted a fantastic kludge for turning on the Bluetooth radio on an Axim X30. Well, like any kludge, as soon as it shipped it broke. Turns out the notification icon isn’t always in the rightmost position – it can move. That screwed with my intricate algorithm of moving in and up 10 pixels from the lower left corner of the screen.
So what’s a developer to do? First, let’s take a quick detour into how those icons work.
They “tray” icons are actually called Notification icons and they are displayed by calling the Shell_NotifyIcon API. When the icon is created you provide a window handle for it to notify when it’s clicked. The icon itself doesn’t have any other abilities. This is a critical piece of info for this hack.
Since I know it’s posting messages to another Window when it’s clicked, I simply needed to figure out exactly what it’s doing. Time to break out Remote Spy++ in eVC (are you in the group that never really knew what the hell that tool was used for? This is a classic case).
Loaded up Spy++ and I see a Window conspicuously named “Bluetooth Console” – that’s promising. I put a watch on it and sure enough, when I tap the icon, messages get posted to that window (off is in the blue box, on in the red). Now all I need to do is post the same messages.
<IMG src="http://blog.opennetcf.org/ctacke/Photos/FindWindowBT.JPG" P
So first, I need the handle for that Window. Time for the FindWindow P/Invoke:
IntPtr btWindow = FindWindow(“WCE_BTTRAY”, “Bluetooth Console”);
Next, replicate the messages the tap generates:
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x201);
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x202);
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x200);
That causes the Bluetooth Console to create and show the popup menu. Now it needs a message to tell it to wait for a menu tap:
SendMessage(btWindow, WM_ENTERMENULOOP, 0x01, 0x00);
Now “generate” the tap:
SendMessage(btWindow, WM_COMMAND, BluetoothRadioState ? CMD_BT_OFF : CMD_BT_ON, 0x00);
And tell it to quit listening for menu taps:
SendMessage(btWindow, WM_EXITMENULOOP, 0x01, 0x00);
It does something else that I can’t tell what the effect is, but since it’s doing it, I will too:
// not sure what this does, but physically clicking does it, so replicate it here
SendMessage(btWindow, WM_USER + ((BluetoothRadioState) ? (uint)0xC00D : 0xC00C), 0x01, 0x00);
And finally get the Menu window and hide it:
IntPtr btmenu = FindWindow(“MNU”, “”);
SendMessage(btmenu, WM_DESTROY, 0x00, 0x00);
SendMessage(btmenu, WM_CANCELMODE, 0x00, 0x00);
While it’s still ugly, it’s a bit cleaner than the original, and much smaller. This is our new class in its entirety:
using System;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace OpenNETCF.Devices
{
public static class AximX30
{
public static bool BluetoothRadioState
{
get
{
RegistryKey key = Registry.LocalMachine.OpenSubKey(@”SOFTWAREWIDCOMMBtConfigGeneral”);
bool currentState = (((int)key.GetValue(“StackMode”)) == 1);
key.Close();
return currentState;
}
set
{
// see if any action is needed
if (BluetoothRadioState == value)
{
return;
}
IntPtr btWindow = FindWindow(“WCE_BTTRAY”, “Bluetooth Console”);
// pop up the menu
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x201);
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x202);
SendMessage(btWindow, WM_USER + 1, 0x1267, 0x200);
// give it time to create the menu
System.Threading.Thread.Sleep(100);
// find the menu that popped up
IntPtr btmenu = FindWindow(“MNU”, “”);
// start the window listening for menu messages
SendMessage(btWindow, WM_ENTERMENULOOP, 0x01, 0x00);
// send it the on or off message
SendMessage(btWindow, WM_COMMAND, BluetoothRadioState ? CMD_BT_OFF : CMD_BT_ON, 0x00);
// tell it it’s done listening
SendMessage(btWindow, WM_EXITMENULOOP, 0x01, 0x00);
// not sure what this does, but physically clicking does it, so replicate it here
SendMessage(btWindow, WM_USER + ((BluetoothRadioState) ? (uint)0xC00D : 0xC00C), 0x01, 0x00);
// now hide the menu
if (btmenu != IntPtr.Zero)
{
SendMessage(btmenu, WM_DESTROY, 0x00, 0x00);
SendMessage(btmenu, WM_CANCELMODE, 0x00, 0x00);
}
return;
}
}
[DllImport(“coredll.dll”)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport(“coredll.dll”)]
private static extern int SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private const uint WM_DESTROY = 0x02;
private const uint WM_CANCELMODE = 0x1F;
private const uint WM_USER = 0x400;
private const uint WM_ENTERMENULOOP = 0x0211;
private const uint WM_EXITMENULOOP = 0x0212;
private const uint WM_COMMAND = 0x0111;
private const int CMD_BT_OFF = 0x1001;
private const int CMD_BT_ON = 0x1002;
}
}