Skip to main content

Examples

Working scripts covering the syntax and the builtin areas. Filter by topic or search their code and text. Scripts that use Context* builtins need a gesture or hotkey trigger. In the diagnostic console those builtins return their empty defaults.

Hello, console​

The smallest script. UtilityPrint writes to the diagnostic console, or to the print log when the script wasn't run from the console.

UtilityPrint("Hello from Input.Observer");

The five value kinds​

One variable of each kind, printed through StringFormat, which is the only way to turn a non-Text value into Text.

count = 3; // Integer
ratio = 0.75; // Real
name = "Notepad"; // Text
enabled = true; // Bool
w = WindowGetActive(); // Window

UtilityPrint(StringFormat("count={0} ratio={1}", count, ratio)); // count=3 ratio=0.750000
UtilityPrint(StringFormat("name={0} enabled={1}", name, enabled)); // name=Notepad enabled=true
UtilityPrint(StringFormat("window handle={0}", w, ""));

Truthiness of every kind​

What if considers true for each kind: non-zero numbers, non-empty Text, non-null handles.

if (0) UtilityPrint("never");
if (-1) UtilityPrint("-1 is true");
if (0.0) UtilityPrint("never");
if ("") UtilityPrint("never");
if (" ") UtilityPrint("a single space is true");
if ("false") UtilityPrint("the text \"false\" is true -- it's not empty");
if (!WindowFindByTitle("no window has this title zzz")) UtilityPrint("null handle is false");

else-if chain​

Sort the cursor's horizontal position into thirds of the monitor under it.

x = MouseGetCursorX();
y = MouseGetCursorY();
left = DisplayMonitorGetRectXFromPoint(x, y, false);
width = DisplayMonitorGetRectWidthFromPoint(x, y, false);

offset = x - left;
if (offset < width / 3) {
zone = "left";
} else if (offset < width * 2 / 3) {
zone = "center";
} else {
zone = "right";
}
UtilityPrint("Cursor is in the " + zone + " third");

Counting loops: up, down and by steps​

All three parts of for are assignments or expressions of your choosing.

sum = 0;
for (i = 1; i <= 100; i = i + 1) sum = sum + i;
UtilityPrint(StringFormat("1..100 = {0}", sum, "")); // 5050

for (i = 5; i > 0; i = i - 1) UtilityPrint(StringFormat("{0}...", i, ""));

for (p = 1; p <= 1024; p = p * 2) UtilityPrint(StringFormat("{0}", p, "")); // powers of two

Nested loops: a multiplication table​

Nested for loops building one line of Text per row.

for (r = 1; r <= 5; r = r + 1) {
line = "";
for (c = 1; c <= 5; c = c + 1) {
line = line + StringFormat("{0}\t", r * c, "");
}
UtilityPrint(line);
}

while loop: wait for a window, with a timeout​

The condition does the "while" work, and the counter caps it at 5 seconds.

ShellRun("notepad.exe");
w = WindowFindByTitle("Notepad");
for (tries = 0; !w && tries < 50; tries = tries + 1) {
UtilityWait(100);
w = WindowFindByTitle("Notepad");
}
if (w) {
WindowCenterToScreen(w);
} else {
UtilityPrint("Notepad didn't appear within 5 seconds");
}

while (true) with an exit flag​

A self-assigning step (go = go) makes a loop that only stops when its body clears the flag. The stop-all hotkey always ends it too.

rolls = 0;
for (go = true; go; go = go) {
rolls = rolls + 1;
if (MathRandom(1, 6) == 6) go = false;
}
UtilityPrint(StringFormat("Rolled a six after {0} tries", rolls, ""));

break and continue​

Set the counter past the limit to break. Put the rest of the body inside an if to continue.

n = StringSplit("keep,,skip-me,keep too,STOP,never reached", ",");
for (i = 0; i < n; i = i + 1) {
part = StringGetSplitPartAt(i);
if (part == "STOP") {
i = n; // break
} else if (part && !StringStartsWith(part, "skip")) { // continue for empty / skip-*
UtilityPrint(part);
}
}

Precedence surprises​

Two C precedence traps, and the explicit parentheses that fix them.

flags = 6;
a = flags & 4 == 4; // flags & (4 == 4) -> 6 & true -> 0 (Bool operand)
b = (flags & 4) == 4; // true

x = 5;
c = 0 < x < 10; // (0 < x) < 10 -> true < 10 -> false (Bool vs Integer)
d = x > 0 && x < 10; // true

UtilityPrint(StringFormat("a={0} b={1}", a, b));
UtilityPrint(StringFormat("c={0} d={1}", c, d));

&& and || evaluate both sides​

Both operands always run, which matters when the right side has an effect or can fail.

StorageSetValue("calls", 0);

// Both sides run even though the left is false; the right side's
// StorageSetValue still happens.
if (false && StorageSetValue("calls", 1)) { }
UtilityPrint(StringFormat("calls = {0}", StorageGetValue("calls"), "")); // calls = 1

// Guard a call that could fail with a nested if, not &&:
text = "no colon here";
idx = StringGetIndexOf(text, ":");
if (idx >= 0) {
UtilityPrint(StringGetSubstring(text, idx + 1, 100));
}

Equality across kinds​

Integer and Real compare numerically. Every other pair of different kinds is simply unequal.

UtilityPrint(StringFormat("5 == 5.0 -> {0}", 5 == 5.0, "")); // true
UtilityPrint(StringFormat("1 == true -> {0}", 1 == true, "")); // false
UtilityPrint(StringFormat("\"5\" == 5 -> {0}", "5" == 5, "")); // false
UtilityPrint(StringFormat("\"a\" == \"A\" -> {0}", "a" == "A", "")); // false (case-sensitive)
w = WindowFindByTitle("zzz no such window");
UtilityPrint(StringFormat("w == 0 -> {0}, !w -> {1}", w == 0, !w)); // false, true

Mixed-type arithmetic falls back to 0​

Combinations an operator doesn't understand give Integer 0 without an error. Print a value when a result looks wrong.

a = "Total: " + 5; // 0 -- not "Total: 5"
b = true + 1; // 0
c = -"text"; // 0
d = 2.5 & 1; // 0 -- bitwise needs Integers
e = "Total: " + StringFormat("{0}", 5, ""); // "Total: 5"
UtilityPrint(StringFormat("{0} {1}", a, b) + StringFormat(" {0} {1} ", c, d) + e);

Comments, blank statements and blocks​

