switchbrew / libnx

Library for Switch Homebrew

Home Page:https://switchbrew.github.io/libnx/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problems with single Joycon (cannot read KEY_SR, KEY_SL, etc.)

rsn8887 opened this issue · comments

I either stumbled across some bugs regarding single Joycon support in libnx, or I just don't understand it.

I am seeking a way to switch to single (split) joycons mode with

  • automatic stick axes/button rotation for horizontal mode
  • an ability to read out KEY_SR and KEY_SL.

I can succesfully switch joycons to single "vertical" mode via hidSetNpadJoyAssignmentModeSingleByDefault((HidControllerID) id).

It works, but it is not really the mode I want. On left controllers, this reassigns udlr to xyab. On right controllers, RStick_xxx is re-assigned to LStick_xxx, as expected. One of the problems is that in this mode it doesn't rotate the inputs. I have to manually re-assign all the buttons and stick axes for horizontal handling.

In addition, I cannot read out KEY_SR, KEY_SL in this mode, there's just no input from those. This is a dealbreaker at the moment. All other keys work using my code, even L and ZL on the side of the horizontal controller, apart from KEY_SR or KEY_SL. I just never get any input from KEY_SR or KEY_SL.

Is there maybe another command I have to use to switch to horizontal mode to make axes and button rotation and KEY_SR and KEY_SL work?
I tried hidSetControllerLayout((HidControllerID) id, LAYOUT_SINGLE); but when I use it, all input from the controller I switch to LAYOUT_SINGLE is muted.

I saw there's a function hidSetNpadJoyHoldType(u64 type) with no documentation. Is this what I have to use to switch to horizontal mode? What are the parameters?

Here's some example code:

for (int id=0; id<8; id++) {
  hidSetNpadJoyAssignmentModeSingleByDefault((HidControllerID) id);
}
// switching the layout to LAYOUT_SINGLE seems broken: no inputs are registered on the controller anymore as soon as I switch to LAYOUT_SINGLE)
//for (int id=0; id<8; id++) {
//  hidSetControllerLayout((HidControllerID) id, LAYOUT_SINGLE);
//}

//SL and SR are never registered for some unknown reason
// all other buttons work using this code, just not KEY_SL or KEY_SR
u64 single_joycon_buttons = hidKeysHeld((HidControllerID) 0);
if (single_joycon_buttons & KEY_SL)
  //"printf("SL pressed")"

You are using hidScan() at some point after using hidSetControllerLayout(), right? "What are the parameters?" unknown

Yes hidScanInput() is called once per frame.

hidSetControllerLayout(CONTROLLER_P1_AUTO, LAYOUT_SINGLE) is called only once when the user changes an option. I get no inputs after I call it, from the controller I call it for.

If I call it with LAYOUT_DEFAULT, e.g. hidSetControllerLayout(CONTROLLER_P1_AUTO, LAYOUT_DEFAULT), I get inputs, but they are identical to not calling hidSetControllerLayout at all. They are not rotated, and KEY_SR and KEY_SL are still never returning a button press. Other buttons work.

Full app code?

Well it is uae4all2, let me make a small test program to demonstrate the problem. I am planning to just insert

for (int id=0; id<8; id++) {
       hidSetNpadJoyAssignmentModeSingleByDefault((HidControllerID) id);
       // the following command should result in horizontal single joycons with autorotated button assignments
       // instead I get zero inputs.
       hidSetControllerLayout((HidControllerID) id, LAYOUT_SINGLE);
    }

before the main loop in the read-controls example.

#include <stdio.h>

#include <switch.h>

//See also libnx hid.h.

