Skip to main content

G3000 GTC Plugins

Introduction

G3000 GTC plugins allow you to register views to the GTC. GTC plugins must implement the G3000GtcPlugin interface.

For convenience, you may elect to have GTC plugins extend the AbstractG3000GtcPlugin abstract class. This class defines no-op versions of all methods required by the GTC plugin interface. Simply override the methods required for the functionality you wish to add without having to worry about the others.

Importing Libraries

GTC plugins can import and use code from the following framework libraries:

  • @microsoft/msfs-sdk
  • @microsoft/msfs-garminsdk
  • @microsoft/msfs-wtg3000-common
  • @microsoft/msfs-wtg3000-gtc

When building your plugin, you should configure your build tools to consume the above libraries as global externals.

Binder

In addition to the references passed to all G3000 plugins, GTC plugins are given the following additional references through binder:

  • The instrument configuration object specific to the GTC instance.
  • A collection of navigation indicators containing only the active NAV source indicator.
  • The GTC service.
  • The flight plan store (only if the GTC supports the MFD control mode).

GTC Views

The G3000 GTC displays content using a page and popup system, and within the GTC framework, both of those elements are implemented using GTC views. As such, GTC views are the primary means by which you can inject aircraft/plugin-specific GTC content. For more information on GTC views, please refer to the G3000 GTC Framework page.

You can register your own views to be displayed on the GTC using the GTC plugin's registerGtcViews() method. This method is guaranteed to be called after the base G3000 has registered all of its views. Therefore, you may replace views registered by the base G3000 by registering your own view under the same key.

In addition to the GTC service, a context object is also passed to registerGtcViews(). The context object contains references to all items not already referenced by the plugin binder that are required to create the base G3000 views. These references are provided primarily to make it easy to override and replace any of the base G3000 views. However, you are also free to use them when creating brand new views.

Knob Control State Overrides

Each GTC has a set of "physical" hardware knobs, each with an associated control state that helps to define the knob's context-dependent function and how the knob is labeled in the label bar. You can inject your own knob control states using the GTC plugin's getKnobStateOverrides() method. If an override is defined, the control state override will replace the state computed by the base G3000 package whenever the override is not null.

You may define overrides separately for each knob. In the following example, we define overrides for the center and map knob control states.

class MyGtcPlugin extends AbstractG3000GtcPlugin {
// Tracks whether the GTC is controlling a display pane view with the key 'MyDisplayPaneView'.
private readonly isMyDisplayPaneViewActive = MappedSubject.create(
([selectedPaneIndex, viewKey]) => {
return selectedPaneIndex !== -1 && viewKey === 'MyDisplayPaneView';
},
this.binder.gtcService.selectedDisplayPane
this.binder.gtcService.selectedPaneSettings.getSetting('displayPaneView')
);

public getKnobStateOverrides(gtcService: GtcService): Readonly<GtcKnobStatePluginOverrides> | null {
// If the GTC is controlling the 'MyDisplayPaneView' display pane view, then override the center knob state to be
// GtcCenterKnobState.Blank and the map knob state to be GtcMapKnobState.MapNoPointer.

// Because we have left the dualKnobState property on the returned object undefined, there are no dual concentric
// knob state overrides.

return {
centerKnobState: this.isMyDisplayPaneViewActive.map(isActive => isActive ? GtcCenterKnobState.Blank : null),

mapKnobState: this.isMyDisplayPaneViewActive.map(isActive => isActive ? GtcMapKnobState.MapNoPointer : null)
};
}
}

If multiple plugins define overrides for the same knob, all overrides are evaluated with those belonging to plugins loaded last taking precedence. For example, if plugins A, B, C are loaded in that order and all define a map knob control state override, C's override will be used unless it is null, in which case B's override will be used unless it is null, etc.

caution

Plugins that inject custom knob control states that are not defined in the base G3000 package must also define label bar handlers that generate appropriate labels for those states. If the label bar cannot find a label to render for a control state, it will throw a runtime error.

