-1

In Short : any suggestion on how to listen to Xbox guide button ? or any suggestion on how to make "inputs" more optimized and faster for low-end PCs ?

hey. I had been looking for a way to make screenshots on windows easier. for now the only built-in function is win+prtsc , which is hard to press during a game when using a controller. I also happen to be good at programming, but i'm not familiar with input libraries on python...

so I used "pyautogui" library to simulate pressing win+prtSc , and used "pynput" library to listen to the button "/" on keyboard to do a screenshot ( well, a cheating screenshot. windows is taking screenshot and im just simulating the pressing of "win+prtSc" , so I named my script "screenshit" ).

so far so good.

it's not very hard to press "/" button for every screenshot. but to extend it, I wanted to use my controller to take the screenshot. but problem is pynput does not support gamepad controllers ! so I used another library called "inputs". this one does support a gamepad controller. however it uses at least 30% of CPU usage for working... which is BAD! here's the code to detect START ( the usual Map button ) with "inputs" library :

while 1:
    events = get_gamepad()
    if events:
        print(events[0].code)
        if events[0].code == 'START' :
            ScreenShot()

I'm guessing 'get_gamepad()' is not optimized enough... but if im wrong or using it wrong, please guide me!

So , another thing that I would like to use would be the center/guide/xbox button. this button : xbox center button

but neither "inputs" nor "pynput" libraries seem to be listen to this button...

any suggestion on how to listen to this button ?

or

any suggestion on how to make "inputs" more optimized and faster for low-end PCs ?

Cool guy
  • 175
  • 2
  • 7
  • 1
    Please don't edit answers into the question. You can [answer your own question](https://stackoverflow.com/help/self-answer) instead. – MisterMiyagi Oct 06 '21 at 17:40

3 Answers3

1

Python's inputs library uses XInput API for gamepad inputs on Windows which has some built-in limitations. In XInput, applications access the current gamepad state with the XInputGetState method:

https://learn.microsoft.com/en-us/windows/win32/api/xinput/nf-xinput-xinputgetstate

The button and axis state is returned in an XINPUT_GAMEPAD struct, which doesn't even have a way to represent the Xbox button. Some applications get around this by calling an alternate (undocumented) method that has the same signature as XInputGetState and returns data in the same format as XINPUT_GAMEPAD but includes an extra bit with the Xbox button state.

https://www.google.com/search?q=%22XInputGetStateEx%22

Even if you use the undocumented API method you still won't get the Xbox button input unless you disable the Windows setting that launches the Xbox Game Bar when you press the Xbox button. When this setting is enabled, the Xbox button is suppressed for all other applications.

Also note that XInput doesn't have any sort of input event. Every time you call XInputGetState it's simply returning the current state of the gamepad based on the most recent inputs. Calling it faster than 250Hz is unnecessary since that's the fastest an Xbox controller can send new inputs. For your screenshot app, 60Hz should be plenty. That means adding a ~16 millisecond sleep between calls to get_gamepad.

nondebug
  • 761
  • 3
  • 5
  • Thanks! trying `sleep(0.016)` did help to get it to around 1 percent lower on CPU, and I also did a `sleep(0.5)` on screenshot to block user ( I'm the only user, but anyways ) to capture screenshot in short times after another. and I noticed the CPU usage gets to ~1% during `sleep`. nice. but still, I found the C++ solution better because it uses 1% in worse case! – Cool guy Oct 06 '21 at 19:36
0

Have you tried putting a sleep in your while loop (for a millisecond, or so)? Maybe the function returns instantly, and that's the cause for your high CPU usage?

E.g. in my currently used library (Java, LWJGL + GLFW),there is no event for controller inputs. Instead, you can request them at any time. This would cause the same issue.

About the XBox-Button: my library has no events for it either. The button opens the XBox Menu on Windows 10. Probably it's 100% dedicated for that and cannot be used otherwise. (therefore it can't be detected)

