pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
820 stars 130 forks source link

Document SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS environment variable. (2962) #1501

Closed GalacticEmperor1 closed 11 months ago

GalacticEmperor1 commented 1 year ago

Issue №2962 opened by KingOfIce77 at 2021-12-28 09:45:03

Hijacking top post for tasks:

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)
    ...

Comments

*Starbuck5 commented at 2021-12-28 11:33:44*

@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 commented at 2021-12-28 13:36:26*

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 commented at 2021-12-28 22:35:53*

@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 commented at 2021-12-30 08:22:39*

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 commented at 2021-12-30 08:55:51*

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


*KingOfIce77 commented at 2021-12-30 09:43:27*

@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 commented at 2021-12-30 09:46:37*

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 commented at 2021-12-30 11:14:03*

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 commented at 2021-12-30 12:03:54*

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 commented at 2021-12-31 04:21:16*

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 commented at 2022-01-11 05:24:08*

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.


*wrybread commented at 2022-01-11 07:36:50*

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 commented at 2022-06-09 18:15:50*

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 commented at 2022-06-09 20:27:22*

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 commented at 2022-06-10 02:45:00*

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 commented at 2022-06-10 20:04:24*

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 commented at 2022-06-11 09:01:23*

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.


*wsyxbcl commented at 2022-07-05 08:36:06*

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.


*navitux commented at 2022-08-08 01:32:42*

Hello, Is this issue still open ?


*MyreMylar commented at 2022-08-08 08:21:54*

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.

MyreMylar commented 11 months ago

Looking at our docs this issue did get resolved at some point.