~/blog/implementing-function-to-run-autohotkey-from-linux-command-line
Published on

LinuxコマンドラインからAutoHotKeyを実行する関数を実装してみた

7224文字13分で読めます–––
閲覧数
Authors
  • avatar
    Name
    Shou Arisaka nyapp.buzz/shou
    short bio
    Z世代の情報技術者。Next.jsで自作SNSを個人開発中。

LinuxコマンドラインからAutoHotKeyを実行する関数を実装してみたので紹介します。 autohotkeyをWSL Bashから引数でフレキシブルなコーディングを可能にする実装です。

autohotkeyでは俗に言うシェルにおいてのstdout、標準出力がないため、例えばpythonやrubyのように、bashスクリプト上での値をautohotkeyに渡してautohotkeyで処理された値を標準出力として受け取りバイプして…といったことができません。

https://yuis.xsrv.jp/images/ss/ShareX_ScreenShot_5ea9f51f-e3b6-496c-81af-89bb166e0c46.gif

OutputDebug

OutputDebugといういかにもなメソッドがあるのですが、こちらはもちろん試しているのですが、できませんでした。 どうやら、専用のエディタ、統合開発環境ソフトウェアでないと出力されないみたいです。意味ないね。

OutputDebug - Syntax & Usage | AutoHotkey autohotkey print - Google Search OutputDebug - Google Search

[Resolved] Print to Console - AutoHotkey Community

ファイルを使う

で、解決策をとりあえず見つけました。

ファイル - ファイルxと呼称 - に一時的に標準出力したいデータをFileAppendで出力して、bashでファイルxをcatすれば、実質stdoutが取得できることになります。

以下はbash上からautohotkey経由で、現在のアクティブウィンドウがatom.exeであればtrueを返し、それ以外であればfalseを返すスクリプトです。

https://yuis.xsrv.jp/images/ss/ShareX_ScreenShot_ad669195-818f-450a-8b6a-147cc471e07f.gif

export AHKPRINT_STDOUT_FILE="/mnt/c/data/ahkprint.txt"
export AHKPRINT_STDOUT_IDENTIFIER="var"

ahkprint(){

: <<<'
e.g. ahkprint
'

# tmpdird

: check AHKPRINT_STDOUT_FILE file and path/dir exist then handling error

[[ ! -f "${AHKPRINT_STDOUT_FILE}" ]] && {
  touch "${AHKPRINT_STDOUT_FILE}" || {
    printf "${red}E: Couldn't make the file." ; return 1 ;
  }
}

: make null the file
> "${AHKPRINT_STDOUT_FILE}"

cat << EOT > /mnt/c/_tmp/tmp.ahk
WinGet, $\{AHKPRINT_STDOUT_IDENTIFIER}, ProcessName, A
FileAppend , %$\{AHKPRINT_STDOUT_IDENTIFIER}% , $( wslpathr "${AHKPRINT_STDOUT_FILE}" )
EOT

psl ahk $( wslpathr /mnt/c/_tmp/tmp.ahk )

cat "${AHKPRINT_STDOUT_FILE}"

}

stdin(標準入力)をautohotkeyに渡す

では次のステップとして、autohotkeyにbashからstdin標準入力を渡してみます。

以下スクリプトがその改良版です。

https://yuis.xsrv.jp/images/ss/ShareX_ScreenShot_35e715ad-4e19-445c-90e4-0810a64a7bde.gif

