0% found this document useful (0 votes)
155 views126 pages

Multikeyboard PDF

Uploaded by

geraldi raditya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
155 views126 pages

Multikeyboard PDF

Uploaded by

geraldi raditya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 126

Contents I

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

Part III Scripting 26


1 Oscar Script ................................................................................................................................................. 26
2 Script basics ................................................................................................................................................ 28
3 if-then-else-endif ......................................................................................................................................... 34
4 for-to-next .................................................................................................................................................... 35
5 Goto and Gosub .......................................................................................................................................... 38
6 Print, Println ................................................................................................................................................ 40
7 Conditional operator ................................................................................................................................... 42
8 Functions ..................................................................................................................................................... 44
9 Type Conversion ......................................................................................................................................... 47
10 String Operators ......................................................................................................................................... 49
11 Clipboard and Key functions ..................................................................................................................... 53
12 Slider Function ............................................................................................................................................ 56
13 Math & Constants ....................................................................................................................................... 62
14 Time and Date ............................................................................................................................................. 65
15 Global Variables, Declaration .................................................................................................................... 66
16 Array Arithmetics ........................................................................................................................................ 68
17 Array Conditional Operator ........................................................................................................................ 75
18 Array Functions ........................................................................................................................................... 77
19 References to Array .................................................................................................................................... 85
20 Using Arrays in user functions .................................................................................................................. 92
21 Multidimensional Hybrid Arrays ................................................................................................................ 95
22 Debugging, trace ......................................................................................................................................... 99
23 User Library Functions ............................................................................................................................. 102

© 2020 Mediachance

I
II MultiKeyboard Macros

24 Macroblocks .............................................................................................................................................. 104


25 Key Off Block ............................................................................................................................................ 106
26 Script Examples ........................................................................................................................................ 108
Clipboard example ............................................................................................................................................................... 108
Secondary Clipboard ............................................................................................................................................................ 110
FIFO Clipboard ...................................................................................................................................................................... 111
XML Tags Extract ................................................................................................................................................................. 112
BASE64 example .................................................................................................................................................................. 114
Mod key Example .................................................................................................................................................................. 116
Recursion .............................................................................................................................................................................. 117

Part IV Keyboards 118


Part V DIY solutions 121
Part VI Limitations 121
Part VII Virus warnings etc... 123

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.

This is what the MultiKeyboard Macro application is about:


Making Windows understand multiple keyboards and define macro keys on any of them separately.
But it also work with just a single keyboard - you can redefine any key, for example get some good usage of
those extended numerical keys on the right that not many people use.

© 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)

New in version 2.0:


Full scripting language:
Every key can now run a script - or multiple scripts - or combination of any of the steps from previous version
and script.The scripting language can also work with clipboard data.

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

An instant text processor where only the sky is the limit.

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.

There could be only one 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.

Sending a shortkey/shortcut is the simplest method.

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.

For example I'll press * on my numerical extra keyboard.


This will get me to the actual definition of the Shortkey (shortcut) - that is what I want my * key to actually do.

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

Note: This assumes we are using external keyboard.

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.

If you want to re-assign numerical keys on your PRIMARY keyboard:

· Switch NUM LOCK to ON


· Assign key and uncheck Ignore Num Lock
· Keep Num Lock On for the macros to be active.

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

On the left is a list of Steps - and there is nothing yet.


To add a step, press Add Command

© 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

Open Web Page / Folder


Opens web page or a folder on local computer, NAS, network... depending on what you specify.

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

2.3 Activate Window


This will help to find an existing window and then bring it to front. Depending how that application is made, it may
not always work or may require consequently sending some keystrokes to set the proper focus to the area we
want to affect. Also each new version of windows puts more restrictions how an application can interact with the
ones it doesn't own.

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.

Also a third combined possibility: you can search by a Class.

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

Searching by Window Title


As an example on the right you have multikeyboard microsoft visual studio. If you enter the whole text you
will not find microsoft visual studio, because the whole window title is actually much longer and has been
abbreviated in the list box.
What you need to do is to enter only microsoft visual studio which is a string that will be common on all
instances.

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.

Searching By File Name is more exact:

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.

You may also use * instead of title or file name

*|MozillaWindowClass

will bring to front any window that has class MozillaWindowClass

Some other thoughts:


Don't expect application that was brought to front being ready to receive keystrokes. Some other things may be
in the focus (for example in firefox it would be the actual web page - so you can't send text to it) and it is hard to
determine what is selected by default.
It would be good to follow the Activate Window command with another such as appropriate key shortcut that
would make sure the application is ready to receive further commands.

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

2.4 Quick Macro Recorder


Quick Macros are recorded keyboard macros that are temporary.
You may question what that is, but if you are programmer you may be familiar with the concept. Sometimes you
need to make lot of repeating text operations on multiple lines. So you record a quick macro of what you need on
one line, then play it on the rest of the lines.
This is useful for editing tables, text fields etc...
The way you do a complex tasks is that you cleverly employ a clipboard and word selection in your application to
record such macro.

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.

Here is a recorded result what I was doing

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

2.5 Start Quick Macros


In order to start recording quick macro you need to be already in the editor where you perform these. So you
can't start by clicking some button on the MultiKeyboard interface because that will bring up the Multikeyboard
interface. The only proper way to start recording in application is by a trigger. And for that you need to first define
the trigger.

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.

For that there is Save QM to Key

This will save the recorded macro as series of steps in a normal Macro.

© 2020 Mediachance
Basics 21

2.6 Swap macro Set


This command will load any previously saved macro set - and it would result in a full macro set replacement
(swap).

Macro set is everything you see on the main interface - including keyboards and all macros associated to them.

To create Macro Set: Use Save on the Main interface.

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.

In our example we may go like this to define 3 switchable layouts:

Start with blank command list.

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

Macro set Save Load


You can save and load entire macro sets - all macros you defined

Start Minimized - the app starts in its minimized state in windows tray so you don't see the interface.

Run on Windows Start


Places the app shortcut to the Startup folder of current user so the MultiKeyboard Macros will be loaded when
users log in or computer boots.

Show Macro Name on Screen

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

In version 2.0 we added a scripting command... and that is a big deal!

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.

or menu Build - Run Script

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

3.2 Script basics


We have used various scripts in different Mediachance products, but this is by far our finest script yet!

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

/* These are comments too


======================
*/
Syntax
There is no special character to end a command line (unlike in C where there is ' ; ' )

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.

Core command set

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

print "Oscar script is alive"

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.

A variable will be auto-assigned a correct type first time it is used.

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:

HEX and BIN integer numbers


Binary literal numbers have prefix 0b, hexadecimal literal numbers have prefix 0x
nHex = 0xFF
nBin = 0b100000000

Escape characters in strings


As common in other languages \ character signifies escape characters, for example \t is tab \n is new line etc....

sEscapeWrong = "Files\text.txt"
Output: Files ext.txt

© 2020 Mediachance
30 MultiKeyboard Macros

In order to have backslash in literal string you need to use \\

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

Auto re-assigning of type


In some cases the script will automatically re-assign a type if there is a possible loss of data. (we will get warning
in Output window)

For example:

//we started with 'a' as integer


a = 23
b = 1.23
//script will auto reassign 'a' to FLOAT
a = a + b
c = INT(a)

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.

Multidimensional arrays are done the typical way

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

#const :Definition of Constant

To define constant use #const keyword on a new line.


Unlike assign operator with variables, there is no '=' .

#const IDENTIFIER expression


The #const will assign a value (integer,float or string) or the result of an expression to a constant during run-
time.
Because the constant is defined at runtime, all the parts of the expressions needs to evaluate before the #const
definition takes place
In general it is best suited for constants or indexes.

#const DEG_MULT 3.1415926/180


a = SIN(90*DEG_MULT)
You can't reassign value to a constant.

#define :Definition of Macro

You can create a macro with #define

#define IDENTIFIER macro


Script will substitute each occurrence of IDENTIFIER in the source with the macro string before it runs.
Because the substitution is done before run-time, the macro doesn't need to evaluate at definition, only where it
is actually used.

#define MY_PRINT println "Value of a: ", a


a = 10
MY_PRINT
#define is of course much harder to debug than ordinary code - because you don't see the substitution taken
place in your editor. It can have some unwanted effects if its name clashes with other names of functions or
variables.

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==0 then print a endif


The rule is simple: there has to be the same amount of endif than if. If it isn't, you have some logic error.
We made this rule, so It is much easier to find problems with nested if-then. Just count the ifs and endifs and
they must be the same.

if a==5 then
print "a is five"
else
print "a is definitely not five"
endif
Comparison operators:

== != <> < <= > >=


Note: that "is equal" is in Oscar Script similar to C equal: ==

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

!(a & b) // NAND - NOT (a AND b)

Else if - nested if.


We can follow else command with another if, which creates nested if-then command

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 counter = nStart to nEnd


....
next counter
It is important to note that on both sides it is inclusive. What you see in the for - to statement will be the numbers
the loop will go through, including those numbers.

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 counter = nStart to nEnd step nStep


....
next counter
Example

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.

for a = 1 to 256 step 1


print a,","
step = a //this changes the step of the loop inside the loop
next a

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:

for a = 1 to 256 step 1


print a,","
step = a

© 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 and Continue

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)

