Showing my Kludge Skillz

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&quot; 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;

  }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s