ButtonThrower and InputDeviceNode: Unexpected mouse-events

I think that I’ve made quite a bit of progress on my key-mapping retro-fit, but I’ve encountered an odd bug: The first time that I click on one of my control-mapping buttons, I get an unintended mouse-event. It happens only that first time in a given run, however–subsequent button-presses don’t produce this behaviour.

Unfortunately, this results in the key-mapper taking that unintended event to be the user’s selected mapping. :/

A little investigation indicates that it seems to be related to the code that generates ButtonThrowers and InputDeviceNodes for additional devices–commenting out that code seems to remove the undesired mouse-event.

I’ve put together a small test-program that demonstrates this behaviour (on my machine, at least).

When first run, this test-program isn’t listening for key-presses. However, pressing the DirectButton that it displays causes it to start doing so; from then on, it should print out notifications of buttons being pressed or released.

On my machine, when I release the mouse-button after pressing the button, I get two mouse-release events: one reporting mouse1, and one reporting mouse3.

If, however, I comment out the section of code that adds ButtonThrowers and InputDeviceNodes for additional devices–but leave in the use of the default keyboard-and-mouse ButtonThrower–I don’t get these unintended events.

Here is the program:

from panda3d.core import ButtonThrower, InputDeviceNode

from direct.gui.DirectGui import DirectButton

from direct.showbase import ShowBase as showBase


class Game(showBase.ShowBase):

    def __init__(self):
        showBase.ShowBase.__init__(self)

        self.accept("escape", self.userExit)

        btn = DirectButton(text = "Start accepting presses",
                           scale = 0.1,
                           command = self.startAcceptingPresses)

        self.buttonThrower = base.buttonThrowers[0].node()

        self.accept("keyInterception", self.keyInterception)
        self.accept("keyRelease", self.keyRelease)

    def startAcceptingPresses(self):
        self.buttonThrower.setButtonDownEvent("keyInterception")
        self.buttonThrower.setButtonUpEvent("keyRelease")

        # Comment out the following to remove the bug

        devices = base.devices.getDevices()
        for device in devices:
            deviceTypeString = str(device.device_class)
            thrower = ButtonThrower(deviceTypeString)
            thrower.setButtonDownEvent("keyInterception_"+deviceTypeString)
            self.accept("keyInterception_"+deviceTypeString, self.keyInterception)
            thrower.setButtonUpEvent("keyRelease")
            dataNP = base.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
            dataNP.node().addChild(thrower)

    def keyInterception(self, key):
        print (key, "was pressed")

    def keyRelease(self, key):
        print (key, "was released")

app = Game()
app.run()

Am I doing something incorrectly? Is this a bug in Panda?

You have set the keyRelease event on the default ButtonThrower (the one that is responding to standard mouse/keyboard input from the window). So as soon as you first release the mouse on the button, Panda calls the button handler and starts listening for that event, and every next time that you do that, you will get the mouse1-up event. What I’m seeing when I run it is that I see “mouse1 was released” every time I release the button except for the first time, which looks completely expected.

I think what might be happening is that on your system you have a mouse that also shows up in the input device list, so you are both receiving mouse events from the window as well as receiving it directly from the input device in question.

As for why it is reporting mouse3, I’m not sure. Do you have your mouse configured in left-handed mode? Which OS are you on?

Hmm… Very odd!

I’ll investigate this tomorrow and report back, I intend. It should be easy enough to check the device-list, I imagine.

I’ll likely also double-check what events I’m getting in my key-mapper, just in case something is different there.

Nope, not to the best of my knowledge. (I haven’t intentionally done so, and I’m using it right-handed.)

Ubuntu 18.04.2, I believe.

Hmm… Some further testing has revealed some points that are interesting, but a little odd:

I commented out the calls to “setButton -Down/-Up -Event” that act on the “keyboard-and-mouse” button-thrower–that is, the two calls above the for-loop. In this case, I (understandably) don’t get any mouse-events–except for those first two.

But conversely, I printed out the device-class associated with the devices that appear in that for-loop, and the mouse doesn’t seem to be there.

However! In further investigating, I think that I’ve found the source of the events: my Bamboo drawing tablet (which I generally leave plugged in). If I unplug that, I get no phantom mouse-events. A quick test with my gamepad plugged in but the tablet disconnected again produced no phantom mouse-events, indicating that the problem is related to the drawing-tablet.

Note that during these tests, the tablet was lying by my side, its pen on the table; I wasn’t using it, so I would expect no events from it.

I note that when printing out the name and device-type of the tablet, while the name looks fine, its device-type is given as “unknown”.

Perhaps that’s the problem, then? That a thrower associated with the “unknown” device-type automatically picks up mouse events for some reason?

One more clue:

If I change the test-program such that, instead of having a DirectButton, it “accepts” a keyboard-button press, I still get those phantom mouse-events. Similarly, if I keep the button, but remove the “command”-keyword and instead bind the button to respond to “DGG.B1PRESS”, I get the phantom events immediately on pressing the button, not on release.

It looks to me like something is generating mouse-up events, either for drawing tablets specifically or for devices of the “unknown” type.

One possibility that comes to mind is that the system with regards to such devices starts off in a state that indicates a button having been pressed–whether that’s accurate or not–and then, on having a ButtonThrower/InputDeviceNode attached, “notices” that the buttons aren’t pressed and so sends “mouse-up” methods for them.

Here is a revised test-program that allows this behaviour to be tested without mouse-presses to muddy the waters. In this version, press the space-bar to start listening for button-events.

from panda3d.core import ButtonThrower, InputDeviceNode

from direct.showbase import ShowBase as showBase


class Game(showBase.ShowBase):

    def __init__(self):
        showBase.ShowBase.__init__(self)

        self.accept("escape", self.userExit)

        self.accept("space", self.startAcceptingPresses)

        self.buttonThrower = base.buttonThrowers[0].node()

        self.accept("keyInterception", self.keyInterception)
        self.accept("keyRelease", self.keyRelease)

    def startAcceptingPresses(self, unused = None):
        self.buttonThrower.setButtonDownEvent("keyInterception")
        self.buttonThrower.setButtonUpEvent("keyRelease")

        # Comment out the following to remove the bug

        devices = base.devices.getDevices()
        for device in devices:
            print (device, device.device_class)
            deviceTypeString = str(device.device_class)
            thrower = ButtonThrower(deviceTypeString)
            thrower.setButtonDownEvent("keyInterception_"+deviceTypeString)
            self.accept("keyInterception_"+deviceTypeString, self.keyInterception)
            thrower.setButtonUpEvent("keyRelease")
            dataNP = base.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
            dataNP.node().addChild(thrower)

    def keyInterception(self, key):
        print (key, "was pressed")

    def keyRelease(self, key):
        print (key, "was released")

app = Game()
app.run()

I’ve filed an issue on GitHub for this: https://github.com/panda3d/panda3d/issues/642