Client Keystroke Event Scripts not triggered on new desktops

ignition79
scripting
#1

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?

0 Likes

#2

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.

0 Likes

#3

Thanks for the guidance.

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

0 Likes

#4

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)
	visionDesktop.setActionMap(actionMap)


# 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
1 Like

#5

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:
			continue
		copyDesktopKeyStrokeEvents(mainHandle, handle)

And the startup event script:

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