Перейти к основному содержимому

Примеры

Рабочие скрипты, охватывающие синтаксис и области встроенных функций. Фильтруйте их по теме или ищите по коду и тексту. Скриптам, использующим встроенные функции Context*, нужен триггер — жест или горячая клавиша. В диагностической консоли эти функции возвращают свои пустые значения по умолчанию.

Привет, консоль​

Самый маленький скрипт. UtilityPrint пишет в диагностическую консоль или, если скрипт запущен не из консоли, в журнал вывода.

UtilityPrint("Hello from Input.Observer");

Пять типов значений​

По одной переменной каждого типа, выведенной через StringFormat — единственный способ превратить значение не типа Text в 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, ""));

Истинность каждого типа​

Что if считает истинным для каждого типа: ненулевые числа, непустой Text, непустые дескрипторы.

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​

Определить, в какой трети монитора по горизонтали находится курсор.

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

Циклы со счётчиком: вверх, вниз и с шагом​

Все три части for — присваивания или выражения на ваш выбор.

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

Вложенные циклы: таблица умножения​

Вложенные циклы for, строящие одну строку Text на каждую строку таблицы.

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: ждать окно с тайм-аутом​

Работу «while» выполняет условие, а счётчик ограничивает ожидание 5 секундами.

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) с флагом выхода​

Шаг, присваивающий переменной её же значение (go = go), создаёт цикл, который останавливается, только когда тело сбрасывает флаг. Горячая клавиша остановки всех действий тоже всегда завершает его.

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 и continue​

Чтобы выполнить break, выведите счётчик за предел. Чтобы выполнить continue, поместите остаток тела в if.

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);
}
}

Сюрпризы приоритета​

Две ловушки приоритета из C и явные скобки, которые их исправляют.

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

&& и || вычисляют обе стороны​

Оба операнда выполняются всегда, что важно, когда правая сторона имеет побочный эффект или может завершиться сбоем.

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));
}

Равенство разных типов​

Integer и Real сравниваются численно. Любая другая пара разных типов просто не равна.

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

Арифметика смешанных типов даёт 0​

Сочетания, которых оператор не понимает, дают Integer 0 без ошибки. Выводите значение, если результат выглядит неправильно.

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

Комментарии, пустые инструкции и блоки​

Строчные и блочные комментарии, лишняя точка с запятой и блок верхнего уровня (который не создаёт область видимости).

// 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 и Real, деление на ноль​

Целочисленное деление отбрасывает дробную часть в сторону нуля, один операнд Real даёт результат Real, а деление на ноль даёт 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, ""));

Остаток без %​

n - (n / d) * d — это % из C. Если делитель — степень двойки, для неотрицательных чисел то же самое делает маска.

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

Округление и встроенные функции для Real​

Integer, переданный туда, где встроенная функция объявляет Real, становится Real. Половины округляются от нуля.

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

Ограничить значение диапазоном​

MathClamp (как и MathMin/MathMax) принимает любой из двух числовых типов и возвращает победителя без изменений.

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

Случайные числа и подбрасывание монеты​

MathRandom включает оба конца диапазона.

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

Длина штриха жеста​

Складывает расстояния по прямой между соседними точками жеста. Сумма квадратов типа Integer становится Real при передаче в 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)));

Провести мышь по кругу​

Градусы в радианы, тригонометрия на Real и MathRound обратно в целочисленные координаты в пикселях.

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

Форматирование Real без шести знаков после запятой​

Масштабируйте и округлите, чтобы получить проценты или разделить значение на целую часть и сотые для двух знаков после запятой. Работает для неотрицательных значений.

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

Маски флагов: установка, сброс, переключение, проверка​

Четыре стандартные операции с маской на ваших собственных значениях флагов.

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

Прочитать цвет пикселя под курсором​

Распаковать Integer 0xRRGGBB сдвигами и масками.

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 в шестнадцатеричный текст​