ahkprint(){

: <<<'
e.g. ahkprint
'

# tmpdird


: If a argment defined use it else if stdin defined use it else make error

[[ -z "${1}" ]] && stdin="$(</dev/stdin)"

[[ ! -z "${1}" ]] && {
  value="${1}"
} || {
  [[ ! -z "${stdin}" ]] && {
    value="${stdin}"
  } || {
    printf "${red}No data to store to the value. at least you'd specify argment(s) or stdin." ; return 1 ;
  }
}

: check AHKPRINT_STDOUT_FILE file and path/dir exist then handling error

[[ ! -f "${AHKPRINT_STDOUT_FILE}" ]] && {
  touch "${AHKPRINT_STDOUT_FILE}" || {
    printf "${red}E: Couldn't make the file." ; return 1 ;
  }
}

: make null the file
> "${AHKPRINT_STDOUT_FILE}"

cat << EOT > /mnt/c/_tmp/tmp.ahk
WinGet, activeWinproc, ProcessName, A

WinActivate, ahk_exe ${value}
winActivateErrorLevel=%ErrorLevel%

WinWaitActive, ahk_exe ${value}

${AHKPRINT_STDOUT_IDENTIFIER} = % activeWinproc . "  " . winActivateErrorLevel

FileAppend , %${AHKPRINT_STDOUT_IDENTIFIER}% , $( wslpathr "${AHKPRINT_STDOUT_FILE}" )
EOT

psl ahk $( wslpathr /mnt/c/_tmp/tmp.ahk )

: パイプ処理の場合に動かない。 ahkのファイル書き込み処理が終わる前にこちらが動いてしまっているものだと思われる

# cat "${AHKPRINT_STDOUT_FILE}"

: 上記の対策
: 数秒間ファイル書き込みがされるかどうかを検証。ファイル書き込みがされればそれをcatしてループを終了し、数秒間が過ぎた場合は変数およびファイルが空であるとしてループを終了する

tmpdate=$(date +"%s")

while [[ "$( echo $(( $( date +"%s" ) - ${tmpdate} )) )" -lt 3 ]] ;
do

[[ ! -z "$( cat "${AHKPRINT_STDOUT_FILE}" )" ]] && {
  cat "${AHKPRINT_STDOUT_FILE}" ; break ;
}

sleep 0.1

done



# echo "hogehoge"

}


汎用性を高める

最後に仕上げとして、任意のautohotkeyスクリプトで簡単に実装できるようにします。

すこしコードを変えたので使い勝手が上記のそれと変わりますが、気に入らなければ自分でコードいじってみてください


ahkprint(){

: <<<'
e.g. ahkprint
'

# tmpdird

stdin="$(</dev/stdin)"

: check AHKPRINT_STDOUT_FILE file and path/dir exist then handling error

[[ ! -f "${AHKPRINT_STDOUT_FILE}" ]] && {
  touch "${AHKPRINT_STDOUT_FILE}" || {
    printf "${red}E: Couldn't make the file." ; return 1 ;
  }
}

: make null the file
> "${AHKPRINT_STDOUT_FILE}"

cat << EOT > /mnt/c/_tmp/tmp.ahk

$( eval "cat << EOT
$( echo "${stdin}" )
EOT" )

FileAppend , %${AHKPRINT_STDOUT_IDENTIFIER}% , $( wslpathr "${AHKPRINT_STDOUT_FILE}" )
EOT

psl ahk $( wslpathr /mnt/c/_tmp/tmp.ahk )

: パイプ処理の場合に動かない。 ahkのファイル書き込み処理が終わる前にこちらが動いてしまっているものだと思われる

# cat "${AHKPRINT_STDOUT_FILE}"

: 上記の対策
: 数秒間ファイル書き込みがされるかどうかを検証。ファイル書き込みがされればそれをcatしてループを終了し、数秒間が過ぎた場合は変数およびファイルが空であるとしてループを終了する

tmpdate=$(date +"%s")

while [[ "$( echo $(( $( date +"%s" ) - ${tmpdate} )) )" -lt 3 ]] ;
do

[[ ! -z "$( cat "${AHKPRINT_STDOUT_FILE}" )" ]] && {
  cat "${AHKPRINT_STDOUT_FILE}" ; break ;
}

sleep 0.1

done


}

軽くまとめると、

  • AHKPRINT_STDOUT_FILE定数にはこの関数で使用するstdoutの値を一時的に置いておくファイルのパスを指定します
  • AHKPRINT_STDOUT_IDENTIFIER定数にはstdoutとして標準出力するautohotkey上の変数名を指定します。(デフォルト: var)
  • stdin、標準入力がautohotkeyのスクリプトコードそれ自体になります。それに加え、そのコードの内部にはbashの引数、変数を埋め込むことができます。(以下の様なことも可能)
  • 今言ったように変数的な意味合いでは、bash上の引数と変数をそれぞれ使うことができます。
  • stdout、標準出力したいデータは、autohotkeyスクリプト上でvar変数( またはAHKPRINT_STDOUT_IDENTIFIERの値、または${AHKPRINT_STDOUT_IDENTIFIER} )に格納します。

e.g.

[autohotkeyスクリプトを生成するコード] | ahkprint or ahkprint <<< "echo [autohotkeyスクリプト]"

(引数の代わりに変数を使う場合)


value=typora.exe

ahkprint <<< "$(cat <<'EOT'
WinGet, activeWinproc, ProcessName, A

WinActivate, ahk_exe ${value}
winActivateErrorLevel=%ErrorLevel%

WinWaitActive, ahk_exe ${value}

var = % activeWinproc . "  " . winActivateErrorLevel
EOT
)"

