Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS environment variable. #2962

Open
3 tasks
KingOfIce77 opened this issue Dec 28, 2021 · 21 comments
Open
3 tasks
Labels
docs easy An easy challenge to solve good first issue joystick pygame.joystick

Comments

@KingOfIce77
Copy link

KingOfIce77 commented Dec 28, 2021

Hijacking top post for tasks:

  • Add environment variable to pygame module docs with the other environment variables
  • Add note about current behaviour (defaults to no joystick events for apps without focus) to joystick module introduction somewhere.
  • Nice to have: open a new issue about adding SetSDLHint() & GetSDLHint() (names not final) to new context module.

Windows
Python 3.10
PyGame 2.1.2

I had to go back to version 2.1.0 and it works again : my pygame window that captures all joystick events doesn't capture anything anymore when I switch to another window

Current behavior:

No joystick events catched

Expected behavior:

joystick events catched (wirks on pygame 2.1.0)

code

# Extract from my 2.1.0 working code
pygame.init()

screen = pygame.display.set_mode((200, 200), pygame.NOFRAME)
hwnd = pygame.display.get_wm_info()["window"]
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 600, 300, 0, 0, win32con.SWP_NOSIZE)
...
joysticks = [pygame.joystick.Joystick(x) for x in range(pygame.joystick.get_count())]

for joy in joysticks:
    print(joy.get_name(), joy.get_id(), joy.get_guid(), joy.get_instance_id())
    if joy.get_name() == 'My joystick 1':
        MyJoysticks['My joystick 1']={}
        MyJoysticks['My joystick 1']['joy'] = joy
        MyJoysticks['My joystick 1']['joy'].init()
...
while 1==1:
    ...
    PosJoy1 = MyJoysticks['My joystick 1']['joy'].get_axis(0)
    ...
@Starbuck5
Copy link
Contributor

@KingOfIce77 This seems like it's an issue with SDL 2.0.16 -> 2.0.18.
In the pygame info prompt, you should see pygame 2.1 running on SDL 2.0.16, and pygame 2.1.2 running on SDL 2.0.18.

I've built a wheel of pygame 2.1.2 for Windows and Python 3.10, but using SDL 2.0.16:
pygame-2.1.2.dev1-cp310-cp310-win_amd64.zip

Unzip that and pip install directly from the file, and then say here whether the problem persists.

@KingOfIce77
Copy link
Author

Hello ! Thanks for the quick answer. It seems to be ok with your package 2.1.2.dev1 using SDL 2.0.16
It works in the quick test I just did.

@Starbuck5
Copy link
Contributor

Starbuck5 commented Dec 28, 2021

@zoldalma999 Do you have the time and stuff to handle this one?

It looks like we need to report upstream to SDL, but I feel bad doing it without a C minimum reproducible example, and I don't have the hardware for this on my end.

@zoldalma999
Copy link
Contributor

Thanks for the bug report. have two more questions to ask you:

  1. What controller are you using?
  2. So the problem is that while the window is inactive (for example switched to another window), there are no joystick events, right? And when switching back to the pygame window, everything works as it should (ie get_axis returns the correct value).

@MyreMylar
Copy link
Contributor

Isn't joystick events not being passed to an inactive window an improvement?

@KingOfIce77
Copy link
Author

@zoldalma999
1- my controller is a steering wheel + pedal (Fanatec)
2- Yes

