メインコンテンツまでスキップ

例

構文と組み込み関数の各領域を扱う、動作するスクリプト集です。トピックで絞り込んだり、コードや説明を検索したりできます。 Context* 組み込み関数を使用するスクリプトには、ジェスチャまたはホットキーのトリガーが必要です。 診断コンソールでは、これらの組み込み関数は空の既定値を返します。

Hello、コンソール​

最小のスクリプトです。UtilityPrint は診断コンソールに書き込みます。コンソールから実行されなかったスクリプトの場合は、 出力ログに書き込みます。

UtilityPrint("Hello from Input.Observer");

5 種類の値​

各種類の変数を 1 つずつ作り、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, ""));

各種類の truthy の判定​

if が各種類で何を真と見なすか: 0 以外の数値、空でない Text、null でないハンドルです。

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 の連鎖​

カーソルの横方向の位置が、その下にあるモニターを 3 等分したどの部分にあるかを判定します。

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 の 3 つの部分はいずれも、自由に選べる代入または式です。

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 ループで、行ごとに 1 行の 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 になります。本体の残りを if の中に入れると 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);
}
}

意外な優先順位​

C 言語の優先順位の 2 つの落とし穴と、それを解消する明示的なかっこです。

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 の除算、0 による除算​

整数除算は 0 方向に切り捨てられ、オペランドの一方が Real なら結果は Real になり、0 による除算は 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 言語の % と同じです。除数が 2 のべき乗なら、負でない数に対してはマスクでも同じ結果になります。

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 の Math 組み込み関数​

組み込み関数が Real と宣言している場所に Integer を渡すと Real になります。丸めでは 0.5 は 0 から遠い方へ丸められます。

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 の 2 乗和は、MathSqrt に渡されるときに Real になります。

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 による Integer のピクセル座標への変換です。

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

小数点以下 6 桁にせずに Real を書式設定する​

スケーリングして丸めることでパーセンテージを求めたり、値を整数部と 100 分の 1 の部分に分けて小数点以下 2 桁で表示したりします。 負でない値に対して使用できます。

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

フラグ マスク: セット、クリア、反転、テスト​

独自のフラグ値に対する 4 つの標準的なマスク操作です。

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

カーソルの下のピクセルの色を読み取る​

0xRRGGBB の Integer をシフトとマスクで分解します。

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 を 16 進のテキストに変換する​

16 進数に書式設定する組み込み関数はないため、4 ビットずつ取り出し、各桁を 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

2 つの 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)));

文字列のエスケープと Windows のパス​

パスの円記号はすべて 2 つ重ねて書きます。\t、\n、\r はエスケープで、その他の円記号は削除されます。

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

3 つ以上の値を書式設定する​

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 で各部分を読み取ります。隣り合う 2 つの区切り文字の間の部分は空になります。

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 は数値ですが、1,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 秒) までポーリングし、何も一致しなければ null のウィンドウを返します。 パターンはタイトルに対する、大文字と小文字を区別しない正規表現です。プログラムのタイトルは 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) で始まらない限り大文字と小文字を区別し、Unicode に対応しています (\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"));

数値を 0 で埋める​

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 単位で 1 つずつ後ろから順にたどります。基本多言語面 (BMP) の外にある文字 (ほとんどの絵文字) は 2 単位なので、 分割されてしまいます。

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

Text の順序は序数比較​

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

クリップボードの複数行を 1 行にまとめる​

\n で分割します。StringTrim は Windows の改行の \r も取り除くため、行の終わりが \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);

選択したテキストを Web で検索する​

ClipboardCopySelection は、推測に頼らずコピーが完了するのを待ちます。StringUrlEncode は、 &、#、スペース、アクセント付きの文字でアドレスが壊れるのを防ぎます。エンコードするのは検索語だけにし、 URL 全体はエンコードしないでください。

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

1 つのジェスチャで複数の選択肢​