Label Bar Handlers

The GTC label bar is an area on the edge of the screen located next to the GTC knobs which displays text describing the knobs' context-dependent functions. Unless overridden by the active GTC view, what the label bar displays is determined by the control states of each GTC knob. You can override how the label bar maps knob control states to labels by using the GTC plugin's getLabelBarHandlers() method. When a handler returns a label that is not null, it will be displayed in place of the label selected by the base G3000 package.

You may define overrides separately for each knob. In the following example, we define overrides for the center and map knobs to provide custom label text.

class MyGtcPlugin extends AbstractG3000GtcPlugin {
// ...

public getKnobStateOverrides(gtcService: GtcService): Readonly<GtcKnobStatePluginOverrides> | null {
// Inject custom knob control states for the center and map knobs while the GTC is controlling a specific display
// pane view.

return {
centerKnobState: this.isMyDisplayPaneViewActive.map(isActive => isActive ? 'MyCenterKnobState' : null),

mapKnobState: this.isMyDisplayPaneViewActive.map(isActive => isActive ? 'MyMapKnobState' : null)
};
}

public getLabelBarHandlers(): Readonly<LabelBarPluginHandlers> | null {
// Override the center and map knob labels with custom strings when the knob control states are equal to the custom
// states we injected using getKnobStateOverrides().

// Because we have left the dualKnobState property on the returned object undefined, there are no dual concentric
// knob label overrides.

return {
centerKnobLabel: knobState => {
return knobState === 'MyCenterKnobState' ? 'My Center Knob Label' : null;
},

mapKnobLabel: knobState => {
return knobState === 'MyMapKnobState' ? 'My Map Knob Label' : null;
}
};
}
}

If multiple plugins define handlers for the same knob, all handlers are evaluated with those belonging to plugins loaded last taking precedence. For example, if plugins A, B, C are loaded in that order and all define a map knob label handler, the label returned by C's handler will be used unless it is null, in which case the label returned by B's handler will be used unless it is null, etc.

Interaction Event Handling

When the user interacts with the GTC's hardware knobs, joysticks, bezel keys, or on-screen button bar, a GtcInteractionEvent is generated. The event is routed to a series of handlers implementing the GtcInteractionHandler interface, each of which has the option to handle the event or defer to the next handler. The first handler notified is always the active GTC view. If the view does not handle the event, it is then routed to all GTC plugins in the reverse order in which they were loaded. Therefore, you can define custom event handling behavior that is not specific to a GTC view by using the GTC plugin's onGtcInteractionEvent() method.

In the following example, we define event handling logic for a custom center knob control state.

class MyGtcPlugin extends AbstractG3000GtcPlugin {
// ...

public getKnobStateOverrides(gtcService: GtcService): Readonly<GtcKnobStatePluginOverrides> | null {
// Inject a custom knob control state for the center knob while the GTC is controlling a specific display pane
// view.

return {
centerKnobState: this.isMyDisplayPaneViewActive.map(isActive => isActive ? 'MyCenterKnobState' : null)
};
}

// ...

public onGtcInteractionEvent(event: GtcInteractionEvent): boolean {
// If the center knob control state is equal to 'MyCenterKnobState', then handle the various center knob events
// with custom logic.

if (this.binder.gtcService.gtcKnobStates.centerKnobState.get() === 'MyCenterKnobState') {
switch (event) {
case GtcInteractionEvent.CenterKnobDec:
// Do something...
return true;
case GtcInteractionEvent.CenterKnobInc:
// Do something...
return true;
case GtcInteractionEvent.CenterKnobPush:
// Do something...
return true;
default:
return false;
}
} else {
return false;
}
}
}
caution

Make sure to always return true from onGtcInteractionEvent() when a plugin handles a specific interaction event. Otherwise, the event will continue to be routed to other handlers and may end up being handled more than once.