petzval / btferret

Python and C Bluetooth Library
MIT License
111 stars 21 forks source link

Help on modifying keyboard.c #47

Closed careylzh closed 1 week ago

careylzh commented 2 weeks ago

Hi there, thanks so much for your contribution, I am able to run keyboard.c on rpi4 (raspbian OS headless) and send keystrokes to my android phone.

However, I am trying to modify the script such that the code allows 2 phones to be connected to the rpi at the same time, and Alt+Tab changes the sending of the keystroke from one target device to the next. Any idea how I might get started?

Alt + Tab --> keystrokes get sent to the other phone

Thanks :)

petzval commented 2 weeks ago

You will have to modify btlib.c, it cannot be done from keyboard.c. These are initial suggestions - I cannot guarantee that they will work. There are two problems:

  1. Only one HID connection is allowed for security. Otherwise a rogue device could connect and see all the keystrokes. This is controlled at line 6583 (see below) with the variable multiflag. If there is an existing connection, multiflag is set to 1 and the connection is refused. So just remove this check for existing connections to leave multiflag=0.
  2. A keystroke is sent as a notification to all connected devices. This is done at line 5205 as below. The for loop looks for all connected devices and sends a notification to each one. Modify this to send a notification to the chosen device only.
btlib.c Version 17

LINE 6583
          multiflag = 0;  // HID multi
          if((gpar.hidflag & 1) != 0)  // (gpar.settings & HID_MULTI) == 0)
            {  // check for existing connection
            for(k = 1 ; multiflag == 0 && devok(k) != 0 ; ++k)
              {
              if(dev[k]->conflag == CON_LX)
                multiflag = 1;                
              } 
            }

LINE 5205

    for(devn = 1 ; cp->notify != 0 && devok(devn) != 0 ; ++devn)
      {  // search all other devices for notify
      dp = dev[devn];
      if((dp->conflag & CON_LX) != 0 && 
         (gpar.btleflag == 0 || gpar.btlenode == 0 || gpar.btlenode == dp->node))
        {  // device devn is connected as LE client
           // send notification
careylzh commented 2 weeks ago

Thanks for your fast response. I've tried out the first fix by commenting the check which changes multiflag to 1, but the raspberry pi is still not discoverable after one successful connection.

As for the second point, I am not sure how to modify the block to connect to other devices. seems like I have to fix issue one first.

petzval commented 2 weeks ago

Yes, there is another issue. Advertising is turned off at line 6663 when an HID device connects. The mesh_on function restarts advertising. Below is what I think is a complete solution, but I have not tested it. It uses the user_function routine to set the node that receives notifications. Call this from your keyboard.c code before starting le_server as below, then as required. The advantage of this code is that the default behavior can be restored, so multiple connections and targeting one phone can be turned on and off, and btlib is still usable for other applications. Modify btlib.c as follows:

in btlib.c Version 17

LINE 6663  

         if(((gpar.hidflag & 1) == 0 && (gpar.meshflag & MESH_W) != 0) ||
                user_function(0,1,0,0,NULL,NULL) != 0)         
           mesh_on();             

LINE 6583       
          multiflag = 0;  // HID multi
          if((gpar.hidflag & 1) != 0)  // (gpar.settings & HID_MULTI) == 0)
            {  // check for existing connection
            for(k = 1 ; multiflag == 0 && devok(k) != 0 ; ++k)
              {
              if(dev[k]->conflag == CON_LX && user_function(0,1,0,0,NULL,NULL) == 0)
                multiflag = 1;                
              } 
            } 

LINE 5205   

    for(devn = 1 ; cp->notify != 0 && devok(devn) != 0 ; ++devn)
      {  // search all other devices for notify          
      dp = dev[devn];
      if((dp->conflag & CON_LX) != 0 &&
       (user_function(0,1,0,0,NULL,NULL) == 0 || dp->node == user_function(0,1,0,0,NULL,NULL)) &&
         (gpar.btleflag == 0 || gpar.btlenode == 0 || gpar.btlenode == dp->node))
        {  // device devn is connected as LE client
           // send notification     

LINE 14404

int user_function(int n0,int n1,int n2,int n3,unsigned char *dat0,unsigned char *dat1)
  {
  static int activenode = 0;

  if(n1 == 0)
    activenode = n0;

  return(activenode);
  }       

IN keyboard.c

Send notifications to node 4 only
*** NOTE *** must be called before le_server
    user_function(4,0,0,0,NULL,NULL);

Send notifications to node 7 only
    user_function(7,0,0,0,NULL,NULL);

Restore default behaviour (one HID only)
    user_function(0,0,0,0,NULL,NULL);       
petzval commented 2 weeks ago

Whoops! The line 6663 modification should be as follows:

LINE 6663  

        if(((gpar.hidflag & 1) == 0 && (gpar.meshflag & MESH_W) != 0) ||
                user_function(0,1,0,0,NULL,NULL) != 0)
          mesh_on();     
careylzh commented 2 weeks ago

Thanks for this, there is some progress :)

With user_function(4,0,0,0,NULL,NULL); or user_function(7,0,0,0,NULL,NULL); before calling le_server in keyboard.c, I am now able to connect 2 android phones to my rpi. However, none of the keyboard inputs are received on any of the connected devices, even though the devices recognise the rpi as a HID device.

petzval commented 1 week ago

You need to call user_function with the node number of the target phone. There are two approaches:

  1. Put info about the phones in devices.txt and choose the node numbers.
  2. As below, let the system allocate the node numbers automatically. Call user_function before le_server with any non-zero number. When a phone connects, lecallback supplies the node. In LE_CONNECT call user_function with clientnode. Save the allocated nodes and switch with user_function as required.

  user_function(4,0,0,0,NULL,NULL);   // any non-zero number
  le_server(lecallback,0);

  close_all();
  return(1);
  }

int lecallback(int clientnode,int op,int cticn)
  {
  int n;
  static char hello[8] = { "Hello\n"};  // \n = Enter

  if(op == LE_CONNECT)
    {
    printf("Connected OK. Key presses sent to client. ESC stops server\n");
    printf("F10 sends Hello plus Enter\n");
    user_function(clientnode,0,0,0,NULL,NULL);    /********* ADD THIS ******/
    }
careylzh commented 1 week ago

Thank you! I tried method 2 above, and have achieved exactly what I needed to do at the start of this post.

Archiving my code here for anyone who is interested in future: https://github.com/careylzh/btferret/commit/051530957e3576c60fd3d3f1f8c829f616131228

Context: I chose the values 1000 and 1001 for clientnode after testing and noticing that my RPi assigns the values 1000+ for clientnode upon successful connection.

Also modified the code to use F10 to switch. Will close this thread. Thanks @petzval! 🙏