Creating Keyboardevents

Hi!

I need to generate keyboard events programatically.
it usually works fine when I use , e.g.,

messenger.send('p')

but when I tried to generate events to be used by DirectEntry object, it didn’t seem to notice.
When I dug deep I found out that directgui object require a PGMouseWatcherParameter in the parameterList.
This is where I am stuck. I tried to create an instance of PGMouseWatcherParameter but it can’t be instantiated.
Is there a way to create a PGMouseWatcherParameter and fill in with parameters?

Regards
Shashank

The DirectEntry gets its events from deep within C++ code, within the data graph traversal. It doesn’t listen to the messenger events, which are mainly intended for the benefit of high-level Python code.

So the short answer is, there’s no way to generate synthetic events for the DirectEntry from Python. But maybe there’s another way to achieve the same thing. For instance, you can set the DirectEntry’s text so that it contains a “p” or any text you need it to contain.

David

thanks for the quick response david :slight_smile:

Actually I need to catch all keyboard events.
To give you the idea, I am having a panda3d display inside a wxPython GUI with panda holding the main loop. But the problem is that once the keyboard focus is shifted to the GUI, it never seems to return to panda, although moue events work.
This is what I need synthetic events for, to pass on keyboard events to panda.
Is there a way to give keyboard focus back to panda?
Otherwise, is it possible to change/replace the C++ class to accept something else as extraArgs?

Regards
Shashank

In order to synthesize keyboard events in low-level C++ Panda, you will need to write a specialized MouseAndKeyboard implementation that generates the appropriate button events in its do_transmit_data() method. Or, you could specialize MouseWatcher the same way, to generate appropriate button events in its own do_transmit_data() method.

There’s not a method you can call from the Python level that will provide a hook into the C++ keyboard event loop, though.

But it seems like you’re tackling the symptom, rather than the problem. Keyboard events stop getting delivered to the Panda window? Intriguing. Have you tried putting:
notify-category-windisplay spam
in your Config.prc? This should generate output with each Windows event message that is sent to Panda’s main window. You should be able to see the keyboard button events being delivered to the window, for instance, before the wxPython window gets focus.

If you still see them being delivered to the window after you return focus to the Panda window, then it must be a Panda problem that’s causing the keyboard button events to be lost. On the other hand, if you longer see them, then it must be a Windows or wxWidgets problem.

David

I tried

notify-level-windisplay spam

but the keyboard events don’t seem to reach panda

In the output i can see sveral initial setfocus-killfocus pairs but this unfortunately ends in a killfocus.

I checked to see if wxPython is gobbling up the key events but I could find any reason to believe that.
Strangely, mouse events are working without problem

Could you give a possible explanation?

Regards
Shashank

I dunno. Any Windows experts out there?

David

Hi!

just now found out that key events are actually not going unheard.
while using

notify-level-windisplay spam

if panda has focus I get two output lines somewhat like
:display:windisplay(debug): keydown 72 (h)
and
:display:windisplay(spam): 10.0126 window_proc(0FC4E91C, 255, 0, 115472265)
but after losing focus the first line doesn’t appear but the second still does.
So in some way panda is actually reacting to keypresses and apparently the event is being eaten up somewhere in the middle.

Also, are these spam and debug notifications coming from a python module or from c++ and can you please tell me the name of the origin module or file?

Regard
Shashank

All notifications are sent from C++. I don’t know where to look in C++, but you can fix it by reopening Panda window. So you can get it right after it’s minimized too, or using other controls, as you described in your old post :
discourse.panda3d.org/viewtopic.php?t=2487

This is based on David’s MDI sample :

import wx
import sys,time

from pandac.PandaModules import *
loadPrcFileData('', '''
  window-type none
#   notify-level-windisplay spam
  win-origin 0 0 # <-------- to remove the offset of the 3D window
''')
from direct.directbase.DirectStart import *
from direct.showbase.DirectObject import DirectObject