Ни одна встроенная функция не форматирует шестнадцатеричные числа, поэтому отделяйте по четыре бита и находите каждую цифру в 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

Подсчитать установленные биты​

Пройти сдвигом все 64 бита и сложить каждый младший бит.

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

Граничные случаи сдвига​

>> сохраняет знак, а величина сдвига вне 0–63 даёт 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

Обменять два Integer​

Обычному способу нужна временная переменная. XOR обменивает без неё.

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

Биты состояния клавиши​

KeyboardGetKeyState возвращает исходное значение Win32: старший бит означает, что клавиша нажата, младший — что она переключена. Встроенные функции Is* выполняют ту же проверку за вас.

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

Escape-последовательности и пути Windows​

Удваивайте каждую обратную косую черту в пути. \t, \n и \r — escape-последовательности, а любая другая обратная косая черта отбрасывается.

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

Форматирование более двух значений​

StringFormat заполняет только {0} и {1}. Вложите вызов или соедините несколько через +.

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

Разбить и перебрать​

StringSplit возвращает количество, а StringGetSplitPartAt читает каждую часть. Части между двумя соседними разделителями пусты.

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]

Вложенные разбиения: пары key=value​

Внутреннее разбиение заменяет части внешнего, поэтому разбивайте внешний список заново в начале каждого прохода.

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)));
}

Последнее вхождение: расширение файла​

LastIndexOf нет, поэтому проверьте каждую позицию и запомните последнее совпадение.

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
}

Прочитать число, введённое человеком​

false читает его в региональном формате пользователя (пользователь из Германии вводит 1.234,5). Группы разрядов тогда должны соответствовать этому формату: для пользователя с форматом «Русский (Россия)» 1 234,5 — число, а 12 34,5 — нет. StringIsNumber отсеивает текст, который не является числом и который StringToNumber сам по себе превратил бы в 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);
}

Запустить программу, дождаться её окна и действовать в нём​

WindowWaitFor опрашивает до истечения тайм-аута (не более 60 секунд) и возвращает пустое окно, если ничего не совпало. Шаблон — регулярное выражение без учёта регистра для заголовка; заголовок программы зависит от языка интерфейса Windows, поэтому сопоставляйте неизменную часть.

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

Извлечь значение из скопированного текста регулярным выражением​

Синтаксис PCRE2, с учётом регистра, если шаблон не начинается с (?i), с поддержкой Юникода (\w совпадает с буквами с диакритикой). Группа 0 — всё совпадение; обратная косая черта в шаблоне записывается внутри строки скрипта как \\. $1 в замене — это группа 1. Встроенные функции Window*Regex/Shell*Regex используют тот же движок, всегда без учёта регистра.

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

Дополнить число нулями​

Добавлять символы в начало, пока Text не станет достаточно длинным.

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

Развернуть Text​

Пройти Text с конца по одной кодовой единице UTF-16. Символы вне Основной многоязычной плоскости (большинство эмодзи) занимают две единицы и окажутся разорваны.

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

Подсчитать слова в буфере обмена​

Заменить табуляции и разрывы строк пробелами, разбить по пробелам и подсчитать непустые части.

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

Сравнения без учёта регистра​

Переведите обе стороны в нижний регистр, потому что все операторы и встроенные функции для Text учитывают регистр.

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

Порядок текста — порядковый​

< сравнивает кодовые единицы UTF-16: каждая прописная буква ASCII идёт раньше каждой строчной.

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

Заполнить шаблон и вставить его​

StringReplace заполняет заполнители %NAME%. Буфер обмена сохраняется и восстанавливается вокруг вставки.

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();

Объединить строки из буфера обмена в одну​

Разбить по \n. StringTrim также удаляет \r окончаний строк Windows, поэтому это работает, заканчиваются ли строки на \r\n или только на \n.

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

Найти выделенный текст в интернете​