Line and block comments, a stray semicolon, and a top-level block (which doesn't create a scope).

// line comment
/* block comment
spanning lines */
x = 1;; // the extra ';' is an empty statement
{
y = x + 1; /* blocks group statements... */
}
UtilityPrint(StringFormat("y = {0}", y, "")); // ...but y is still visible here

Integer vs Real division, and dividing by zero​

Integer division truncates toward zero, one Real operand gives a Real result, and dividing by zero gives 0.

a = 7 / 2; // 3
b = 7 / 2.0; // 3.5
c = 7 * 1.0 / 2; // 3.5 -- convert first, then divide
d = -7 / 2; // -3 (toward zero)
e = 5 / 0; // 0, no error
UtilityPrint(StringFormat("{0} {1}", a, b) + StringFormat(" {0} {1}", c, d) + StringFormat(" {0}", e, ""));

Remainder without %​

n - (n / d) * d is C's %. With a power-of-two divisor, a mask does the same for non-negative numbers.

n = 17;
d = 5;
rem = n - (n / d) * d; // 2
isEven = (n & 1) == 0; // false
low3 = n & 7; // 17 % 8 == 1
UtilityPrint(StringFormat("17 % 5 = {0}, even = {1}", rem, isEven));
UtilityPrint(StringFormat("17 % 8 = {0}", low3, ""));

Rounding and Real math builtins​

An Integer passed where a builtin declares Real becomes a Real. Rounding goes halves away from zero.

n = 16;
root = MathSqrt(n); // 4.0 -- n is widened to 16.0

UtilityPrint(StringFormat("round 2.5 -> {0}", MathRound(2.5), "")); // 3
UtilityPrint(StringFormat("round -2.5 -> {0}", MathRound(-2.5), "")); // -3
UtilityPrint(StringFormat("floor -1.5 -> {0}", MathFloor(-1.5), "")); // -2
UtilityPrint(StringFormat("ceil 1.01 -> {0}", MathCeil(1.01), "")); // 2
UtilityPrint(StringFormat("sqrt 16 -> {0}", MathRound(root), "")); // 4

Clamp a value to a range​

MathClamp (like MathMin/MathMax) takes either numeric kind and returns the winner as it was.

v = 300;
v = MathClamp(v, 0, 255); // 255
dist = MathAbs(-42); // 42 (Integer stays Integer)
UtilityPrint(StringFormat("clamped={0} abs={1}", v, dist));

Random numbers and a coin flip​

MathRandom includes both ends of the range.

die = MathRandom(1, 6);
if (MathRandom(0, 1) == 1) side = "heads"; else side = "tails";
UtilityPrint(StringFormat("die={0}, coin={1}", die, side));

Length of a gesture stroke​

Adds up the straight-line distance between consecutive gesture points. The Integer sum of squares becomes Real on its way into MathSqrt.

n = ContextGetPointCount();
total = 0.0;
for (i = 1; i < n; i = i + 1) {
dx = ContextGetPointX(i) - ContextGetPointX(i - 1);
dy = ContextGetPointY(i) - ContextGetPointY(i - 1);
total = total + MathSqrt(dx * dx + dy * dy);
}
UtilityPrint(StringFormat("{0} points, {1} px long", n, MathRound(total)));

Move the mouse in a circle​

Degrees to radians, trigonometry on Reals, and MathRound back to Integer pixel coordinates.

cx = MouseGetCursorX();
cy = MouseGetCursorY();
radius = 80.0;
for (deg = 0; deg <= 360; deg = deg + 10) {
rad = deg * MathGetPi() / 180;
MouseMoveTo(cx + MathRound(radius * MathCos(rad)), cy + MathRound(radius * MathSin(rad)));
UtilityWait(15);
}
MouseMoveTo(cx, cy);

Formatting a Real without six decimals​

Scale and round to get a percentage, or to split a value into whole and hundredths for two decimal places. Works for non-negative values.

ratio = 2.0 / 3.0;
pct = MathRound(ratio * 100); // 67

value = 3.14159;
cents = MathRound(value * 100); // 314
whole = cents / 100; // 3
frac = cents - whole * 100; // 14
if (frac < 10) fracText = StringFormat("0{0}", frac, ""); else fracText = StringFormat("{0}", frac, "");
UtilityPrint(StringFormat("{0}% and {1}", pct, StringFormat("{0}.", whole, "") + fracText)); // 67% and 3.14

Flag masks: set, clear, toggle, test​

The four standard mask operations on your own flag values.

BOLD = 0x1;
ITALIC = 0x2;
UNDERLINE = 0x4;

style = BOLD | UNDERLINE; // 0b101 = 5
style = style | ITALIC; // set -> 7
style = style & ~BOLD; // clear -> 6
style = style ^ UNDERLINE; // toggle -> 2

if ((style & ITALIC) != 0) UtilityPrint("italic is on");
if ((style & BOLD) == 0) UtilityPrint("bold is off");
UtilityPrint(StringFormat("style = {0}", style, ""));

Read the pixel color under the cursor​

Unpack the 0xRRGGBB Integer with shifts and masks.

c = DisplayGetPixelColorFromPoint(MouseGetCursorX(), MouseGetCursorY());
if (c < 0) {
UtilityPrint("The cursor isn't over any monitor");
} else {
r = (c >> 16) & 0xFF;
g = (c >> 8) & 0xFF;
b = c & 0xFF;
UtilityPrint(StringFormat("R={0} G={1}", r, g) + StringFormat(" B={0}", b, ""));
if (r > 200 && g < 80 && b < 80) UtilityPrint("That's red");
}

Integer to hex text​

No builtin formats hex, so peel off four bits at a time and look each digit up in a Text.

digits = "0123456789ABCDEF";
value = 48879;
hex = "";
for (v = value; v > 0; v = v >> 4) {
hex = StringGetSubstring(digits, v & 0xF, 1) + hex;
}
if (!hex) hex = "0";
UtilityPrint("0x" + hex); // 0xBEEF

Count the set bits​

Shift through all 64 bits and add each low bit.

v = 0xB6; // 1011 0110
bits = 0;
for (i = 0; i < 64; i = i + 1) {
bits = bits + ((v >> i) & 1);
}
UtilityPrint(StringFormat("0xB6 has {0} bits set", bits, "")); // 5

Shift edge cases​

>> keeps the sign, and a shift amount outside 0–63 gives 0.

UtilityPrint(StringFormat("1 << 10 = {0}", 1 << 10, "")); // 1024
UtilityPrint(StringFormat("-16 >> 2 = {0}", -16 >> 2, "")); // -4
UtilityPrint(StringFormat("1 << 64 = {0}", 1 << 64, "")); // 0
UtilityPrint(StringFormat("~0 = {0}", ~0, "")); // -1

Swap two Integers​

The ordinary way needs a temporary variable. XOR swaps without one.

a = 12; b = 34;
t = a; a = b; b = t; // with a temporary
a = a ^ b; b = a ^ b; a = a ^ b; // XOR swap back
UtilityPrint(StringFormat("a={0} b={1}", a, b)); // a=12 b=34

Key state bits​

KeyboardGetKeyState returns the raw Win32 value: the high bit means the key is down, the low bit means it's toggled. The Is* builtins do the same test for you.

caps = KeyboardGetKeyState(VirtualKey.CapsLock);
shift = KeyboardGetKeyState(VirtualKey.ShiftKey);
UtilityPrint(StringFormat("Caps Lock on: {0}, Shift held: {1}", (caps & 1) != 0, (shift & 0x8000) != 0));
UtilityPrint(StringFormat("same via builtins: {0}, {1}",
KeyboardIsKeyToggled(VirtualKey.CapsLock), KeyboardIsKeyDown(VirtualKey.ShiftKey)));

String escapes and Windows paths​

Double every backslash in a path. \t, \n and \r are escapes, and any other backslash is dropped.

good = "C:\\Temp\\report.txt"; // C:\Temp\report.txt
bad = "C:\Temp\new.txt"; // C:<no backslash>Temp<line break>ew.txt
quote = "He said \"stop\"";
tabs = "Name\tValue";
cont = "one logical line, \
written on two";
UtilityPrint(good);
UtilityPrint(bad);
UtilityPrint(quote);
UtilityPrint(tabs);
UtilityPrint(cont);

Formatting more than two values​

StringFormat fills {0} and {1} only. Nest a call, or join several with +.

w = WindowGetActive();
x = WindowGetX(w); y = WindowGetY(w);
width = WindowGetWidth(w); height = WindowGetHeight(w);

// nest: the inner call leaves "{0},{1}" for the outer call to fill
a = StringFormat(StringFormat("{0}x{1} at ", width, height) + "{0},{1}", x, y);
// concatenate: each call formats its own piece
b = StringFormat("pos {0},{1}", x, y) + StringFormat(" size {0}x{1}", width, height);
UtilityPrint(a);
UtilityPrint(b);

Split and iterate​

StringSplit returns the count, and StringGetSplitPartAt reads each part. Parts between two adjacent delimiters are empty.

n = StringSplit("red,green,,blue", ",");
for (i = 0; i < n; i = i + 1) {
UtilityPrint(StringFormat("{0}: [{1}]", i, StringGetSplitPartAt(i)));
}
// 0: [red] 1: [green] 2: [] 3: [blue]

Nested splits: key=value pairs​

The inner split replaces the outer split's parts, so re-split the outer list at the top of every pass.

config = "width=800;height=600;title=Main";
n = StringSplit(config, ";");
for (i = 0; i < n; i = i + 1) {
StringSplit(config, ";"); // restore the outer parts...
pair = StringGetSplitPartAt(i); // ...and copy this one out
StringSplit(pair, "="); // now the cache holds key, value
UtilityPrint(StringFormat("{0} -> {1}", StringGetSplitPartAt(0), StringGetSplitPartAt(1)));
}

Last index of: a file extension​

There's no LastIndexOf, so check every position and keep the last match.

file = "report.final.v2.pdf";
last = -1;
len = StringGetLength(file);
for (i = 0; i < len; i = i + 1) {
if (StringGetSubstring(file, i, 1) == ".") last = i;
}
if (last >= 0) {
ext = StringGetSubstring(file, last + 1, len - last - 1);
base = StringGetSubstring(file, 0, last);
UtilityPrint(StringFormat("base={0} ext={1}", base, ext)); // base=report.final.v2 ext=pdf
}

Read a number someone typed​

false reads it in the user's regional format (a German user types 1.234,5). Digit groups must then follow that format: for an English (United States) user 1,234.5 is a number and 1,5 isn't. StringIsNumber catches text that isn't a number, which StringToNumber alone would turn into 0.

text = UIShowInputBox("Width in pixels:", "Resize", "800");
if (!StringIsNumber(text, false)) {
UIShowMessageBox("Not a number: " + text, "Resize", "OK", "Warning");
} else {
width = StringToNumber(text, false); // Integer 800, or a Real if they typed a fraction
shown = StringFromNumber(width * 1.5, 1, false); // "1,200.0" in en-US, "1.200,0" in de-DE
UtilityPrint("one and a half times that: " + shown);
}

Launch a program, wait for its window, act on it​

WindowWaitFor polls for up to the timeout (60 seconds at most) and gives a null window if nothing matched. The pattern is a case-insensitive regex over the title; a program's title follows Windows' display language, so match the part that doesn't change.

ShellRun("notepad.exe");
w = WindowWaitFor("notepad$", 10000);
if (w) {
WindowSetActive(w);
KeyboardTypeText("Notes for today:\n");
WindowWaitClose(w, 60000); // carries on once the user closes it, or after a minute
UtilityPrint("Notepad is gone");
} else {
UtilityPrint("Notepad didn't open in 10 seconds");
}

Pull a value out of copied text with a regex​

PCRE2 syntax, case-sensitive unless the pattern starts with (?i), and Unicode-aware (\w matches accented letters). Group 0 is the whole match; a backslash in the pattern is written \\ inside a script string. $1 in a replacement is group 1. The Window*Regex/Shell*Regex builtins use the same engine, always case-insensitive.

text = ClipboardGetText();
if (StringRegexMatch(text, "(?i)order\\s*#?(\\d+)")) {
UtilityPrint("order number: " + StringRegexGetGroupAt(1));
} else {
UtilityPrint("no order number on the clipboard");
}
// every run of spaces, tabs and line breaks becomes one space; "#1234" becomes "order 1234"
text = StringRegexReplace(text, "\\s+", " ");
ClipboardSetText(StringRegexReplace(text, "#(\\d+)", "order $1"));

Zero-pad a number​

Prepend characters until the Text is long enough.

n = 7;
s = StringFormat("{0}", n, "");
for (s = s; StringGetLength(s) < 3; s = s) {
s = "0" + s;
}
UtilityPrint(s); // 007

Reverse Text​

Walk the Text backwards one UTF-16 unit at a time. Characters outside the Basic Multilingual Plane (most emoji) are two units and would come out split.

text = "Input.Observer";
rev = "";
for (i = StringGetLength(text) - 1; i >= 0; i = i - 1) {
rev = rev + StringGetSubstring(text, i, 1);
}
UtilityPrint(rev); // revresbO.tupnI

Count words on the clipboard​

Turn tabs and line breaks into spaces, split on spaces, and count the parts that aren't empty.

text = ClipboardGetText();
text = StringReplace(text, "\n", " ");
text = StringReplace(text, "\t", " ");
n = StringSplit(text, " ");
words = 0;
for (i = 0; i < n; i = i + 1) {
if (StringTrim(StringGetSplitPartAt(i))) words = words + 1;
}
UtilityPrint(StringFormat("{0} words", words, ""));

Case-insensitive comparisons​

Lower-case both sides, because every Text operator and builtin is case-sensitive.

title = WindowGetTitle(WindowGetActive());
if (StringContains(StringToLower(title), "visual studio")) {
UtilityPrint("In Visual Studio");
}
exe = WindowGetExecutableName(WindowGetActive());
if (StringToLower(exe) == "notepad.exe") UtilityPrint("In Notepad");

Text ordering is ordinal​

< compares UTF-16 code units: every uppercase ASCII letter sorts before every lowercase one.

UtilityPrint(StringFormat("\"apple\" < \"banana\" : {0}", "apple" < "banana", "")); // true
UtilityPrint(StringFormat("\"Zebra\" < \"apple\" : {0}", "Zebra" < "apple", "")); // true
UtilityPrint(StringFormat("\"apple\" < \"Banana\" : {0}", "apple" < "Banana", "")); // false
a = "apple"; b = "Banana";
UtilityPrint(StringFormat("case-folded : {0}", StringToLower(a) < StringToLower(b), "")); // true

Fill a template and paste it​

StringReplace fills %NAME% placeholders. The clipboard is saved and restored around the paste.

template = "Hi %NAME%,\n\nThanks for the report on %APP%.\n";
text = StringReplace(template, "%NAME%", "Sam");
text = StringReplace(text, "%APP%", ContextGetApplicationName());
ClipboardSave();
ClipboardPasteReplacementText(text);
UtilityWait(150); // let the target app read the clipboard first
ClipboardRestore();

Join clipboard lines into one line​

Split on \n. StringTrim also removes the \r of Windows line endings, so this works whether the lines end with \r\n or \n alone.

n = StringSplit(ClipboardGetText(), "\n");
out = "";
for (i = 0; i < n; i = i + 1) {
part = StringTrim(StringGetSplitPartAt(i));
if (part) {
if (out) out = out + " ";
out = out + part;
}
}
ClipboardSetText(out);

Search the web for the selected text​

ClipboardCopySelection waits for the copy to land instead of guessing. StringUrlEncode keeps &, #, spaces and accented letters from breaking the address. Encode the search term only, never the whole URL.

ClipboardSave();
term = StringTrim(ClipboardCopySelection(1000));
ClipboardRestore();
if (term) ShellRun("https://www.bing.com/search?q=" + StringUrlEncode(term));

One gesture, several choices​

UIShowMenu takes one item per line (- alone is a separator) and returns the chosen item's index, counting from 0 and skipping separators, or -1 when dismissed. UIShowInputBox returns "" on Cancel.

choice = UIShowMenu("Maximize\nMinimize\n-\nRename window...");
w = ContextGetWindow();
if (choice == 0) WindowMaximize(w);
else if (choice == 1) WindowMinimize(w);
else if (choice == 2) {
title = UIShowInputBox("New title for this window:", "Rename", WindowGetTitle(w));
if (title) WindowSetTitle(w, title);
}

Today's date, and a timestamped file name​

DateTimeGetNow returns ISO 8601 text. DateTimeFormat writes it the user's way, or as a FileStamp that sorts by time and is safe in a file name.

now = DateTimeGetNow();
KeyboardTypeText(DateTimeFormat(now, DateTimeStyle.LongDate));
path = ShellExpandEnvironmentVariables("%USERPROFILE%\\Desktop\\note-") + DateTimeFormat(now, DateTimeStyle.FileStamp) + ".txt";
FileCreate(path, ClipboardGetText());
if (DateTimeGetPart(now, DateTimePart.Hour) >= 17) UtilityPrint("after hours");

Named constants vs raw numbers​

A constant compiles to its number, so these calls are identical. Hex matches the Win32 documentation.

KeyboardPressKey(VirtualKey.VolumeMute);
KeyboardPressKey(0xAD); // the same key, as a hex VK code
KeyboardPressKey(173); // and in decimal
UtilityPrint(StringFormat("VirtualKey.A = {0}, MouseButton.Right = {1}", VirtualKey.A, MouseButton.Right));

Toggle maximize for the gesture's window​

The most common gesture action. ContextGetWindow is the window the gesture started over.

w = ContextGetWindow();
if (WindowIsMaximized(w)) WindowRestore(w); else WindowMaximize(w);

Pin a window on top​

WindowSetTopmost sets always-on-top explicitly, and WindowToggleTopmost flips it. A window property remembers which state this script chose.

w = ContextGetWindow();
pinned = WindowGetPropertyInteger(w, "io.pinned") == 1;
WindowSetTopmost(w, !pinned);
if (pinned) {
WindowRemoveProp(w, "io.pinned");
} else {
WindowSetPropertyInteger(w, "io.pinned", 1);
ShellShowToast("Pinned on top", WindowGetTitle(w));
}

Cycle window transparency​

Steps opacity from 255 down to 105 and back to 255. A window that isn't layered reports 255.

w = ContextGetWindow();
a = WindowGetAlpha(w) - 50;
if (a < 100) a = 255;
WindowSetAlpha(w, a);

Snap the active window to the left half of its monitor​

Uses the work area (true) so the window doesn't cover the taskbar.

w = WindowGetActive();
cx = WindowGetX(w) + WindowGetWidth(w) / 2;
cy = WindowGetY(w) + WindowGetHeight(w) / 2;
x = DisplayMonitorGetRectXFromPoint(cx, cy, true);
y = DisplayMonitorGetRectYFromPoint(cx, cy, true);
width = DisplayMonitorGetRectWidthFromPoint(cx, cy, true);
height = DisplayMonitorGetRectHeightFromPoint(cx, cy, true);
WindowRestore(w);
WindowMoveTo(w, x, y);
WindowResizeTo(w, width / 2, height);

Snap a window into a 3×2 grid cell under the cursor​

Region* builtins split a rectangle into cells, find the cell containing a point, and give its bounds.

mx = MouseGetCursorX();
my = MouseGetCursorY();
x = DisplayMonitorGetRectXFromPoint(mx, my, true);
y = DisplayMonitorGetRectYFromPoint(mx, my, true);
w = DisplayMonitorGetRectWidthFromPoint(mx, my, true);
h = DisplayMonitorGetRectHeightFromPoint(mx, my, true);

cell = RegionGetCellIndexAt(x, y, w, h, 3, 2, mx, my);
win = ContextGetWindow();
if (cell >= 0 && win) {
WindowRestore(win);
WindowMoveTo(win, RegionGetX(x, y, w, h, 3, 2, cell), RegionGetY(x, y, w, h, 3, 2, cell));
WindowResizeTo(win, RegionGetWidth(x, y, w, h, 3, 2, cell), RegionGetHeight(x, y, w, h, 3, 2, cell));
}

Throw a window to the next monitor​

The second argument moves the mouse along with the window.

WindowSendToNextScreen(ContextGetWindow(), true);

List visible top-level windows​

Enumerate-then-index, skipping hidden, cloaked and untitled windows.

n = WindowGetAllTopLevel();
shown = 0;
for (i = 0; i < n; i = i + 1) {
w = WindowGetEnumeratedAt(i);
if (WindowGetVisible(w)) {
if (!WindowIsCloaked(w)) {
title = WindowGetTitle(w);
if (title) {
UtilityPrint(StringFormat("{0} [{1}]", title, WindowGetExecutableName(w)));
shown = shown + 1;
}
}
}
}
UtilityPrint(StringFormat("{0} of {1} windows", shown, n));

Minimize every window of one app​

Matches the executable with a case-insensitive regular expression. Note the \\.: a regex \. written inside a script string.

n = WindowFindAllByModuleRegex("notepad\\.exe$");
for (i = 0; i < n; i = i + 1) {
WindowMinimize(WindowGetEnumeratedAt(i));
}
UtilityPrint(StringFormat("Minimized {0} Notepad windows", n, ""));

Close windows by title pattern, after confirming​

A regex over titles, a count in the message, and a check of which button was clicked.

n = WindowFindAllByTitleRegex("^Untitled - ");
if (n > 0) {
answer = UIShowMessageBox(StringFormat("Close {0} untitled window(s)?", n, ""),
"Tidy up", "YesNo", "Question");
if (answer == "Yes") {
for (i = 0; i < n; i = i + 1) WindowClose(WindowGetEnumeratedAt(i));
}
}

Remember and restore a window's position​

Window properties keep Integers on the window itself, between runs, until it closes. A property that was never set reads 0.

w = ContextGetWindow();
if (WindowGetPropertyInteger(w, "io.saved") == 1) {
WindowMoveTo(w, WindowGetPropertyInteger(w, "io.x"), WindowGetPropertyInteger(w, "io.y"));
WindowRemoveProp(w, "io.saved");
WindowRemoveProp(w, "io.x");
WindowRemoveProp(w, "io.y");
} else {
WindowSetPropertyInteger(w, "io.x", WindowGetX(w));
WindowSetPropertyInteger(w, "io.y", WindowGetY(w));
WindowSetPropertyInteger(w, "io.saved", 1);
WindowCenterToScreen(w);
}

Inspect a window's child controls​

Children use the same window cache as top-level enumeration.

w = ContextGetWindow();
n = WindowGetAllChildren(w, true);
for (i = 0; i < n; i = i + 1) {
c = WindowGetEnumeratedAt(i);
UtilityPrint(StringFormat("{0}: {1}", WindowGetClassName(c), WindowGetTitle(c)));
}

From process to window​

Enumerate process ids by executable name, then find each one's window.

n = ShellEnumerateProcessIdsByExeRegex("^explorer\\.exe$");
for (i = 0; i < n; i = i + 1) {
pid = ShellGetEnumeratedProcessIdAt(i);
w = WindowFromProcessId(pid);
UtilityPrint(StringFormat("pid {0}: {1}", pid, WindowGetTitle(w)));
}

Describe what's under the cursor​

The control under a point, its top-level window, and the point translated into client coordinates.

x = MouseGetCursorX();
y = MouseGetCursorY();
control = WindowControlFromPoint(x, y);
top = WindowGetRoot(control);
UtilityPrint(StringFormat("control class: {0}; window: {1}", WindowGetClassName(control), WindowGetTitle(top)));
UtilityPrint(StringFormat("client point: {0},{1}",
WindowMapScreenPointToClientX(top, x, y), WindowMapScreenPointToClientY(top, x, y)));

Hide a window in the tray​

Minimize to the notification area. TrayRestoreAllWindows() brings every one back.

TrayMinimizeWindow(ContextGetWindow());

Everything the trigger context knows​

Attach this to a gesture to see what its script can read.

UtilityPrint(StringFormat("action={0} gesture={1}", ContextGetActionName(), ContextGetGestureName()));
UtilityPrint(StringFormat("app={0} window={1}", ContextGetApplicationName(), WindowGetTitle(ContextGetWindow())));
UtilityPrint(StringFormat("box x={0} y={1}", ContextGetBoundingBoxX(), ContextGetBoundingBoxY())
+ StringFormat(" w={0} h={1}", ContextGetBoundingBoxWidth(), ContextGetBoundingBoxHeight()));
UtilityPrint(StringFormat("points={0} button={1}", ContextGetPointCount(), ContextGetStrokeButton()));

Which way did the stroke go?​

Compare the first and last points and pick the dominant axis.

n = ContextGetPointCount();
if (n > 1) {
dx = ContextGetPointX(n - 1) - ContextGetPointX(0);
dy = ContextGetPointY(n - 1) - ContextGetPointY(0);
if (MathAbs(dx) > MathAbs(dy)) {
if (dx > 0) dir = "right"; else dir = "left";
} else {
if (dy > 0) dir = "down"; else dir = "up";
}
UtilityPrint("The stroke went " + dir);
}

Branch on the stroke button​

ContextGetStrokeButton returns a MouseButton.* value, or -1 outside a gesture or mouse-button trigger.

b = ContextGetStrokeButton();
if (b == MouseButton.Secondary) {
WindowMinimize(ContextGetWindow());
} else if (b == MouseButton.Middle) {
WindowClose(ContextGetWindow());
} else if (b == -1) {
UtilityPrint("Not triggered by a mouse button");
}

Screenshot the area you circled​

Saves the gesture's bounding box as a PNG in Pictures.

path = ShellExpandEnvironmentVariables("%USERPROFILE%\\Pictures\\gesture-capture.png");
ok = CaptureSaveRegion(path, ContextGetBoundingBoxX(), ContextGetBoundingBoxY(),
ContextGetBoundingBoxWidth(), ContextGetBoundingBoxHeight());
if (ok) ShellShowToast("Captured", path);

Let an unrecognized drawing through​

If a gesture was drawn over a drawing app, replay the stroke to the app as a normal drag.

if (StringToLower(ContextGetApplicationName()) == "mspaint.exe") {
ContextRelayGesture();
}

Click somewhere, then put the cursor back​

Save the cursor position in variables, click, and return.

x = MouseGetCursorX();
y = MouseGetCursorY();
MouseClick(20, 20, MouseButton.Primary);
UtilityWait(50);
MouseMoveTo(x, y);

Click a point inside a window​

Client coordinates stay correct wherever the window has been moved.

w = WindowFindByTitle("Calculator");
if (w) {
WindowSetActive(w);
MouseClickAtClientPoint(w, 40, 60, MouseButton.Primary);
}

A scripted drag​

Press, move in steps, release. The separate down, move and up builtins exist for this.

x = MouseGetCursorX();
y = MouseGetCursorY();
MouseButtonDown(MouseButton.Primary);
for (i = 1; i <= 20; i = i + 1) {
MouseMoveTo(x + i * 10, y);
UtilityWait(10);
}
MouseButtonUp(MouseButton.Primary);

Scroll by notches​

One wheel notch is 120. Negative scrolls down or left.

MouseScrollVertical(-3 * 120); // three notches down
MouseScrollHorizontal(120); // one notch right

Confine the cursor to a window for 5 seconds​

MouseLockToRect applies system-wide until MouseUnlock, so always pair them.

w = ContextGetWindow();
MouseLockToRect(WindowGetX(w), WindowGetY(w), WindowGetWidth(w), WindowGetHeight(w));
UtilityWait(5000);
MouseUnlock();

Key combos​

Modifier prefixes ^ Ctrl, + Shift, % Alt and @ Win, then one key.

KeyboardPressKeyCombo("^c"); // Ctrl+C
KeyboardPressKeyCombo("^+{ESC}"); // Ctrl+Shift+Esc
KeyboardPressKeyCombo("@{LEFT}"); // Win+Left
KeyboardPressKeyCombo("%{F4}"); // Alt+F4

Type a signature​

KeyboardTypeText types characters. Send Enter as a key combo between lines, so every app sees a real Enter key press.

KeyboardTypeText("Best regards,");
KeyboardPressKeyCombo("{ENTER}");
KeyboardTypeText("Sam");

Shift-click​

Hold a key around a mouse action, and release it in the same script. A key left down stays down.

KeyboardKeyDown(VirtualKey.ShiftKey);
MouseClick(MouseGetCursorX(), MouseGetCursorY(), MouseButton.Primary);
KeyboardKeyUp(VirtualKey.ShiftKey);

Media keys​

Send media and volume keys by their VirtualKey names.

KeyboardPressKey(VirtualKey.MediaPlayPause);
UtilityWait(200);
KeyboardPressKey(VirtualKey.MediaNextTrack);

Change behavior while Ctrl is held​

Check a live key state to give one gesture two meanings.

w = ContextGetWindow();
if (KeyboardIsKeyDown(VirtualKey.ControlKey)) {
WindowClose(w);
} else {
WindowMinimize(w);
}

Uppercase the selected text​

Copy the selection, change it, paste it back, and restore whatever was on the clipboard before.

ClipboardSave();
ClipboardClear();
KeyboardPressKeyCombo("^c");
UtilityWait(100);
text = ClipboardGetText();
if (text) {
ClipboardPasteReplacementText(StringToUpper(text));
UtilityWait(100);
}
ClipboardRestore();

Copy the selection and open a search URL. Spaces become +. To encode every other character too, use StringUrlEncode, as Search the web for the selected text does.

ClipboardSave();
KeyboardPressKeyCombo("^c");
UtilityWait(100);
q = StringTrim(ClipboardGetText());
ClipboardRestore();
if (q) {
ShellRun("https://www.bing.com/search?q=" + StringReplace(q, " ", "+"));
}

Save a copied image to a file​

Writes the clipboard's image to disk and reports the outcome.

path = ShellExpandEnvironmentVariables("%USERPROFILE%\\Desktop\\clipboard.png");
if (ClipboardSaveImage(path)) ShellShowToast("Saved", path);
else UtilityPrint("No image on the clipboard");

Append to a log file​

Creates the file with a header the first time, then appends one line per run.

log = ShellExpandEnvironmentVariables("%TEMP%\\input-observer-gestures.log");
if (!FileExists(log)) FileCreate(log, "gesture\tapplication\n");
FileAppendText(log, StringFormat("{0}\t{1}\n", ContextGetGestureName(), ContextGetApplicationName()));

Read a file and count its lines​

FileReadText returns the whole file as Text, which then splits on \n.

path = "C:\\Windows\\System32\\drivers\\etc\\hosts";
text = FileReadText(path);
n = StringSplit(text, "\n");
comments = 0;
for (i = 0; i < n; i = i + 1) {
if (StringStartsWith(StringTrim(StringGetSplitPartAt(i)), "#")) comments = comments + 1;
}
UtilityPrint(StringFormat("{0} lines, {1} comments", n, comments));
UtilityPrint(StringFormat("{0} bytes, modified {1}", FileGetSize(path), FileGetModifiedDate(path)));

Count file types in a folder​

Enumerates a folder without recursion and counts matches case-insensitively.

folder = ShellExpandEnvironmentVariables("%USERPROFILE%\\Downloads");
n = FolderEnumerateAll(folder, false);
pdf = 0; zip = 0;
for (i = 0; i < n; i = i + 1) {
p = StringToLower(FolderGetEnumeratedPathAt(i));
if (StringEndsWith(p, ".pdf")) pdf = pdf + 1;
else if (StringEndsWith(p, ".zip")) zip = zip + 1;
}
UtilityPrint(StringFormat("{0} PDFs, {1} ZIPs", pdf, zip) + StringFormat(" of {0} entries", n, ""));

Back up a file before editing it​

Copy with overwrite, then replace the contents.

path = ShellExpandEnvironmentVariables("%USERPROFILE%\\notes.txt");
if (FileExists(path)) {
FileCopy(path, path + ".bak", true);
FileEditText(path, StringToUpper(FileReadText(path)));
}

Watch a folder​

Combine FileNotify flags with |. The watch's script is Text, so its inner quotes are escaped. It reads what happened through ContextGetWatch*.

FolderWatchCreate("downloads",
ShellExpandEnvironmentVariables("%USERPROFILE%\\Downloads"),
false,
FileNotify.FileName | FileNotify.LastWrite,
"UtilityPrint(ContextGetWatchAction() + \": \" + ContextGetWatchPath());");
// later: FolderWatchDelete("downloads");

A repeating timer that counts​

A timer's script has its own variables, so Storage carries the count between firings. Fires 5 times, a second apart.

StorageSetValue("ticks", 0);
TimerCreate("counter", 1000, 1000, 5,
"t = StorageGetValue(\"ticks\") + 1; StorageSetValue(\"ticks\", t); UtilityPrint(StringFormat(\"tick {0}\", t, \"\"));");

Timer script from a snippet, no escaping​

Keep a long timer script in a snippet and pass its text, so there are no nested quotes to escape.

// Fires once, 10 minutes from now (repeat count 1).
TimerCreate("stretch-reminder", 600000, 1000, 1, SnippetGetScript("Stretch reminder"));

List and stop timers​

Enumerate the named timers, and delete one or all of them.

n = TimerEnumerateAll();
for (i = 0; i < n; i = i + 1) UtilityPrint(TimerGetEnumeratedNameAt(i));
TimerDelete("counter");
// TimerDeleteAll();

A toggle that survives between runs​

StorageGetValue of a key that was never set is Integer 0, which is falsy, so the first run turns the mode on. The value lasts until Input.Observer exits; the Persistent builtins keep one across restarts.

on = !StorageGetValue("quietMode");
StorageSetValue("quietMode", on);
MultimediaSetMute(AudioEndpoint.Playback, on);
if (on) ShellShowToast("Quiet mode", "On"); else ShellShowToast("Quiet mode", "Off");

A counter that survives a restart​

The Storage*Persistent* builtins keep values in storage.toml beside the configuration, so they're still there after Input.Observer restarts. Plain text, never encrypted: don't keep passwords there. Windows can't be kept; a handle means nothing after a restart. If storage.toml exists but can't be read when Input.Observer starts, StorageSetPersistentValue returns false: the value is kept only until Input.Observer exits, and nothing is saved until the next start.

n = StorageGetPersistentValue("gestures.used") + 1; // 0 the first time ever
StorageSetPersistentValue("gestures.used", n);
UtilityPrint(StringFormat("{0} gestures since this counter started", n, ""));

A list kept in Storage​

No arrays, but numbered keys built with StringFormat work like one.

count = StorageGetValue("recent.count");
StorageSetValue(StringFormat("recent.{0}", count, ""), WindowGetTitle(ContextGetWindow()));
StorageSetValue("recent.count", count + 1);

for (i = 0; i <= count; i = i + 1) {
UtilityPrint(StorageGetValue(StringFormat("recent.{0}", i, "")));
}

Let only one action run a section at a time​

A named lock stops two overlapping runs from interleaving. Check the result against LockResult, and release the lock when you're done.

r = UtilityLockAcquire("clipboard-work", 5);
if (r == LockResult.Acquired) {
ClipboardSave();
// ...work that must not overlap another run...
ClipboardRestore();
UtilityLockRelease("clipboard-work");
} else {
UtilityPrint("Another action is busy with the clipboard");
}

Ask a question​

Buttons and icon are Text names. The result is the name of the clicked button.

answer = UIShowMessageBox("Save the layout before closing?", "Input.Observer", "YesNoCancel", "Question");
if (answer == "Yes") {
UtilityPrint("saving");
} else if (answer == "No") {
UtilityPrint("discarding");
} else {
UtilityPrint("cancelled");
}

Volume up with an on-screen display​

Volume is a Real from 0.0 to 1.0. UIShowDisplayMessage takes 19 positional arguments.

v = MathMin(MultimediaGetVolume(AudioEndpoint.Playback) + 0.05, 1.0);
MultimediaSetVolume(AudioEndpoint.Playback, v);
UIShowDisplayMessage(
"Volume", StringFormat("{0}%", MathRound(v * 100), ""),
1200, // durationMs
0.9, // opacity (Real)
Location.BottomCenter, // or a "x,y" Text point
"Segoe UI", 14, true, false, // title font, size, bold, italic
"Segoe UI", 12, false, false, // message font, size, bold, italic
"#1F1F1F", "#F7F7F5", // fore / back color: name, #RRGGBB or "R,G,B"
12, // padding
false, // usePrimaryScreen
TextAlign.Center, TextAlign.Center);

A live-updating display message​

Keep the session id and pass it to UIUpdateDisplayMessage with the same 19 arguments.

id = UIShowDisplayMessage("Working", "0%", 10000, 0.95, Location.TopRight,
"Segoe UI", 12, true, false, "Segoe UI", 11, false, false,
"black", "255,255,255", 10, true, TextAlign.Left, TextAlign.Left);
for (p = 0; p <= 100; p = p + 20) {
UIUpdateDisplayMessage(id, "Working", StringFormat("{0}%", p, ""), 10000, 0.95, Location.TopRight,
"Segoe UI", 12, true, false, "Segoe UI", 11, false, false,
"black", "255,255,255", 10, true, TextAlign.Left, TextAlign.Left);
UtilityWait(400);
}
UICloseDisplayMessage(id);

A Windows notification​

A title and a message, shown as a toast.

ShellShowToast("Input.Observer", StringFormat("Profile: {0}", GestureProfileGetActiveId(), ""));

Toggle the microphone mute​

The Capture endpoint is the default recording device.

MultimediaToggleMute(AudioEndpoint.Capture);
if (MultimediaGetMute(AudioEndpoint.Capture)) ShellShowToast("Microphone", "Muted");
else ShellShowToast("Microphone", "Live");

Play a sound​

WAV and MP3 files from a path.

MultimediaPlayWavFile("C:\\Windows\\Media\\chimes.wav");

Launch or switch to an app​

Activates the app if it's already running, otherwise starts it.

ShellRunOrActivate("notepad.exe");

Run a program with a verb and window style​

The verb is a ShellVerb constant or any verb as Text. The window style is a WindowStyle constant.

ShellRunProgram("notepad.exe", "C:\\Temp\\todo.txt", ShellVerb.Open, WindowStyle.Maximized, false);
ShellRunProgram("C:\\Temp\\report.pdf", "", "print", WindowStyle.Hidden, false); // Text verb

Run and wait for exit​

With waitForExit set to true, the script's thread blocks until the program closes. Input keeps flowing meanwhile. Stopping all actions ends the wait: the program keeps running and the call returns false.

ShellRunProgram("notepad.exe", "", ShellVerb.Open, WindowStyle.Normal, true);
ShellShowToast("Notepad", "was closed");

Expand environment variables​

Builds a path that works on every user's machine.

UtilityPrint(ShellExpandEnvironmentVariables("%APPDATA%\\Input.Observer"));
UtilityPrint(ShellExpandEnvironmentVariables("%COMPUTERNAME% / %USERNAME%"));

Hand off to AutoHotkey​

Runs AutoHotkey code out of process and returns its exit code, or -1 if AutoHotkey isn't configured.

rc = AutoHotkeyExecuteScript("SoundBeep 750, 200");
if (rc == -1) UtilityPrint("AutoHotkey isn't set up");

List monitors​

Name, full bounds, work area and DPI for each monitor, by index.

n = DisplayMonitorEnumeratedAll();
for (i = 0; i < n; i = i + 1) {
UtilityPrint(StringFormat("#{0} {1}", i, DisplayMonitorGetEnumeratedFriendlyNameAt(i)));
UtilityPrint(StringFormat(" bounds {0} x {1}", DisplayMonitorGetEnumeratedWidthAt(i, false), DisplayMonitorGetEnumeratedHeightAt(i, false))
+ StringFormat(", work area {0} x {1}", DisplayMonitorGetEnumeratedWidthAt(i, true), DisplayMonitorGetEnumeratedHeightAt(i, true))
+ StringFormat(", {0} dpi", DisplayMonitorGetEnumeratedDpiAt(i), ""));
}

Send a window to a specific monitor​

By index from the enumeration, falling back to the next screen when that monitor doesn't exist.

w = ContextGetWindow();
if (DisplayMonitorEnumeratedAll() > 1) {
WindowSendToMonitorIndex(w, 1, false);
} else {
WindowSendToNextScreen(w, false);
}

Cycle to the next gesture profile​

Find the active profile's index, add one, and wrap around with the remainder idiom.

n = GestureProfileEnumerateAll();
if (n > 0) {
active = GestureProfileGetActiveId();
next = 0;
for (i = 0; i < n; i = i + 1) {
if (GestureProfileGetEnumeratedIdAt(i) == active) next = i + 1;
}
next = next - (next / n) * n; // next % n
GestureProfileSwitch(GestureProfileGetEnumeratedIdAt(next));
ShellShowToast("Gesture profile", GestureProfileGetEnumeratedNameAt(next));
}

Engine state​

Read whether the engine is disabled or in Safe Mode, then pause it. Re-enable it from the tray menu.

UtilityPrint(StringFormat("disabled={0} safeMode={1}", EngineIsDisabled(), EngineIsSafeMode()));
if (!EngineIsDisabled()) {
EngineDisable();
ShellShowToast("Input.Observer", "Paused. Re-enable it from the tray menu.");
}

Let the next right-drag through​

Skips gesture capture once, so the next right-button drag goes to the app.

EngineDisableNextGesture();

Snippets as reusable functions​

Pass arguments and results through Storage. The snippet runs to completion before the next line.

// Snippet "Tile window" reads tile.index and positions ContextGetWindow() accordingly.
StorageSetValue("tile.index", 2);
SnippetExecuteScript("Tile window");
UtilityPrint(StringFormat("snippet reported: {0}", StorageGetValue("tile.result"), ""));

Talk to a plugin​

Sends a Text command to a connected plugin and waits up to the timeout for its Text reply.

reply = PluginSendMessage("MyRenderer", "theme dark", 2);
UtilityPrint("Plugin replied: " + reply);

Ask a serial device a question​

Open the port, write a line, wait up to 2 seconds for the reply, and close the port. Parity and stop bits are constants.

if (SerialOpenPort("COM3", 9600, SerialParity.None, 8, SerialStopBits.One, "\n")) {
SerialWriteTextLine("COM3", "PING", 9600);
reply = SerialGetTextLine("COM3", 2, 9600);
SerialClosePort("COM3");
if (reply) UtilityPrint("Device: " + reply); else UtilityPrint("No reply");
}

Map a serial device's buttons to media keys​

A monitor runs its script for every received line. ContextGetSerialTextLine is the line.

SerialMonitorCreate("pad", "COM3", 115200, SerialParity.None, 8, SerialStopBits.One, "\n",
"line = StringTrim(ContextGetSerialTextLine()); if (line == \"B1\") KeyboardPressKey(VirtualKey.MediaPlayPause); else if (line == \"B2\") KeyboardPressKey(VirtualKey.MediaNextTrack);");

List the COM ports​

Which ports exist right now, such as the one an Arduino just got when it was plugged in. See Serial devices and Arduino.

n = SerialEnumeratePorts();
if (n == 0) UtilityPrint("No COM ports. Is the board plugged in?");
for (i = 0; i < n; i = i + 1) {
UtilityPrint(SerialGetEnumeratedPortAt(i));
}

Keep an Arduino's port open and send it commands​

Open the port once, so a board that restarts when its port opens doesn't miss commands, then send a line and read the reply.

// Load global event: open the port once, at the sketch's speed. An Uno or Nano
// restarts every time its port opens, so a port opened per command would lose
// the command while the board boots.
if (!SerialOpenPort("COM4", 115200, SerialParity.None, 8, SerialStopBits.One, "")) {
UtilityPrint("COM4 isn't available: unplugged, or open in the Arduino IDE's Serial Monitor?");
}

// Any gesture or hotkey: send a command line and read the board's reply.
// (The sketch reads a line with Serial.readStringUntil('\n') and answers OK.)
if (SerialWriteTextLine("COM4", "LED 255 0 0", 115200)) {
reply = SerialGetTextLine("COM4", 1, 115200);
if (reply != "OK") UtilityPrint(StringFormat("Board said: {0}", reply, ""));
}

Turn an Arduino knob into a volume control​

A potentiometer reading from 0 to 1023 on each line becomes the playback volume.

// Arduino side: a potentiometer on A0, one reading per line.
// void setup() { Serial.begin(115200); }
// void loop() { Serial.println(analogRead(A0)); delay(50); }
// Empty text as the terminator means CR LF, which Serial.println sends.
SerialMonitorCreate("knob", "COM4", 115200, SerialParity.None, 8, SerialStopBits.One, "",
"line = ContextGetSerialTextLine(); if (StringIsNumber(line, true)) MultimediaSetVolume(AudioEndpoint.Playback, StringToNumber(line, true) / 1023.0);");