I'm just pointing out that it works in version 2.1.0 and not in 2.1.2. Personally, I need this.
We can discuss about the notion of "active" window... When I stream a game with OBS Studio, I play my game and OBS Studio is then an "inactive" window: fortunately it still captures ;-D Same thing for getting information from my computer, AIDA64, CPU-Z and others don't stop the collection as soon as I switch to another window.
I'm doing simracing and I need a lot of additional information when I'm piloting, my pygame window is displayed as an overlay (so it's active for me and looked at even if the game is technically the active window).

Don't hesitate if you have other questions or if you want I perform specifical tests.

@zoldalma999
Copy link
Contributor

Isn't joystick events not being passed to an inactive window an improvement?

Yes, it would be new to pygame. That is why I asked if this is the issue - it should not work this way on either versions. However, there is an SDL hint for it, SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, which allows you to toggle that. We might want to expose this trough something like pygame.joystick.allow_background_events.

@KingOfIce77
Copy link
Author

It's personal so it's selfish but as long as I still have the functionality, I'm fine with it!
(for information if not too long to explain, since it's possible why is it annoying to leave by default ? penalizing in performance ?)

@MyreMylar
Copy link
Contributor

MyreMylar commented Dec 30, 2021

I think having it on by default is a bit counterintuitive. Users normally expect only the active application to receive input style events like mouse clicks - joystick/controller events would be in that class for me. In practice it doesn't likely come up that much because who plays two games simultaneously on PC without closing one down? Then again with modern consoles having freeze and resume features for games maybe we'll get a new generation who will do this...

Anyway, I think exposing the SDL_HINT is a fine solution for those that are using this behaviour, but I think the default should follow SDL's logic.

@ankith26
Copy link
Contributor

import os

# needs to be set before importing pygame, or can also be set from shell
# while calling the script
os.environ["SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"

import pygame

[other code here]

You can do something like this to set the SDL hint to get it behave the way you want. zoldalma has confirmed this works for him, so it should work for you as well. Oh well, this looks a bit inelegant from code quality POV, but until pygame exposes more of SDL hint get/set API, this is the only way to set SDL hints from pygame

@Starbuck5
Copy link
Contributor

Reopening because I just directed someone here from the pygame mailing list.

Which means (anecdotally) that people care about this functionality. So we should probably document it somehow, at least.

@Starbuck5 Starbuck5 reopened this Jan 11, 2022
@wrybread
Copy link

wrybread commented Jan 11, 2022

os.environ["SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"

Thanks for that!

And does it really require a numerical string, or is this more correct?

os.environ["SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = True

And unfrotunately I won't be back at that computer to test for probably a week, but will post how it works then. For now I reverted to 1.9.6.

I think having it on by default is a bit counterintuitive. Users normally expect only the active application to receive input style events like mouse clicks - joystick/controller events would be in that class for me.

For whatever it's worth joystick input seems to be an exception to that rule. Every other Python library that works with joysticks, such as the inputs library, has the joystick input as global. And on Windows for example the joystick config utility in the control panel doens't need focus to catch joystick events. Same goes for every linux joystick testing utility I've tried. I can't think of any joystick utilities or games that need focus.

@HeliPylot
Copy link

Hi,

I currently have the exact same problem - I need to capture joystick events even when the pygame window is not in focus. I tried including os.environ["SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1", however, this does not work.

I am using Python 3.9 and pygame 2.1.2 on Windows 10.

@MyreMylar
Copy link
Contributor

I can also confirm that the environment variable doesn't work on it's own on Windows.

The SDL hint does work if you add it directly to the pygame code base before SDL_Init and recompile pygame.
I guess that SetHint() doesn't get called before SDLinit() based on environment variables. Or maybe at all? Not sure how zoldama had it working, different platform perhaps?

We could add a patch like:

if (strcmp(SDL_getenv("SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"), "1") == 0){
        SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,"1");
}

to pg_init in base.c before SDL_Init - but perhaps a more generic solution would be better to allow setting more SDL hints than just this one?

@ankith26
Copy link
Contributor

I can also confirm that the environment variable doesn't work on it's own on Windows.

Are you sure you are setting it before importing pygame and calling pygame.init()?

@MyreMylar
Copy link
Contributor

I can also confirm that the environment variable doesn't work on it's own on Windows.

Are you sure you are setting it before importing pygame and calling pygame.init()?

Yes. This was the test code I used:

import os

# needs to be set before importing pygame, or can also be set from shell
# while calling the script
os.environ["SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"

import pygame

# Define some colors.
BLACK = pygame.Color('black')
WHITE = pygame.Color('white')


# This is a simple class that will help us print to the screen.
# It has nothing to do with the joysticks, just outputting the
# information.
class TextPrint(object):
    def __init__(self):
        self.reset()
        self.font = pygame.font.Font(None, 20)


    def tprint(self, screen, textString):
        textBitmap = self.font.render(textString, True, BLACK)
        screen.blit(textBitmap, (self.x, self.y))
        self.y += self.line_height

    def reset(self):
        self.x = 10
        self.y = 10
        self.line_height = 15

    def indent(self):
        self.x += 10

    def unindent(self):
        self.x -= 10


pygame.init()

# Set the width and height of the screen (width, height).
screen = pygame.display.set_mode((500, 700))

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates.
clock = pygame.time.Clock()

# Initialize the joysticks.
pygame.joystick.init()

# Get ready to print.
textPrint = TextPrint()

# -------- Main Program Loop -----------
while not done:
    #
    # EVENT PROCESSING STEP
    #
    # Possible joystick actions: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN,
    # JOYBUTTONUP, JOYHATMOTION
    for event in pygame.event.get(): # User did something.
        if event.type == pygame.QUIT: # If user clicked close.
            done = True # Flag that we are done so we exit this loop.
        elif event.type == pygame.JOYBUTTONDOWN:
            print("Joystick button pressed.")
        elif event.type == pygame.JOYBUTTONUP:
            print("Joystick button released.")

    #
    # DRAWING STEP
    #
    # First, clear the screen to white. Don't put other drawing commands
    # above this, or they will be erased with this command.
    screen.fill(WHITE)
    textPrint.reset()

    # Get count of joysticks.
    joystick_count = pygame.joystick.get_count()

    textPrint.tprint(screen, "Number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        try:
            jid = joystick.get_instance_id()
        except AttributeError:
            # get_instance_id() is an SDL2 method
            jid = joystick.get_id()
        textPrint.tprint(screen, "Joystick {}".format(jid))
        textPrint.indent()

        # Get the name from the OS for the controller/joystick.
        name = joystick.get_name()
        textPrint.tprint(screen, "Joystick name: {}".format(name))

        try:
            guid = joystick.get_guid()
        except AttributeError:
            # get_guid() is an SDL2 method
            pass
        else:
            textPrint.tprint(screen, "GUID: {}".format(guid))

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()
        textPrint.tprint(screen, "Number of axes: {}".format(axes))
        textPrint.indent()

        for i in range(axes):
            axis = joystick.get_axis(i)
            textPrint.tprint(screen, "Axis {} value: {:>6.3f}".format(i, axis))
        textPrint.unindent()

        buttons = joystick.get_numbuttons()
        textPrint.tprint(screen, "Number of buttons: {}".format(buttons))
        textPrint.indent()

        for i in range(buttons):
            button = joystick.get_button(i)
            textPrint.tprint(screen,
                             "Button {:>2} value: {}".format(i, button))
        textPrint.unindent()

        hats = joystick.get_numhats()
        textPrint.tprint(screen, "Number of hats: {}".format(hats))
        textPrint.indent()

        # Hat position. All or nothing for direction, not a float like
        # get_axis(). Position is a tuple of int values (x, y).
        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.tprint(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()

        textPrint.unindent()

    #
    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
    #

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

    # Limit to 20 frames per second.
    clock.tick(20)

# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

Which is the joystick module test code from the docs plus your snippet above.

Perhaps the environment variables in os.environ don't get turned into SDL environment variables/Hints quick enough?

@zoldalma999
Copy link
Contributor

Seems like when using environment variables, SDL hints do not work with the "HINT_" part in the name. SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS works for me, while SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS does not. Also, this specific hint works after importing pygame, but before calling pygame.init, so you don't need to violate PEP 8 either.

I still support the idea of either exposing set/get_hint (maybe the new context module would be a nice place for it), with documenting the important hints and linking to all SDL hints, or adding a function to joystick, like allow_background_events, as I mentioned above. But at the very least there should be something added to the joystick docs about this.

@zoldalma999 zoldalma999 added the joystick pygame.joystick label Jun 24, 2022
@wsyxbcl
Copy link

wsyxbcl commented Jul 5, 2022

Seems like when using environment variables, SDL hints do not work with the "HINT_" part in the name. SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS works for me, while SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS does not.

Yes, SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS did work.

@MyreMylar MyreMylar changed the title Joystick events are not captured anymore (window in foreground) Document SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS environment variable. Jul 16, 2022
@MyreMylar MyreMylar added docs good first issue easy An easy challenge to solve and removed bug labels Jul 16, 2022
@navitux
Copy link

navitux commented Aug 8, 2022

Hello, Is this issue still open ?

@MyreMylar
Copy link
Contributor

Hello, Is this issue still open ?

Yes. there are three tasks in the first post in this thread that still need doing to tie it off.

@VebjornRiiser
Copy link

VebjornRiiser commented Nov 12, 2023

Hey, it seem like the two first tasks are fixed, but not yet marked as completed:

[ ] Add environment variable to pygame module docs with the other environment variables

https://www.pygame.org/docs/ref/pygame.html
image

[ ] Add note about current behaviour (defaults to no joystick events for apps without focus) to joystick module introduction somewhere.

https://www.pygame.org/docs/ref/joystick.html
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs easy An easy challenge to solve good first issue joystick pygame.joystick
Projects
None yet
Development

No branches or pull requests

10 participants