ClipboardCopySelection ждёт, пока копирование завершится, вместо того чтобы угадывать. StringUrlEncode не даёт &, #, пробелам и буквам с диакритикой испортить адрес. Кодируйте только поисковый запрос, никогда не весь URL.

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

Один жест, несколько вариантов​

UIShowMenu принимает по одному пункту на строку (- отдельно — разделитель) и возвращает индекс выбранного пункта, считая с 0 и пропуская разделители, или -1, если меню закрыто. UIShowInputBox возвращает "" при нажатии «Отмена».

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);
}

Сегодняшняя дата и имя файла с отметкой времени​

DateTimeGetNow возвращает текст ISO 8601. DateTimeFormat записывает его так, как принято у пользователя, или как FileStamp, который сортируется по времени и безопасен в имени файла.

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

Именованные константы и простые числа​

Константа компилируется в своё число, поэтому эти вызовы идентичны. Шестнадцатеричная запись совпадает с документацией Win32.

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

Переключить развёртывание окна жеста​

Самое частое действие жеста. ContextGetWindow — окно, над которым начался жест.

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

Закрепить окно поверх остальных​

WindowSetTopmost явно задаёт режим «поверх всех окон», а WindowToggleTopmost переключает его. Свойство окна запоминает, какое состояние выбрал этот скрипт.

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));
}

Циклически менять прозрачность окна​

Уменьшает непрозрачность с 255 до 105 и возвращает к 255. Окно, не являющееся многослойным, сообщает 255.

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

Прикрепить активное окно к левой половине его монитора​

Использует рабочую область (true), чтобы окно не закрывало панель задач.

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

Поместить окно в ячейку сетки 3×2 под курсором​

Встроенные функции Region* делят прямоугольник на ячейки, находят ячейку, содержащую точку, и дают её границы.

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));
}

Перебросить окно на следующий монитор​

Второй аргумент перемещает мышь вместе с окном.

WindowSendToNextScreen(ContextGetWindow(), true);

Список видимых окон верхнего уровня​

Сначала перечисление, затем обращение по индексу, с пропуском скрытых, замаскированных окон и окон без заголовка.

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

Свернуть все окна одного приложения​

Сопоставляет исполняемый файл регулярным выражением без учёта регистра. Обратите внимание на \\.: это \. регулярного выражения, записанный внутри строки скрипта.

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

Закрыть окна по шаблону заголовка после подтверждения​

Регулярное выражение для заголовков, количество в сообщении и проверка нажатой кнопки.

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));
}
}

Запомнить и восстановить положение окна​

Свойства окна хранят значения Integer на самом окне между запусками, пока оно не закроется. Свойство, которое никогда не задавалось, читается как 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);
}

Исследовать дочерние элементы управления окна​

Дочерние окна используют тот же кэш окон, что и перечисление окон верхнего уровня.

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)));
}

От процесса к окну​

Перечислить идентификаторы процессов по имени исполняемого файла, затем найти окно каждого.

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)));
}

Описать то, что под курсором​

Элемент управления под точкой, его окно верхнего уровня и точка, переведённая в клиентские координаты.

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

Спрятать окно в трей​

Свернуть в область уведомлений. TrayRestoreAllWindows() возвращает все такие окна.

TrayMinimizeWindow(ContextGetWindow());

Всё, что знает контекст триггера​

Назначьте это жесту, чтобы увидеть, что может прочитать его скрипт.

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()));

В какую сторону шёл штрих?​

Сравнить первую и последнюю точки и выбрать преобладающую ось.

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);
}

Ветвление по кнопке штриха​

ContextGetStrokeButton возвращает значение MouseButton.* или -1 вне триггера жеста или кнопки мыши.

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

Снимок обведённой области​

Сохраняет ограничивающий прямоугольник жеста как PNG в папке «Изображения».

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

Пропустить нераспознанный рисунок​

Если жест нарисован над программой для рисования, воспроизвести штрих в этой программе как обычное перетаскивание.

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