you can use either reasonably big number, or even INT_MAX

string = "one two three four five"

for a = 0 to INT_MAX

© 2020 Mediachance
38 MultiKeyboard Macros

token = Tokenize(string," ",a)

if (token=="") then
break
endif

array[a] = token

next a

arrayLength = a

print "We've got ",arrayLength, " items"

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

3.5 Goto and Gosub


goto label
Goto statement is used to branch from one part of the code into another that is marked with a label.

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)

//for every if there has to be endif!


if (test==0) then
gosub subroutine0
else
if (test==1) then
gosub subroutine1
else
gosub subroutine2
endif
endif

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

3.6 Print, Println


print command prints to Output Window.
println command is same as print but ends the command with new line escape characters ("\r\n")
Printing to Output window makes sense only during Script Editing. It does nothing during normal operation -
when called from within a key macro. However, the print has one more trick in its sleeve, called print to
OUTPUT.

Syntax:

print expression, expression,....


The expressions can be variables, strings, or in fact whole "expression"

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.

//clears OUTPUT in case we used print before this line


OUTPUT = ""
bookid = "0021313"
TAB = "\t"
QT = "\""
println "<?xml version=\"1.0\"?>"

© 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

3.7 Conditional operator


operand ? expressionYes : expressionNo
Conditional operator is a sort of ternary inline 'if' operator that can be used to evaluate two different expressions

if the operand is evaluated as TRUE (>0) then expressionYes is used


if the operand is evaluated as FALSE (==0) then expressionNo is used

For example:

b = a>5 ? a*2 : a/2

can be written using if then as

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.

a = 32 + (a>5 ? a*2 : a/2) * 4


It can be used with string variables as well:

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:

temp = ( a == "R") ? "red" : "green"


result = ( a == "B" )? "blue" : temp
we can write

result = ( a == "B" )? "blue" : ( a == "R") ? "red" : "green"


The exact equivalent of the above single line can be described by if-then as:

if a=="B" then result = "blue"


else
if a=="R" then result = "red"
else
result = "green"
endif

© 2020 Mediachance
Scripting 43

endif

Another example:

a = ""
type = (TYPE(a)==FLOAT) ? "float" : TYPE(a)==STRING ? "string":
"integer"

print "a is ", type

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

// We declare B in main section of script


B = "Test"
MyFunction()
println B
end

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

3.9 Type Conversion


There are few ways how to convert between the common types.

Explicit type conversion for integer and float


Normally we can let the script to worry about figuring out the best way to assign types, but in some cases we
may want to explicitly convert the type (for example rounding up numbers)

Syntax Explanation Example

float = FLT(int) Explicitly converts integer number into a intNum = 125


float. floatNum = FLT(intNum)

int = INT(float) Explicitly converts float number into an a = 4.25


integer b = INT(a)

float 4.25 will convert into integer 4

String type conversion


Here it is a little more interesting. There are few ways how you can convert string to number and numbers to
string.

Syntax Explanation Example

string = CHAR(ascii_number) Converts ASCII number into a string


character

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"

*STR also works on integer or float


arrays

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

* VAL also works on string arrays

string = Format(int, Converts integer to string and fill the rest


minWidth) with leading 0 to have at least minWidth
number of characters

*Format also works on integer arrays

Type

type = TYPE(variable) Test the type of a variable. If it was never a = ""


assigned it returns 0 (can be tested if TYPE(a)==STRING then
against FALSE) print "it is string"
endif

© 2020 Mediachance
48 MultiKeyboard Macros

Otherwise it returns INTEGER, FLOAT,


STRING
with respective values 1,2 or 3
It will also return REFERENCE (-1) in
case the type is a reference to array.

© 2020 Mediachance
Scripting 49

3.10 String Operators


Oscar Script has very large and comprehensible set of string tools. It is especially good with using text
clippboard where the script can process strings in clipboard in various ways.

String Syntax:
string = "this is string"
Be aware of escape characters that are defined by backslash: \

Syntax Explanation Example

\" quotation mark inside string, also \042 'octal" value can string = "this is \"quotation\" mark"
be used or
string = "this is \042quotation\042 mark"

\r\n New line inside string "line 1\r\nline 2"

\\ Backslash, also \134 'octal" value can be used string = "MyFolder\\myfile.txt"

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

_R Raw string syntax


String following immediately the _R will be considered str = "C:\file\MyFile.txt"
RAW string and no escape sequence will be recognized. str2 = _R"C:\file\MyFile.txt"
If you put for example _R"\" there will be literally \
written in the string - not a escape sequence ".

