Skip to content
Clive Galway edited this page Oct 5, 2016 · 16 revisions

UCR plugins are simply AHK scripts that reside in the Plugins folder. A new plugin can easily be created by either starting from scratch or duplicating an existing plugin and altering it as desired.

There are two types of Plugin - "Core" plugins ship as part of UCR and cover most basic functionality (eg Remapping one IO type to another) and "User" plugins are third-party plugins created by end-users.

Plugins\Core contains plugins that ship with UCR. Unless you are trying to improve one of these core plugins, then you should not edit files in here, but they make great templates to use as a basis for your plugin.
Plugins\User is for your custom plugins. When writing a new plugin, place the file in this folder.

A plugin is simply an AutoHotkey script that lives in one of the Plugin folders.

##Creating the Class Plugin classes must follow a specific template. For example, to create a plugin called MyPlugin, you must call the file MyPlugin.ahk and the class must be declared like so:

class MyPlugin extends _UCR.Classes.Plugin {
	Type := "My Plugin"
	Description := "Does something useful"
}

Important note.
Do Not use a standard __New() Constructor in your class.

##Mandatory class properties Your class must implement the following properties:
###Type The name of the type of the plugin, eg "Remapper (Button To Axis)"

###Description A description for what your plugin does.
eg "Remaps up to four button inputs (eg WSAD, a stick POV hat) to two virtual joystick axes"

##Mandatory class methods All plugins must implement the following methods:

###Init() The Init() method is your constructor for the class. Use it to add the GuiControls / IOControls that your plugin will need and initialize any variables.

###KillReferences() If your class contains any self-references, this class must clear those references.
Your plugin will still work without this method, but if you delete the plugin, it will not free the memory that it used.
For example, if in your Init() method, you had the line this.SomeFn := this.MyFunc.Bind(this) then your KillReferences method should contain this.SomeFn := ""
G-labels also count as self references, so you may want to do a GuiControl -g on anything you configured a g-label for.

##Optional class methods ###OnActive() This will be called whenever your plugin becomes Active (eg the user switched to a profile containing your plugin)

###OnInActive() This will be called whenever your plugin becomes InActive (eg the user switched from a profile containing your plugin)

##Important notes about plugin writing. ###Scope All plugins from all profiles exist in the same "Scope".
ie, it's like you took all the plugins and put them in the same AHK file.
Therefore, it is best if you "encapsulate" - place all functions and variables inside the plugin class.
Avoid global variables - if you have two copies of the same plugin, they will both use the same global variable, and furthermore if another plugin author uses the same global variable name, they will overwrite each other.
You should not need globals anyway, just store the variable on the class, eg this.myvar. That way, each plugin gets it's own variables.

###Static variables Beware static variables! Do not prefix your variables with static unless you understand it's implications.
Basically, any static variable will only exist once for all instances of the plugin. Statics are great for storing things that don't change such as look-up arrays (eg static AxisNames := ["x", "y" ... ] to create a way to translate axis numbers to axis names) but do not store any variables in them unless you know what you are doing.

###Thread locking If you have an Input BindControl with a ChangeStateCallback, be sure to allow that function to end.
For example, if you were writing an auto-fire macro that spammed an OutputButton while the InputButton was held, do not do the following:

Init(){
	FireFn := this.DoFire.Bind(this)
}

InputChangedState(state){
	while (state){	; InputButton went down
		Loop {
			this.GuiControls.OB1.Set(1) ; press output
			[...]
		}
	}
}

This would mean that your function would never end, as InputChangedState would already be running when UCR tried to call it again with the release event for the button, and so couldn't run.

Use SetTimer to create a new "Pseudo-Thread" which does the work, so that InputChangedState can terminate after receiving the press event for the key, something like:

Init(){
	[...] ; GuiControl setup etc
	this.FireFn := this.DoFire.Bind(this)
}

InputChangedState(state){
	this.ButtonHeld := state	; Cache state of button, so we do not need to query GuiControl in other functions
	if (state){
		fn := this.FireFn ; We cannot use this.that syntax in a SetTimer command, so pull FireFn to a variable
		SetTimer, % fn, -0 ; Launch the function immediately after the thread ends, and do only once
	}
}

DoFire(){
	while (this.ButtonHeld){	; While the button is held...
		this.GuiControls.OB1.Set(1) ; press output
		[...]
	}
}

Also worthy of note is that the technique of storing the BoundFunc object FireFn as a class property is very useful if you wish to stop a running timer. Timers that fire BoundFuncs can only be stopped by passing the original BoundFunc object. eg

fn := this.SomeFn.Bind(this)
SetTimer, % fn, 100

[...]

fn := this.SomeFn.Bind(this)
SetTimer, % fn, Off ; Does not turn off the timer, as the BoundFunc is a different one

###AHK Modes As mentioned above, all plugins run in the same thread, so any AHK command that can operate in different modes - including Send, MouseMove etc apply to all plugins.
ie, In a plugin, you set CoordMode, Mouse, Screen to set mouse commands to use screen coordinates.
This will affect all plugins.
So if you are using a certain non-default mode, set that mode once in each function that uses one of these commands. UCR uses SetBatchLines to stop any function from interrupting another, so you should only need to do this once per function call, not before every command. It would probably be good etiquette to other plugin authors to set these modes back to the default at the end of the function, so that those plugins that only use the default modes do not have to do anything.
This is probably the biggest gripe I have with UCR, so if anyone can think of a way to handle this problem better, please let me know.