class App(wx.PySimpleApp,DirectObject):
    def __init__(self):
        wx.PySimpleApp.__init__(self)

        #Create a new event loop (to overide default wxEventLoop)
        self.evtloop = wx.EventLoop()
        self.old = wx.EventLoop.GetActive()
        wx.EventLoop.SetActive(self.evtloop)
        taskMgr.add(self.wx, "Custom wx Event Loop")

    # wxWindows calls this method to initialize the application
    def OnInit(self):
        self.SetAppName('My wx app')
        self.SetClassName('MyAppClass')

        self.parent = wx.MDIParentFrame(None, -1, 'My wx app',size=(600,-1))
        self.PandaWnd = wx.MDIChildFrame(self.parent, -1, 'Panda window')
        self.controlsFrame = wx.MDIChildFrame(self.parent, -1, 'controls window')
        self.dummyFrame = wx.MDIChildFrame(self.parent, -1, 'dummy window',style=wx.FRAME_NO_TASKBAR)
        self.panel = wx.Panel(self.controlsFrame)
        self.dummyPanel = wx.Panel(self.dummyFrame)

        self.parent.SetClientSize((550, 450))
        self.parent.Center()
        self.parent.Show(True)
        PandaWndSize=(500,400)
        self.PandaWnd.SetClientSize(PandaWndSize)
        self.PandaWnd.Show(True)
        # I need to know when I have to re-open PANDA window
        self.PandaWnd.Bind(wx.EVT_SET_FOCUS,self.onPandaWindowGetFocus)

        # opens Panda window
        base.windowType = 'onscreen'
        props = WindowProperties.getDefault()
        props.setParentWindow(self.PandaWnd.GetHandle())
        props.setSize(*PandaWndSize)
        base.openDefaultWindow(props = props)

        # controls
        self.textbox = wx.TextCtrl(self.panel,-1,size=(200,-1))
        self.button = wx.Button(self.panel,wx.ID_OK)

        # controls window sizer
        controlsSizer = wx.BoxSizer(wx.VERTICAL)
        controlsSizer.Add(self.textbox, 0, wx.ALIGN_CENTER_HORIZONTAL, 5)
        controlsSizer.Add(self.button, 0, wx.ALIGN_CENTER_HORIZONTAL, 5)

        self.panel.SetSizer(controlsSizer)
        controlsSizer.Fit(self.controlsFrame)
        controlsSizer.SetSizeHints(self.controlsFrame)

        # app window sizer
        MDISizer = wx.BoxSizer(wx.VERTICAL)
        MDISizer.Add(self.controlsFrame, 0, wx.ALIGN_LEFT, 5)
        MDISizer.Add(self.PandaWnd, 0, wx.ALIGN_LEFT, 5)
        MDISizer.Add(self.dummyFrame, 0, wx.ALIGN_LEFT, 5)

        self.dummyPanel.SetSizer(MDISizer)
        MDISizer.Fit(self.parent)
        MDISizer.SetSizeHints(self.parent)

        # events
        self.acceptOnce('delete',self.delVP2)
        self.accept('escape',self.exit)
        self.accept('a',printTM)

        camera.setPos(7.61, -12.01, 3.52)
        camera.setHpr(36.70, -4.25, 2.87)
        mat=Mat4(camera.getMat())
        mat.invertInPlace()
        base.mouseInterfaceNode.setMat(mat)

        # main scene
        p4=loader.loadModel('panda-model')
        p4.setScale(.01)
        p4.reparentTo(render)

        # scene 1
        scene1=NodePath('SCENE 1')
        panda=loader.loadModel('panda')
        panda.reparentTo(scene1)
        panda.hprInterval(5,Vec3(360,0,0)).loop()
        self.VPcam1 = Camera('vpcam1')
        self.VPcam1.setScene(scene1)
        self.VPcamNP1 = scene1.attachNewNode(self.VPcam1)
        self.VPcamNP1.setPos(panda.getBounds().getCenter())
        self.VPcamNP1.setY(-panda.getBounds().getRadius()*3.5)
        self.makeDisplayRegion('dr1',base.win,self.VPcamNP1,.7,1,0,.33,sort=1000)

        # scene 2
        scene2=NodePath('SCENE 2')
        smiley=loader.loadModel('smiley')
        smiley.reparentTo(scene2)
        smiley.hprInterval(5,Vec3(360,0,0)).loop()
        self.VPcam2 = Camera('vpcam2')
        self.VPcam2.setScene(scene2)
        self.VPcamNP2 = scene2.attachNewNode(self.VPcam2)
        self.VPcamNP2.setPos(smiley.getBounds().getCenter())
        self.VPcamNP2.setY(-smiley.getBounds().getRadius()*3.5)
        self.makeDisplayRegion('dr2',base.win,self.VPcamNP2,.7,1,.33,.66,sort=1100)

        # scene 3
        scene3=NodePath('SCENE 3')
        teapot=loader.loadModel('teapot')
        teapot.reparentTo(scene3)
        teapot.hprInterval(5,Vec3(360,0,0)).loop()
        self.VPcam3 = Camera('vpcam3')
        self.VPcam3.setScene(scene3)
        self.VPcamNP3 = scene3.attachNewNode(self.VPcam3)
        self.VPcamNP3.setPos(teapot.getBounds().getCenter())
        self.VPcamNP3.setY(-teapot.getBounds().getRadius()*3.5)
        self.makeDisplayRegion('dr3',base.win,self.VPcamNP3,.7,1,.66,1,sort=1200)

        return True

    def makeDisplayRegion(self,name,win,cam,l=0,r=1,b=0,t=1,sort=1000):
        dr=win.makeDisplayRegion(l,r,b,t)
        dr.setCamera(cam)
        dr.setSort(sort)
        cam.setSx(base.getAspectRatio()*(r-l)/(t-b))
        setattr(self,name,dr)
        if not hasattr(self,'DisplayRegionsCollection'):
           self.DisplayRegionsCollection=[]
        self.DisplayRegionsCollection.append((name,cam,l,r,b,t,dr))