This is especially useful for RegEx functions as trying to


write regex with escape sequences in place is just recipe
for disaster.

As such when using _R prefix, it is impossible to write "


character. You would either need to resort back to
standard string or add the character to the string with +
operator.

Syntax Explanation Example


string = Left(string, nNum) returns nNum characters from left string = Left("ABCDEFGH", 5)
Output: ABCDE
string = Right(string, nNum) returns nNum characters from right string = Right("ABCDEFGH", 5)
Output: DEFGH
string = Mid(string, nPos, returns nCount characters starting at string = Mid("ABCDEFGH", 2,
nCount) nPos. nCount of 0 means 'till the end' 3)
Output: CDE
string = Mid("ABCDEFGH", 3,0)

© 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

string = MakeUpper(string) returns uppercase of the string string = MakeUpper("My


String")
Output: MY STRING
string = MakeLower(string) returns lowercase of the string string = MakeLower("My
String")
Output: my string
char = returns a character (string) from string sChar = GetCharAt("ABCDEF",3)
GetCharAt(string,nPos) at a position nPos (zero based) Output: D
string = sets 'char' at position nPos (zero string =
SetCharAt(string,char,nPos) based) and returns the string SetCharAt("ABCDEF","d",3))
Output: ABCdEF
int = Equals(string,string) No Case compare, returns 1 if two res = Equals("Hello
strings say the same, otherwise 0 World","HELLO world")
Output: 1

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

string = FindNumbers(string) returns a string with extracted first str =


occurrence of numbers from left FindNumbers("File0019.T222")
Output: 0019
string = returns a string with extracted first str =
ReverseFindNumbers(string) occurrence of numbers from right ReverseFindNumbers
("File009T123En")
Output: 123
string = Replace(string, Replace all sOld substrings with sNew string = "Mon Tue Wed Thu
sOld, sNew) inside string Fri"
string = Replace(string, " ",
",")
Output: Mon,Tue,Wed,Thu,Fri
string = No Case Sensitive version of
ReplaceNoCase(string, sOld, Replace. Will replace sOld, regardless
sNew) of the case.
string = Insert(string, Inserts sInsert to string at nPos str = Insert("ABCFG",3,"de")
nPos, sInsert) Output: ABCdeFG
string = Delete(string, Deletes nCount characters from string str = Delete("ABCDEFG",2,3)
nPos, nCount) starting at nPos (zero based) Output: ABFG
If nPos is -1 then it deletes nCount of str = Delete("ABCDEFG",-1,1)
strings from the back of the string Output: ABCDEF

© 2020 Mediachance
Scripting 51

string = Reverse(string) returns a string that is a reverse of the str = Reverse("ABCDEFG")


original string Output: GFEDCBA

int = IsNumeric(string) returns 1 if string is numeric only str = IsNumeric("AB192") - NO


(integer), otherwise 0. Tests only for str = IsNumeric("1235") - YES
integer numbers
string = returns next token in a string str = Tokenize("Apple,
Tokenize(string,delimiter,nS separated by delimiter, nSkip Banana, Car",",",1)
kip) determines how many of such found Output: Banana
tokens to skip before returning the
token - essentially it is a zero based str = Tokenize("Apple
occurrence of the token; ,Banana, Car",",",2)
0 will find first token, 1 will skip first Output: Car
and return second....
str[] = Tokenize("Apple
If nSkip is REFERENCE (-1) then the ,Banana, Car",",",REFERENCE)
function will return a reference to a Output array: ["Apple
string array containing all the tokens. ","Banana"," Car"]

string = Extract strings between sStartTag and string =


Extract(string,sStartTag,sEn sEndTag strings. Good for parsing "<A>oscar</A><B>Banana</B>,<A
dTag,nSkip) html or xml strings or other structured >Apple</A>"
text that have tags. str =
nSkip determines how many of such Extract(string,"<A>"
strings to skip before returning one, if ,"</A>",1)
set to 0 then it returns the first of such
string. Outupt: Apple
if sStartTag = "" it returns string from
beginning to the sEnd;
if sEndTag ="" it returns string from
sStart till the end
The nSkip is ignored if either
sStartTag or sEndTag is ""
The operation will work even if start
Tag and end tags are the same
int = Saves string to FileName in bOK = SaveString("Test
SaveString(string,sFilename) Documents: /My String","filename.txt")
Document/Multikeyboard/Files/
returns 0 if failed, 1 if OK
string = Loads string from File in string =
LoadString(sFilename) Documents: /My LoadString("filename.txt")
Document/Multikeyboard/Files/,
return the loaded string or "" if failed
string = encode/decode string using BASE64, output =
BASE64(string,ENCODE|DECODE) ENCODE =1, DECODE = 0 BASE64
("VGhpcyBpcyBhIHRlc3Q="
When DECODE is used, the string will ,DECODE)
return "" if non BASE64 characters
are found. Output: This is a test

int = Returns 1 if string matches regular regex = _R"^[\w-\.]+@([\w-]+


RegexMatch(string,regex) expression defined in regex \.)+[\w-]{2,4}$"
otherwise 0 int =
RegexMatch
It can be used to test if string matches ("[email protected]",regex)
certain conditions. See some useful Outupt: 1
Regex strings
test = "UPPERCASE LETTERS
123"

© 2020 Mediachance
52 MultiKeyboard Macros

Note: use the _R raw string prefix int = RegexMatch(test,_R"[A-


before the string. This will not parse Z0-9\s]+")
the string for escape sequences and
take it exactly as it is written. Outupt: 1

string[] = RegexSearch is described in array


RegexSearch(string,regex) Functions 77 as it always returns
array

Some useful RegEx strings for RegexMatch

RegexMatch Regex pattern


is uppercase and numbers _R"[A-Z0-9\s]+"
is lowercase and numbers _R"[a-z0-9\s]+"
is single word only (no numbers) _R"^[A-Za-z]+$"
is integer or float number _R"^[-+]?[0-9]*\.?[0-9]+$"
is float number (will not match integer) _R"^[-+]?[0-9]+\.([0-9]+\b)?|\.[0-9]+$"
is integer number (will not match float) _R"^[-+]?\d+$"

Tokenize example:

date = GetDate()

//date is in format MM/DD/YYYY


//extract parts with Tokenize
//into strings
month = Tokenize(date,"/",0)
day = Tokenize(date,"/",1)
year = Tokenize(date,"/",2)

day_as_number = VAL(day)
month_number = VAL(month)

m_str = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC"

currentmonth = Tokenize(m_str," ", month_number-1)

print "Today is: ", day_as_number," ",currentmonth

© 2020 Mediachance
Scripting 53

3.11 Clipboard and Key functions

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.

Syntax Explanation Example

clipboard = Returns a text in Clipboard


GetClipboardText()

SetClipboardText(string) Sends text in string to clipboard

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

SendKeyStroke works on hardware keys


not characters. Not all character could be
represented by hardware key as some SendKeyStroke("SHIFT 0xde")
keys can type different character under
different locale/country. Hence
SendKeyStroke will not accept every
character as a parameter.

SendText(string) Send (type) text to keyboard buffer. The SendText("This is a text")


text will be typed in active window as if
typed by keyboard. The text is
represented as characters (not hardware
keys) which is the difference between
SendText and SendKeystroke. Where
SendKeyStroke is for single key
combination, SendText is for typing entire
texts.

CallMacroBlock(string) Calls Named Macro Block outside the CallMacroBlock("finish"))


Script (see Macroblocks 104 )

DisplayText(string) Display overlay text on bottom part of the DisplayText("This is overlay")


screen

© 2020 Mediachance
54 MultiKeyboard Macros

If Show Macro Name on Screen is used, then


the macro Name will flash quickly first before the
DisplayText

To disable showing the Macro Name, put a .


(dot) in front of the Macro key name.

Saving and loading variable tables.


Variables can be saved and also loaded back. The file format is a readable/editable XML file. This is a simple
way how to save some data or settings that can be recalled later.

Syntax Explanation Example

SaveVarTable(file) Save current local and global variables into SaveVarTable("test.xml")


a XML file

LoadVarTable(file) Loads local and global variables from XML LoadVarTable("test.xml")


file

Format of the file:


<?xml version="1.0"?>
<variables>
<global>
</global>
<local>
<var name="someInteger">
<type>INTEGER</type>
<value>46</value>
</var>
<var name="someFloat">
<type>FLOAT</type>
<value>123.456001</value>
</var>
<var name="someString">
<type>STRING</type>
<value>This is a string&#13;&#10;this is a new
line</value>
</var>
</local>
</variables>

Loading variable table in script


LoadVarTable("test.xml")

© 2020 Mediachance
Scripting 55

© 2020 Mediachance
56 MultiKeyboard Macros

3.12 Slider Function


Virtual slider function allows you to manipulate sliders, buttons, toolbar buttons or other on-screen controls on
host applications.

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.

Syntax Explanation Example

nSliderID = Define horizontal slider on the screen. See slider helper


DefineSlider(nX,nY,nW,nH) The function returns the ID of the defined
slider that you use in the SetSliderPos

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

SetSliderPos(nSliderID, fPos) "click" on the slider at fPos (0...100) SetSliderPos(nSliderID, 83)


percentage.

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.

1. Maximize the host application (for example Photoshop)


2. Set it so the slider you want to control is always visible - for example Brush Size
3. Click Capture Slider from screen
4. Draw rectangle around the slider - draw a bigger rectangle than the slider as you can crop to exact
dimensions in the next step

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

You can also move the virtual slider handle

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.

Horizontal and Vertical sliders


Sliders can be horizontal or vertical. They differ in the DefineSlider vs DefineSliderY, otherwise everything is the
same.

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.

To use the 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.

Using it for buttons, row of buttons etc...

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.

Brush Plus key

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:

GLB_BrushSize = GLB_BrushSize + 1+(GLB_BrushSize/10.0)


And the same line in the reverse the (Brush Minus) script:

© 2020 Mediachance
Scripting 61

GLB_BrushSize = GLB_BrushSize - (1+(GLB_BrushSize/10.0))


The result will be that the brush increases very slowly on the left side of the slider and progressively faster
towards right side which will work much better.

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.

// position of the brushes as we determined from Slider helper


nPos[] = {10,30,50,70,90}

GLB_BrushSel = GLB_BrushSel+1;

// when at the end roll again from 0


if GLB_BrushSel > 4 then
GLB_BrushSel = 0
endif

nBrush = DefineSlider(1867,265,158,58)

SetSliderPos(nBrush,nPos[GLB_BrushSel])

© 2020 Mediachance
62 MultiKeyboard Macros

3.13 Math & Constants

Syntax Explanation Example

nMin = MIN(nNum, nNum) finds minimum and maximum of two fNum = 100.0
nMax = MAX(nNum, nNum) numbers fMin = MIN(255.0, fNum)

nrand = RND(nMax) Generates random number between nMin nrand = RND(10)


nrand = RND(nMin, nMax) (or 0 if nMin is omitted) and less than
nMax
nrand = RND(-10,10)
nMin>= nrand < nMax

(nMax value will not be generated)

nAbsoluteVal = ABS(nNumber) returns absolute value of number nAbs = ABS(-3)

fRes = COS(nFloat) Sine and cosine


fRes = SIN(nFloat)

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

string = dec2hex(nNumber) Converts integer number to hex in string. string = dec2hex(2000)


There will be no prefix 7d0

Syntax Explanation Example

INT_MAX maximum positive number integer can a = INT_MAX


have. After this value the integer sign will b = a+1
flip.

SETTIMEOUT_MS Sets a safety timeout in milliseconds. SETTIMEOUT_MS = 3000


After that time the script will simply abort
to avoid infinite loops sets timeout to 3 seconds

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")

step can be used within a loop to assign a new for i = 1 to 256


step of the for -next loop print i , ","
step = step*2
next i

1,3,7,15,31,63,127,255,

© 2020 Mediachance
Scripting 63

ENCODE Used for Base64 49 command. Encode output =


DECODE is value 1, Decode value 0 BASE64
("VGhpcyBpcyBhIHRlc3Q=",DECODE)

true Boolean helper constants true is 1, false a = 12 > 7


false is 0 b = a==true? "YES": "NO"
TRUE
FALSE
nil is value 0
nil
NULL NULL is empty string

M_PI 3.141593 a = SIN (M_PI)

INTEGER, FLOAT, STRING, Constants that are returned by if TYPE(a)==STRING then


REFERENCE TYPE(variable)command. print "'a' it is string"
They have integer value of 1,2 endif
and 3
REFERENCE has value of -1

Boolean operators & | !


Apart to the obvious <,>, != operators we can use AND, OR and NOT
When these operators are used on values in range 0-1 they are boolean operators

AND OR NOT
BOOL1 & BOOL2 BOOL1 | BOOL2 !BOOL

AND operator OR operator NOT operator


0 & 0 = 0 0 | 0 = 0 !0 = 1
1 & 0 = 0 1 | 0 = 1 !1 = 1
0 & 1 = 0 0 | 1 = 1
1 & 1 = 1 1 | 1 = 1

You can derive other logic

NAND = !(a & b)


XOR = (!(a & b)) & (!(!a & !b))
or much simpler using != (is not equal)
XOR = (a!=b)
Bitwise operators & | ~
Using the operators on integer numbers will compare each bits of the number and give us a result that will be a
different number

Bitwise AND Bitwise OR Bitwise NOT

INTEGER1 & INTEGER2 INTEGER1 | INTEGER2 ~INTEGER

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

Left and Right Shift

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

Set bit Clear bit Test bit

a = 0b01111 //15 a = 0b11111 //31 // checking bit


// we want to set bit 5 // we want to clear bit 3 bit = 5
// bit 5 is 10000 // bit 3 is 00100 // Checking bit formula
bit = 5 bit = 3 b = ( a >> (bit-1)) & 1
// Set bit formula // Clear bit formula
a = a | (1 << (bit-1)) a = a & ~(1 << (bit -1)) print "Number: ",a," Bit ",bit,
// result is 11111 = 31 // result is 11011 = 27 " is ",b

Example 2
Convert decimal number to binary using bitshift:

// input number
a = -30000

print "Dec: ",a," = Bin: "


strbin = ""
for i = 32 to 1 step -1
b = ( a >> (i-1)) & 1
strbin = strbin + STR(b)
next i

println strbin

Script Started
Dec: -30000 = Bin: 11111111111111111000101011010000
Script Ended OK

© 2020 Mediachance
Scripting 65

3.14 Time and Date

Syntax Explanation Example

string = GetTime() Get time in HH:MM format

string = GetDate() Get date in MM/DD/YYYY format

integer = GetTickCount() Number of milliseconds since the app


started. Can be used for timing.

nMSelapsed = returns number of elapsed milliseconds startTime = GetTickCount()


TimeElapsed(nTickCount) between now and nTickCount. // longer operation
It is basically now-startTime but with diff2 = TimeElapsed(startTime)
various checking for integer roll println "TimeElapsed: ",diff2,"ms"

Example:

// date is in format MM/DD/YYYY


date = GetDate()

//extract parts with Tokenize


//into strings
month = Tokenize(date,"/",0)
day = Tokenize(date,"/",1)
year = Tokenize(date,"/",2)

day_as_number = VAL(day)
month_as_number = VAL(month)

© 2020 Mediachance
66 MultiKeyboard Macros

3.15 Global Variables, Declaration


Global variables
Normal variables are local - they exist only within the script or within a function. In order for scripts to
communicate with each other or remember values, some variables could be set global.
Any variable that you want to be global needs to have prefix GLB

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.

Global variable self destruction


Local variables stop existing when the script finish, but not global variables. Since you may create a bunch of
global variables during testing, It would be confusing if unused ones will linger still there.
Similarly if you only want to use global variable to communicate across different functions in single script, you
would probably want to destroy the global variable at the end of the script.

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

...it is assumed numerical zero. but we want a global string!

This can be solved by optional variable declaration.

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 is done with declare as keyword wit syntax:

declare <variable> as STRING INTEGER FLOAT


Example:

declare GLB_string as STRING

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

3.16 Array Arithmetics


What makes Oscar Script great for arrays is that we can do arithmetic operations with arrays same way as with
any other variables.

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

Create Array of certain size


Use the ARRAY command to create or fill Array. See more in Functions 77 .

A[] = ARRAY(0,5, 100)


The parameters are nMin, nMax and fill. The above will create array from 0 to 5 inclusive and fill it up with
numerical 100

Fill Array with value


Because assigning ARRAY can only add arrays and never remove them, you can use it to fill existing array with
a numbers or strings. You can in fact fill only a certain part by using nMin and nMax smaller than Array bounds.

A[] = ARRAY(0,10, 0)
A[] = ARRAY(3,7, 100)

Implicit Array initialization


An array can be initialized implicitly using this syntax:

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

The only arithmetic operation that woks with string is +

A[0] = "one"
A[1] = "two"
A[2] = "three"

B[] = "I say " +A[]

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)

Boolean operator for numbers: == != < > <= >= | &


Boolean operator for strings: == != < > <= >=

A[0] = "one"
A[1] = "two"
A[2] = "three"

B[0] = "four"
B[1] = "two"
B[2] = "five"

© 2020 Mediachance
Scripting 71

C[] = A[] == B[]

With numbers:

A[] = RND(ARRAY(0,5,0),10)
B[] = RND(ARRAY(0,5,0),10)

C[] = A[] >= B[]


D[] = A[] < B[]
E[] = C[] | D[]

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)

//This is correct Boolean expression because each "is larger"


produces only BOOLEAN results
correct_bool[] = (A[] > B[]) & (B[] > 5)
//This would be incorrect as BOOLEAN because if B[] has values > 1
//the result of bitwise & and integer number value would be vastly
different
//than what we expect
wrong_bool[] = (A[] > B[]) & B[] > 5

Additionally you can use ! (NOT) operator in front of boolean expression

a[0] = 1
a[1] = 0
a[2] = 1

c[] = !a[]

© 2020 Mediachance
72 MultiKeyboard Macros

Bitwise oerators & | ~

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.

For clarity we used arrays with only single element.

Bitwise negation using ~:


a[0] = 103 // binary: 0000000001100111
a[1] = 2024 // binary: 0000011111101000
b[] = ~a[]
// binary: 1111111110011000 b[0] = -104
// binary: 1111100000010111 b[1] = -2025

Bitwise OR and AND


a[0] = 42 //00101010
a[1] = 142 //10001110
b[0] = 21 //00010101
b[1] = 121 //01111001
c[] = a[] | b[]
d[] = a[] & b[]

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

b[] = a[] << 2

Arrays in if-then condition

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

Unsupported Operations inside arrays

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

b[] = "green "+ a[]

© 2020 Mediachance
74 MultiKeyboard Macros

© 2020 Mediachance
Scripting 75

3.17 Array Conditional Operator


ArrayOperand[] ? ArrayExpressionYes[] : ArrayExpressionNo[]
We already had conditional operator with normal variables, but now it is time for arrays

for every item in ArrayOperand:


if the operand is evaluated as TRUE (>0) then the result of expressionYes for that item is used
if the operand is evaluated as FALSE (==0) then the result of expressionNo for that item is used

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

result[] = condition[] ? a[] : b[]

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"

result[] = Length(b[])>Length(a[]) ? b[] : a[]

© 2020 Mediachance
76 MultiKeyboard Macros

In this example the result will have the longest strings from each array parts.

It is important to distinguish between

Operand ? ArrayYes[] : ArrayNo[]


and

Operand[] ? ArrayYes[] : ArrayNo[]


The first will return the whole ArrayYES or the ArrayNO depending on the normal number Operand
the second will process each individual members in the array depending on the members of the operand array.
The result may be a mixture of members from ArrayYES and ArrayNO

It is easy to make this mistake especially if you try to cram everything on one line like I did.

Operand ? ArrayYes[] : ArrayNo[]


condition = RND(0,2)
result[] = condition ? b[] : a[]
This returned either a[] or b[] array depending on the randomized number. The condition is a normal value.

Operand[] ? ArrayYes[] : ArrayNo[]


condition[] = RND(ARRAY(0,2,0),2)
result[] = condition[] ? b[] : a[]
This processed each member of the array separately according to the condition array and returned a mixture of
a[] and b[]

© 2020 Mediachance
Scripting 77

3.18 Array Functions


In Oscar Script nearly all functions work on arrays directly.
So instead of looping over the array and calling a function on its data we can process it all at once using the
array [] syntax.

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

for i = 0 to 2 format_str[] = Format(integer[],4)


format_str[i] =
Format(integer[i],4)
next i

Basic Array helper functions

Syntax Explanation Example


array[] = creates an array that starts from index array[] = ARRAY(0,10,"")
ARRAY(nMin,nMax,fill) nMin and ends at index nMax
inclusive
or 'fill' is a value the array will be filled
with. It allows you to create 2500
array[] = items max at one time. two dimensional array
ARRAY(nMin,nMax,fill[]) Fill can be also array fill[], in which
case the returned array would be B[] = ARRAY(0,6, ARRAY(0,2,0))
multidimensional.

Be aware that this function will return


error if more than 2500 item are
created regardless of dimensions.
This is a safety precaution.

ARRAY doesn't destroy previous


existing arrays only adds to them.

A[] = ARRAY(0,5, 0)
A[] = ARRAY(6,10, 0)

will ultimately create A[ ] from 0 to 10


nMin = FIRST(array[]) returns first and last index of an array. m_str = "JAN FEB MAR APR MAY
nMax = LAST(array[]) Assumes the array is sequential. JUN JUL AUG SEP OCT NOV DEC"

Error handling: array[] = Tokenize(m_str," ",


If array has not yet been initialized or REFERENCE)

© 2020 Mediachance
78 MultiKeyboard Macros

has no members, the return of FIRST


will be 0 and return of LAST will be -1 for i = FIRST(array[]) to
LAST(array[])
nMin = FIRST(array) println array[i]
nMax = LAST(array) next i

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.

a[] = FLT(RND(ARRAY(0,9,500),1000)) /1000.0


This will create an array a[] with 10 items [0...9] and fill it with random float number 0.5-1.0

Syntax Explanation Example

float[] = FLT(int[]) Explicitly converts integer array into a float value[0] = 1


array value[2] = 2
value[3] = 3

float[] = FLT(value[])
Output:
[1.000000,2.000000,3.000000]

int[] = INT(float[]) Explicitly converts float array into an value[0] = 1.12


integer array value[2] = 2.21
value[3] = 3.34

int[] = INT(value[])
Output: [1,2,3]

string[] = STR(number[]) Converts number (float or integer) array value[0] = 1


into a string array. value[2] = 2
value[3] = 3
strArray[] = STR(value[])

We can nest the fuctions to force conversion


to float in one line:
strArray[] = STR(FLT(value[]))

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]

string[] = CHAR(ascii_number[]) Converts ASCII number into a string


character

ascii_number[] = Converts string character into an ASCII string[0] = "interdum"


ASC(string_character[]) number. In case if the string has more string[1] = "tempus"
characters, only the first one will be taken string[2] = "consequat"
into account
ascii[] =
ASC(GetCharAt(string[],0))

Output: [105,116,99]

© 2020 Mediachance
Scripting 79

string[] = Format(int[], Converts integer array to string array while value[0] = 1


minWidth) filling leading 0 to have at least minWidth value[2] = 2
number of characters value[3] = 3
str[] = Format(value[],3)

Output: ["001","002","003"]

String and Numerical Functions


In many functions,more than one parameter can be an array.

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.

For example function written here:

string[] = Left(string[], nNum~[])


means it will use first parameter as an array and the second can be optionally a number or an array of numbers.

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

string[0] = "interdum" string[0] = "interdum"


string[1] = "tempus" string[1] = "tempus"
string[2] = "consequat" string[2] = "consequat"

strRes[] = Left(string[],4) left[0] = 1


left[1] = 2
left[2] = 3

strRes[] = Left(string[],left[])

Syntax Explanation Example

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

nrand[] = RND(nMin[],nMax~[]) Generates random number between the nMin = 0


values stored in nMin[..] array and less nMax = 100
than value of nMax or values inside nMax[.. random[] = ARRAY(0,10,nMin)
] array. random[] = RND(random[],nMax)

range is:

nMin>= nrand < nMax

(nMax value will not be generated)

nAbsoluteVal[] = ABS(nNumber[]) returns absolute value of number array[] = RND(ARRAY(0,10,-


100),100)
abs[] = ABS(array[])

fRes[] = COS(nFloat[]) Sine and cosine on array


fRes[] = SIN(nFloat[])

Syntax Explanation Example

string[] = Left(string[], returns nNum characters from left string[0] = "interdum"


nNum~[]) string[1] = "tempus"
nNum can be just integer, or it can be an string[2] = "consequat"
array itself
strRes[] = Left(string[],4)

Output: ["inte","temp","cons"]

string[] = Right(string[], returns nNum characters from right strRes[] = Right(string[],4)


nNum~[]) nNum can be just integer, or it can be an
array itself Output: ["rdum","mpus","quat"]

string[] = Mid(string[], returns nCount characters starting at nPos. string[0] = "interdum"


nPos~[], nCount~[]) nCount of 0 means 'till the end' string[1] = "tempus"
string[2] = "consequat"
nPos and nCount can optionally be arrays as
nC[0] = 1
well. nC[1] = 2
nC[2] = 3

strRes[] = Mid(string[],4,nC[])

Output:["r","us","equ"]

int[] = Length(string[]) returns string length string[0] = "interdum"


string[1] = "tempus"
string[2] = "consequat"

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[])

string[] = MakeUpper(string[]) returns uppercase of the string string_o[] =


MakeUpper(string[])

string[] = MakeLower(string[]) returns lowercase of the string

© 2020 Mediachance
Scripting 81

char[] = returns a character (string) from string at a


GetCharAt(string[],nPos~[]) position nPos (zero based)

str[] = sets 'char' at position nPos (zero based) and


SetCharAt returns the string
(string[],char~[],nPos~[])

int[] = No Case compare, returns integer array with


Equals(string[],string~[]) values: 1 if two string pairs are the same,
otherwise 0

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]

