Starting with version
0.3.9.0, you can now subscribe to the low level mouse and keyboard hooks created by StrokesPlus.net via scripts or plug-ins.
Edit: Changed to match
0.3.9.1 alterations.
Note that versions
0.3.9.0 or
0.3.9.1 were not listed as an automatic update due to the risk and need for testing, go to the
Downloads page to download it directly.
This is an advanced topic and implementation - you run the risk S+ and system stability if not implemented properly!User Must Enable Option(s)Given the nature of exposing these hooks to scripts and plug-ins, the user
must opt-in (check) the new option(s) in
Options > Advanced for the hooks to be exposed; they are disabled by default.
- Enable Mouse Hook Event Subscription
- Enable Keyboard Hook Event Subscription
Synchronous Versus AsynchronousEvents can be subscribed to synchronously or asynchronously, it is recommended to use asynchronous events unless you need to be able to block input.
Synchronous events run in the same thread as the S+ hook and cause the hook to wait (block) until all events are processed. You must use care when using synchronous events as you risk destabilizing S+ and/or Windows if not handled properly.
Asynchronous events run on their own thread and are
not blocking, nor can the event be marked for consumption (prevent event from happening in Windows). This is the recommended use as it avoids interfering with the user's keyboard/mouse input and reduces the risk of hanging Windows or S+.
You would only want to use synchronous events if you need to be able to consume the event so it doesn't happen, like a key press or mouse button click which you do not want to be received by Windows or any applications. You must exercise an abundance of caution when using synchronous events as they can create instability within S+ and/or Windows if not carefully implemented.
When using synchronous events, the event args object's .Consume property can be set to true, which tells S+ to instruct Windows that the event was consumed and should not propagate further. The .Consume property has no effect for asynchronous events.
Subscribing to EventsAvailable events are:
- MouseHook.OnMouseHookButtonEventAsync
- MouseHook.OnMouseHookButtonEvent
- MouseHook.OnMouseHookMoveEventAsync
- MouseHook.OnMouseHookMoveEvent
- KeyboardHook.OnKeyboardHookEventAsync
- KeyboardHook.OnKeyboardHookEvent
When binding via script, note that the event is raised within the engine(s) which bound the event. So if your
Max Script Pool Size is set to 3 and you bind the event on load without limiting to a specific engine, the event will be raised 3 times, once in each engine.
It is recommended that for script binding, you only bind in one engine and preferably the last engine in the pool. This reduces the chances that another script is executing in the engine where the event was bound, as the event will not be fired until the currently executing script has finished. For example, if you enabled synchronous events and bound the event in engine #1 while having a long running script executing, S+ will hang and after some time, Windows will evict (remove) the hook and S+ will need to be restarted.
Always wrap your event functions in a try/catch, as exceptions raised within an event can cause S+ to crash.
The example in the spoiler below shows how to bind to the keyboard synchronously and only once in the last available script engine - this script would be placed in the
Global Actions > Load/Unload > Load script tab:
Code:// It is recommended to bind these to the last engine to avoid contention with other
// scripts still executing. S+ starts with the first engine, then works toward the
// last, so using the last engine increases the chances that it is not currently
// executing - though it is still possible.
// If the last engine is executing while synchronous events are being used
// S+ may hang, Windows will evict the Mouse/Keyboard hook, and S+ will need to
// be restarted. This is only recommended for advanced users.
// Only bind this event in the last script engine by name
if(__spEngineWrapper.Engine.Name == sp.EngineList().Last().Engine.Name)
{
var keyboardEventObj = sp.GetStoredObject("keyboardEvent");
if(!keyboardEventObj.GetType().FullName.includes('EventConnection'))
{
//Bind to the synchronous event
var keyboardEvent = KeyboardHook.OnKeyboardHookEvent.connect(
function (sender, keyboardHookEvent) {
//Wrap all code in try/catch, exceptions will crash S+, such as calling clip.SetText with a null value
try
{
if(keyboardHookEvent.Key == vk.VK_Z)
{
if(keyboardHookEvent.KeyState == KeyState.Up)
{
sp.MessageBox("Z key release\n\n _spEngineName: " + __spEngineWrapper.Engine.Name, "ZU");
}
//Consume the event, so Windows/apps do not see the keypress
keyboardHookEvent.Consume = true;
}
}
catch {}
});
sp.StoreObject("keyboardEvent", keyboardEvent);
}
}
Note that the event binding object is stored via
sp.StoreObject, this allows other engines to be able to see the object if needed, but it also enables S+ to unbind (disconnect) the event on reload. Keep in mind that events will need to be re-subscribed after reload, which includes clicking Apply or OK in the S+ Settings window.
__spEngineWrapper ObjectThis was added in release 0.3.9.0 to support determining the engine for the current script, and in case there are other current/future needs for exposing this object. Expand the spoiler to see the properties, note that you should really not alter the object as doing so could cause S+ to become unstable.
Code:__spEngineWrapper.Engine [V8ScriptEngine - see https://microsoft.github.io/ClearScript/Reference/html/T_Microsoft_ClearScript_V8_V8ScriptEngine.htm]https://microsoft.github.io/ClearScript/Reference/html/T_Microsoft_ClearScript_V8_V8ScriptEngine.htm]
__spEngineWrapper.IsExecuting [bool - note, is not true during event execution]
__spEngineWrapper.Execute([string])
__spEngineWrapper.Interrupt()
Keyboard Hook EventsThe event are
KeyboardHook.OnKeyboardHookEventAsync and
KeyboardHook.OnKeyboardHookEvent.
Looking at the keyboard event binding example above, the
keyboardHookEvent object has the following properties:
Note that
registered hot keys will not be detected in the hook as those are handled and consumed by Windows. You will see any modifiers leading up to the hot key (e.g. for Control+F10 you would receive the vk.LCONTROL key but not F10).
For unregistered hot keys, the event will be processed prior to S+ handling the event, so if you are using synchronous events and set .Consume to true, S+ will
not trigger the hot key as the hook immediately returns if the event function says to consume it.
Simple example which toggles an
asynchronous keyboard event binding on and off in the
current script engine:
Code://This toggles on and off an asynchronous keyboard hook event subscription
//If event already bound, disconnect
var keyboardEventObj = sp.GetStoredObject("keyboardEvent");
if(keyboardEventObj.GetType().FullName.includes('EventConnection'))
{
keyboardEventObj.disconnect();
sp.DeleteStoredObject("keyboardEvent");
}
else
{
//Otherwise, create the event binding
var keyboardEvent = KeyboardHook.OnKeyboardHookEventAsync.connect(
function (sender, keyboardHookEvent) {
//Wrap all code in try/catch, exceptions will crash S+, such as calling clip.SetText with a null value
try
{
if(keyboardHookEvent.Key == vk.VK_Z && keyboardHookEvent.KeyState == KeyState.Up)
{
sp.MessageBox("Z up", "ZU");
}
}
catch {}
});
//Note that on S+ reload, events in Stored Object list have disconnect called on them
sp.StoreObject("keyboardEvent", keyboardEvent);
}
Mouse Hook EventsThere are two events for the mouse hook which can be subscribed, mouse click/wheel scroll and mouse move. Only use the mouse move event if you really need to control mouse movement - it generates a
LOT of events and can result in noticeably slowing the down the mouse (synchronous) or having unexpected delays (asynchronous) due to the number of executions which need to be made. If you just need positioning, use sp.GetCurrentMousePoint() within your click/wheel event.
Events:
- MouseHook.OnMouseHookButtonEventAsync (async, button click, wheel scroll)
- MouseHook.OnMouseHookButtonEvent (sync [blocking], button click, wheel scroll)
- MouseHook.OnMouseHookMoveEventAsync (async, mouse move)
- MouseHook.OnMouseHookMoveEvent (sync [blocking], mouse move)
mouseHookEvent args object:
Mouse Button/Wheel script example,
synchronous and in the
currently executing script engine:
Code://This toggles on and off a synchronous mouse button hook event subscription
//If event already bound, disconnect
var mouseButtonEventObj = sp.GetStoredObject("mouseButtonEvent");
if(mouseButtonEventObj.GetType().FullName.includes('EventConnection'))
{
mouseButtonEventObj.disconnect();
sp.DeleteStoredObject("mouseButtonEvent");
}
else
{
//Otherwise, create the event binding
//bind to synchronous event
var mouseButtonEvent = MouseHook.OnMouseHookButtonEvent.connect(
function (sender, mouseHookEvent) {
//Wrap all code in try/catch, exceptions will crash S+, such as calling clip.SetText with a null value
try
{
if(mouseHookEvent.Button == MouseButtons.Middle))
{
//Consume all middle click events
mouseHookEvent.Consume = true;
}
else if(mouseHookEvent.Button == MouseButtons.Left && mouseHookEvent.ButtonState == ButtonState.Up)
{
//Do something when the left mouse button is released, but don't consume
}
}
catch{}
});
//Note that on S+ reload, events in Stored Object list have disconnect called on them
sp.StoreObject("mouseButtonEvent", mouseButtonEvent);
}
Mouse Move script example, asynchronous and in the
currently executing script engine:
Code://This toggles on and off an asynchronous mouse button hook event subscription
//If event already bound, disconnect
var mouseMoveEventObj = sp.GetStoredObject("mouseMoveEvent");
if(mouseMoveEventObj.GetType().FullName.includes('EventConnection'))
{
mouseMoveEventObj.disconnect();
sp.DeleteStoredObject("mouseMoveEvent");
}
else
{
//Otherwise, create the async event binding
var mouseMoveEvent = MouseHook.OnMouseHookMoveEventAsync.connect(
function (sender, mouseHookEvent) {
//Wrap all code in try/catch, exceptions will crash S+, such as calling clip.SetText with a null value
try
{
if(mouseHookEvent.Button == MouseButtons.Left && mouseHookEvent.ButtonState == ButtonState.Down)
{
//Do something when mouse move is happening and left button is down
}
else
{
//Do something otherwise, just a mouse movement or other button combinations
}
}
catch {}
});
//Note that on S+ reload, events in Stored Object list have disconnect called on them
sp.StoreObject("mouseMoveEvent", mouseMoveEvent);
}
Plug-InsThe examples above should illustrate generally how to utilize these events in a plug-in. You can directly add a reference to
StrokesPlus.net.exe and
WindowsInput.dll in the StrokesPlus.net installation folder to pull in the classes needed.
Add these declarations in your code:
Code:using System.Windows.Forms;
using WindowsInput.Native;
using StrokesPlus.net.Hooks;
using StrokesPlus.net.Code;
Edited by user Tuesday, November 24, 2020 1:59:26 PM(UTC)
| Reason: Changed for 0.3.9.1