さて、上記コードをもとにして、以下のようなフレキシブルなコードで上記と同様のことが実装できます。


ahkprint "typora.exe" <<< "$(cat <<'EOT'
WinGet, activeWinproc, ProcessName, A

WinActivate, ahk_exe ${1}
winActivateErrorLevel=%ErrorLevel%

WinWaitActive, ahk_exe ${1}

var = % activeWinproc . "  " . winActivateErrorLevel
EOT
)"

以下に同じ理屈で量産した実用例を置いておきます。

現在のアクティブウィンドウを取得

getActiveWindowName(){

ahkprint ${@} <<< "WinGet, var, ProcessName, A"

}

使用例

[[ ! "$( getActiveWindowName )" == atom.exe ]] && { : アクティブウィンドウがatom.exeじゃないときにしたい処理here ; }

マウス座標を取得


getMousePos(){

  : e.g. getMousePos
  : e.g. getMousePos Relative

ahkprint ${1:-Screen} ${@:2} <<< "$( cat << 'EOT'
CoordMode, Mouse, ${1}
MouseGetPos, posX, posY
var = % posX . "  " . posY
EOT
)"

}

使用例

$  getMousePos Relative
998  510
$  getMousePos
991  923
$

ミュートならミュート解除

unmute(){

  : e.g. unmute


ahkprint ${@} <<< "$( cat << 'EOT'

;ミュートならミュート解除
SoundGet, MuteState, Master, Mute

if ErrorLevel
{
MsgBox, %ErrorLevel%
Return
}

prevMuteState = %MuteState%

if MuteState=On
{
MuteState=1
}
if MuteState=1
{
Send, {Volume_Mute}
}

SoundGet, currentMuteState, Master, Mute

var = % prevMuteState . "  " . currentMuteState

EOT
)"

}

使用例


$ unmute
Off  Off
$ unmute
On  Off
$

音量を取得

getVolume(){

  : e.g. getVolume

ahkprint ${@} <<< "$( cat << 'EOT'
SoundGet, var
EOT
)"

}

使用例

$ getVolume
6.999999

独自定義の関数とエイリアス

$ psl which ahk
C:\Program Files\AutoHotkey\AutoHotkeyU64.exe
$ psl which ahk^C
$ type psl wslpathr
psl is aliased to `/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe'
wslpathr is a function
wslpathr ()
{
    wslpath -w $(realpath $1)
}

まとめ

一応bashやその他プログラミング言語の外部コマンドから任意のブログラマブルなautohotkeyスクリプトを実行して結果を受け取る、というところまで実装してみました。

どこか抜けているところやもっといい方法などあればコメントなどで教えてくれたら嬉しいです。

avatar

Shou Arisaka

情報技術者 / Z世代プログラマー / SaaSアプリやSNSを開発
今すぐ話そう!

15歳でWordPressサイトを立ち上げ、ウェブ領域に足を踏み入れる。翌年にはRuby on Railsを用いたマイクロサービス開発に着手し、現在はデジタル庁を支えたNext.jsによるHP作成やSaaS開発のプロジェクトに携わりながら、React.js・Node.js・TypeScriptによるモダンなウェブアプリの個人開発を趣味でも行う。
フロントエンドからバックエンドまで一貫したアジャイルなフルスタック開発を得意とし、ウェブマーケティングや広告デザインも必要に応じて担当、広告運用・SEO対策・データ分析まで行う低コストかつ高品質な顧客体験の提供が好評。
国内外から200万人を超える人々に支えられ、9周年を迎えるITブログ「yuipro」の開発者、デザイナーでありライター。現在ベータ段階の自作SNS「nyapp.buzz」を日本一の国産SNSとするべく奮闘中。

Created with Fabric.js 5.2.4 何かご質問がありますか?