int[] = ReverseFind(string[], returns int position of substring inside the


substring~[]) string but searched from back

int[] = returns int position of first character that


FindOneOf(string[],charSet) matches any character in charSet, position is
zero based

string[] = returns a string with extracted first occurrence see example in Replace
FindNumbers(string[]) of numbers from left

string[] = returns a string array with extracted first


ReverseFindNumbers(string[]) occurrence of numbers from right from each
string

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"]

string[] = No Case Sensitive version of Replace. Will


ReplaceNoCase(string[], replace sOld, regardless of the case.
sOld~[], sNew~[])

string[] = Insert(string[], Inserts sInsert to each item string at nPos


nPos~[], sInsert~[])

string[] = Delete(string[], Deletes nCount characters from string


nPos~[], nCount~[]) starting at nPos (zero based)

string[] = Reverse(string[]) returns an array of strings where each item is


a reverse of the original string

int[] = IsNumeric(string[]) returns array, 1 if string is numeric only str[1] = "12345"


(integer), otherwise 0. Tests only for integer str[2] ="hello"
numbers str[3] =" hello 123"

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

Array is returned when REFERENCE (-1) is


used in place of nSkip parameter monthArr[] = Tokenize(m_str,"
", REFERENCE)
You can use FIRST(str[]) and
LAST(str[]) to get bounds of the returned
array.
Using Tokenize and VAL to quickly create
an array of integers
string = "1,23,45,32,56,78,45"
value[] =
VAL
(Tokenize(string,","
,REFERENCE))

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

nSkip determines how many of found tags to Output:


skip before returning one, if set to 0 then it this is first
returns the first occurrence . this is second

if sStartTag = "" it returns string from


beginning to the sEnd;
if sEndTag ="" it returns string from sStart till
the end

The nSkip is ignored if either sStartTag or


sEndTag is ""
The operation will work even if start Tag and
end tags are the same

You can use FIRST(str[]) and


LAST(str[]) to get bounds of the returned
array.

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.

There are few more examples in the String


Functions 49

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

If no match will be found there will be only


one member of string array with empty string for i = FIRST(result[]) to
at 0. LAST(result[])
println result[i]
string[0] = "" next i

Output:
You can use FIRST(str[]) and Saturday
LAST(str[]) to get bounds of the returned Sunday
array. Monday

If correct syntax is used, this can quickly


search through a string and extract the
corresponding matches saving you writing a
lot of code. The syntax is rather complex but
there are numerous sites with examples.
Beware that regex for Match and Search may
differ!

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.

Extracts all e-mail addresses from text

string = "My email is [email protected] and other is


[email protected]"
regex = _R"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\b"
result[] = RegexSearch(string,regex)
print result[]
["[email protected]","[email protected]"]
Extracts text between <TAG> and </TAG>

string = "This is test <TAG>and this text is</TAG> between


<TAG>tags</TAG>"
regex = _R"<TAG\b[^>]*>(.*?)</TAG>"
result[] = RegexSearch(string,regex)
print result[]

["<TAG>and this text is</TAG>","<TAG>tags</TAG>"]


Extracts all numbers from text

string = "Can it find 123.4 and 234 or it cant?"


regex = _R"[-+]?([0-9]*\.[0-9]+|[0-9]+)"
result[] = RegexSearch(string,regex)
print result[]
["123.4","234"]
Extract float numbers but not integers

string = " 123 , 990 and 123.5 or -34.6"


regex = _R"[-+]?(\b[0-9]+\.([0-9]+\b)?|\.[0-9]+\b)"

© 2020 Mediachance
84 MultiKeyboard Macros

result[] = RegexSearch(string,regex)
print result[]
["123.5","-34.6"]

Extract hexadecimal numbers format

string = " hex 0xFFAA number and 0xAA3FB2 number 123"


regex = _R"\b0[xX][0-9a-fA-F]+\b"
result[] = RegexSearch(string,regex)
print result[]
["0xFFAA","0xAA3FB2"]
Extract uppercase letters

string = "Give Me ONLY uppercase leTTers"


regex = _R"[A-Z]+"
result[] = RegexSearch(string,regex)
println "Any Uppercase: ", result[]

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[])

