Welcome Guest! To enable all features please Login or Register.



Go to last post Go to first unread
#1 Posted : Tuesday, June 2, 2020 2:11:04 PM(UTC)

Rank: Administration


Groups: Translators, Members, Administrators
Joined: 1/11/2018(UTC)
Posts: 1,066
United States
Location: Tampa, FL

Thanks: 22 times
Was thanked: 315 time(s) in 276 post(s)
Starting with version, you can now subscribe to the low level mouse and keyboard hooks created by via scripts or plug-ins.

Edit: Changed to match alterations.

Note that versions or 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 Asynchronous

Events 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 Events

Available 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:

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 Object

This was added in release 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.

Keyboard Hook Events

The event are KeyboardHook.OnKeyboardHookEventAsync and KeyboardHook.OnKeyboardHookEvent.

Looking at the keyboard event binding example above, the keyboardHookEvent object has the following properties:

- vk as used in other script functions, e.g. vk.VK_Z for the letter 'z'

- KeyState.Down, KeyState.Up, or KeyState.None

- true if the event was synthesized - not a physical key press on the attached keyboard

- true if the event was synthesized by, e.g. during a sp.SendKeys call for example

- see
- also

keyboardHookEvent.wParam (long)
- see

keyboardHookEvent.lParam (long)
- see

- if synchronous events are active, setting this to true in your code will cause the event to be consumed

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:

Mouse Hook Events

There 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.

  • 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:

- MouseButtons enum, e.g. MouseButtons.Left

- ButtonState.Down, ButtonState.Up, or ButtonState.None

- Point indicating the location of the mouse on the screen

- true if the wheel scroll was a horizontal directional scroll, false means it was a normal up/down wheel scroll if WhellDelta is also not equal to 0 (zero)

- Positive or negative number indicating wheel scroll direction, usually in +/- 120 increments

- true only when the mouse button or wheel event occurred within a matching user defined exclusion zone. This is always false during mouse move events.

- true if the event was synthesized - not a physical mouse event from the attached mouse

- true if the event was synthesized by, e.g. during a sp.MouseMove/sp.MouseClick call for example

- see
- also

mouseHookEvent.wParam (long)
- see

mouseHookEvent.lParam (long)
- see

- if synchronous events are active, setting this to true in your code will cause the event to be consumed

Mouse Button/Wheel script example, synchronous and in the currently executing script engine:

Mouse Move script example, asynchronous and in the currently executing script engine:


The examples above should illustrate generally how to utilize these events in a plug-in. You can directly add a reference to and WindowsInput.dll in the installation folder to pull in the classes needed.
Add these declarations in your code:
using System.Windows.Forms;
using WindowsInput.Native;

Edited by user Tuesday, November 24, 2020 1:59:26 PM(UTC)  | Reason: Changed for

thanks 2 users thanked Rob for this useful post.
liuxilu on 6/5/2020(UTC), Rob Otter on 11/24/2020(UTC)
#2 Posted : Wednesday, June 3, 2020 3:05:30 AM(UTC)

Rank: Advanced Member


Groups: Translators, Moderators, Approved
Joined: 4/6/2020(UTC)
Posts: 76

Thanks: 1 times
Was thanked: 21 time(s) in 21 post(s)
I just realized to have individual plugin/script event and sync setting, then it can pass reference to plugins, vlaue to scripts - without unstable behavior with scripts and effective passing for plugins.
By adding CallNextHook functionality right after the sync event call, it will also allow plugins to change the input

Edited by user Wednesday, June 3, 2020 3:11:01 AM(UTC)  | Reason: Not specified

Rob Otter  
#3 Posted : Tuesday, November 24, 2020 1:27:12 PM(UTC)
Rob Otter

Rank: Advanced Member


Groups: Approved
Joined: 10/26/2020(UTC)
Posts: 50
Location: Darmstadt

Thanks: 15 times
Was thanked: 2 time(s) in 2 post(s)
When I use the async keyboard event binding script given above in the global Load script section, I get the following error when saving (no changes have been made to your sample code):

Failed to process load automation. Check your Load Automation for errors:
Error: The object has no suitable property or field named 'EnableSyncHookEvents'
at LoadScripts:40:39 ->
KeyboardHook.EnableSyncHookEvents = false; //async

Keyboard Hook is enabled in Options.
#4 Posted : Tuesday, November 24, 2020 2:00:04 PM(UTC)

Rank: Administration


Groups: Translators, Members, Administrators
Joined: 1/11/2018(UTC)
Posts: 1,066
United States
Location: Tampa, FL

Thanks: 22 times
Was thanked: 315 time(s) in 276 post(s)
Oops, remove that line - I forgot to take that out when I updates the scripts snippets for
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.