Щёлкнуть где-то и вернуть курсор на место​

Сохранить положение курсора в переменных, щёлкнуть и вернуться.

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

Щёлкнуть в точке внутри окна​

Клиентские координаты остаются верными, куда бы окно ни переместили.

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

Перетаскивание из скрипта​

Нажать, переместить по шагам, отпустить. Для этого и существуют отдельные встроенные функции нажатия, перемещения и отпускания.

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

Прокрутка на несколько делений​

Одно деление колеса — 120. Отрицательное значение прокручивает вниз или влево.

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

Ограничить курсор окном на 5 секунд​

MouseLockToRect действует во всей системе до вызова MouseUnlock, поэтому всегда используйте их в паре.

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

Сочетания клавиш​

Префиксы модификаторов ^ Ctrl, + Shift, % Alt и @ Win, затем одна клавиша.

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

Набрать подпись​

KeyboardTypeText набирает символы. Между строками отправляйте Enter как сочетание клавиш, чтобы каждое приложение получило настоящее нажатие клавиши Enter.

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

Щелчок с Shift​

Удерживать клавишу во время действия мыши и отпустить её в том же скрипте. Неотпущенная клавиша остаётся нажатой.

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

Мультимедийные клавиши​

Отправить мультимедийные клавиши и клавиши громкости по их именам VirtualKey.

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

Другое поведение, пока удерживается Ctrl​

Проверить текущее состояние клавиши, чтобы придать одному жесту два значения.

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

Перевести выделенный текст в верхний регистр​

Скопировать выделение, изменить его, вставить обратно и восстановить прежнее содержимое буфера обмена.

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

Скопировать выделение и открыть URL поиска. Пробелы становятся +. Чтобы закодировать и все остальные символы, используйте StringUrlEncode, как в примере Найти выделенный текст в интернете.

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

Сохранить скопированное изображение в файл​

Записывает изображение из буфера обмена на диск и сообщает результат.

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

Дописать в файл журнала​

В первый раз создаёт файл с заголовком, затем при каждом запуске дописывает одну строку.

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

Прочитать файл и подсчитать его строки​

FileReadText возвращает весь файл как Text, который затем разбивается по \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)));

Подсчитать типы файлов в папке​

Перечисляет папку без рекурсии и подсчитывает совпадения без учёта регистра.

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

Сделать резервную копию файла перед изменением​

Скопировать с перезаписью, затем заменить содержимое.

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

Отслеживать папку​

Объединяйте флаги FileNotify через |. Скрипт отслеживания — это Text, поэтому кавычки внутри него экранированы. Что произошло, он читает через ContextGetWatch*.

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

Повторяющийся таймер со счётчиком​

У скрипта таймера собственные переменные, поэтому счётчик между срабатываниями переносит Storage. Срабатывает 5 раз с интервалом в секунду.

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

Скрипт таймера из фрагмента, без экранирования​

Держите длинный скрипт таймера во фрагменте и передавайте его текст, тогда не придётся экранировать вложенные кавычки.

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

Перечислить и остановить таймеры​

Перечислить именованные таймеры и удалить один или все.

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

Переключатель, сохраняющийся между запусками​

StorageGetValue для ключа, который никогда не задавался, — это Integer 0, то есть ложь, поэтому первый запуск включает режим. Значение сохраняется до выхода из Input.Observer; встроенные функции Persistent сохраняют его и между перезапусками.

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

Счётчик, переживающий перезапуск​

Встроенные функции Storage*Persistent* хранят значения в storage.toml рядом с конфигурацией, поэтому они остаются после перезапуска Input.Observer. Это обычный текст, никогда не шифруемый: не храните там пароли. Окна сохранить нельзя: после перезапуска дескриптор ничего не значит. Если storage.toml существует, но его не удаётся прочитать при запуске Input.Observer, StorageSetPersistentValue возвращает false: значение хранится только до завершения работы Input.Observer, и до следующего запуска ничего не сохраняется.

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