#         print 'DR:',name,win,cam,l,r,b,t

    def removeDisplayRegion(self,name):
        for c in self.DisplayRegionsCollection:
            if c[0]==name:
               self.DisplayRegionsCollection.remove(c)
               return

    def recreateDisplayRegions(self):
        lastDRNC=self.DisplayRegionsCollection
        self.DisplayRegionsCollection=[]
        for name,cam,l,r,b,t,dr in lastDRNC:
            self.makeDisplayRegion(name,base.win,cam,l,r,b,t)

    def delVP2(self):
        self.dr2.getCamera().getTop().removeChildren()
        self.dr2.getCamera().getTop().removeNode() # remove scene 2
        self.removeDisplayRegion('dr2')

    def exit(self):
        Mdlg = wx.MessageDialog(None,'Are you sure to quit ?',caption='Confirmation',
             style=wx.YES_NO|wx.NO_DEFAULT|wx.ICON_QUESTION|wx.CENTER)
        if Mdlg.ShowModal()==wx.ID_NO:
           return
        # upon showbase shutdown, Panda window will regain focus, avoid it
        self.PandaWnd.Unbind(wx.EVT_SET_FOCUS)
        sys.exit()

    def onPandaWindowGetFocus(self, e):
        mouse2camEnabled = base.mouse2cam.getParent()==base.mouseInterface
        props=WindowProperties(base.win.getProperties())
#         props.setParentWindow(self.PandaWnd.GetHandle())
        # re-open the window
        base.openMainWindow(props=props, gsg=base.win.getGsg(), keepCamera=1)
        # restore mouse interface
        if mouse2camEnabled and not base.mouseInterface.isStashed():
           base.changeMouseInterface(base.mouseInterface)
           print 'MOUSE_2_CAMERA INTERFACE ACTIVE'
        # user-made display regions are gone due to closing the main window,
        # so re-create them again
        self.recreateDisplayRegions()
        # who knows the aspect ratio is changed ? let showbase takes care of it
        messenger.send('window-event',[base.win])

    def wx(self, task):
        while self.evtloop.Pending():
            self.evtloop.Dispatch()
#         time.sleep(0.01)
        self.ProcessIdle()
        return task.cont

    def close(self):
        wx.EventLoop.SetActive(self.old)

def printTM():
    print taskMgr

# messenger.toggleVerbose()
app = App()
run()

Read more :
discourse.panda3d.org/viewtopic.php?t=2848

David, what is the origin used for here :
WinGraphicsWindow::open_regular_window (winGraphicsWindow.cxx) :

      _hWnd = CreateWindow(wclass._name.c_str(), title.c_str(), 
                       WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS ,
                       //x_origin, y_origin, 
                       x_origin,y_origin,
                       x_size, y_size,
                       _hparent, NULL, hinstance, 0);

It’s when we use other parent window set in window properties. I don’t think anyone would want the offset.

