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;

  }
}

Connecting to a Bluetooth device through the Widcomm/Broadcom stack

I recently worked on a project that required me to connect to a printer from a Pocket PC with the Widcomm bluetooth stack on it.  Frustrating as it was, one positive thing came from it – I generated a nice start to a set of classes for using High-Point Software‘s BTConnect.


Of course it means that you’ll need to buy BTConnect to use this library without the “evaluation mode” popup, but it abstracts the ugliness of sommand-line parameters away from the developer so you can focus on creating your app.


I’ve parked the library here:


www.opennetcf.org/shared

The Mother of All Kludges

So I spent all of today fighting with a Dell Axim X30, and I’ve again come to the conclusion that companies who keep things proprietary really suck.


My task was to print over Bluetooth.  I decided that I’d use PrinterCE from Field Software for simplicity, and all worked well if the radio was already on when I tried to print.  If the radio was off, however, things went bad quickly and the device had to be soft reset to recover.  Not a good thing for usability.


Since they use the Widcomm/Broadcomm stack, I decided to look at the BTConnect stuff from High-Point software.  After four or five hours of creating a nice class wrapper for the app (which is going to become a shared-source library from OpenNETCF shortly) I found that the radio is on whenever I am actively searching or connecting, but as soon as it’s done, the on-board BT manager shuts the radio off.  That means I can find the printer and connect with it, but as soon as I had off printing to PrinterCE, the radio shuts off and the device hangs.  Beautiful.  A plague upon the engineers at Dell.


Searching the web I find nothing hinting at how to programmatically do this.  Curses on the yet again.


I use Spy++ to see if I’m lucky and any messages might jump out when I use the BT Manager.  Nothing.  My hatred for Dell increases.


I search the registry for anything that might affect behavior and I find only one key that reports the current state.  Damn you Dell!


So I’m now a day into just turning on a Radio for a job I’ve quoted at 2 days (which I’ve already used doing the reporting code).  Now not only am I highly irritated, I’m highly irrated on my own dime.


So I make a last ditch effort to get something that at least functions.  I decided for a kludge, and a really nasty one at that.  As soon as I thought of it, I was disgusted by the idea, but I’ve really been left with no choice.  I’d simulate tapping the screen to turn the radio on.


And without further ado, I give unto you the following ugliness.  Hopefully no one elese ever has to use it, but since I’ve stooped this far, someone else probably will have to as well.  If you do, please curse Dell as well.


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;
        }

        // save where we were
        IntPtr topWindow = GetForegroundWindow();

        int bticonX = Screen.PrimaryScreen.Bounds.Width 10;
        int bticonY = Screen.PrimaryScreen.Bounds.Height 10;
        int btmenuX = Screen.PrimaryScreen.Bounds.Width 20;
        int btmenuY = Screen.PrimaryScreen.Bounds.Height 80;
        int starticonX = 10;
        int starticonY = 10;
        int todayX = 10;
        int todayY = 60;

        // start
         SendTap(starticonX, starticonY);

         // let the menu draw
         System.Threading.Thread.Sleep(300);

         // today screen
         SendTap(todayX, todayY);

         // let the screen draw
         System.Threading.Thread.Sleep(300);

         // Blutooth icon tap
         SendTap(bticonX, bticonY);

         // let the menu draw
         System.Threading.Thread.Sleep(300);

         // bt menu tap
         SendTap(btmenuX, btmenuY);

         System.Threading.Thread.Sleep(300);

         // restore where we were
         ShowWindow(topWindow, SW.NORMAL);
         UpdateWindow(topWindow);
       }
      }

    [DllImport(“coredll”)]
    private static extern void mouse_event(
            MOUSEEVENTF dwFlags, int dx, int dy, int dwData, int dwExtraInfo);

    [DllImport(“coredll”)]
    private static extern IntPtr GetForegroundWindow();


    [DllImport(“coredll”)]
    private static extern bool ShowWindow(IntPtr hWnd, SW nCmdShow);

    [DllImport(“coredll”)]
    private static extern bool UpdateWindow(IntPtr hWnd);


    [Flags]
    private enum HWND
    {
    TOP = 0,
    BOTTOM = 1,
    TOPMOST = -1,
    NOTOPMOST = -2
    }

    private enum SW
    {
    HIDE = 0,
    SHOWNORMAL = 1,
    NORMAL = 1,
    SHOWMINIMIZED = 2,
    SHOWMAXIMIZED = 3,
    MAXIMIZE = 3,
    SHOWNOACTIVATE = 4,
    SHOW = 5,
    MINIMIZE = 6,
    SHOWMINNOACTIVE = 7,
    SHOWNA = 8,
    RESTORE = 9,
    SHOWDEFAULT = 10,
    FORCEMINIMIZE = 11,
    MAX = 11
    }

    [Flags]
    private enum MOUSEEVENTF
    {
        MOVE = 0x1, /* mouse move */
        LEFTDOWN = 0x2, /* left button down */
        LEFTUP = 0x4, /*left button up */
        RIGHTDOWN = 0x8, /*right button down */
        RIGHTUP = 0x10, /*right button up */
        MIDDLEDOWN = 0x20, /*middle button down */
        MIDDLEUP = 0x40, /* middle button up */
        WHEEL = 0x800, /*wheel button rolled */
        VIRTUALDESK = 0x4000, /* map to entrire virtual desktop */
        ABSOLUTE = 0x8000, /* absolute move */
        TOUCH = 0x100000, /* absolute move */
    }

    private static void SendTap(int x, int y)
    {
      mouse_event(MOUSEEVENTF.LEFTDOWN | MOUSEEVENTF.ABSOLUTE, 
            (int)((65535 / Screen.PrimaryScreen.Bounds.Width) * x), 
            (int)((65535 / Screen.PrimaryScreen.Bounds.Height) * y), 0, 0);
      mouse_event(MOUSEEVENTF.LEFTUP, 0, 0, 0, 0);
    }
  }
}