The newdata2 has now strings instead of integers.

© 2020 Mediachance
Scripting 85

3.19 References to Array


This is a more technical/advanced topic.
References are in the Oscar Script for some advanced tricks but if you feel overwhelmed you don't have to use
References at all or even think about them and skip this entire chapter.

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:

A[] = A[] * B[] + 45


or

A[] = ABS(A[] -100)


That's in a nutshell all you need to know about arrays if you don't want to know anything about references.

But if you want to know more about references, go on, read the rest of this chapter:

Ok, so this is an array:

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.

See the example:

array[0] = 1
array[1] = 2
array[2] = 3

© 2020 Mediachance
86 MultiKeyboard Macros

// this is reference - it references the array


pRef = array[]

println pRef
// it is exactly the same as calling
// println array[]

// Output [1,2,3]

// if we change the original data in any way


array[4] = 100
array[0] = 10

// the print of reference will obviously reflect that


println pRef

// Output [10,2,3,100]

Reference: Copy or not to copy data.

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

A function written as:

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!

Pointing to some other data in the middle of calculation

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

Of course the above can be achieved many other ways:

© 2020 Mediachance
Scripting 89

temp[] = test<5 ? a[]: b[]


result[] = temp[] * 2 + temp[]*3
In which case we created an useless temporary array

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

In which case we made it, I don't know, 'iffy"

Reference to a non existing Global data

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:

But that is not all.

What if we define instead our array as multidimensional 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

3.20 Using Arrays in user functions


The syntax arry[] allows passing arrays to user defined functions same way as any other variables. This
mechanism why it works is explained more in the References 85 chapter.

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.

So at the end of function you can use:

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.

Receive array from function


To receive the array data returned from the function into an array variable we can use syntax:

result[] = MyFunc( a[] )


Or:

a[] = MyFunc( a[] )


which will update the same array that was used for input.

Example:
This example demonstrate using arrays in user functions

//create an array in function


in_array[] = CreateArray(10)

PrintArray(in_array[])

println

// you can pass the array to function using []


out_array[] = QuadrFunc( in_array[] )

// print output
PrintArray( out_array[] )

// dont forget end in main function!


end

// creates array

© 2020 Mediachance
94 MultiKeyboard Macros

function CreateArray(num)

for k=0 to num


arr[k] = k
next k
return arr[]

// here is the clever part


// when written using straight syntax (not array[]) it will work
// for normal numbers AND arrays alike
// in one case 'a' is just a normal variable
// in other case it is a reference to array
function QuadrFunc(a)
a = a ^ 2
return a

function PrintArray(a)
print a
return nil

© 2020 Mediachance
Scripting 95

3.21 Multidimensional Hybrid Arrays


Arrays in Oscar Script are not your ordinary arrays you may have seen in other programming languages.
The arrays can be multidimensional, but also hybrid and non sequential - and the arithmetic still work on them!

Let me define array:

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.

Now lets add:

other[2][2][0] = -1
other[1] = -1
other[100] = -1

And then a an operation involving both arrays:

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.

Array can have multiple types inside


Array members can be of different type (unlike most other languages)

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

3.22 Debugging, trace


Oscar Script has quite comprehensive way of debugging.

To go to the Debug mode, click the Debug tab:

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.

The Compile & Run button also changed into:

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

Debugging inside Functions


Debugger normally does 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 break
point will be deleted as soon as it is reached so the debugger can function properly. Once you return from the
function in the step debugger the line that was calling the function will need to be executed again. This may in
some rare cases produce wrong results (for example an IF statement with a global value in the condition and
calling a function after 'then' that changes that global value). This is usually rare.

Copy strings from output.


You can select a line, then right click to open menu.

Changing Variables mid Debugging


When you are debugging, you can change variables during breakpoint and so change the outcome of next step.

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

if a<1 then throw "Oops, 'a' is zero - that's not good!"


endif

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

trace "Counter is ", counter


Trace statement can have color tags <..> such as <R> that would change color of the output text.

trace "<R>red <P>pink <B>blue <W>white <G>green <C>cyan <Y>yellow


<E>white on red <!>red on white"

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

3.23 User Library Functions


You can place your custom functions you use often into an User Library.
These functions will be available for every script.

// User Library functions


// *****************************************************
// DONT use GOTO or GOSUB inside User Library functions!

// use this part to test the functions


// while still in script editor
// this part will be never called by the main script.

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.

Don't use goto or gosub in the Library Functions.

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

3.25 Key Off Block


Since 2.05 version a key trigger can have a section dedicated to Key off trigger.

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

This is how all software expect the keys to actually function.

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.

The first script:

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.

Note: KEY OFF Queue

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

3.26 Script Examples


3.26.1 Clipboard example

//Working with clipboard

// get what is in the clipboard now


prevclipboard = GetClipboardText()

// send "copy" keystroke to windows so it will capture


// selected text in whatever app we are
//(in the Script Editor SendText and SendKeyStroke
// is disabled - obviously - or we will have mayhem
// so just copy something to clipboard manually when testing)
SendKeyStroke("CTRL C")

// get the text from clipboard to another string


clipboard = GetClipboardText()

trace "This is now in Clipboard: ",clipboard

// process the clipboard however you want


// and what I want is to make every second letter capital
// making it hard to read :)

newstring = ""
// length of the string
k = Length(clipboard)

for i = 0 to k

// get one char at a time


char = Mid(clipboard,i,1)

// i % 2 is modulus = remainer of i divided by 2


// so basically it flipflops between 0 and 1
if (i % 2==0) then
char = MakeUpper(char)
else
char = MakeLower(char)
endif
// and make a new string out of it
newstring = newstring+char
next i

© 2020 Mediachance
Scripting 109

// set back what was in clipboard previously


SetClipboardText(prevclipboard)

//send new string to the windows as text


//so it will replace the selected text with the new one
SendText(newstring)

trace "\a01,13 mischief managed "

© 2020 Mediachance
110 MultiKeyboard Macros

3.26.2 Secondary Clipboard


Task: We want to have a secondary text clipboard
CTR+C and CTRL+V would work with normal windows clipboard
and we want to assign two other keys that would work as a Copy 2 and Paste 2 - a secondary clipboard, so we
are able to copy and paste two things independently

COPY 2 button

// remember what is in the clipboard now


previous = GetClipboardText()

// send COPY to app


SendKeyStroke("CTRL C")

// grab the clipboard


GLB_clipboard2 = GetClipboardText()

// put the previous one back


SetClipboardText(previous)

PASTE 2 button

//declare variable just in case it was not yet assigned


//so we don't get warnings
declare GLB_clipboard2 as STRING

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

3.26.3 FIFO Clipboard


Task: make FIFO clipboard
Pressing "CopyStack" key will copy item into stack. So we can select multiple items and copy them to stack one
after another.
Pressing "PasteStack" key will type the text that is on the top of the stack and then roll to previous item so we
can "paste" items one after another that are in the stack

Button Stack Copy:

// send COPY to app


SendKeyStroke("CTRL C")

GLB_FIFO[GLB_fifo_counter] = GetClipboardText()
DisplayText("Copy "+STR(GLB_fifo_counter))
GLB_fifo_counter = GLB_fifo_counter+1

Button Stack Paste:

GLB_fifo_counter=GLB_fifo_counter-1

// stack is at the bottom


if GLB_fifo_counter<0 then
GLB_fifo_counter = 0
DisplayText("Stack Empty")
terminate
endif

// avoid errors - test if the array item is already defined as


STRING
if TYPE(GLB_FIFO[GLB_fifo_counter])==STRING then
DisplayText("Paste "+STR(GLB_fifo_counter))
// type the text
SendText(GLB_FIFO[GLB_fifo_counter])
endif

© 2020 Mediachance
112 MultiKeyboard Macros

3.26.4 XML Tags Extract


Task: parse XML document and find tags we are interested in

//Example of String parsing using XML tags

//Imagine we receive following string which is in XML


//and we need to extract name and associated product IDs

//It can come to the script from a clipboard


// string = GetClipboardText()
//...but for this example we just define it directly here:

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

// a super simple way to extract a single element:


sFname = Extract(string,"<FirstName>","</FirstName>",0)
sLname = Extract(string,"<LastName>","</LastName>",0)

sFullName = sFname+" "+sLname

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

// now print it all


println "Found ",nNumProducts," ID's for ",sFullName
for k = 0 to nNumProducts-1
println "ID",k+1,": ", sProdId[k]
next k

// now we can either send it to clipbard, send it as text


// to current app, save it as a file ...

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

3.26.5 BASE64 example


BASE64 example, Encode/Decode Secret Text:

Script A: Encodes string in clipboard to BASE64, obfuscating it by encoding it multiple times in a loop

//Encode to BASE64 multiple times


// get the text from clipboard
clipboard = GetClipboardText()

trace "Text in clipboard: ",clipboard

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

//BASE64 "Secret" decoding example


// get the text from clipboard
clipboard = GetClipboardText()

nc = Length(clipboard)

if nc==0 then
DisplayText("No String in Clipboard")
terminate
endif

// loop sufficiently enough


for i = 0 to 10
test = BASE64(clipboard,DECODE)
// if non BASE64 characters are found
// then it returns ""

© 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

3.26.6 Mod key Example


Implementing modifier key:
Pressing KEY (Num 9) will do one action
Pressing MODKEY (Num 8) and then KEY (Num9) one after another within 1 sec will do different action

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")

Now script on the action 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

// mod key was pressed;

DisplayText("Mod Key 1 Action")


// do the MOD action here

// don't forget to end so we don't go to NoModKey


end

NoModKey:
DisplayText("No Mod Key Action")

// do the NO MOD action here

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

p_count = permutate("", str , 0)


println "Total: ",p_count," premutations"

end

//recursive permutation function


//formula was taken from somewhere on "internets"
function permutate(candidate,remaining, count)

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.

What may not work or will not work


Dedicated keypads and gameboards that require a driver may not work. Some may work once their driver is
installed (for example Logitech G13).
These "keyboards" are typical by not having normal keyboard keys printed on them, rather just a generic pad
number or G1, G2.. as in logitech case.

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.

You can test a keyboard with the MON button

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

These may or may not work, depending on the driver.

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

VII Virus warnings etc...


An antivirus applications may eventually trigger warning, that MultiKeyboard Macros or kbdhook.dll behave like a
keyloger. (or may give you some other cryptic warnings like that)
And there is no way around it - MultiKeyboard macros has to monitor and filter the keyboard on low level in order
to know what trigger keys you pressed and send different keys to keyboard buffer instead - because that is its
function.

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

You might also like