Client Keystroke Event Scripts not triggered on new desktops

Using Ignition 7.9.11 I am unable to trigger client event scripts from newly spawned desktop instances.
I have an operator console with 4 displays that get initialized with system.gui.openDesktop(), but client keystrokes only trigger events on the initial desktop.

Similar topic with no resolution: Client event script (keystroke) doesn´t run on screen2 (multiple monitors)

Is there a method to copy these event handlers via scripting upon creating the new desktops?

First, you’ll need a reference to the root FPMIApp instance. Technically, I think you can get there via SwingUtilities.getRoot. That should have a getScriptConfig method. The ClientScriptConfig object has a getKeyScripts method that returns a map of keystrokes : scripts. Then you’ll need to get a reference to the SecondaryDesktop you’re on, and get it’s input and action maps. Then loop through the keystroke/script map, creating a new KeystrokeScriptAction and adding it to the action/input map as appropriate.

It’s not going to be easy, in short.

Thanks for the guidance.

Will update with my solution or failure. Surprised this isn’t an issue for more folks!

Here’s my solution:

from javax.swing import SwingUtilities, KeyStroke, JComponent
from com.inductiveautomation.factorypmi.application import FPMIApp, VisionDesktop
from com.inductiveautomation.factorypmi.application.script import KeystrokeScriptAction

# BFS the component tree under 'root' for a specified component type
def findComponentOfType(root, type):
	queue = [root]
	checked = []
	while len(queue) != 0:
		comp = queue.pop()
		if isinstance(comp, type):
			return comp
		checked += [comp]
		children = comp.getComponents()
		queue += [child for child in children if child not in checked]
	return None

# Returns dicts containing the FPMIApp's java.xswing.InputMap and ActionMap
#     inputMapping - <javax.swing.KeyStroke> : <String>
#     actionMapping - <String> : <com.inductiveautomation.factorypmi.application.script.KeystrokeScriptAction>
def genKeyEventMappings(fpmiApp):
	scriptManager = fpmiApp.getScriptManager()
	scriptConfig = fpmiApp.getScriptConfig()
	keyScripts = scriptConfig.getKeyScripts()
	inputMapping = {}
	actionMapping = {} 
	for entry in keyScripts.entrySet():
		stroke = KeyStroke.getKeyStroke(entry.key.getStroke())
		name = entry.key.getDisplay()
		inputMapping[stroke] = name
		actionMapping[name] = KeystrokeScriptAction(name, scriptManager, entry.value)
	return inputMapping, actionMapping

# Adds provided javax.swing.InputMap and ActionMap dicts to the given visionDesktop instance
def addAllVisionKeyEvents(visionDesktop, inputMapping, actionMapping):
	actionMap = visionDesktop.getActionMap()
	inputMap = visionDesktop.getInputMap()
	# update input mapping:
	for keystroke in inputMapping.keys():
		inputMap.put(keystroke, inputMapping[keystroke])
	# update action mapping:
	for name in actionMapping.keys():
		actionMap.put(name, actionMapping[name])
	# write both updated maps back to the VisionDesktop JComponent:
	visionDesktop.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap)

# Returns the JRootPanel for a given desktop handle
# TODO: Replace with window-independent solution
def findDesktopRootPanel(handle):
	desktop = system.gui.desktop(handle)
	window = desktop.getOpenedWindows()[0]
	rc = window.getRootContainer()
	return SwingUtilities.getRoot(rc)

# Copies all Ignition Client Keystroke Event Scripts from one desktop to another
def copyDesktopKeyStrokeEvents(fromHandle, toHandle):
	# find both desktop root panels and shared app reference:
	root1 = findDesktopRootPanel(fromHandle)
	root2 = findDesktopRootPanel(toHandle)
	visionDesktop2 = findComponentOfType(root2, VisionDesktop)
	fpmiApp = findComponentOfType(root1, FPMIApp)
	# find and copy KeyStrokeEvent mappings from desktop1 and set in desktop2:
	inputMapping, actionMapping = genKeyEventMappings(fpmiApp)
	addAllVisionKeyEvents(visionDesktop2, inputMapping, actionMapping)

Call CopyDesktopKeyStrokeEvents(fromHandle, toHandle) sometime after both desktops are already opened.

Potential improvements:

  • Don’t use window instances to find the JRootPanels and FPMIApp instances
  • Use direct paths to get FPMIApp and VisionDesktop instances instead of searching the tree
  • Remove fromHandle parameter from copy method, since the Input and Action Maps belong to the shared FPMIApp ancestor and not any one desktop

I’m placing the above utiliy scripts in a shared.gui file along with some initialization functions to call on-startup:

def initDesktops():
	system.gui.openDesktop(0, 'second', 'second', 400, 400, 400, 400, ['Main Window'])
	# open further desktops or do init-y stuff here

def initAllDesktopKeyEvents():
	mainHandle = system.gui.getCurrentDesktop()
	handles = system.gui.getDesktopHandles()
	for handle in handles:
		if handle == mainHandle:
		copyDesktopKeyStrokeEvents(mainHandle, handle)

And the startup event script:

system.util.invokeLater(shared.gui.initAllDesktopKeyEvents, 200)
1 Like

Is this bug will be fixed in a future version of Ignition? My thinking is that the keystroke events should be the same on every desktop…

Yes, this is a (low priority) bug on our backlog. I can’t provide a timeline, but it should be an easy enough thing to fix first-party, so I kicked the ticket to get re-prioritized, which is as much as I can do.

1 Like

This is fixed for 8.0.13 :slight_smile: