key polling

Very helpful topic) Maybe my example of application of approaches described here may also be of some interest? ^^

from panda3d.core import *

class InputManager(object):
	# Allow only 1 copy
	def __new__(cls, *args, **kwargs):
		if '_inst' not in vars(cls):
			cls._inst = super(InputManager,cls).__new__(cls, *args, **kwargs)
		return cls._inst
	
	def __init__(self, noCompoundEvents=True, dontDriveCamera=True):
		if noCompoundEvents:
			# Turn off modifier compound events.
			# If it is left on, pressing modifier+KEY won't send KEY!
			base.mouseWatcherNode.setModifierButtons(ModifierButtons())
			base.buttonThrowers[0].node().setModifierButtons(ModifierButtons())
		
		if dontDriveCamera:
			# Disable default mouse control of main camera
			base.disableMouse()
		
		self._keys_prev = 0
		self._keys_changed = 0
		self._keys_held = 0
		self._keys_down = 0
		self._keys_up = 0
		self._printscreen = 0
		self._wheelAccum = Vec2()
		self._wheel = Vec2()
		self._lockMouse = None
		self._lastUnlockedPos = None
		self._limitToScreen = True
		self._wasInWindow = False
		self._ignoreNextDelta = True
		self._mouseSpeed = Vec2()
		self._mousePos = Point2()
		
		self._allKeys = {}
		self._pollKeys = []
		
		self.setMouseLocked(False, True)
		
		# KeyboardButton.asciiKey() in range 0-127 won't raise an error
		# 178 is current maximal key code (wheel_right key)
		for i in xrange(178+1):
			bh = KeyboardButton.asciiKey(i) if i < 128 else ButtonHandle(i)
			if bh.getIndex() == 0: continue # Ignore 'none' button
			self._addKey(bh)
		
		base.accept('wheel_up', self._setWheel, [Vec2(0, -1)])
		base.accept('wheel_down', self._setWheel, [Vec2(0, 1)])
		base.accept('wheel_left', self._setWheel, [Vec2(-1, 0)])
		base.accept('wheel_right', self._setWheel, [Vec2(1, 0)])
		
		# Update keys before everything else
		taskMgr.add(self._updateKeys, 'inputManager.Keys', sort=-60)
		
		# Update mouse right after dataloop
		try:
			# See manual about sort and priority
			task = taskMgr.getTasksNamed("dataLoop")[0]
			taskMgr.add(self._updateMouse, 'inputManager.Mouse',
				sort=task.getSort(), priority=task.getPriority()-1)
		except IndexError:
			taskMgr.add(self._updateMouse, 'inputManager.Mouse', sort=-49)
	
	def _addKey(self, bh):
		name = bh.getName()
		if name.startswith("wheel"): return
		
		mask = 1 << len(self._allKeys)
		self._allKeys[name] = mask
		if name == 'print_screen':
			# See manual about keyboard support
			base.accept('print_screen-up', self._setPrintScreen, [mask])
		else:
			self._pollKeys.append((bh, mask))
	
	def _setWheel(self, delta):
		self._wheelAccum += delta
	
	def _setPrintScreen(self, mask):
		self._printscreen = mask
	
	def _updateMouse(self, task):
		md = base.win.getPointer(0)
		mpos = Point2(md.getX(), md.getY())
		
		isInWindow = md.getInWindow()
		
		if not self._lockMouse:
			if self._wasInWindow and isInWindow:
				self._mouseSpeed = mpos - self._mousePos
			else:
				self._mouseSpeed = Vec2()
			self._mousePos = mpos
		else:
			sizeX = base.win.getXSize()
			sizeY = base.win.getYSize()
			halfX = sizeX * 0.5
			halfY = sizeY * 0.5
			if isInWindow:
				if self._ignoreNextDelta:
					self._mouseSpeed = Vec2()
					self._ignoreNextDelta = False
				else:
					self._mouseSpeed = mpos - Point2(halfX, halfY)
				self._mousePos += self._mouseSpeed
				if self._limitToScreen:
					x, y = self._mousePos
					x = min(max(x, 0), sizeX)
					y = min(max(y, 0), sizeY)
					self._mousePos = Point2(x, y)
			base.win.movePointer(0, int(halfX), int(halfY))
		
		self._wasInWindow = isInWindow
		
		return task.cont
	
	def _updateKeys(self, task):
		self._wheel = self._wheelAccum
		self._wheelAccum = Vec2()
		
		mask = 0
		# Store function handle in local variable for faster access
		isPressed = base.mouseWatcherNode.isButtonDown
		for c in self._pollKeys:
			if isPressed(c[0]): mask |= c[1]
		
		self._keys_held = mask
		self._keys_changed = self._keys_held ^ self._keys_prev
		self._keys_down = self._keys_held & self._keys_changed
		self._keys_up = self._keys_prev & self._keys_changed
		self._keys_prev = self._keys_held
		
		self._keys_up |= self._printscreen
		self._printscreen = 0
		
		return task.cont
	
	def getAllKeynames(self):
		return sorted(self._allKeys.iterkeys(),
			key=lambda (x) : (len(x)>1, x))
	
	def isPressed(self, key=None):
		if key is None: return bool(self._keys_held)
		return bool(self._keys_held & self._allKeys[key])
	
	def isChanged(self, key=None):
		if key is None: return bool(self._keys_changed)
		return bool(self._keys_changed & self._allKeys[key])
	
	def isHit(self, key=None):
		if key is None: return bool(self._keys_down)
		return bool(self._keys_down & self._allKeys[key])
	
	def isReleased(self, key=None):
		if key is None: return bool(self._keys_up)
		return bool(self._keys_up & self._allKeys[key])
	
	def getWheel(self):
		return self._wheel
	
	def getMouseLocked(self):
		return self._lockMouse
	
	def setMouseLocked(self, locked, unhideOnUnlock=True, useLastPosition=False):
		if self._lockMouse == locked: return
		
		props = WindowProperties()
		if locked:
			props.setCursorHidden(True)
			props.setMouseMode(WindowProperties.MRelative)
			props.setRawMice(True)
		else:
			if unhideOnUnlock: props.setCursorHidden(False)
			props.setMouseMode(WindowProperties.MAbsolute)
			props.setRawMice(False)
		base.win.requestProperties(props)
		
		self._ignoreNextDelta = False
		if locked:
			md = base.win.getPointer(0)
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if md.getInWindow():
				self._mousePos = Point2(md.getX(), md.getY())
			else:
				self._mousePos = Point2(halfX, halfY)
			self._lastUnlockedPos = self._mousePos
			self._ignoreNextDelta = True
		else:
			if useLastPosition:
				x, y = self._lastUnlockedPos
			else:
				x, y = self._mousePos
			if not (self._lockMouse is None):
				base.win.movePointer(0, int(x), int(y))
		
		self._lockMouse = locked
	
	def getMouseLimited(self):
		return self._limitToScreen
	
	def setMouseLimited(self, limit):
		self._limitToScreen = limit
	
	def getMouseSpeed(self, relative=False, aspect=False):
		if not (relative or aspect): return self._mouseSpeed
		halfX = base.win.getXSize() * 0.5
		halfY = base.win.getYSize() * 0.5
		x = self._mouseSpeed[0] / halfX
		y = -self._mouseSpeed[1] / halfY
		if aspect: x *= base.getAspectRatio()
		return Vec2(x, y)
	
	def setMouseSpeed(self, speed, relative=False, aspect=False):
		x, y = speed
		if relative or aspect:
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if aspect: x /= base.getAspectRatio()
			x = x * halfX
			y = -y * halfY
		self._mouseSpeed = Vec2(x, y)
	
	def getMousePos(self, relative=False, aspect=False):
		if not (relative or aspect): return self._mousePos
		halfX = base.win.getXSize() * 0.5
		halfY = base.win.getYSize() * 0.5
		x = (self._mousePos[0] - halfX) / halfX
		y = -(self._mousePos[1] - halfY) / halfY
		if aspect: x *= base.getAspectRatio()
		return Point2(x, y)
	
	def setMousePos(self, pos, relative=False, aspect=False):
		x, y = pos
		if relative or aspect:
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if aspect: x /= base.getAspectRatio()
			x = x * halfX + halfX
			y = -y * halfY + halfY
		self._mousePos = Point2(x, y)
		
		if not self._lockMouse:
			base.win.movePointer(0, int(x), int(y))