These are generated from C++, in the file panda/src/windisplay/winGraphicsWindow.cxx. The “keydown” message is printed only in response to WM_KEYDOWN or WM_SYSKEYDOWN. The “window_proc” message is printed in response to all windows events; the first number after window_proc is the window handle, and the second number indicates the WM_* code. In the line you quoted, the WM_* code is 255, which is WM_INPUT, but not WM_KEYDOWN (this would be 256).

So for some reason the WM_KEYDOWN messages are not getting through to Panda any more.

Why not? When you’re parenting a window to another window, the origin specifies the relative location on that other window. Certainly it’s a meaningful value, unless you always want to attach your Panda windows to the upper-left corner of their parent windows.

I meant this offset :

It’s when I used 100, 10.

Right, in your example, you have attached the Panda window to a parent window that contains nothing else, so you wouldn’t ever want to use an offset other than 0,0.

But if your parent window had included some other details, like a decorative frame, or a wxListBox or something to fill in the whitespace, then maybe you would want to use an offset to make room for that stuff.

David

You’re right, but it’s only good to give some decoration space. Stuffing the window with controls yields weird result. It’s harder to give the focus back to the 3d window without further tricks.

thanks for this piece of code. :slight_smile:

But this approach doesn’t work well for my heavy panda scene.
First it takes too much time to recreate the window and all the while the window is cleared to black. Secondly, if there are some tasks going on, they don’t appear to be liking this whole operation.

I have a feeling that there must be a less forceful way to get the focus back.

Also, can anyone advice me on a way to replace a c++ class in panda at runtime?
For python I can simply call module.class = new_class and it works well but I am clueless as to how to do this with a c++ class, assuming it can be done at all.

Regards
Shashank

Really ? I’m just fine with my 20 fps scene, I can toggle fullscreen and windowed in a blink.
If you want to compile Panda yourself, all you need is calling SetFocus(_hWnd); to get the keyboard focus back. I did a quick fix by hijacking foreground properties, which I think responsible for this. I inserted it here :
WinGraphicsWindow::set_properties_now

  if (properties.has_foreground() && properties.get_foreground())
  {
    SetFocus(_hWnd); // <----- YNJH
    if (!SetActiveWindow(_hWnd))
    {
      windisplay_cat.warning()  << "SetForegroundWindow() failed!\n";

    }
    else
    {
      _properties.set_foreground(true);
    }

    properties.clear_foreground();
  }

So to restore keyboard focus, you just need to request a foreground :

    def onPandaWindowGetFocus(self, e):
        props=WindowProperties()
        props.setForeground(1)
        base.win.requestProperties(props)
        print 'KEYB FOCUS IS BACK !!'

thanks for the suggestion :slight_smile:
There is a small problem though.
I don’t wan’t to force the user to have his panda built with my changed c++ file. Is there a way, as I asked in my last post, to replace this file with mine at runtime.
I have used this trick for python classes and it works well but I am clueless with c++

Regards
Shashank

Do you mean you don’t want to force the user to build Panda ?
He don’t have to build it. You just need to replace 2 of his .dll : libpanda and libpandadisplay (if I’m not mistaken, I’m half asleep now) with yours.

Right, the only way to provide custom C++ code to the user is to build Panda yourself and distribute the built Panda with your application. The user must use your version of Panda instead of any other version.

You can’t really shoehorn new C++ code in at runtime, not the same way you can with Python. This is one of the reasons we love Python so much.

David

I think I have found a solution :laughing:

For the sake of others who might be interested:

I use the ctypes module in the standard python distribution to access user32.dll on windows and call the setFocus method on the pandaWindow:

from ctypes import windll
user32dllHandle = windll.user32

pandaWindowHandle = user32dllHandle.GetWindow(parentWindow.GetHandle(), 5)
#the second parameter stands for the constant GW_HWNDPREV in windows.h
#the value of this parameter might depend on the order in which the #children are added to the parent of pandaWindow, assuming the 
#parent might contain other windows beside pandaWindow 

#finally when the parent gets focus I call this
user32dllHandle.SetFocus(pandaWindowHandle)

this way I can avoid any restrictions on the panda library used by the end user and everything works fine

However, there can be some unwanted implications of this forced call.
I will surely like to hear from someone with expertise in this field

Regards
Shashank