UIShowMenu は 1 行に 1 項目を受け取り (- だけの行は区切り線)、選択された項目のインデックスを返します。 インデックスは 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");

名前付き定数と数値の比較​

定数はその数値にコンパイルされるため、これらの呼び出しはまったく同じです。16 進数は 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));
}

ウィンドウを次のモニターに移動する​

2 番目の引数を指定すると、ウィンドウと一緒にマウスも移動します。

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

1 つのアプリのすべてのウィンドウを最小化する​

大文字と小文字を区別しない正規表現で実行可能ファイルを照合します。\\. に注意してください: スクリプトの文字列内で 正規表現の \. を書くとこうなります。

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

プロセスからウィンドウへ​

実行可能ファイル名でプロセス ID を列挙し、それぞれのウィンドウを見つけます。

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

ノッチ単位でスクロールする​

ホイールの 1 ノッチは 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) の後に、キーを 1 つ指定します。

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 キーを押している間は動作を変える​

現在のキーの状態を確認して、1 つのジェスチャに 2 つの意味を持たせます。

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 を開きます。スペースは + になります。その他の文字もすべてエンコードするには、 選択したテキストを Web で検索すると同様に 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");

ログ ファイルに追記する​

初回はヘッダー付きでファイルを作成し、その後は実行ごとに 1 行を追記します。

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

カウントする繰り返しタイマー​

タイマーのスクリプトは独自の変数を持つため、起動間でのカウントの受け渡しにはストレージを使います。 1 秒間隔で 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"));

タイマーを一覧表示して停止する​

名前付きのタイマーを列挙し、1 つまたはすべてを削除します。

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

実行をまたいで保持されるトグル​

一度も設定されていないキーの StorageGetValue は Integer の 0 で、これは falsy なので、初回の実行でモードがオンになります。 値は 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 を再起動しても値は残ります。プレーン テキストで、暗号化されることはありません。パスワードは保存しないでください。 Window は保存できません。ハンドルは再起動後には意味を持たないためです。Input.Observer の起動時に storage.toml が存在するのに読み取れない場合、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, ""));

ストレージに保持するリスト​

配列はありませんが、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, "")));
}

ある区間を一度に 1 つのアクションだけが実行するようにする​

名前付きロックを使うと、重なって実行された 2 つの処理が交互に入り混じるのを防げます。結果を 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");
}

画面表示付きで音量を上げる​

音量は 0.0~1.0 の Real です。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);

ライブ更新される表示メッセージ​

セッション ID を保持しておき、同じ 19 個の引数とともに UIUpdateDisplayMessage に渡します。

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 のコードを別プロセスで実行し、その終了コードを返します。AutoHotkey が構成されていない場合は -1 を返します。

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

次のジェスチャ プロファイルに切り替える​

アクティブなプロファイルのインデックスを見つけて 1 を加え、剰余の書き方で先頭に戻します。

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

次の右ドラッグをそのまま通す​

ジェスチャのキャプチャを 1 回スキップし、次の右ボタンのドラッグがアプリに届くようにします。

EngineDisableNextGesture();

再利用可能な関数としてのスニペット​

引数と結果はストレージを介して受け渡します。スニペットは最後まで実行されてから次の行に進みます。

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

プラグインと通信する​

接続されているプラグインに Text のコマンドを送信し、タイムアウトまで Text の応答を待ちます。

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

シリアル デバイスに問い合わせる​

ポートを開き、1 行書き込み、応答を最大 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");
}

シリアル デバイスのボタンをメディア キーに割り当てる​

モニターは受信した行ごとにスクリプトを実行します。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 を接続した直後に割り当てられたポートなど) を一覧表示します。 シリアル デバイスと 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 のポートを開いたままにしてコマンドを送信する​

ポートを一度だけ開くことで、ポートが開かれると再起動するボードでもコマンドを取りこぼさないようにし、 1 行送信して応答を読み取ります。

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