Antonio Noack
  • 309
  • 3
  • 11
  • 1
    Thank you for the response. I did try adding 0.5 seconds sleep after each screenshot, but it didnt help much. actually for some reason it's taking 9% CPU today, but the sleep was affectless in that regard. maybe it's just that my CPU is cooler now than yesterday... though I noticed something very disappointing. I made an empty while loop inside Pythoon and it took 6% CPU usage... so poor `get_gamepad()` only took 3% itself. I guess I'll have to switch to c++ if I really want it optimized – Cool guy Oct 06 '21 at 14:14
  • 1
    hey. I solved it with c++. it's very dirty but it does the job with less than 0.2% CPU usage!!! I can send the binary file too if anyone wanted. ( though compilation would be the best option ) – Cool guy Oct 06 '21 at 17:38
0

solution :

I googled for some hours and finally found a solution to use Xbox360's guide button to take screenshot by virtually pressing win+PrtSc together. then also playing a little capture.wav sound to play whenever taking it.

so the code is :

// compile using :
// g++ prtScX.cpp -lwinmm
    
    
#define WIN32_LEAN_AND_MEAN
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

static const int guide_button_value = 0x0400;

/* the secret function outputs a different struct than the official GetState. */
typedef struct
{
    unsigned long eventCount;
    WORD wButtons;
    BYTE bLeftTrigger;
    BYTE bRightTrigger;
    SHORT sThumbLX;
    SHORT sThumbLY;
    SHORT sThumbRX;
    SHORT sThumbRY;
} XINPUT_GAMEPAD_SECRET;

// returns 0 on success, 1167 on not connected. Might be others.
int(__stdcall *secret_get_gamepad)(int, XINPUT_GAMEPAD_SECRET *); // function pointer to secret (guide compatible) getstate */
void Screenshot();

int main(int argc, char **argv)
{
    TCHAR xinput_dll_path[MAX_PATH];
    GetSystemDirectory(xinput_dll_path, sizeof(xinput_dll_path));
    strcat(xinput_dll_path, "\\xinput1_3.dll");
    HINSTANCE xinput_dll = LoadLibrary(xinput_dll_path);
    secret_get_gamepad = (int(__stdcall *)(int, XINPUT_GAMEPAD_SECRET *))GetProcAddress(xinput_dll, (LPCSTR)100); // load ordinal 100

    XINPUT_GAMEPAD_SECRET pad1;
        
    printf("listening to keys now...\n");

    if (secret_get_gamepad(0, &pad1) != 0)
    {
        printf("Error, make sure your player 1 pad is connected.\n");
        return -1;
    }

    for (;;) // forever
    {
        secret_get_gamepad(0, &pad1);
        if (pad1.wButtons & guide_button_value)
        {
            printf("Guide button is down.\n");
            Screenshot();
            Sleep(500);
        }
        Sleep(33);
    }

    // you should probably clean up by unloading the DLL.
    return 0;
}

void Screenshot()
{
    // WIN KEY
    
    // ...
    INPUT win_in;
    // ...
    // Set up a generic keyboard event.
    win_in.type = INPUT_KEYBOARD;
    win_in.ki.wScan = 0; // hardware scan code for key
    win_in.ki.time = 0;
    win_in.ki.dwExtraInfo = 0;

    // Press the "A" key
    win_in.ki.wVk = VK_LWIN;  // virtual-key code for the "win" key
    
    // PRTSC KEY
    
    INPUT prtcs_in;
    
    // Set up a generic keyboard event.
    prtcs_in.type = INPUT_KEYBOARD;
    prtcs_in.ki.wScan = 0; // hardware scan code for key
    prtcs_in.ki.time = 0;
    prtcs_in.ki.dwExtraInfo = 0;

    // Press the "A" key
    prtcs_in.ki.wVk = VK_SNAPSHOT;  // virtual-key code for the "PrtSc" key
    
    
    
    // actually hitting 
    
    win_in.ki.dwFlags = WM_KEYDOWN; // key press down
    SendInput(1, &win_in, sizeof(INPUT));
    
    prtcs_in.ki.dwFlags = 0; // key press down
    SendInput(1, &prtcs_in, sizeof(INPUT));
    
    win_in.ki.dwFlags = WM_KEYUP; // key press up
    SendInput(1, &win_in, sizeof(INPUT));
    
    PlaySound(TEXT("capture.wav"), NULL, SND_FILENAME | SND_ASYNC);
    
    
}

to work, it needs a "capture.wav" sound in the same directory as this script.

thanks to : link1 link2 link3 link4 link5

Cool guy
  • 175
  • 2
  • 7