Список в Storage​

Массивов нет, но пронумерованные ключи, построенные через StringFormat, работают как массив.

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, "")));
}

Пусть участок выполняет только одно действие за раз​

Именованная блокировка не даёт двум перекрывающимся запускам перемешивать работу. Проверяйте результат по LockResult и освобождайте блокировку, когда закончите.

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

Задать вопрос​

Кнопки и значок задаются именами в виде Text. Результат — имя нажатой кнопки.

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

Увеличение громкости с экранной индикацией​

Громкость — Real от 0.0 до 1.0. UIShowDisplayMessage принимает 19 позиционных аргументов.

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

Обновляемое на лету экранное сообщение​

Сохраните идентификатор сеанса и передайте его в UIUpdateDisplayMessage с теми же 19 аргументами.

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

Уведомление Windows​

Заголовок и сообщение, показанные во всплывающем уведомлении.

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

Переключить отключение микрофона​

Конечная точка Capture — устройство записи по умолчанию.

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

Воспроизвести звук​

Файлы WAV и MP3 по пути.

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

Запустить приложение или переключиться на него​

Активирует приложение, если оно уже запущено, иначе запускает его.

ShellRunOrActivate("notepad.exe");

Запустить программу с глаголом и стилем окна​

Глагол — константа ShellVerb или любой глагол в виде Text. Стиль окна — константа WindowStyle.

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

Запустить и дождаться завершения​

Когда waitForExit равно true, поток скрипта блокируется, пока программа не закроется. Ввод тем временем продолжает поступать. Остановка всех действий прерывает ожидание: программа продолжает работать, а вызов возвращает false.

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

Развернуть переменные среды​

Строит путь, который работает на компьютере любого пользователя.

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

Передать работу AutoHotkey​

Выполняет код AutoHotkey вне процесса и возвращает его код завершения или -1, если AutoHotkey не настроен.

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

Список мониторов​

Имя, полные границы, рабочая область и DPI каждого монитора по индексу.

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), ""));
}

Отправить окно на определённый монитор​

По индексу из перечисления, с переходом на следующий экран, если такого монитора нет.

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

Переключиться на следующий профиль жестов​

Найти индекс активного профиля, прибавить единицу и зациклить приёмом с остатком.

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));
}

Состояние движка​

Прочитать, отключён ли движок или работает ли он в безопасном режиме, затем приостановить его. Снова включите его из меню в трее.

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

Пропустить следующее перетаскивание правой кнопкой​

Один раз пропускает захват жеста, чтобы следующее перетаскивание правой кнопкой мыши досталось приложению.

EngineDisableNextGesture();

Фрагменты как повторно используемые функции​

Передавайте аргументы и результаты через Storage. Фрагмент выполняется до конца перед следующей строкой.

// 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"), ""));

Общение с плагином​

Отправляет текстовую команду подключённому плагину и ждёт его текстового ответа до истечения тайм-аута.

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

Задать вопрос устройству на COM-порту​

Открыть порт, записать строку, ждать ответа до 2 секунд и закрыть порт. Чётность и стоповые биты задаются константами.

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

Кнопки устройства на COM-порту как мультимедийные клавиши​

Монитор выполняет свой скрипт для каждой принятой строки. ContextGetSerialTextLine — это строка.

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

Список COM-портов​

Какие порты есть прямо сейчас, например тот, который Arduino получила при подключении. См. Устройства с COM-портом и 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));
}

Держать порт Arduino открытым и отправлять ей команды​

Откройте порт один раз, чтобы плата, перезапускающаяся при открытии порта, не пропускала команды, затем отправьте строку и прочитайте ответ.

// 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, ""));
}

Ручка Arduino как регулятор громкости​

Показание потенциометра от 0 до 1023 в каждой строке становится громкостью воспроизведения.

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