int main(int argc, char **argv)
{
    //Matrix containing the name of each key. Useful for printing when a key is pressed
    char keysNames[32][32] = {
        "KEY_A", "KEY_B", "KEY_X", "KEY_Y",
        "KEY_LSTICK", "KEY_RSTICK", "KEY_L", "KEY_R",
        "KEY_ZL", "KEY_ZR", "KEY_PLUS", "KEY_MINUS",
        "KEY_DLEFT", "KEY_DUP", "KEY_DRIGHT", "KEY_DDOWN",
        "KEY_LSTICK_LEFT", "KEY_LSTICK_UP", "KEY_LSTICK_RIGHT", "KEY_LSTICK_DOWN",
        "KEY_RSTICK_LEFT", "KEY_RSTICK_UP", "KEY_RSTICK_RIGHT", "KEY_RSTICK_DOWN",
        "KEY_SL", "KEY_SR", "KEY_TOUCH", "",
        "", "", "", ""
    };

    consoleInit(NULL);

    u32 kDownOld = 0, kHeldOld = 0, kUpOld = 0; //In these variables there will be information about keys detected in the previous frame

    printf("\x1b[1;1HPress PLUS to exit.");
    printf("\x1b[2;1HLeft joystick position:");
    printf("\x1b[4;1HRight joystick position:");

    for (int id=0; id<8; id++) {
       hidSetNpadJoyAssignmentModeSingleByDefault((HidControllerID) id);
       // the following command should result in horizontal single joycons with autorotated button assignments
       // instead I get zero inputs.
       hidSetControllerLayout((HidControllerID) id, LAYOUT_SINGLE);
    }

    // Main loop
    while(appletMainLoop())
    {
        //Scan all the inputs. This should be done once for each frame
        hidScanInput();

        //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame)
        u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
        //hidKeysHeld returns information about which buttons have are held down in this frame
        u64 kHeld = hidKeysHeld(CONTROLLER_P1_AUTO);
        //hidKeysUp returns information about which buttons have been just released
        u64 kUp = hidKeysUp(CONTROLLER_P1_AUTO);

        if (kDown & KEY_PLUS) break; // break in order to return to hbmenu

        //Do the keys printing only if keys have changed
        if (kDown != kDownOld || kHeld != kHeldOld || kUp != kUpOld)
        {
            //Clear console
            consoleClear();

            //These two lines must be rewritten because we cleared the whole console
            printf("\x1b[1;1HPress PLUS to exit.");
            printf("\x1b[2;1HLeft joystick position:");
            printf("\x1b[4;1HRight joystick position:");

            printf("\x1b[6;1H"); //Move the cursor to the sixth row because on the previous ones we'll write the joysticks' position

            //Check if some of the keys are down, held or up
            int i;
            for (i = 0; i < 32; i++)
            {
                if (kDown & BIT(i)) printf("%s down\n", keysNames[i]);
                if (kHeld & BIT(i)) printf("%s held\n", keysNames[i]);
                if (kUp & BIT(i)) printf("%s up\n", keysNames[i]);
            }
        }

        //Set keys old values for the next frame
        kDownOld = kDown;
        kHeldOld = kHeld;
        kUpOld = kUp;

        JoystickPosition pos_left, pos_right;

        //Read the joysticks' position
        hidJoystickRead(&pos_left, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
        hidJoystickRead(&pos_right, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);

        //Print the joysticks' position
        printf("\x1b[3;1H%04d; %04d", pos_left.dx, pos_left.dy);
        printf("\x1b[5;1H%04d; %04d", pos_right.dx, pos_right.dy);

        consoleUpdate(NULL);
    }

    consoleExit(NULL);
    return 0;
}

The above code shows the problem. No inputs. If I comment out the line

hidSetControllerLayout((HidControllerID) id, LAYOUT_SINGLE);

I get inputs, but no rotation, no SR/SL either.

Hmm, if I use LAYOUT_LEFT, I get no button re-assignment whatsoever, but at least I can read SR/SL. I don't quite get it.

repro'd :-/

Disabling init stuff in hidInitialize didn't help, even with the app as /hbmenu.nro. :-/

I found a workaround that allows me to read all buttons on all split joycons, including SL/SR. However, there is no rotation or reassignment whatsoever.

I think the default layout for all split joycons is LAYOUT_DEFAULT. I think LAYOUT_DEFAULT corresponds to what the user setting in the controller configuration screen is. However that is somehow reset whenever I start a home-brew. This might be good, since homebrew doesn't really allow to go to the controller screen and adjusting things like grip/orientation while it is running. I can go to the grip/orientation screen and see my Joycons in vertical mode. I can change them there to horizontal. But then I cannot return to my homebrew. If I restart the homebrew, the orientation is reset to vertical again.

The default layout LAYOUT_DEFAULT, with single joycons in "vertical" mode, as they are somehow always present in homebrew, doesn't allow reading SL/SR.

By forcing the layout to LAYOUT_LEFT or LAYOUT_RIGHT, all buttons become available. However, no rotation is done, and left Joycons can only be set to LAYOUT_LEFT, right Joycons only to LAYOUT_RIGHT etc.

I think LAYOUT_SINGLE (or even LAYOUT_DEFAULT) might work with SL/SR, if there was any way to force the controller config screen of the OS to also display the single horizontal joycon, but in my homebrew, it is always reset to the vertical orientation, once I split my Joycons using hidSetNpadJoyAssignmentModeSingleByDefault(). I think we might be missing an OS function to force the orientation change.

#include <string.h>
#include <stdio.h>

#include <switch.h>

//See also libnx hid.h.

int main(int argc, char **argv)
{
    //Matrix containing the name of each key. Useful for printing when a key is pressed
    char keysNames[32][32] = {
        "KEY_A", "KEY_B", "KEY_X", "KEY_Y",
        "KEY_LSTICK", "KEY_RSTICK", "KEY_L", "KEY_R",
        "KEY_ZL", "KEY_ZR", "KEY_PLUS", "KEY_MINUS",
        "KEY_DLEFT", "KEY_DUP", "KEY_DRIGHT", "KEY_DDOWN",
        "KEY_LSTICK_LEFT", "KEY_LSTICK_UP", "KEY_LSTICK_RIGHT", "KEY_LSTICK_DOWN",
        "KEY_RSTICK_LEFT", "KEY_RSTICK_UP", "KEY_RSTICK_RIGHT", "KEY_RSTICK_DOWN",
        "KEY_SL", "KEY_SR", "KEY_TOUCH", "",
        "", "", "", ""
    };

    consoleInit(NULL);

    u32 kDownOld = 0, kHeldOld = 0, kUpOld = 0; //In these variables there will be information about keys detected in the previous frame

    printf("\x1b[1;1HPress PLUS to exit.");
    printf("\x1b[2;1HLeft joystick position:");
    printf("\x1b[4;1HRight joystick position:");

    for (int id=0; id<8; id++) {
        hidSetNpadJoyAssignmentModeSingleByDefault((HidControllerID) id);
    }
    hidScanInput();
    for (int id=0; id<8; id++) {
        HidControllerType type = hidGetControllerType((HidControllerID) id);
        if (type == TYPE_JOYCON_LEFT)
            hidSetControllerLayout((HidControllerID) id, LAYOUT_LEFT);
        if (type == TYPE_JOYCON_RIGHT)
            hidSetControllerLayout((HidControllerID) id, LAYOUT_RIGHT);
    }


    // Main loop
    while(appletMainLoop())
    {
        //Scan all the inputs. This should be done once for each frame
        hidScanInput();

        //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame)
        u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
        //hidKeysHeld returns information about which buttons have are held down in this frame
        u64 kHeld = hidKeysHeld(CONTROLLER_P1_AUTO);
        //hidKeysUp returns information about which buttons have been just released
        u64 kUp = hidKeysUp(CONTROLLER_P1_AUTO);

        if (kDown & KEY_PLUS) break; // break in order to return to hbmenu

        //Do the keys printing only if keys have changed
        if (kDown != kDownOld || kHeld != kHeldOld || kUp != kUpOld)
        {
            //Clear console
            consoleClear();

            //These two lines must be rewritten because we cleared the whole console
            printf("\x1b[1;1HPress PLUS to exit.");
            printf("\x1b[2;1HLeft joystick position:");
            printf("\x1b[4;1HRight joystick position:");

            printf("\x1b[6;1H"); //Move the cursor to the sixth row because on the previous ones we'll write the joysticks' position

            //Check if some of the keys are down, held or up
            int i;
            for (i = 0; i < 32; i++)
            {
                if (kDown & BIT(i)) printf("%s down\n", keysNames[i]);
                if (kHeld & BIT(i)) printf("%s held\n", keysNames[i]);
                if (kUp & BIT(i)) printf("%s up\n", keysNames[i]);
            }
        }

        //Set keys old values for the next frame
        kDownOld = kDown;
        kHeldOld = kHeld;
        kUpOld = kUp;

        JoystickPosition pos_left, pos_right;

        //Read the joysticks' position
        hidJoystickRead(&pos_left, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
        hidJoystickRead(&pos_right, CONTROLLER_P1_AUTO, JOYSTICK_RIGHT);

        //Print the joysticks' position
        printf("\x1b[3;1H%04d; %04d", pos_left.dx, pos_left.dy);
        printf("\x1b[5;1H%04d; %04d", pos_right.dx, pos_right.dy);

        consoleUpdate(NULL);
    }

    consoleExit(NULL);
    return 0;
}

I feel like the OS might be preventing a layout change to a layout that is not the same as what is displayed in the grip/orientation screen.

Thought it worked at some point though. :-/

It worked in PFBA, you are right. Maybe it is a regression.

"PFBA" That and SDL2 doesn't use LAYOUT_SINGLE.

But PFBA reads out SR and SL somehow, and doesn't seem to do manual button reassignment. Maybe when the last version of PFBA was compiled, SL/SR reading and button rotation worked even in LAYOUT_DEFAULT?

I used my work-around in UAE4All2 for now. I am forcing the LAYOUT_LEFT/LAYOUT_RIGHT depending on split-Joycon type, and manually do all the rotation/button reassignment. It was a pain to code but now it works very robustly, including SR/SL readout.

It would be nice to have the Switch do all the button/axes re-assignment and rotation automatically though.

Another thing I noticed is that for LAYOUT_RIGHT, the SR key has to be read using KEY_SR << 2 and similar for KEY_SL. For LAYOUT_LEFT, the bits are KEY_SR and KEY_SL. It makes sense to me that the bits should be distinct since they are distinct buttons. In the read-controls test program this can be seen when pressing KEY_SL on a LAYOUT_RIGHT single Joycon and it display as "Key_TOUCH", while KEY_SR displays as empty string.

commented

Interesting discovery. libnx needs to be updated for this. Here are my proposed changes:

  • I think KEY_TOUCH (which is a custom pseudokey added by us) should not overlap with the SL/SR buttons and should be moved higher up to BIT28.
  • KEY_SL/SR should be renamed to KEY_SL/SR_LEFT.
  • KEY_SL/SR_RIGHT should be introduced at bits 26&27.
  • KEY_SL/SR would then be defined as be KEY_SL/SR_LEFT | KEY_SL/SR_RIGHT.

I think that would be great. I made a PR to libnx
#207