User-Space Java Bluetooth (JSR-82) Keyboard Driver

... stay tuned!

Motivation

Requirements

Explanation

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.io.IOException;

import javax.bluetooth.L2CAPConnection;
import javax.microedition.io.Connector;

public class BluetoothKeyboard {

    public static void main(String[] args) throws 
                                           AWTException,
                                           InterruptedException, 
                                           IOException {
        new BluetoothKeyboard().go();
    }

    void go() throws IOException, AWTException {

        // allow connections to PSM below 0x1001
        System.setProperty("bluecove.jsr82.psm_minimum_off", "true");

        // connect
        L2CAPConnection con = (L2CAPConnection) Connector.open("btl2cap://"
                               + kbAddr + ":13;authenticate=true;");
        System.out.println("Connected");

        // process reports
        byte hidReport[] = new byte[255];
        while (true){
            // get packet from L2CAP connection
            int packetLen = con.receive(hidReport);

            // dump report
            dumpBuffer(hidReport, packetLen);

            // process event
            handlePacket(hidReport, packetLen);
        }
    }

    void handlePacket(byte[] hidReport, int packetLen) throws 
                                           IOException,
                                           AWTException {

        // decode keys, only process up to NUM_KEYS
        int modifier = hidReport[2];
        int nrKeys = packetLen - 4;
        if (nrKeys > NUM_KEYS){
            nrKeys = NUM_KEYS;
        }

        // modifier keys
        boolean ctrl_pressed    = (modifier & 0x11) != 0;
        boolean shift_pressed   = (modifier & 0x22) != 0;
        boolean alt_pressed     = (modifier & 0x44) != 0;
        boolean command_pressed = (modifier & 0x88) != 0;

        // process events
        for (int i=0; i< nrKeys; i++){
            // find key in last state
            int new_event = hidReport[4+i];
            if (new_event == 0 || new_event == 44) continue;
            for (int j=0; j<NUM_KEYS; j++){
                if (new_event == last_keyboard_state[j]){
                    new_event = 0;
                    break;
                }
            }
            if (new_event == 0) continue;

            // get from table
            int new_key = 0;
            if (new_event <= 57){
                new_key = keytable_us_none[new_event];
            } else if (new_event >= 0x4f && new_event <= 0x52) {
                new_key = cursor_keys[ new_event - 0x4f];
            } else {
                continue;
            }

            injectKey(new_key, shift_pressed, ctrl_pressed,
                       alt_pressed, command_pressed);

        }

        // store keyboard state
        System.arraycopy(hidReport, 4, last_keyboard_state, 0, NUM_KEYS);
        last_modifier = modifier;
    }

    void injectKey(int new_key, boolean shift_pressed, 
                   boolean ctrl_pressed, boolean alt_pressed,
                   boolean command_pressed) throws AWTException {

        // create Robot instance if necessary
        if (robot == null){
            robot = new Robot();
        }

        // use upper case chars
        char key = Character.toUpperCase((char) new_key);

        // set up modifier keys
        if (shift_pressed) {
            robot.keyPress(KeyEvent.VK_SHIFT);
        }
        if (ctrl_pressed) {
            robot.keyPress(KeyEvent.VK_CONTROL);
        }
        if (alt_pressed) {
            robot.keyPress(KeyEvent.VK_ALT);
        }
        if (command_pressed){
            robot.keyPress(KeyEvent.VK_META);
        }

        // inject single press
        robot.keyPress(key);
        robot.keyRelease(key);

        // unset modifier keys
        if (command_pressed){
            robot.keyRelease(KeyEvent.VK_META);
        }
        if (alt_pressed) {
            robot.keyRelease(KeyEvent.VK_ALT);
        }
        if (ctrl_pressed) {
            robot.keyRelease(KeyEvent.VK_CONTROL);
        }
        if (shift_pressed) {
            robot.keyRelease(KeyEvent.VK_SHIFT);
        }
    }

    public void dumpBuffer(byte[] data, int len){
        System.out.print("DATA: ");
        for (int i=0;i<len;i++){
            int value = data[i];
            if (value<0){
                value += 256;
            }
            System.out.print( Integer.toHexString(value) + " ");
        }
        System.out.println();
    }

    public static final int cursor_keys[] = {
                            KeyEvent.VK_RIGHT, 
                            KeyEvent.VK_LEFT, 
                            KeyEvent.VK_DOWN, 
                            KeyEvent.VK_UP 
                         };

    public static int keytable_us_none [] = {
        0, 0, 0, 0, /* 0-3 */
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /*  4 - 13 */
        'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14 - 23 */
        'u', 'v', 'w', 'x', 'y', 'z',                     /* 24 - 29 */
        '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30 - 39 */
        KeyEvent.VK_ENTER, 27, KeyEvent.VK_BACK_SPACE,    /* 40 - 42 */
        KeyEvent.VK_TAB, ' ',                             /* 42 - 44 */
        '-', '=', '[', ']', '\\', 0, ';', '\'', '`', ',', /* 45 - 54 */
        '.', '/', KeyEvent.VK_ENTER                       /* 55 - 57 */
    }; 

    public static final int NUM_KEYS = 5;
    Robot robot;

    byte last_keyboard_state[] = new byte[NUM_KEYS];
    int last_modifier = 0;

    // address of your keyboard
    public static final String kbAddr = "001b63fa61c3";
}



Further Exploration
Build your own HalfKeyboard (tm).