Multikeyboard PDF
Multikeyboard PDF
Table of Contents
Part I Introduction 1
1 Overview ........................................................................................................................................................ 2
2 Features ......................................................................................................................................................... 4
Part II Basics 5
1 Shortkeys ....................................................................................................................................................... 7
2 Macros ......................................................................................................................................................... 10
3 Activate Window ......................................................................................................................................... 14
4 Quick Macro Recorder ................................................................................................................................ 17
5 Start Quick Macros ..................................................................................................................................... 19
6 Swap macro Set .......................................................................................................................................... 21
7 Settings ........................................................................................................................................................ 24
© 2020 Mediachance
I
II MultiKeyboard Macros
Index 0
© 2020 Mediachance
Introduction 1
I Introduction
You may have seen some shortcut or macro hardware keyboards.
These are very useful when working with complex applications - for example drawing applications, 3D
applications, video editing, audio or music application where your hand (or even both hands) are not always near
the keyboard. Nothing is more distracting than to take your hands from the controller, mouse, music keyboard or
stylus just to type CTRL+U which for most of us require two hands.
Downside is that these macro keyboards are expensive, need custom drivers and are often tied to a certain
macro application - which may suit you or not. Not to mention the elephant in the room - which happened far too
many times for the author: most of such keyboards require special drivers and would become obsolete when the
developer no longer decide to support new operating systems.
Wouldn't be great if we can use any non expensive no-driver external numerical or standard keyboards as our
own special macro board?
While you can actually plug-in multiple HID keyboards into a single computer, it isn't very useful - both keyboards
can only do the exact same thing. You can't make for example letter "P" do something else on the second
keyboard because it will affect the first keyboard as well.
© 2020 Mediachance
2 MultiKeyboard Macros
1.1 Overview
You can plug in multiple USB (or wireless) keyboards to your computer and then define macros and shortkeys
on every single of them separately. Something that is not normally possible, unless you get a special keyboard
for that very purpose. But now you can use any! And more than one!
For example: you can leave your normal keyboard for typing (always a good idea!), then plug in two additional
Numerical Keyboards and redefine every single key on them to do something else - shortcuts, macros, type text
or even simulate mouse click. For example one numerical keyboard can have shortcuts for photoshop, the
second for video editing app. Or whatever else you want.
In version 2.0 we added a full scripting language and now you can also process clipboard and do a custom
keyboard logic that no other macro keyboard will allow - not even close.
Important: In order for the MKM to recognize multiple keyboards, they all need to be different models. See more
in Limitations 121 .
If you really want to go BIG style, use entire full keyboard as your second macro board. Or two, or three...
© 2020 Mediachance
Introduction 3
But you can choose also minimalistic approach. Your keyboard may have numerical part that not very many
people actually use. now you can redefine them to do something useful!
When using only one keyboard, there are some restrictions in regards to Num Lock (basically it should be ON,
otherwise you will be remapping arrows, PG UP/Down etc...and that is probably not what you want. There is
more about it here 7 .
DIY TIP
And before you say that having many keyboards may be confusing, here is a DIY tip:
If you don't follow any arts and crafts channels, you may be surprised to learn that this it is actually a thing on
internet: putting WASHI tape over normal keys to make them colorful.
While I am not personally too keen on taping my good trusty primary keyboard - this is a perfect solution for the
extra macro keyboards to make the key distinctive! Well, maybe not necessary as wild as some people do, but
certainly an interesting idea.
© 2020 Mediachance
4 MultiKeyboard Macros
1.2 Features
Allows you to re-define any keys across multiple keyboards to do something else:
· define simple shortcuts that are triggered by pressing certain key: for example pressing 4 on numerical
keyboard will send CTRL+C
· define Macros,m which are whole sequences of such shortcuts, so it can be CTR+C followed by 3 times right
arrow, followed by CTRL+V
· simulate mouse click within macros
· type whole text (signature, greetings etc...) by pressing a single key
· run application
· open folder or file
· open web page
· record keystrokes and then play them back as quick macro (software developers are quite familiar with this
type)
An Example:
a single macro key key could:
· send CTR+C to capture selected text under cursor
· process the text with full and rich suite of string operations (including string tokenizer, tag extraction and full
regex)
· type it changed back to the application.
all with pressing just a single key
Scripts can talk to each other through global variables so another obvious function can be to use some of the
keys as modifiers.
If I press 7 then quickly 8 on a numerical keyboard it can do different things than pressing 8 alone.
Of course those are just a few ideas. The script language is incredibly rich and extensive. It can work on arrays,
it can load and save text files and more. We can also enhance it with more functionality easily in the future if
there is need.
© 2020 Mediachance
Introduction 5
II Basics
This is the software interface
Initial setup:
First thing is a little house order to let the software know what is the Primary Keyboard and what are the
secondary keyboards. There are two tables on the left side. Top one will list the Primary keyboard. This is the
keyboard we use for typing.
Just click Detect and you will be prompted to press any key on your Primary keyboard.
Next do that for any other plugged secondary keyboards that will fill the second table.
When you add any secondary keyboard it will asks you about the keyboard behavior (or behaviour as we spell
it):
© 2020 Mediachance
6 MultiKeyboard Macros
If you choose "Disable all keys by default" then none of the external keys will type anything. Only keys with
defined macros will perform their function.
If you leave all keys enabled then the keys that don't have any defined macros will still type their original
characters.
You can redefine the behavior later, just press Detect and press again any key on the external keyboard.
This initial setting up keyboards serve mostly semantic function - they keyboards will work fine regardless if you
define them or not, in fact if you don't define them they will be added automatically when you add macros - but it
is a good idea to tell software which is primary keyboard at least - so we don't accidentally disable all keys on
primary keyboard by default...
© 2020 Mediachance
Basics 7
2.1 Shortkeys
When the initial setup is done, now it is time to add some shortkeys.
It is important to note that MultiKeyboard macros will allow you to define shortkeys and macros on any keyboard,
including the Primary. (for example you may utilize unused numerical part).
And a shortkey or shortcut can be anything, even just a letter. Which sounds like a prank to type F when you
press D but in fact it may have some use to swap out some odd keys on your keyboard.
Press Add Shortcut (it could be a good idea in the future if we are clear how to call these -but for now shortcuts
and shortkeys are the same thing)
This will follow with a familiar procedure, press the trigger key - that is the key on the keyboard you want to
define.
© 2020 Mediachance
8 MultiKeyboard Macros
In this case I want it to send copy command - which is CTRL+C. So I Hold that combination. Then press OK.
The Shortcut will be added to the list of shortcuts and macros.
Note: If you select a trigger that is on a numerical keyboard and is modified by NumLock you will get a bit
different window:
In our previous example pressing * will not give me the "Ignore Num Lock" option because the key * is on that
keyboard always (Num *) regardless if NumLock is on or off. On PC the numerical * and normal * are two
different hardware keys that send two different key commands (yet type the same thing - how odd?)
But if I use for example number 5 on the numerical keyboard, now I have a small choice to make: Either use
that key as a single trigger for my macro regardless of Num Lock (which is the reasonable choice), or have
actually two different triggers - one with NumLock, one without which would double the functionality.
So in fact if I use my trigger 5 and uncheck Ignore Num Lock, I will be able then to define another different
macro for case when the NumLock is off.
Here is that idea in example: When Num Lock is ON, my 5 key will type E, when it is OFF it will type W. Not very
useful but it explains the point.
The simplest way is of course to have Ignore Num Lock ON when dealing with external or numerical
keyboards so one key = one macro regardless - we don't have to remember too many things and if the Num
Lock doesn't have its own LED on the keyboard, it is actually hard to know the status of it.
© 2020 Mediachance
Basics 9
There are of course other, better ways how you can assign multiple functions to one key: using Swap Macro
Set 21 or using Scripting 26 .
However if you remap keys on your primary keyboard, which is perfectly fine, here the functionality is a bit
different with regards to the numerical part of the keyboard.
Keys / * - + have their own hardware key - you can verify this with Monitor 24 - that differs from the same keys
on main part of the keyboard (even if it types the same /*-+) so they are free to be assigned other functions to
them.
However assigning functions to the rest of numerical part of primary keyboard is different.
Only when the Num Lock is ON these keys will generate their own "Num..." hardware key (again, you can check
with monitor).
When Num Lock is OFF then the keys will send the very same hardware key as arrows, pg down, up etc...which
is not the keys you want to redefine.
© 2020 Mediachance
10 MultiKeyboard Macros
2.2 Macros
Shortcut is just a single step - like pressing CTRL+C
Macro is a sequence of many of such steps. Shortcut is in fact a Macro with only single step in it.
To define macro is a similar procedure to Shortcut except now we press Add Macro.
After setting the trigger key (as in Shortcut) we will get into a bit different window where we can define the steps.
Macro name: this is the name that will appear on the list and also can appear as a label on screen if that option
is used.
© 2020 Mediachance
Basics 11
This opens a list of available commands that we can stack together or use single as a single command. Some
commands are Internal function commands and have FN prefix. Those will perform only a single function that is
related to the app itself such as Record Quick macro 17 .
Adding command will also show its available settings on the right side
The commands:
Keyboard Shortcut
This is our familiar shortcut 7 (that we also call shortkey to confuse everyone). It will perform one key
combination and hold that key for certain amount of time. 100ms is a good number.
Pause
This adds a pause of ms. Sometimes application may not accept shortkeys or keys if they are fired too quickly
after each other so we can set a pause between commands.
Mouse Click
Simulates mouse click on absolute coordinates on the screen. This can be used in software that doesn't have
any shortcut command for a function we want but it has a button on the interface. The said software window has
to be on the same position every time because the coordinates are absolute- so probably best is to work on it
maximized.
Once you click Edit Mouse click you can visually click on certain part of the host application
Type Text
Types text in the host application. This could be an address, greetings, footer, recipe for a cookie or whatever
you need to type often.
© 2020 Mediachance
12 MultiKeyboard Macros
You can specify 'wait' between keys. 5ms will type it pretty fast. Now pressing my trigger in any text application
will type that text.
Run Application
Runs an application.
If you want to open a document with its default app, instead of selecting application select All files and choose
the document file.
In our case I selected txt file which will be opened with notepad (or whatever else is registered to open txt files
on your computer)
© 2020 Mediachance
Basics 13
D:\\Docs\ will open folder, \\DISKSTATION\Volume1 will open NAS, 192.168.1.2\Shared files\ will open network
location etc...
http:\\www.mediachance.com will open web page
© 2020 Mediachance
14 MultiKeyboard Macros
This of course requires such application is already running. This function will not open the application, just
search for it and if it is running it will bring it to front. If you are opening an application in the same macro, it is
probably no need to add Activate Windows as the app will automatically open in front. But if you need to do it,
remember to have some Pause between opening and Activate as the app needs to be fully loaded first.
You can search for a window by its exe name or by the window title.
Generally you need to type a substring in the "Search the Title..." box. It should be a partial or full name. In case
of Window Title, it should be the significant part of the string that will be always present in the title bar.
Obviously you want to search the title for "Notepad" and not "Untitled - Notepad" whole title, which will change
depending what file the notepad has opened.
When you double click the list of opened windows or use the << button it will type selected item in the box.
Important Note: in case of Window Title, what will be shown in the Opened Windows are already processed
strings - with removed spaces, characters, stuff in parenthesis etc... as such if you search for that exact string
you will likely not find the window. You need to select only the important part - which is the name of the
application. - but it is hard for the MKM to determine that by itself - it needs your eyes.
© 2020 Mediachance
Basics 15
In some cases, such as web browsers, the Windows Title may not always mention the application name at all
and may be often just a document title, web page name etc... so it may be hard to find window like that.
When you press Test, the application(s) that satisfy the search string will be brought to front. In case of multiple
opened applications that satisfy the search, all of them will be brought to front. And all windows that had been
found will be written in the lower box with its class name.
These could be multiple instances of the application with the same class or sometimes a single application that
has few hidden windows - and in some cases bringing those hidden windows to front may not be the best idea.
This is where you can enter class name after | sign.
For example
firefox.exe|MozillaWindowClass
In this particular example it would not be that practical as every instance of firefox.exe will always have the same
MozillaWindowClass class, but we can switch now to By Window Title and write:
© 2020 Mediachance
16 MultiKeyboard Macros
mediachance|MozillaWindowClass
This will brig to front firefox as found by its class, but only if its Window title says mediachance.
Note: in case of firefox or other apps with tabs - only the active tab window title will be known. This command will
not switch the tab in firefox to tab that says mediachance - it has to be currently opened tab. There is really no
single mechanism that would work on all apps as everybody develops it a bit differently and there is only so
much an app can know about other apps.
*|MozillaWindowClass
For example we may first send "ESC" to the window which would close any opened menus or windows.
In case of firefox, we may then send CTRL+E that would put focus on its search bar, or CTRL + T which will
open new tab and put cursor in the search bar as well. Because this may take some time, a pause will make
sure the firefox is ready.
Then we can type something in its search bar "mediachance" and press enter
© 2020 Mediachance
Basics 17
Here is an example:
As it would be a common task in programming, you may need to reverse these lines where what is on the left
needs to go to right etc. It is fine for few lines but what if you have 50?
You can record your keystrokes as a simple macro on the first line by using common editing commands such as
select word, copy, paste that are used in most editors.
There are some rules and tricks doing it this way but the benefits are big once you master it. It is important that
you use only keyboard commands, don't move your cursor by mouse, move it by arrows. Instead of deleting a
word letter by letter which would mess up if other line has different length word - use CTR+SHIFT+Right Arrow -
which select the next word. Then press Delete. If you need to skip a word, use CTRL-Right Arrow... Tricks like
these. Every programmer can tell you these.
We ended our one line macro by using Arrow down and pressing Home which simply put cursor to the beginning
of next line. And now we are ready to play the macro few times:
© 2020 Mediachance
18 MultiKeyboard Macros
In many editors you can also combine search commands CTRL+F and F3 for example in your macro line to do
even crazier stuff.
So this is why quick macros are powerful tools once you master the keyboard language.
© 2020 Mediachance
Basics 19
That's what these buttons are on main interface. They do the exact same thing like creating a macro and adding
FN: Record Quick Macro command.
So basically they will ask you which key trigger you want to use for recording quick macro and which for
playback.
For example I often use my right part of my Primary keyboard where the numeric keypad is and assign the / and
* to these record and play commands as I never use those keys otherwise and they are not modified by Num
Lock.///-
The triggers will appear in the list as any other macro - because that's what they are anyway - a trigger that runs
internal command.
Once you set both record and play key, you can go to your application and trigger the record.
Recording window will appear on top to remind you you are recording macro.
© 2020 Mediachance
20 MultiKeyboard Macros
To stop recording, you can press the record trigger again. (or click Stop button)
To play back the macro, place cursor where you want and press Play trigger on your keyboard.
When you actually create a useful recording that can be used later you can export it into a trigger key of your
choice.
This will save the recorded macro as series of steps in a normal Macro.
© 2020 Mediachance
Basics 21
Macro set is everything you see on the main interface - including keyboards and all macros associated to them.
This option is great if you need to often load different macros onto one keyboard during your normal use. For
example one macro set for video editing, one for graphics...etc.
In following example with a numerical keyboard we can define keys / * - to swap between sets; Set1, Set2, Set3
© 2020 Mediachance
22 MultiKeyboard Macros
Note: Each set has to also have defined the same Swap buttons as well - otherwise you won't be able to swap
back from the new set.
To avoid chicken/egg problem let's first create 3 dummy placeholder sets; Save the currently blank set using
Save Macro Set button to 3 different sets: Set1.kbset, Set2.kbset, Set3.kbset
Now use Add Macro and choose key "/", Add Command: Swap Macro Set, Select set Set1.kbset
Repeat for two additional keys * and - each loading Set2.kbset and Set3.kbset respectively
Now you see why used placeholders - our 3 swap buttons need to load the sets but we are yet to going to fully
define the sets. We should have 3 buttons defined with Swap commands:
© 2020 Mediachance
Basics 23
Now it would be a good idea to save all this yet again to all three kbsets we saved previously: Set1.kbset,
Set2.kbset and Set3.kbset
Now you can define all the necessary keys for the Set 1, then save as Set1.kbset.
Load Set2.kbset, define keys for this set and save as Set2.kbset.
Load Set3.kbset, define keys for this set and save as Set3.kbset.
So each of these sets will have three same keys defined to the same action: Swap macro Set1,2,3 but all other
keys will be different.
You need to make sure all sets are saved before you go and start defining new set.
Now if you press any of those three buttons, you should see the macro set automatically swap.
© 2020 Mediachance
24 MultiKeyboard Macros
2.7 Settings
Start Minimized - the app starts in its minimized state in windows tray so you don't see the interface.
It will briefly display the macro name on the bottom part of screen when triggered.
Note: if you want to suppress only certain macros from displaying its name on screen (for example if it triggers
screen capture) prefix the macro name with a dot, Example: .ScreenCapture. Also in script you may use
DisplayText 53 to display your own label, in which case you don't want the default macro name to appear briefly.
MON
Monitor functionality, shows pressed keys in a monitor window
© 2020 Mediachance
Basics 25
The numerical part allows you to determine the hardware hex code for a key and to see exactly what MKM see
when you press a key on primary and secondary keyboards.
© 2020 Mediachance
26 MultiKeyboard Macros
III Scripting
3.1 Oscar Script
Now we are getting into a much more advanced area!
Till now we could add a sequence of various pre-set steps. Often that functionality may be enough, but what if
we want more?.
This little block hides an an enormous power - almost like a whole new application itself.
It is important to note that using script is entirely optional. You don't have to if you don't want to, but it can open
the door to things that other macro software can only dream of.
The script itself is quite well suited to process clipboard text and has a large amount of string operation including
tag extraction, tokenizer, even regex. A script can grab text from under your cursor, process it in different ways
and then type it back or save it to a file. Think of selecting a name in your mailbox, pressing a key and it will
instantly format a whole letter. Or just copy a block of text and it will extract names, email addresses etc.
We made few examples, that only scratch the surface - multiple clipboards, XML tag extraction etc.
It can be also used to create a very special logic, where some keys would be modifiers to other keys.
© 2020 Mediachance
Scripting 27
The script editor consist of few parts - the main typing area, on the right is the list of used variables in the script,
and list of all commands and output window.
Before script can be accepted it needs to be Run to make sure it has no errors.
A script that has an error or has not Run after changes were made cannot be applied.
The List of Variables displays used variables with their values in the script. It is updated every time you Run the
script.
List of commands is a sort of short help file that list all available commands. Double-clicking on a command will
insert it into the script.
The richness of the script language may feel almost an overkill for this application - as I was developing it, it
soon made detour from a simple few day of work script and became more like a full programming language with
many interesting aspects that I was missing in other scripts: we can have user functions, it has rich array
arithmetic, local and global variables, even reference variables and pretty great step debugger. I decided to
continue working on the script language, enhance it and use it for other applications as well.
© 2020 Mediachance
28 MultiKeyboard Macros
The language is loosely based on a BASIC for its simplicity and familiarity but it omits many of the old Basic
idiosyncrasies and where it was beneficial it borrows syntax and features from modern languages like Java, Lua
or C.
If you ever programmed in any of the modern languages you will be right at home.
The core language is kept at minimum to keep it familiar. On top of the core language are arithmetic and string
functions for both normal parameters and their array equivalents.
Comments
Comments follow standard C type of comments
// This is comment
a = RND(0, 100)
Note: Typing ' ; ' at the line will not return error but neither it signifies end of the command. It is simply ignored - I
did that far too many times during testing as an old habit.
© 2020 Mediachance
Scripting 29
The core command set consist only of few basic commands, such as for next, if then,.... All
core commands are written in lowercase.
After core commands the script has extension (functions) commands that would work on strings, time, clipboard,
send text etc.. These use mixed Upper and Lower case and can be extended in future with more commands
when needed.
newstring = FindNumbers(string)
Variables
There is no mandatory declaration, nor type declaration. There are 4 types of variables:
· integer
· float
· string
· reference
Reference will be explained later in more details, the other are self explanatory.
Variable name is Case Sensitive. Underscores are fine as well as numbers if they are not the first character.
nVariable = 123
fVariable = 3.1415
sVariable = "Oscar Script"
If you Validate such script you will see in the Variable list:
sEscapeWrong = "Files\text.txt"
Output: Files ext.txt
© 2020 Mediachance
30 MultiKeyboard Macros
sEscapeCorrect = "Files\\text.txt"
Output: Files\text.txt
Preceding string literals with _R will turn on RAW string option that will ignore any escape characters in the
string that follows.
sNoEscape = _R"Files\text.txt"
Output: Files\text.txt
RAW string option is especially good for RegEx patterns where entering double \\ will make it even more
unreadable than it is now.
regex = _R"[-+]?(\b[0-9]+\.([0-9]+\b)?|\.[0-9]+\b)"
Uninitialized variables
If you use a variable that wasn't yet initialized (assigned any value to it), it isn't an error, but a warning is issued
and the script continues assuming integer zero value.
c = var1
print c
For example:
In this case script started with 'a' as integer but then re-assigned it to float to prevent loss of data when we added
float number to it.
If we explicitly need to keep integer we can use INT function
© 2020 Mediachance
Scripting 31
Arrays
Arrays are done the very same way, without declaration.
In fact Oscar Script could have one of the most clever system for arrays. But more about it later.
k[6] = 12
sString[1] = "test"
sString[k[6]*1000] = "test 1000"
integer[0] = 1234
integer[-200] = 4325
You may notice a peculiar thing on the above listing: one of the index is negative - that is perfectly valid in Oscar
Script! Another thing is, we can index arrays any way we wish even non sequentially.
for y = 0 to 5
for x = 0 to 5
array[x][y] = x+y
next x
next y
While there is no limit into dimensions, remember this is a script - so don't go overboard. The bellow is perfectly
fine as a syntax, but it makes very little sense:
variable[1][34][123][100+2][2][25] = "testing"
String Arrays
It is important to mention that any member of array when not assigned value will be an integer zero, even if other
members could be strings.
So if you assign
string[0] = "zero"
You can't just assume the string[10] will be also string, unless you actually assign a string to it
beforehand.
Therefore if you need array of 10 empty strings you should assign "" to them first
© 2020 Mediachance
32 MultiKeyboard Macros
for k = 0 to 9
string[k] = ""
next k
In general #const is preferred for defining constants and should be used instead of #define
#define macro can be multi-line if the last character is space \ backslash followed by immediate new line
the macro will also consist of the next line.
Example:
#define FORLOOP for i=0 to nM \
print i \
next i
© 2020 Mediachance
Scripting 33
nM = 5
FORLOOP
Output: 012345
© 2020 Mediachance
34 MultiKeyboard Macros
3.3 if-then-else-endif
A standard if condition statement that allows identifying if a certain condition is true, and executes a block of
code if it is the case.
if condition then
statements
else
statements
endif
The very basic condition is one without else:
if a==5 then
print "a is five"
endif
One rule is that each condition has to have endif - because we don't have block separators as in C { }, the
script needs to know when if -then condition starts and where it ends.
if (a == 5) then print a
endif
This is enforced even if you put everything in one line - you have to use endif.
if a==5 then
print "a is five"
else
print "a is definitely not five"
endif
Comparison operators:
a == b // a is equal b
a != b // a is NOT equal b
Boolean operands
a | b // boolean OR
a & b // boolean AND
Negation
!a // NOT a - negation of a
© 2020 Mediachance
Scripting 35
if condition then
statement
else if condition then
statement
endif
endif
You can have many nested if blocks etc, just always remember the endif rule.
It helps if you write nested if conditions tabbed so it become more obvious what if belongs to what endif
a = 3
if a<1 then print "a<1"
else
if a<2 then print "a<2"
else
if a<3 then print "a<3"
else
print "a=3"
endif
endif
endif
3.4 for-to-next
For - next is your basic loop.
Syntax:
for a = 0 to 5
print a
next a
Output:
© 2020 Mediachance
36 MultiKeyboard Macros
012345
Advanced loop using 'step' parameter, which specified the value at which a variable is incremented. It can be
negative to have the loop decrease the variable instead of increasing it
Syntax:
for a = 5 to 1 step -1
print a
next a
Output:
54321
The for - to line is evaluated only once at the beginning. The loop variable is increased (or decreased) every time
next command is found.
Changing control variable inside the loop will change how the loop behaves! It is probably a bit risky to use it this
way.
for a = 0 to 10
print a
a = a*2 // this will change the condition
next a
Output:
0137
Non linear loops
Even more advanced loop is one with changing step
Unlike standard BASIC, Oscar Script allows you to change step within inside the loop by simply assigning it a
new value. This creates some new possibilities in creating special non-linear loops.
Output:
1,2,4,8,16,32,64,128,256,
Note: at no point the step can be assigned value of 0 (that would create infinity loop)
step behaves as a hidden variable and can be also used on the right side of equation in the loop:
© 2020 Mediachance
Scripting 37
if (step == 4) then
break
endif
next a
But if you try to use it outside the loop you will get an error.
Break will exit the loop. In case of nested loops it will exit only the closest loop it is in
for k = 0 to 2
for a = 1 to 10
if (a == 5) then break
endif
print a,"|",k," "
next a
println "break"
next k
Output:
1|0 2|0 3|0 4|0 break
1|1 2|1 3|1 4|1 break
1|2 2|2 3|2 4|2 break
Continue will skip the rest part of the loop and directly do a next loop iteration
for a = 1 to 10
if (a == 5) then
print "five,"
continue // go back to beginning of loop
endif
print a,","
next a
Output:
1,2,3,4,five,6,7,8,9,10,
Infinite Loop
While definitely not a good idea, sometimes you may not know the predetermined number of loops you need (for
example searching for substring)
for a = 0 to INT_MAX
© 2020 Mediachance
38 MultiKeyboard Macros
if (token=="") then
break
endif
array[a] = token
next a
arrayLength = a
There is an array version of Tokenize, that will create the array without loop, on just single line. (More about it
later) The normal Tokenize was used here for demonstration.
Note: The script will still abort after predetermined safety time to avoid infinite loop. The default is set to 5
seconds
label is any name that is at the beginning of a line and ends with :
label:
You can jump out of the loops, if statements or skip large chunk of code etc... It is usually said to avoid goto
statement because it makes the code harder to follow.
That may or may not be true, depends how you use it. Sometimes it saves a lot of additional conditional code
especially when nested if-then are involved
if a>-1 then
....
if (c>-1) then
...
goto finish
endif
....
endif
© 2020 Mediachance
Scripting 39
finish:
println "done"
A general idea is to avoid going back to previous lines - that may create infinity loops and it is definitely much
harder to follow.
Gosub
gosub label
Unlike goto statement that simply goes away, gosub also remembers where it was and can return with
return statement creating a basically subroutines in your code
When you are creating subroutines, make sure you mark the end of the main program with end statement.
test = RND(0,3)
println "Finished"
// if we use subroutines, we need 'end' of main program
end
subroutine0:
DisplayText("We are in Subroutine A")
return
subroutine1:
DisplayText("We are in Subroutine B")
return
subroutine2:
DisplayText("We are in Subroutine C")
return
Oscar script has also functions which are much more modern way of doing a subroutine jumps. The difference
between gosub and function is that all variables inside functions are local while with gosub we share the same
variables with the rest of the script..
© 2020 Mediachance
40 MultiKeyboard Macros
Syntax:
print a,b
print "a=",a,", b=",b
print "Random number: ", RND(0,10)
Example:
for i = 0 to 5
print i,","
next i
0,1,2,3,4,5,
The same, but using println
for i = 0 to 5
println i,","
next i
1,
2,
3,
4,
5,
Print to OUTPUT
Print is not just printing to Output Window, that would be a lackluster feature for normal operation. Print
commands also add sequentially all print output in the current script into a string variable called OUTPUT
This serves as a simple and painless way to format strings that can be then used further in a string operations,
clipboard or save to file.
© 2020 Mediachance
Scripting 41
println "<catalog>"
println TAB,"<book id =",QT,bookid,QT,">"
println TAB,TAB,"<author>Misc, Jones</author>"
println TAB,TAB,"<title>How to compute</title>"
println TAB,"</book>"
println "</catalog>"
SaveString(OUTPUT,"file.xml")
Content of the file.xml
© 2020 Mediachance
42 MultiKeyboard Macros
For example:
if a>5 then
b = a*2
else
b = a/2
endif
The power of course comes from the fact that this operator can be used as any other operator (+,/,*,-...) inside
longer expressions, dramatically reducing the need for if -then.
string = "my " + (RND(0,10) > 5 ? "car " : "dog ") +"is blue"
Because it is an expression it can be nested into itself, making very efficient condition.
Instead of:
© 2020 Mediachance
Scripting 43
endif
Another example:
a = ""
type = (TYPE(a)==FLOAT) ? "float" : TYPE(a)==STRING ? "string":
"integer"
Note: Unlike if-then command, in the Conditional operator both expressions (YES and NO) are processed
regardless of the state of the operand and then the correct answer will be used. This is a safer way than using if-
then for the same expression, because we will be notified of any error immediately regardless if the operand is
yes or no, nut it can also may came up as a surprise.
a = 0
b = 1
c = (b>2) ? 1/b : 2/a
Error on line:4 - Division by zero: <int>2 / <int>0
© 2020 Mediachance
44 MultiKeyboard Macros
3.8 Functions
Oscar Script can also have user functions. Function is declared with syntax:
function MyFunction(var1,var2,var3)
...
...
return nret
NOTE: When using function, you need to use end in your main (also called root) program: The program
execution should never get to the function declaration itself.
Example:
// calling the function
MyFunction(0)
...
// we need to end main program
end
// Function declaration
function MyFunction(A)
...
return 0
Function Parameters
The arguments list the input parameters. They can be from 0 to 9 arguments
function Test(nVar1,nVar2)
There is no type declaration in Oscar Script and so the function arguments will be assigned the type on run-time
depending what you will pass into the function
Test(0,"Script")
Return Value
Function should return a value using return command. While return value is not mandatory, you should specify
return of 0 or nil even in function that doesn't return value just to keep warning off.
return value
Example:
//calling function
rnd = RandomFL()
println rnd
end
// function declaration
function RandomFL()
A = RND(0,100)/100.0
return A
Local instance
© 2020 Mediachance
Scripting 45
One very important and unique property of functions is that variables (except Global variables) inside functions
are local to the function instance only. You may think of function as a whole separate script and can
communicate to other parts only through function parameters, return value and global variables.
Example:
A = 10
MyFunction()
// A is still 10
println "A outside: ",A
end
function MyFunction()
// A is a local variable declared only within the function
A = 100
println "A inside: ",A
return 0
A inside: 100
A outside: 10
Variables declared in main program will not be visible in the functions, unless they are passed through the
function arguments
Variables declared in functions will not be visible in other functions or main program unless passed as return
value
function MyFunction()
// B doesn't exist in this instance !
// We will get warning that B has not been initialized yet
A = B
return 0
Warning at line 9: B was used without being initialized first.
Recursive Calling
Normally recursive calling should be avoided as it is very hard to debug. However there is a limited number of
depth a function can call itself from within itself (or another proxy function) - the depth is set to 10 recursive calls
after which an error will be issued and the program will termnate.
funct_A()
end
function funct_A()
funct_B()
return
© 2020 Mediachance
46 MultiKeyboard Macros
function funct_B()
funct_A()
return
Script Started
Error on line: 10 - Unsafe Nested Recursion - aborting << funct_B ( ) << funct_A ( ) << funct_B ( ) <<
funct_A ( )
<< funct_B ( ) << funct_A ( ) << funct_B ( ) << funct_A ( ) << funct_B ( ) << funct_A ( )
Script terminated due to Error
Debugging Functions
Debugger 99 would normally not jump inside functions when using step commands, just evaluate them like any
normal functions. You can however set break point inside a function if you need to, but be aware that the
breakpoint will be deleted as soon as it is reached for the debugger to function properly. Read more in the
debugging 99 section.
© 2020 Mediachance
Scripting 47
ascii_number = Converts string character into an ASCII string "A" will convert into integer 65. Sting
ASC(string_character) number. In case if the string has more "ABCD" will also convert to 65
characters, only the first one will be taken
into account
string = STR(number) Converts number (float or integer) into a integer 123 converts to string "123", float
string. 123.5 converts to string "123.500000"
number = VAL(string) Converts number in the string into an converts "123" into integer 123 , converts
integer or float "123.5" into float 123.5
Type
© 2020 Mediachance
48 MultiKeyboard Macros
© 2020 Mediachance
Scripting 49
String Syntax:
string = "this is string"
Be aware of escape characters that are defined by backslash: \
\" quotation mark inside string, also \042 'octal" value can string = "this is \"quotation\" mark"
be used or
string = "this is \042quotation\042 mark"
\xxx A character from ASCII can by typed directly using its 3 string = "Mediachance \251 2020"
number octal value after \ - if you look at most ASCII
tables, they will be represented using Decimal, Hex and
Octal values.
This allows you to enter characters for which you don't
have key on your keyboard.
© 2020 Mediachance
50 MultiKeyboard Macros
Output: DEFGH
int = Length(string) returns string length intA = Length("ABCDEFGH")
Output: 8
string = Trim(string) removes white-spaces from beginning string = Trim(" abcdef\r\n ")
and end of the string Output: abcdef
int = Find(string, returns int position of substring inside nPos = Find("My name is
substring) the string, -1 if nothing was found. Script", " "))
Position is zero based Output: 2
* accepts arrays as string
int = ReverseFind(string, returns int position of substring inside nPos = ReverseFind("My name
substring) the string but searched from back is Script", " ")
Output: 10
int = returns int position of first character nA = FindOneOf("New(old)","()
FindOneOf(string,charSet) that matches any character in {}[]")
charSet, position is zero based Output: 3
© 2020 Mediachance
Scripting 51
© 2020 Mediachance
52 MultiKeyboard Macros
Tokenize example:
date = GetDate()
day_as_number = VAL(day)
month_number = VAL(month)
m_str = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC"
© 2020 Mediachance
Scripting 53
These functions will not be active during Script Editing - their output will be directed only to the Output Window.
Once the script is validated and you exit Script Editor into Macro window, these commands are active for testing
using the macro trigger.
SendKeyStroke(string) Send hardware keystroke to active modifiers: SHIFT CTRL WIN ALT
window such as CTRL C.
SendKeyStroke("CTRL C")
Special commands: HOLD, RELEASE
and PAUSE Send Key Stroke to Windows: <CTRL>
+ C
These special commands can instruct to
hold and release key at precise sequence Special Keys
with other keystrokes and also add pause. RETURN DOWN UP LEFT RIGHT DELETE
BACKSPACE F1..F12 PAGEDOWN PAGEUP
SendKeyStroke("HOLD CTRL") SPACE TAB HOME ESC
SendKeyStroke("A")
SendKeyStroke("1") You can also send a direct hex code of the
SendKeyStroke("PAUSE") keyboard key inside the string with prefix:0x
SendKeyStroke("RELEASE CTRL")
Such code can be obtained when using the
Monitor 24 functionality
Warning: You have to RELEASE the key
if you use HOLD or the key will remain
stuck! See more in Key Off block 106 how
this can be used
© 2020 Mediachance
54 MultiKeyboard Macros
© 2020 Mediachance
Scripting 55
© 2020 Mediachance
56 MultiKeyboard Macros
Obviously, for seamless control it is best if you can first find a shortkey for the function you want to control; for
example in Photoshop brush size can be changed by sending shortkey [ or ].
However shortkeys or shortcuts are not always available for all functions that are visible on screen in most apps
and this is where you can utilize the Slider Function as a sort of "plan B".
It is important to note that it works with absolute coordinates so the application window (and the control object
you are manipulating) has to be always in the same position. The best is to use Activate window 14 step with
Maximize Window function before the script to bring the desired application to front and maximize it.
nSliderID = Define vertical slider on the screen. The See slider helper
DefineSliderY(nX,nY,nW,nH) function returns the ID of the defined slider
that you use in the SetSliderPos
SetSliderPosDL(nSliderID, fPos, same as above, but can introduce larger SetSliderPosDL(nSliderID, 83,20)
nDelay) delay (milliseconds) in the mouse click in
case the host application fails to register
the click
There are two steps, first is to define the slider coordinates as it is presented on screen.
To help you define the slider position, we added Slider Helper function:
This will allow you to capture a portion of the screen around the slider and precisely define the slider dimensions,
then create automatically DefineSlider command.
© 2020 Mediachance
Scripting 57
5. Refine the slider sides and center line using the white tabs.
The blue center line determines where the function will "click" on the slider. It is important so it goes through
active part of the slider - which usually is the handle of the slider. A slider may have other embellishments that
are not actually active, for example the slider line on the above slider as captured from Photoshop is in fact on
the edge of where the slider is active - which starts at the line and goes down the height of the slider handle.
Making the blue center line cross the handle is a safe way to be sure we are clicking on the active part.
Move the vertical red lines so it tightly crop the sides of the slider. Move the horizontal lines so the blue center
line crosses the handle of the slider (the actual height of slider doesn't matter, the function will "click" along the
blue line)
© 2020 Mediachance
58 MultiKeyboard Macros
You can press Test button which will hide the Slider Helper and simulate clicks on the slider on the screen
(make sure you have the host window opened bellow) - one at minimum, one in middle and one at maximum. If
one of the edge ones (minimum or maximum) doesn't register, you need to crop that side tighter.
The Slider Define command will show the actual define command line for this situation.
When you press Insert / Replace in Editor , this line will be entered in the editor at cursor place.
Which will change the SetSliderPos entry - this is only for your information to see what value of the fPos
correspond to the actual slider position.
Note: If you select the DefineSlider in the text editor and then call Slider Helper the actual numbers will be used
in the dialog and you can refine them, or re-capture the slider and overwrite with new data.
Scroll
When you capture area larger than the preview you can scroll around with the scroll buttons.
© 2020 Mediachance
Scripting 59
Note:
The Capture slider image is only for initial setting up the slider - the image is not going to be stored anywhere
after you press OK so when you call Slider Helper again there will be just a generic image of a slider.
Once the slider is defined, you can then use SetSliderPos. You can of course define many sliders and the way
they are recognized is by nSliderID value the DefineSlider returns when called. Each time you use DefineSlider
in your script it will create a new slider and return a new slider ID (that starts from 0 and then increments)
nSliderID = DefineSlider(1869,402,201,56)
then you can use the nSliderID in your script to adjust that particular virtual slider.
SetSliderPos(nSliderID,50)
The values of the slider are 0...100 and this is a float numbers, so you can use a finer step such as 35.5, The
range basically represents a percentage of the slider width so 50 would be slider at the middle.
You can easily use this function to click on buttons or a row of buttons as well not just on slider.
Capture the button coordinates or the button row, set the left and right crop and make sure the blue line goes
through the center the of button(s). Then use SetSliderPos with fPos = 50 and it will click in the middle of the
button. If you use row of buttons, you can move the yellow slider around and see what fPos value represents
with each button middle:
Clicking + button will add the value to the Button array helper line that you can then copy to clipboard with Copy
Button
For example we can set a different brush in photoshop by simply setting a different value to the "slider" defined
around the brush buttons.
It is important to note that if you press a macro key quickly few times you are essentially calling the script
multiple times in rapid succession - which on buttons can be interpreted as a double-click.
Particularly the example above with Photoshop Brushes - the brushes also respond to double click.
Delay Version
The SetSliderPos is set for a reasonably fluid speed on sliders, but on some software this may be too
fast and the software will not register the mouse movement or click, particularly on some buttons.
© 2020 Mediachance
60 MultiKeyboard Macros
Therefore there is a second version that allows to fine tune the delay between mouse clicks (particularly the
delay between clicking mouse down and releasing it)
SetSliderPosDL(nSliderID,fPos, nDelay)
The delay is in milliseconds and you should try to slow it down by trying 20 ms. The maximum is 200ms. If it
doesn't work even with 200 ms delay then something else may be amiss.
Example:
SetSliderPosDL(nSliderID,50, 20)
Real-Life Examples
Example 1:
We will use two keyboard buttons to increase brush size by moving the brush size slider. GLB_BrushSize global
variable will remember the current slider position.
Obviously DefineSlider is set for the particular screen situation with your photoshop and cannot be just blindly
copied from here.
Note: in this very particular example, it would be in fact much better and easier to simply send shortkeys '[' and ']'
as they are mapped in Photoshop to brush size and that would work better than sending mouse and clicks to a
slider. But it is an example that is easy to understand and can be transformed to any other slider by simply
capturing and defining different area with Slider Helper. Point is: check first if the function you trying to
manipulate doesn't have shortkeys.
slider = DefineSlider(1869,411,200,36)
// global variable holding brush size
GLB_BrushSize = GLB_BrushSize + 10;
GLB_BrushSize = MIN(GLB_BrushSize,100)
SetSliderPos(slider,GLB_BrushSize)
Similarly the Brush Minus key will subtract 10 from the global variable.
slider = DefineSlider(1869,411,200,36)
GLB_BrushSize = GLB_BrushSize - 10;
GLB_BrushSize = MAX(GLB_BrushSize,0)
SetSliderPos(slider,GLB_BrushSize)
Now it does work, but in this case the slider in Photoshop is logarithmic, so on the left side change of 10%
makes actually really big jump in the brush size from a tiny to quite big brush in just one macro click so we need
much finer steps while on the right side the brushes are simply gigantic and don't need any finer steps.
We need to change the brush size slider progressively - approximating the non linear increase.
We can replace the second line instead of having constant +10 in Brush+ key to a non-linear increase where so
the bigger the brush is, the bigger jump the slider will do. For example a simple:
© 2020 Mediachance
Scripting 61
Note: Because the action is performed by mouse clicking at the slider on screen you have to be careful not to
draw with the mouse on the same time you are pressing the brush changes macro keys.
Example 2:
Using single key cycle between row of brushes in Photoshop.
We will capture the row of brushes, crop it and then determine with the yellow slider what number the center of
each button corresponds to. These will be the positions our "slider" will click through.
Global variable GLB_BrushSel remembers the position.
GLB_BrushSel = GLB_BrushSel+1;
nBrush = DefineSlider(1867,265,158,58)
SetSliderPos(nBrush,nPos[GLB_BrushSel])
© 2020 Mediachance
62 MultiKeyboard Macros
nMin = MIN(nNum, nNum) finds minimum and maximum of two fNum = 100.0
nMax = MAX(nNum, nNum) numbers fMin = MIN(255.0, fNum)
nNumber = hex2dec(string) Converts hex number in string into decimal nNumber = hex2dec("0xFF")
number. The hex string can have prefix 0x 255
or not. Large hex number will result in nNumber = hex2dec("AB4EA")
negative integer if it reaches INT_MAX 701674
OUTPUT Special variable that receives output of all OUTPUT = "" // clears any previous
print commands within the current script. print
println "This is a test of OUTPUT
variable"
for i = 1 to 5
print i
next i
// save the output to file
SaveString(OUTPUT,"numbers.txt")
1,3,7,15,31,63,127,255,
© 2020 Mediachance
Scripting 63
AND OR NOT
BOOL1 & BOOL2 BOOL1 | BOOL2 !BOOL
a = 0b101010 //42 a = 0b101010 //42 // bitwise NOT is inverse of all bits in the
b = 0b010101 //21 b = 0b010101 //21 integer (making positive number negative)
c = a & b //000000 = 0 c = a | b //111111 = 63 a = 103 // binary:
0000000001100111
b = ~a; // binary:
1111111110011000 = -104
How does the system knows when | and & it is boolean operator and when it is bitwise? It doesn't; a bitwise | and
& operation on 0 and 1 are simply behaving as Boolean operations.
© 2020 Mediachance
64 MultiKeyboard Macros
Bitwise left shift << and bitwise right shift >> operators will shift the integer number to the left or to the right
a = 0b100 // 00100 = 4
b = a << 2 // 10000 = 16
c = b >> 1 // 01000 = 8
Example: set and clear bit in integer using bitwise operators
Example 2
Convert decimal number to binary using bitshift:
// input number
a = -30000
println strbin
Script Started
Dec: -30000 = Bin: 11111111111111111000101011010000
Script Ended OK
© 2020 Mediachance
Scripting 65
Example:
day_as_number = VAL(day)
month_as_number = VAL(month)
© 2020 Mediachance
66 MultiKeyboard Macros
localVariable = 1.23
GLB_globalVariable = 1
If you exit the script and come back or open another script you will see that the GLB_globalVariable is
still defined.
Any global variable that is assigned value of 0 or empty string will be marked for self destruction.
GLB_counter = 0
and arrays:
GLB_array[] = 0
Using self destructed Global variables
You may plan to use 0 or empty string as a valid value in a global variable. In case of numbers this doesn't
create any problem because when script finds undefined global variables it automatically assumes them as
numerical 0
GLB_counter = GLB_counter + 1
this line will be valid even if GLB_counter is not yet defined, because it will be assumed 0
however this approach would not work for strings. When variable doesn't exist but we refer to it...
result = GLB_string
Variable declaration
© 2020 Mediachance
Scripting 67
In some complex cases when multiple scripts are involved with global variables shared between them it may be
beneficial to tell in advance to the script the type of variables used so we don't get an error in case they don't
exist.
This will make sure the GLB_String will be further assumed as an string regardless if it was defined or what type
of variable it was..
Important
The declare doesn't modify the value of the variable. If it was never used before it would be then assumed
an empty, if it had some value of the same declared type, that would be still carried over.
© 2020 Mediachance
68 MultiKeyboard Macros
A[0] = 10
A[1] = 20
A[2] = 30
B[0] = 1
B[1] = 2
B[2] = 3
C[] = A[]+B[]
D[] = A[]* B[]+C[]*2
The arrays are expected to be the same or overlapping range. If they are not the same range, only the
overlapping area will be validated.
Example:
A[0] = 10
A[1] = 20
A[2] = 30
B[1] = 100
C[] = A[]*B[]
Arrays are validated regardless if they are sequential or not. An array can have gaps.
A[0] = 10
A[10] = 20
A[100] = 30
C[] = A[]*10
© 2020 Mediachance
Scripting 69
A[] = ARRAY(0,10, 0)
A[] = ARRAY(3,7, 100)
A[] = {member,member,...}
This will always fill the array starting at index 0
Note this is similar to C++ array initialization.
Example:
A[] = {12,13,14}
The implicit array initialization can be also used in array arithmetic operations directly, but it may create less
readable code if overused.
A[] = {12,13,14}*2
if A[]=={24,26,28} then
println "is Equal"
endif
You can also use implicit array initialization in functions, but the script will let you to use only one implicit array
argument per function, the rest of the arguments need to be assigned to variables before you call the function.
© 2020 Mediachance
70 MultiKeyboard Macros
rArray[] = {1,2,3,4}
A[] = Right({"one","two","three","four"},rArray[])
Delete Array
Assigning empty implicit array will remove all array members.
In general it is not necessary to call this function on local members as they will be removed regardless - but you
can use it if you want to clean-up a global array inside your script.
A[]={}
Example:
A[] = {12,13,14}
print A[]
A[]={}
print A[]
[12,13,14]
Warning at line 4 : The Array A[] was used without being initialized first. Possible error in this context!
String Arithmetic
A[0] = "one"
A[1] = "two"
A[2] = "three"
Boolean Arithmetic
Boolean operators will return an integer array with 0 or 1 depending on the condition.
You can compare both strings and numbers where appropriate (just not strings with numbers)
A[0] = "one"
A[1] = "two"
A[2] = "three"
B[0] = "four"
B[1] = "two"
B[2] = "five"
© 2020 Mediachance
Scripting 71
With numbers:
A[] = RND(ARRAY(0,5,0),10)
B[] = RND(ARRAY(0,5,0),10)
The | (or) and & (and) operator are both boolean and bitwise operators. Make sure when you want to use them
as boolean operators that you correctly use ( ) or to split them to lines to make sure they apply to other boolean
operators as in the example before (C[] and D[] are always in 0..1 range)
a[0] = 1
a[1] = 0
a[2] = 1
c[] = !a[]
© 2020 Mediachance
72 MultiKeyboard Macros
As with the normal counterparts, you can use bitwise (binary) operators with integer arrays
If the integer number in the array item has only values 0 and 1 then it is equal to BOOLean operators, but for
every other number the result is an integer number.
Bitshift operators
In bitshift operators only the left side can be array - the right side should be integer
a[0] = 4
a[1] = 8
a[2] = 16
Arrays could be used in if-then condition. In such case the condition is TRUE only if all BOOLEAN results in the
array are TRUE as well.
© 2020 Mediachance
Scripting 73
A[] = RND(ARRAY(0,5,0),10)
res = "NO"
if A[]<8 then
print "All A values are smaller than 8"
res = "YES"
endif
while using arithmetic with different types (strings with numbers for example) will produce error in any normal
variables:
a = "apple"
b = a + 12
Inside array this is dropped to only a warning and the operation will proceed with only items that are supported.
This allows for processing arrays with multiple types 95 without causing error where only the correct type will
continue the operation.
a[0] = "apple"
a[1] = 24
b[] = a[] + 12
or
a[0] = "apple"
a[1] = 24
© 2020 Mediachance
74 MultiKeyboard Macros
© 2020 Mediachance
Scripting 75
in case of operand being a string an empty string evaluates as FALSE otherwise it is TRUE.
See example:
a[0] = "apple"
a[1] = "car"
a[2] = "house"
b[0] = "MIXER"
b[1] = "AIRPORT"
b[2] = "KEYBOARD"
condition[0] = 0
condition[1] = 1
condition[2] = 0
In the previous example we used a condition variable for clarity, but that is only one way to use it.
Of course you could put a true array condition:
a[0] = "apple"
a[1] = "car"
a[2] = "keyboard"
b[0] = "SOCK"
b[1] = "AIRPORT"
b[2] = "HOUSE"
© 2020 Mediachance
76 MultiKeyboard Macros
In this example the result will have the longest strings from each array parts.
It is easy to make this mistake especially if you try to cram everything on one line like I did.
© 2020 Mediachance
Scripting 77
Normal way, without Array Operators The clever way With Array Operators
integer[0] = 1 integer[0] = 1
integer[1] = 2 integer[1] = 2
integer[2] = 3 integer[2] = 3
A[] = ARRAY(0,5, 0)
A[] = ARRAY(6,10, 0)
© 2020 Mediachance
78 MultiKeyboard Macros
By creating array directly we can nest other commands on top of it to make quick function in one single line ! Of
course it is a bit harder to understand when written like that.
float[] = FLT(value[])
Output:
[1.000000,2.000000,3.000000]
int[] = INT(value[])
Output: [1,2,3]
number[] = VAL(string[]) Converts numbers in the string array into string[0] = "1.34"
an integer or float array. string[1] = "2.25"
string[2] = "4.55"
value[] = VAL(string[])
Output:
[1.340000,2.250000,4.550000]
Output: [105,116,99]
© 2020 Mediachance
Scripting 79
Output: ["001","002","003"]
There is always single parameter that is a master parameter that actually determines if the function is an array
function or just ordinary function as from previous pages 49 .
The master parameter would be written in this document as:
string[]
If any other parameters can be optional arrays then it is written in this document:
nNum~[]
which means the parameter can be either normal parameter or an array.
If you put master parameter as ordinary (non array) parameter then the normal function will be assumed. With
multiple arrays used in one function it is assumed they both have the same range and use the same indexes
otherwise only the overlapping range will have the correct answer.
Operation using only master parameter as an array Operation with both parameters as array
strRes[] = Left(string[],left[])
nMin[] = MIN(nNum[], nNum~[]) finds minimum and maximum of two arrays array[] = RND(ARRAY(0,10,0),100)
nMax[] = MAX(nNum[], nNum~[]) or an array and a number nMin[] = MIN(array[], 50)
© 2020 Mediachance
80 MultiKeyboard Macros
range is:
Output: ["inte","temp","cons"]
strRes[] = Mid(string[],4,nC[])
Output:["r","us","equ"]
iRes[] = Length(string[])
Output:[8,6,9]
string[] = Trim(string[]) removes white-spaces from beginning and string[0] = " interdum "
end of the string string[1] = " tempus "
string[2] = " consequat "
string[] = Trim(string[])
© 2020 Mediachance
Scripting 81
int[] = Find(string[], returns int array of position of substring inside array[0] = "test me"
substring~[]) the string, -1 if nothing was found. Position is array[1] = "Summer"
zero based array[2] = "Domestic"
//using array
find[] = Find(array[],"e")
Output:[1,4,3]
string[] = returns a string with extracted first occurrence see example in Replace
FindNumbers(string[]) of numbers from left
string[] = Replace(string[], Replace all sOld substrings with sNew inside str[1] = "file0123.txt"
sOld~[], sNew~[]) each string in array str[2] = "file653.txt"
str[3] = "file12643.txt"
sNums[] = FindNumbers(str[])
nNums[] = VAL(sNums[])
sNewNums[] = Format(nNums[],6)
str2[] =
Replace(str[],sNums[],sNewNums[
])
Output:
["file000123.txt","file000653.t
xt","file012643.txt"]
isnum[] = IsNumeric(str[])
str[] = This function doesn't have any array as Using Tokenize to fill string array from a
Tokenize(string,delimiter,REFER parameters but will return an array of all string:
ENCE) tokens specified by delimiter. m_str = "JAN FEB MAR APR MAY
JUN JUL AUG SEP OCT NOV DEC"
© 2020 Mediachance
82 MultiKeyboard Macros
Extract has two modes: Extract strings between sStartTag and m_str = "<f1>this is first<e>
Mode A - string is Master sEndTag strings. Good for parsing html or and <f2>this is second<e> tag"
str[] = xml strings or other structured text that have tagS[0]="<f1>"
Extract tags. tagS[1]="<f2>"
(string[],sStartTag,sEndTag,nSk
ip) array[] = Extract(m_str,tagS[],
There are two modes: "<e>",0)
Mode A when string is array, it will extract the
Mode B - start Tag is Master same tag from all items in array
str[] = for i = FIRST(array[]) to
Extract(string,sStartTag[],sEnd LAST(array[])
Tag~[],nSkip~[]) Mode B will extract multiple tags from a println array[i]
single string into array. next i
int[] = Returns integer array with members having Test if strings are emails:
RegexMatch(string[],regex) value of 1 if string member matches regular
expression in the pattern otherwise 0 emails[0] = "[email protected]"
emails[1] = "bambus@script"
Note: use the _R raw string prefix before the emails[2] =
string. This will not parse the string for "[email protected]"
escape sequences and take it exactly as it is
written. int[] =
RegexMatch(emails[],_R"^[\w-\.]
+@([\w-]+\.)+[\w-]{2,4}$")
The pattern is a Regex syntax. It is beyond
the scope of this document to deal with regex Output:[1,0,1]
syntax.
str[] = Search sub-string that matches the regular string = "Saturday and Sunday
RegexSearch(string,regex) expression in regex is fine but not Monday"
Will always return string array. result[] =
RegexSearch(string,_R"\w+day")
© 2020 Mediachance
Scripting 83
Output:
You can use FIRST(str[]) and Saturday
LAST(str[]) to get bounds of the returned Sunday
array. Monday
RegexSearch samples
There are many resources on the web about RegEx syntax. Remember use raw string option _R in front of the
regex literal so you don't have to deal with escape characters and can use regex strings directly as written.
© 2020 Mediachance
84 MultiKeyboard Macros
result[] = RegexSearch(string,regex)
print result[]
["123.5","-34.6"]
regex2 = _R"\b[A-Z]"
result2[] = RegexSearch(string,regex2)
println "Single Uppercase At the start of word: ", result2[]
Any Uppercase: ["G","M","ONLY","TT"]
Single Uppercase At the start of word: ["G","M","O"]
Unsupported type
if you apply function to an array with unsupported type, the data will be turned into the output type and processed
that way. This allows for hybrid arrays with multiple types inside to be processed without errors.
data[0] = "John"
data[1] = "Crichton"
data2[0] = 23325
data2[1] = 7657
newdata[] = MakeUpper(data[])
newdata2[] = MakeUpper(data2[])
© 2020 Mediachance
Scripting 85
You can just simply use arrays, array functions and arithmetic's on arrays straight forward as described before
and everything will work
All you need to do is to use [ ] syntax and work with arrays as with any other variables.
A simple example would be:
But if you want to know more about references, go on, read the rest of this chapter:
array[0] = 1
array[1] = 2
array[2] = 3
This syntax describes the array as an object and that object represents existing data
array[]
And we can use it this way in arithmetic operation as with normal numbers.. We can however assign a normal
variable to that very same array[ ] :
pReference = array[]
In this case the variable will became a reference.
The reference variable doesn't have any value, nor copies the array, it only points back to that array. It can be
used as a substitute to the array. In fact you can actually name it the same as the array which will then became
super confusing.
array[0] = 1
array[1] = 2
array[2] = 3
© 2020 Mediachance
86 MultiKeyboard Macros
println pRef
// it is exactly the same as calling
// println array[]
// Output [1,2,3]
// Output [10,2,3,100]
It is important to understand what syntax copies data and what not. So far we didn't copy any data at all in the
above example, just referenced it.
1. This syntax will create a reference variable that points to an existing array data - no actual copy of data will be
done, we will get one more "reference" variable and that's it
pRef = array[]
2. This syntax will copy the data that are referenced by pRef (which is the array[ ]) to a new array array2[ ], so
we will have two copies of the same data now.
array2[] = pRef
3. This syntax will create a copy of one array into another directly. It is basically a combination of 1. and 2.
omitting the reference variable and it is basic array arithmetic.
array2[] = array[]
© 2020 Mediachance
Scripting 87
4. This syntax will create a duplicate reference - you will have two reference variables pointing both at the same
data. Syntactically correct but not very useful and misleading.
pRef2 = pRef
Reference restrictions
There is a certain limit what you can reference.
You can reference only existing data. So referencing existing array[] variable directly is fine, but referencing
output of a Function or array arithmetic operation may not be always possible:
ref = array[] + 2
if the ref doesn't already reference some other array that can take the result data of the expression you will get
an error. If you think about it, when you trying to create a new reference to a result of expression - where would
the new data be stored unless the reference already points to some array? .
However if the reference variable is already assigned to an existing array (even to the same array that is in
expression)
ref = array[]
ref = array[] + 2
This will work fine. So can be reference used as a part of the expression:
ref = array[]
ref = ref + 2
Why do we even need references ?
Reference in Functions
The absolutely main use of a reference is as arguments and return arguments in functions
function Test(a)
a = a *10
return a
may look like a simple expression that work on normal numbers - but wait, I was just showing you that
references looks like normal variables as well...so what if we pass an array to the function?
© 2020 Mediachance
88 MultiKeyboard Macros
If you somehow read all the above gobbledygook you will know that the variable 'a' will actually then assume role
of a reference to an array and as such the expression will correctly validate for arrays, even it is not written a[]
= a[] *10
Thanks to the references the function will actually accept both normal variable:
var = Test(var)
and array:
array[] = Test(array[])
without us doing anything special about it. Pure magic!
Because reference is just a pointer to the actual data, reassigning reference can then point to other data in a
whim, without the need to create copy or making the program less readable with if/then.
Note: There is usually not a big need to save memory or time on copy operations, especially in script as we are
never working with data that is too big. Not to mention that all this is written in 2020 and not 1980s.
But it is here left as an example of possible reference usage.
a[0] = 2.34
a[1] = 35.6
a[2] = 17.0
b[0] = 1
b[1] = 2
b[2] = 3
test = RND(0,10)
pReference = test<5 ? a[]: b[]
result[] = pReference * 2 + pReference*3
The above will evaluate the expression with either a[] or b[] depending on the random generator
© 2020 Mediachance
Scripting 89
or
result[] = (test<5 ? a[]: b[]) * 2 + (test<5 ? a[]: b[])*3
In which case we didn't create temporary array but made it very hard to understand just two days later
or
if test<5 then
result[] = a[] * 2 + a[]*3
else
result[] = b[] * 2 + b[]*3
endif
The references can actually point to a Global array that doesn't yet exist. This may be a bit confusing, but with
global variables, a certain variable may be created by another script at a much later time. It is almost same as in
fuction, when the argument will be filled at some other time.
We can type:
pToData = GLB_array[]
And we get a reference to some future and not yet existing array GLB_array and nothing else. We don't even
know what type it is. Now any time later if we create the array GLB_array the reference will then point to its data.
You may question why this even exist? Well, without jumping too much ahead, any operations performed on the
reference will be still valid regardless if the data exist or not.
So if we type:
pToData = GLB_array[]
pToData = pToData * 2
print pToData
We still get no actual data taking place, nor any array was created, but the program works without error!
Now if we or other script creates the global variable that we are referencing:
GLB_array[0] = 2
GLB_array[1] = 4
GLB_array[2] = 8
© 2020 Mediachance
90 MultiKeyboard Macros
You can for example copy each line into the edit box bellow variables and press enter. Then run the script
again.
Boom, as a magic, the program now works with some actual data:
GLB_array[0][0] = 12
GLB_array[0][1] = 14
GLB_array[0][2] = 22
GLB_array[1][0] = 34
GLB_array[1][1] = 24
Well, the script doesn't care about that either and simply process it same way:
Reference as an Array
Before you ask, yes you can have a reference to an array in an array configuration
a[0] = 1
a[1] = 2
a[2] = 3
b[0] = 10
b[1] = 20
b[2] = 30
p[0] = a[]
p[1] = b[]
println p[0]
println p[1]
© 2020 Mediachance
Scripting 91
println p[0]+p[1]
println p[]
Script Started
[1,2,3]
[10,20,30]
[11,22,33]
[<*ref>a[],<*ref>b[]]
Script Ended OK
I assume there could be some clever way how to utilize it, but so far I think it only makes everything even more
confusing....
© 2020 Mediachance
92 MultiKeyboard Macros
a[0] = "apple"
a[1] = "car"
a[2] = "dog"
MyFunction(a[])
The function is defined just as any other function would be - nothing special needs to be there.
function MyFunction(var)
print var
return var
if we put a break point and stop inside the function, we will see there will be not only the array but also a
reference 85 variable (and both will be named the same as it is defined in function). This is a crucial point that
allows function to work with both array and normal variables the same way.
© 2020 Mediachance
Scripting 93
Inside the function we can then use the new array as any other array by using the name from the function
definition:
var[n] = ....
You are working on a local copy of the array inside the function. If you want the changes to return back to main
program you need to return the var[] (or reference 85 to the var ) from the function and then assign it in main
program to some array variable.
return var[]
or
return var
Note: As shown above we have two things going on: the array var[] and a reference variable var that points to
the data of var[]. So you can in fact type return both ways.
However when you use only reference variable inside the function and also return the reference variable, then
the function will work with both normal numbers and arrays alike. It is explained in the reference chapter 85 in
details why it is like that.
Example:
This example demonstrate using arrays in user functions
PrintArray(in_array[])
println
// print output
PrintArray( out_array[] )
// creates array
© 2020 Mediachance
94 MultiKeyboard Macros
function CreateArray(num)
function PrintArray(a)
print a
return nil
© 2020 Mediachance
Scripting 95
data[0] = 20
data[1] = 40
Nothing weird about it. Now let me just add some other data to it:
data[0][1] = 100
data[0][2] = 200
data[1][1] = 300
data[1][2] = 400
We now created hybrid multi-dimensional array. It is still same array, but it is both one dimensional and two
dimensional at the same time. Now lets add more:
data[2][1][0] = 1000
data[2][2][0] = 2000
data[2][3][0] = 3000
data[2][1][1] = 4000
Our data array still holds them all even if they are now in three different dimensions.
if we type:
© 2020 Mediachance
96 MultiKeyboard Macros
data[] = data[] * 2
All dimensions will be updated! And it doesn't even matter if we have gaps in the arrays either.
other[2][2][0] = -1
other[1] = -1
other[100] = -1
data[] = data[]*other[]
Only the parts that overlapped in our two arrays were updated with the arithmetic!
This overlapping arithmetic however depends on the order written.
If we used
data[] = other[]*data[]
we will make the other[] array significant and the result will have only 3 members.
data[0] = 20
data[1] = 3.1415
© 2020 Mediachance
Scripting 97
First item is integer, second is float. This will in fact continue with arithmetic operations if there is no loss of data,
the script will keep the first one integer.
result[] = data[]*2
However if we multiply the array by a float number then both members will became float.
This can have advantage in creating mixed data arrays (structures) without much of any effort.
#const FIRST_N 1
#const LAST_N 2
#const ID 3
data[0][FIRST_N] = "John"
data[0][LAST_N] = "Crichton"
data[0][ID] = 23325
data[1][FIRST_N] = "Dominar"
data[1][LAST_N] = "Rygel XVI"
data[1][ID] = 45646
for i = 0 to 1
println "Name: ",data[i][FIRST_N]," ",data[i][LAST_N]," ID:
",data[i][ID]
next i
When using arithmetic with multi-type array, only the parts that give correct answer will be processed. For
example multiplying array will multiply only its numerical parts and leave string parts untouched.
However when using functions then all data will revert to the output type the best way it can and be processed
that way.
On the above:
© 2020 Mediachance
98 MultiKeyboard Macros
newdata[] = VAL(data[])
will produce:
© 2020 Mediachance
Scripting 99
or press the Debug Mode toolbar button or Debug Mode in Menu Build
This will slightly change the look of the editor and add few buttons.
Break Points
The editor left marker bar can be used to add or remove Break Points with mouse. Break point on current line
can be also toggled with the button on the tab bar Toggle BP.
Break point is where the execution will stop and we will get to see the current variables at that point. Break point
will stop the line before it is being executed.
(If you don't set any break points the script will simply run its course)
Once we press that button the program will run but then stop at the break point. At that moment two additional
buttons will appear: Step and Continue
The same buttons will be enabled in the toolbar along with Terminate button
© 2020 Mediachance
100 MultiKeyboard Macros
Continue will run the script from the break point till it finish or find another break point. In the case of break point
in a loop we will stop next loop cycle.
The [Step] will go into a step debugging, that means the program will advance one command then stop again.
During the Debugging you can add or remove break points... but if you try to change something in the editor, the
debugging session will stop as the program will need to restart from beginning to update the changes you have
made.
Click on the variable in the list of variables, then put cursor in the edit box and change the value. Press Enter.
© 2020 Mediachance
Scripting 101
throw statement
Throws user defined error and terminates the script. This may be used for debugging parameters if they go out
of desired bounds
trace statement
Trace statement has similar syntax to println statement and it is used to display messages in the Output
Window. Unlike print or println trace command doesn't change the OUTPUT string
terminate
Terminate can be used to exit Script, but a bit more verbally than with end
While end is meant to be a quiet exit, the terminate will write to the output window the line where it terminates so
it can be used for debugging to quickly determine where a complex script ended.
© 2020 Mediachance
102 MultiKeyboard Macros
a = TestLibrary(1)
end
//********** FUNCTIONS START HERE ****************
function TestLibrary(a)
a = a*10
println "Hello From library, fParam * 10 = ",a
DisplayText("Hello")
return a
User Library is a whole script that you can run and debug. While its main body will be never called outside the
editing window (so you can and should freely use it to test the functions and even leave any code there), the
functions themselves can be accessed by any other script.
Make sure you test the user library functions well for various parameters to avoid errors.
Important:
During testing you have to call the functions you just wrote from inside the main body of User Library script
(where it says: use this part to test the functions ) otherwise the function will not be
tested and so you wouldn't even know if it runs well or not until you call it in other script.
You can call other Library Functions from inside the Library Functions - if you need to, although from speed
perspective, it is far better to put everything in one function, even if you may need to duplicate code.
When everything looks good close the User Library by clicking click Save (You can Save only when there are no
errors)
© 2020 Mediachance
Scripting 103
Then back in main script you can test the function you just created:
k = TestLibrary(5)
Hello From library, fParam * 10 = 10
If you read the paragraph about References 85 , you will know that the function as is written will also work with
arrays.
k[] = TestLibrary(A[])
Notes:
It is harder to debug User Library functions than normal functions (as normal functions are in the same code
listing as the rest of your program) so it is better to put only well working and well debugged functions into user
library to avoid un-necessary errors.
The #define can be used in Library functions as it the macros are substituted before run-time, however
#const can be used only if it is within each function limits (as it is evaluated on run-time)
© 2020 Mediachance
104 MultiKeyboard Macros
3.24 Macroblocks
Macro blocks are special subroutines inside the Macro steps window that allow for structuring the steps and also
various scripts in certain way from within the main script.
Everything after a Macro Block will be skipped normally, but can be accessible from inside script as a sort of
GOTO command using CallMacroBlock.
Note: There is a special macro Block called KEY_OFF block and it is described in next section.
To call Macro block from within the script use CallMacroBlock(string) such as:
sClip = GetClipboardText()
if sClip=="notepad" then
CallMacroBlock("notepad")
endif
The script will exit and the Macro block "notepad" will be called. The macro block will stop itself on the another
Macro Block object (browser)
Important:
To avoid infinite loops which may lock up your keyboard, Script can call only Macro Blocks that are bellow the
script. So a script within a macro block, cannot call macro block that is above itself
© 2020 Mediachance
Scripting 105
© 2020 Mediachance
106 MultiKeyboard Macros
Normally macros are triggered when you press a key but it doesn't matter when you release the key. This is
probably 99% situations you need.
However in special cases, you may want to control the function of key down and key up - that is pressing key
down will execute certain macro step and releasing the key will execute additional steps.
The most common way would be to use it with script 106 and HOLD RELEASE SendKeyStroke command.
As described in a Macro block section: Macro blocks are special subroutines inside the Macro steps window that
allow for structuring the steps.
While normal MACRO BLOCKS can be called from within script as a sort of external subroutines, the KEY_OFF
block which is a special MACRO BLOCK is used to dedicate part of the macro steps for Key Off (release key)
command.
The macro block function as a stop. The normal sequence of steps will be always executed on Key Press until a
Macro block then return. The sequences after KEY_OFF macro block will be executed when the key is released
There is also the issue with key repeat that you have to keep in mind. On windows the keyboard keys are set to
auto repeat when you hold them. That means also when you holding key the Normal key sequence will be
executed over and over untill you release the key then the Release sequence will be executed.
So when you holding a key for longer period of time and then release your keyboard does this:
KEY DOWN, (wait) KEY DOWN, KEY DOWN.... KEY UP
There isn't really any good example for normal macro sequence that would be suitable for this scenario except
when using script and HOLD, RELEASE commands in SendKeyStroke
Example:
For example in Photoshop when you are using Brush tool, holding ALT key will change the tool to eye dropper
(color pick) and when you release the alt it will go back to brush.
If we want to map this functionality to other external key by simply sending keystroke ALT, it would not work
because the macro will hold ALT then release it shortly afterward. If we hold the trigger, because of key repeat
this will continue in a loop where the brush will change to eyedropper then to brush then to eyedropper...
However with KEY_OFF block and scripts we can mimic this function.
© 2020 Mediachance
Scripting 107
We will need one script before KEY_OFF block and one script after.
SendKeyStroke("HOLD ALT")
DisplayText("ALT ON")
The Key OFF script after KEY_OFF macro block:
SendKeyStroke("RELEASE ALT")
DisplayText("ALT OFF")
This will do exactly what we expect the original ALT key in Photoshop to do. While the trigger is held the ALT will
be hold as well, when trigger key is released, the ALT will be also released.
The DisplayText is just for show.
Note: because of the key repeat the first script will be executed in the loop over and over while we are holding
the trigger key. It has no downside functionality if used with SendKeyStroke and HOLD. The ALT is held
regardless how many times you call the script. Of course if you try to put other functionality there, this may
backfire.
While you may think of using global parameters to make sure the first script is executed only once until the key
off script runs - but the suggestion would be simply don't. In fact while you are holding down key, the natural way
for windows is to repeat KEY PRESS over and over. It is unnatural for windows to have KEY press event, then
nothing and wait long time for KEY Release event - and some applications may be confused by this behavior.
Once the OFF (release) triggers are defined in the KEY_OFF Block they will use message Queue - that is even
if OFF (key release) event occurs while the Key press part of macro is still executing (for example some long
script on the key press or use of delays), the commands defined for the key KEY_OFF part will be added to
Queue and will be executed once the ON commands are finished. This way the OFF sequence will be always
triggered, but it may not be right away if long scripts are used.
© 2020 Mediachance
108 MultiKeyboard Macros
newstring = ""
// length of the string
k = Length(clipboard)
for i = 0 to k
© 2020 Mediachance
Scripting 109
© 2020 Mediachance
110 MultiKeyboard Macros
COPY 2 button
PASTE 2 button
if GLB_clipboard2!="" then
SendText(GLB_clipboard2)
endif
Similarly you can extend it to more than one alternative clipboards, or a FIFO clipboard (see next example)
© 2020 Mediachance
Scripting 111
GLB_FIFO[GLB_fifo_counter] = GetClipboardText()
DisplayText("Copy "+STR(GLB_fifo_counter))
GLB_fifo_counter = GLB_fifo_counter+1
GLB_fifo_counter=GLB_fifo_counter-1
© 2020 Mediachance
112 MultiKeyboard Macros
string = "<LastName>Holden</LastName>\
<FirstName>James</FirstName>\
<Company>Rocinante Consulting LLC</Company>\
<ManuelOrderPrice>0</ManuelOrderPrice>\
<ShippingVatPct>0</ShippingVatPct>\
<ProdId>11302-42-0</ProdId>\
<PurchaseItemKey><Key>826724</Key>\
</PurchaseItemKey>\
<ProdId>12342-23-1</ProdId>\
<PurchaseItemKey><Key>225664</Key>\
</PurchaseItemKey>"
//we can have multiple elements with the same ProdId tag
//but we don't know yet how many
//let's try a really big number of such elements to test
for k=0 to 100
// extract new element in each loop
// - see the k used as nSkip in Extract
sId = Extract(string,"<ProdId>","</ProdId>", k )
if (sId=="") then
// no more elements to extract
// exit loop
break
endif
// valid element, so add it to array
© 2020 Mediachance
Scripting 113
sProdId[k] = sId
next k
//this is number of elements found
nNumProducts = k
SetClipboardText(OUTPUT)
Output:
>Script Started
Found 2 ID's for James Holden
ID1: 11302-42-0
ID2: 12342-23-1
>Script Ended OK
© 2020 Mediachance
114 MultiKeyboard Macros
Script A: Encodes string in clipboard to BASE64, obfuscating it by encoding it multiple times in a loop
nDifficulty = 3
for i = 0 to nDifficulty
clipboard = BASE64(clipboard,ENCODE)
next i
SendText(clipboard)
Script B: Decodes string in clipboard from Base64. Determines number of times it has been encoded
nc = Length(clipboard)
if nc==0 then
DisplayText("No String in Clipboard")
terminate
endif
© 2020 Mediachance
Scripting 115
if (test=="") then
if i>0 then
// it failed this iteration, but we are > 0
// it means previous iteration have succeeded
SetClipboardText(clipboard)
DisplayText(clipboard)
break
else
// it failed to decode on first try
// must be garbage, not BASE64
DisplayText("Failed to decode, no Base64")
terminate
endif
endif
clipboard = test
next i
© 2020 Mediachance
116 MultiKeyboard Macros
This of course makes sense only if we have more than one action KEY defined, otherwise we don't need to
bother with modifier key, just define 2 keys
//Script on MODKEY
GLB_ModKeyTime = GetTickCount()
DisplayText("Mod Key")
//Script on ACTIONKEY
timeElapsed = TimeElapsed(GLB_ModKeyTime)
if timeElapsed>1000 then
// too long, we assume the modifier was not pressed or it was too
long time ago
goto NoModKey
endif
NoModKey:
DisplayText("No Mod Key Action")
© 2020 Mediachance
Scripting 117
3.26.7 Recursion
This is a classic example of calculating permutations of letters in a word.
It is using recursion and while such algorithms are not encouraged in script, we used it for testing purpose.
// RECURSION example
// The recursion depth is set at 10 for security reasons
// after which error would be issued
// so the maximum length for permutation in this example
// would be 9 letters, and that would be 362880 permutations
// 9 letters would take probably around 30 minutes anyway
// so don't ry it
str = "OSCAR"
// with 5 letters it is only 120 permutations
// 6 letters is 720 permutations etc....
end
g = Length(remaining)
if g == 0 then
count = count+1
println " Permutation: ",Format(count,3)," = ",candidate
endif
rl = Length(remaining)-1
for i = 0 to rl
newCandidate = candidate + GetCharAt(remaining,i)
newRemaining = Left(remaining,i) + Mid(remaining,i+1,0)
count = permutate (newCandidate, newRemaining,count)
next i
return count
© 2020 Mediachance
118 MultiKeyboard Macros
IV Keyboards
MultiKeyboard Macros work with any USB or Wireless keyboard that is HID compliant. However each of the
keyboard needs to be different model 121 . You cannot use two exact same keyboards.
HID compliant keyboards will type characters when plugged in without any special driver.
Pretty much any standard full size keyboard today is HID compliant and will work.
Most if not all numerical keyboards are HID compliant and will work.
Half (one hand) keyboards that have letters on them will likely work.
Basically anything that has printed normal letters on it should be HID compliant keyboard these days and will work.
Half keyboards became somehow popular recently and there is a quite a number of them on market. They look
as if you break a normal keyboard in half somewhere around letters T, G and B and keep only the left part. They
don't need any drivers and are really exactly what they look like - a keyboard with only +half the letters.
© 2020 Mediachance
Keyboards 119
These keyboards are a good candidates for MultiKeyboard macros due to their size. The above image is an
illustration - of the type of keyboard that came up when searching.
Also the familiar numerical USB keyboards that nobody seems to want are great for this purpose as well. You
will probably find a bucket full of them in a thrift store somewhere.
Not everything that has keyboard buttons is a keyboard. Gaming keypads may look like keyboards but they are
just a general USB devices with buttons and require a driver because only the manufacturer knows how they
work. If you plug in a keyboard and it does not type anything, then it isn't a keyboard.
If you press buttons while the monitor is on and nothing is displayed on the monitor HID string then this isn't a
keyboard, it is an USB device that needs its own driver and the driver may or may not turn it to a normal
keyboard.
in this case we are in luck, this is a HID compliant device and can be used.
Some keypads will install a virtual keyboard driver and that will then work as a HID device, so if the pad keys on
it are mapped to any keyboard keys, MultiKeyboard macro will see them as well.
That is the case with Logitech G13 for example, once the drivers are installed the keypad then pretends to be a
normal keyboard (all the G buttons will be mapped to letters and numbers - and in fact you can type with it in
notepad). However the driver software may change the mapping profile depending on which application is in
front. In any case game pads will come with their own shortcut or macro software.
If the manufacturer doesn't make drivers anymore then you are likely out of luck.
© 2020 Mediachance
120 MultiKeyboard Macros
Note on installing keyboard drivers. It is wise to quit MultiKeyboard Macros app before you install any keyboard
drivers and then start it again.
© 2020 Mediachance
Keyboards 121
V DIY solutions
I like to make my own keyboards as much as the next guy.
But honestly, it is a thing.
You can reuse the HID chip from any normal keyboard and wire some fancy big buttons to it, or program the
whole thing using tiny microprocessors such as ATmega32U4
A keyboard doesn't need to have push buttons. A keyboard can be also a dial using rotary controller.
In fact it is perfectly doable to do your own dial and use them with MKM and we have some projects on our web
page.
https://mediachance.com/multikeyboard/diy.html
VI Limitations
In order for the MKM to recognize multiple keyboards, they all need to be different models.
The reason for this is that while in theory USB chips should have different serial number for every item, in reality
this is not true and most of the keyboard these days have simply mass cloned USB chips - that is: every single
keyboard of the same model have also the same (or most likely bogus) serial number. So there is absolutely no
way for any software to determine which of two plugged exact same keyboard is which because they are in and
out exact clones of each other.
Using different models will ensure MKM will be able to clearly distinguish the keyboards and correctly assign the
macros to them.
© 2020 Mediachance
122 MultiKeyboard Macros
For example if you want to plug-in two numerical keyboards beside your primary keyboard: Both numerical
keyboards need to be each different model.
Some companies such as Logitech will have various internal revisions of the same keyboard model over time. In
that case MKM will actually work fine even if the keyboards have same model number - but of course it is
impossible to know the revisions from packaging. Well, if you have two same keyboards at home - you can just
try it.
The monitor will show you a HID string with the identification data of the keyboard and each of the plugged
keyboard needs to have different identification. Having different models will assure the HID strings are different.
© 2020 Mediachance
Limitations 123
This is of course a false positive. If such warning occurs you will need to put an exception to your anti-virus rules.
Again, there is no other way around it.
This application was written to use around our own office. It is digitally signed by digicert on the time of
deployment. We of course completely trust our own code because we don't use any third party libraries -
everything is done in house so there is no possibility of injecting some other code. Any virus or malware
warnings could be triggered because of the way this particular application has to work to do its job. It is our
experience that these warnings may come and go with different anti-virus releases back and forth and there is
usually nothing we can do about it.
Again, setting up exception if such warning occurs is the only reliable way to go.
At the time of writing this - none of the malware application we run trigger any warning on MultiKeyboard Macros.
Sending it to virustotal shows 0 of 69. Hopefully it will stay that way.
© 2020 Mediachance
124 MultiKeyboard Macros
© 2020 Mediachance