Ghidra’s event system provides a publish-subscribe mechanism for plugins to communicate. Events enable loose coupling between plugins, allowing them to broadcast notifications and respond to changes without direct dependencies.
package ghidra.app.events;import ghidra.framework.plugintool.PluginEvent;import ghidra.program.model.listing.Program;import java.lang.ref.WeakReference;public class ProgramActivatedPluginEvent extends PluginEvent { static final String NAME = "Program Activated"; private WeakReference<Program> programRef; /** * Construct a new plugin event. * @param source name of the plugin that created this event * @param activeProgram the program associated with this event */ public ProgramActivatedPluginEvent(String source, Program activeProgram) { super(source, NAME); this.programRef = new WeakReference<>(activeProgram); } /** * Returns the Program that is being activated. * @return the Program, or null if it has been garbage collected */ public Program getActiveProgram() { return programRef.get(); }}
Events can be passed between tools using the @ToolEventName annotation:
@ToolEventName("Program Opened")public class OpenProgramPluginEvent extends PluginEvent { // Event implementation}
This annotation enables events to be transmitted across tool boundaries via ToolConnection.Reference: ~/workspace/source/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginEvent.java:62
public class ProgramManagerPlugin extends Plugin { private Program currentProgram; public void activateProgram(Program program) { this.currentProgram = program; // Fire event to notify other plugins ProgramActivatedPluginEvent event = new ProgramActivatedPluginEvent(getName(), program); firePluginEvent(event); }}
Always fire events AFTER the state change is complete to ensure consistency.
public void setSelection(ProgramSelection selection) { // Update state first this.currentSelection = selection; // Then notify listeners firePluginEvent(new ProgramSelectionPluginEvent( getName(), selection, currentProgram));}
Use Weak References
For events containing large objects, use WeakReference to avoid memory leaks.
public class MyEvent extends PluginEvent { private WeakReference<LargeObject> objectRef; public MyEvent(String source, LargeObject obj) { super(source, "My Event"); this.objectRef = new WeakReference<>(obj); }}
Avoid Excessive Events
Don’t fire events for every minor change. Batch updates when possible.
// Bad - fires many eventsfor (Item item : items) { firePluginEvent(new ItemChangedEvent(getName(), item));}// Good - single event for batchfirePluginEvent(new ItemsChangedEvent(getName(), items));
Events from the source plugin are automatically filtered out:
@Overridepublic final void eventSent(PluginEvent event) { // Don't process events we generated ourselves if (!SystemUtilities.isEqual(event.getSourceName(), getName())) { processEvent(event); }}
// Program openedpublic class OpenProgramPluginEvent extends PluginEvent { private Program program;}// Program closedpublic class CloseProgramPluginEvent extends PluginEvent { private Program program;}// Program activated (switched to)public class ProgramActivatedPluginEvent extends PluginEvent { private WeakReference<Program> programRef;}
Multiple plugins can stay synchronized through events:
// Plugin A changes state and fires eventpublic class PluginA extends Plugin { public void changeState(String newState) { this.state = newState; firePluginEvent(new StateChangedEvent(getName(), newState)); }}// Plugin B responds to state changepublic class PluginB extends Plugin { @Override public void processEvent(PluginEvent event) { if (event instanceof StateChangedEvent) { StateChangedEvent stateEvent = (StateChangedEvent) event; updateUI(stateEvent.getNewState()); } }}
public class AnalysisPlugin extends Plugin { @Override public void processEvent(PluginEvent event) { if (event instanceof ProgramActivatedPluginEvent) { Program program = ((ProgramActivatedPluginEvent) event).getActiveProgram(); if (shouldAnalyze(program)) { performAnalysis(program); // Fire follow-up event firePluginEvent(new AnalysisCompletedEvent(getName(), program)); } } }}
The EventManager handles event distribution within a tool:
// Register event listenertool.addEventListener(ProgramActivatedPluginEvent.class, this);// Remove event listenertool.removeEventListener(ProgramActivatedPluginEvent.class, this);// Listen to all eventstool.addListenerForAllPluginEvents(this);// Fire an eventtool.firePluginEvent(event);// Get last events (for debugging)PluginEvent[] lastEvents = tool.getLastEvents();
Create specific event types rather than generic ones. This makes code more maintainable and type-safe.
// Good - specific eventsProgramOpenedEventProgramClosedEventProgramActivatedEvent// Avoid - generic events requiring type checkingProgramStateChangedEvent(StateType type)
Immutable Event Data
Make event data immutable to prevent modification by consumers.
public class MyEvent extends PluginEvent { private final String data; private final List<Item> items; public MyEvent(String source, String data, List<Item> items) { super(source, "My Event"); this.data = data; this.items = Collections.unmodifiableList(new ArrayList<>(items)); } public String getData() { return data; } public List<Item> getItems() { return items; }}
Event Documentation
Document when events are fired and what information they carry.
/** * Fired when a program is activated in the tool. * <p> * This event is fired after the program becomes the active program * but before any UI updates occur. * * @see ProgramActivatedPluginEvent#getActiveProgram() */public class ProgramActivatedPluginEvent extends PluginEvent { // ...}
Avoid Blocking Operations
Event handlers should be fast. Move long operations to background tasks.
@Overridepublic void processEvent(PluginEvent event) { if (event instanceof DataChangedEvent) { // Good - schedule work for later SwingUtilities.invokeLater(() -> updateUI()); // Bad - blocks event processing // performLengthyOperation(); }}
@Overridepublic void processEvent(PluginEvent event) { if (event instanceof ProgramLocationPluginEvent) { ProgramLocationPluginEvent locEvent = (ProgramLocationPluginEvent) event; // Only process events for current program if (locEvent.getProgram() == currentProgram) { handleLocationChange(locEvent.getLocation()); } }}