ぺんちゃん日記

食と歴史と IT と。 Web の旅人ぺんじろうが好奇心赴くままに彷徨います 。

クリップボードに転送するスクリプトをスタートアップに入れるとこける【AutoHotkey】。

f:id:yasushiito:20190403184928p:plain

Windows 起動時にメッセージボックスが表示されている。

私の autohotkey スクリプトクリップボードを多用するので可能な限り破壊しないようにできないだろうかと考えました。
そこで頻繁に利用される関数の中でクリップボードの内容をバックアップして帰り際に戻すようにしてみました。

それ以降 PC の起動時に「 操作に失敗した」メッセージボックスが表示されて実行に失敗するようになりました。
autohotkey スクリプトをスタートアップに登録して PC 起動時に実行されるようにしていましたからね。
クリップボードを操作できないタイミングで実行してしまったのでしょう。

ディレイしてみる。

クリップボード操作処理に入る処理の前にスリープを入れてタイミングをずらしてみました。
しかしこれは効果なし。
注意深く観察すると、 PC 起動時のロック画面で変化がありました。
これまではワンクリックすることでパスワード入力フォームが表示されていたのですが、何もしなくてもパスワードを入力を求められるようになりました。
スリープのタイミングをずらすとロック画面の状況も変化するので、PC 起動時にスクリプトが実行されてクリップボードを操作してしまっているのでしょう。
確かにこのタイミングでプログラムからクリップボードを操作できるのは色々ヤバそうです。

タスクマネージャから起動するように変更してみる。

スタートアップからの起動は順序的に早すぎるのかもしれない。
そこでスタートアップからの起動はやめてタスクマネージャーにタスクを追加します。
サインインした時にスクリプトを実行して常駐するようにタイミングを変えてみます。

この対策も効果なし。
相変わらずロック画面でパスワード入力待ちになるので、サインインしてしまっているのでしょう。
実行ユーザーは自分だけに絞っているのですけど何か間違ってるのかな。
サインインとロック画面は別物なのかもしれない。

ギブアップ。

結局回避する方法は見つからず、諸々の事情もあり AutoHotkey スクリプトの 実行は PC 起動後に手動で行うことになりました。
またつまらぬ記事を書いてしまった…。
インターネットさん、役に立たないゴミクズを撒き散らかしてしまってごめんね。

Visual Studio Code の Ctrl + D は押すたびに次の候補も検索して選択する。

Visual Studio CODE で作業中に詳細のわからない関数などに遭遇した時にスクロールロックキーの長押しで該当の単語を Google 検索できるような Auto HOT Key スクリプトを動かしています。
単語を選択するのがだるいので Ctrl + D のショートカットで単語を選択してからブラウザの検索ページにコピペするという仕様なのですが、時々検索ワードがダブっていることに気がつきました。
autohotkey スクリプトのバグだな、時間のある時に直さなきゃなと思ってはいたのですが、これといった実害もないので2週間ほど放置されていました。
今日時間が取れたので確認してみたところ、 ダブったりダブらなかったり、最終的には三つに分身したり不思議な現象に出会いました。
Visual Studio Code の編集画面をよく見ると、 Ctrl + D で選択した単語がいくつも反転していることに気がつきました。
発生するのは Ctrl + D キーを連続で押した時だけ。

マウスオンリーで操作しているといろんなところをクリックするから単語選択は自然と解除されるんですよ。
まあ言い訳は良しとして、原因はわかったので Ctrl + D キーをポチポチ押してみます。
すると、1度目はカーソルのある周辺の単語を選択。
以降はキーを押すたびに次の単語を検索して選択。
検索は通常の検索と同じ条件で次の候補を探します。
この時に前回の選択範囲も保持したまま次の単語を選択します。
単語を複数選択した状態でキー入力して編集すると、全ての選択範囲が修正されます。
置換したい時に便利ですね。


安心したところで今更ながら公式情報を調べてみます。
どうやら Ctrl + D は単語を選択するショートカットではなく、検索を複数 select セレクトするために用意された機能だったようです。
悩んだら公式にあたってリファレンス見ろよと言う話ですね。
ホンとプログラムは向いてないわ。

https://go.microsoft.com/fwlink/?linkid=832145go.microsoft.com

https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdfcode.visualstudio.com


心より反省しました。
Ctrl + D が単語選択なんてどこから情報を拾ってきたんだろう?
以前の記事を見ても書いてないなあ。
デタラメにショートカットを叩いて発見したのだろうか?

空文字列と if 文の書き方を一度調べてみる【AutoHotkey】。

f:id:yasushiito:20190403184928p:plain

全然わかってない。

AutoHotkeyを使い始めて半年。
未だに その動作に悩まされてはまることがあります。
特に if 文と文字列の扱いについては、なんとなく雰囲気で書いている感があって動いていないコードを知らず知らずのうちに書き散らかしていることもあります。
これではいかんと思い直したので実際に動く行動書きながら再学習したいと思います。
テストに使ったコードは記事の一番後ろに掲載することにします。
途中で登場する断片的なコードは抜粋したもので、そこだけをコピペしても動きません。
ご了承ください。
それでは参りたいと思います。

空の文字列を変数に代入したい。

まず分からないのが 変数に空文字列が入っていたりいなかったりする場合の if 文の動作です。
そこで 未定義の変数を条件に与えたらどうなるか試してみます。
念のために初期化した変数も条件に与えてみましょう。

def := True
if def
    MsgBox, , 変数真偽値でif。, 変数が初期化されている。 ,5
if !undef
    MsgBox, , 変数真偽値でif。, 初期化されていない変数をnotで判定できるか。 ,5
if !""
    MsgBox, , 真偽値でif。, 空文字列は真か偽か。 ,5

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

問題なく動きます。
メッセージボックスは3つとも表示されました。
一度も宣言されていない変数名を与えても文法エラーとはならずに偽の値として判断されるようです。
なおビックリマークは真偽を反転させる not の意味です。
空の文字列は偽とされることがわかります。

空の文字列を変数に代入したい。

次に空文字列の代入です。
代入の時に引用符がいるのかいらないのか、いまだにわかっていません。
代入した結果をメッセージボックスを表示して確認してみます。
空文字列だけでなく、長さのある文字列を代入した場合にも引用符の必要性の有無を確認してみます。

initstr :=
abc := "abcdefg"
c := "c"
emptystr := ""
MsgBox , , 代入 右側に何もない時 , % initstr ,5
;次の行の 影響はない。

MsgBox, , 代入 “”で囲んで代入した場合。, % abc ,5
;代入された文字列には引用符が含まれていないことを確認。

MsgBox, , 代入 “”で代入した場合。, % emptystr ,5
;代入された文字列には引用符が含まれていないことを確認。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

変数 abc だけ文字が表示されました。
代入される値は引用符を付けても付けなくても空文字列になるようです。

文字列のサイズを調べる。

ところで私が確認している変数の内容は本当に空文字列でしょうか?
空白が入っているのに視覚的に認知できていないだけではないでしょうか。
文字列の長さを確認することで、 本当に空文字列と言うことを確認します。
実は文字列長の測定も自信がありません。
空文字列の文字の長さが0であることを確認したいです。
文字列のサイズの測り方は複数あるので挙動の違いがないか確認してみます。

MsgBox, , 文字列の長さ確認。(StrLen)。, % StrLen(abc),5
;文字列の長さを測る関数は正しく動いている。

StringLen, ln, c
MsgBox, , 文字列の長さ確認。(StringLen), % ln ,5
;関数ではなくコマンドで書いても結果は同じ。

MsgBox, , 文字列の長さでinistrの確認。, % StrLen(initstr) ,5
MsgBox, , 文字列の長さでemptystrの確認。, % StrLen(emptystr) ,5
MsgBox, , 文字列の長さでabcの確認。, % StrLen(abc) ,5
MsgBox, , 未定義の変数を文字列の長さで計った時。, % StrLen(undef) ,5
;空文字列またはnull? 的な場合は長さ0として扱われることを確認。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

上記のコードで確認していたところ、 期待通り長さ0を返してくれました。
代入される値が 引用符ありなしどちらも0です。
未定義の変数の長さもゼロです。
もちろん abc は7です。

長さの測り方は strlen と stringlen があります。
前者は関数で後者はコマンドです。
関数は戻り値があるので結果を即座に使えるのでメッセージボックスに表示するにしても一行で書けます。
コマンドでは受け取る変数を指定して 結果を受け取るので1行では書けません。
documentによると結果はどちらも同じのようなので、関数で呼び出した方がスマートにかけますね。
これからは文字列の長さは strlen で行うことにします。

ahkwiki.net

ahkwiki.net

If 文で空の文字列を条件分けする。

いよいよ本丸の空文字列が代入された変数の条件判定です。

これまでの経験で、かっこがあったりなかったり、引用符があったりなかったりで結果が変わってくることが分かっています。
何が正解か分からなくなって不信感しかありません。
まずはかっこありなしと引用符ありなしのパターンを確認してみます。

;If 文で空文字列を判定する時の書き方。
if initstr =
    MsgBox, , 空文字列を判定, 右側に何も書かない。,5
if initstr = ""
    MsgBox, , 空文字列を判定,  引用符で囲むと不成立。,5
;括弧で囲まない場合=の右側はどんなときも文字列として扱われる。
;引用符は書く必要がないというか書いてはいけない。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

かっこがない場合は引用符を書いてはいけません。

if (initstr =)
    MsgBox, , 括弧で囲んで空文字列を判定, 右側に何も書かない不成立。,5
if (initstr = "")
    MsgBox, , 括弧で囲んで空文字列を判定,  引用符で囲むと成立。,5
;括弧で囲んだ場合、 右辺が文字列とは限らないので引用符が必要。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

かっこがある場合は引用符を書かなければいけません。

これテストに出ます。

If 文で真偽条件で分岐する。

次は if 文を真偽値で判定した時の格好の有り無しについてです。

if !initstr
    MsgBox, , 空文字列を判定,notでも条件は成立。,5
if (!initstr)
    MsgBox, , 括弧で囲んで空文字列を判定, notでも条件は成立。,5
;真偽だけで=がない場合はかっこがあってもなくても問題ない。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

こちらはかっこがあってもなくても良いです。
十分にシンプルな状態ならIf 文が括弧の有無で動作が変わるということはないということですね。

複雑な IF 文の書き方を調べる。

そして条件が複雑になった場合の確認です。
これも過去の経験でかっこがないと動かなかった記憶があります。
その時は or でつないだ条件でしたが、 and でも同じはずです。
引用符の有無で結果がどのようになるか調べたいと思います。

;条件が複数になるなど if 文の中が複雑になる時の書き方。
if 1 = 1 and initstr =
    MsgBox, , and で繋ぐ複雑な判定。, 右側に何も書かない時不成立。,5
;かっこがついていない時は比較として装備されるので=の右側は文字列扱い。
;つまり次のように判定される。
;1 = "1 and initstr ="
if 1 = 1 and initstr =""
    MsgBox, , and で繋ぐ複雑な判定。, 引用符で囲んでもうまくいかない。,5
;この場合も同様に次のように判定される。
;1 = "1 and initstr ="""

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

かっこがない場合はどちらも成立できませんでした。

if (1 = 1 and initstr= )
    MsgBox, , and で繋ぐ複雑な判定。, かっこを追加する。右側には何も書かない。,5
;括弧で囲まれているのでカッコ内を実行してから、その結果を真偽値として判定する。
;二つの条件を and でつないで判定している。
;=の右側は文字列である保証はないので引用符が必要なのはこれまでのテストで確認済み。

if (1 = 1 and initstr= "")
    MsgBox, , and で繋ぐ複雑な判定。, 括弧で囲んで引用句を使うと OK。,5
;括弧で囲まれているので instr の右側に引用符を入れることで空文字列と比較できる。

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

かっこがある時は引用符を付けた場合だけ成立しました。
なんでこれだけ動くのだろう?

憮然としたとはまさにこのことで、納得いきませんけど複雑な条件を与える場合は括弧で囲んで引用符を記述することで期待通りに条件分岐できます。
これは確実にテストに出るでしょう。

念のため変数の内容が空の文字列ではなく、内容が詰まっている場合も確認してみます。

;念のため空文字列ではない時も試してみる。
if 1 = 1 and abc = abcdefg
    MsgBox, , and で繋ぐabc判定。, 右側の文字列を引用符で囲まない時不成立。,5

if 1 = 1 and abc = "abcdefg"
    MsgBox, , and で繋ぐabc判定。, 右側を引用符で囲むとき不成立。,5

if (1 = 1 and abc = abcdefg)
    MsgBox, , and で繋ぐabc判定。, 括弧で囲んで引用符では囲まない時不成立。,5

if (1 = 1 and abc = "abcdefg")
    MsgBox, , and で繋ぐabc判定。, 括弧で囲んで引用符でも囲むときok,5

myahk/teststr.ahk at master · yasushiito/myahk · GitHub

こちらの場合も、括弧で囲んで引用符を明記した場合のみ条件が成立します。

考察してみる。

頭悪いのでdocumentを読んでもわかんないんですよね。
何度読んでもわからない。
ソースコードを追いかければ分かるのかもしれませんけど、そこまでの情熱をありません。
そこで自分なりに考えます。

まずAutoHotkeyには文字列型とか数値型の違いはありません。
全ては文字列として扱われます。
変数に数値を代入して比較するコードがあるとします。

if age > 19

人間からは、 変数の中の数値を参照して比較しているように見えますが、変数の中身は文字列"19"と比較されています。
AutoHotkeyは内部的には、変数に保存されている文字列の内容を解釈して、なんとなく数字のようなら文字列を数値化して比較するのでしょう。
だから空文字列または未定義の変数を真偽値として条件分けする場合でも 、一度内容を解析するので両者ともに同じ扱いになるということでしょうか。
なおAutoHotkeyでは、真の値は TRUE だけではなく、数値の0も含まれます。

If 文の複雑な条件の謎の動きですが、少しわかってきました。
if 文は二つの値を比較することしかできないということです。

if left = right

最初に出現したイコールまたは不等号の右と左を分けて、両者を比較するだけというシンプルな構造。
しかもこの時に右側は文字列であるというお約束付き。
内部的にはその文字列を一度解釈して、数値ぽければ数値として比較して、文字列のようなら文字列として比較するという動作をしているようです(もちろん私の想像ですよ?
式の右側は必ず文字列であると決まっているので、引用符は必要ありません。
ここで引用符を書いてしまうと、それも文字列の一部だと認識されます。

つまり、次のような条件文は人間から読むと二つの条件が組み合わさっていますが、

if 1 = 1 and initstr =

AutoHotkeyにとっては文字列比較の条件ひとつだけに見えます。

if 1 = "1 and initstr ="

コメントにもありますが、そんな条件式成立するわけないわ。


ただし if 文の中を括弧で囲むと話は変わってきます。
かっこで囲まれている時は、1度括弧の中身を実行してから、その結果を真偽値で判定します。
イメージとしてはこんな感じです。

a := 1 = 1
b := initstr =""
tmp := a and b
if tmp

一度実行されるということは、 必ず文字列であるというお約束は適用されないことを意味します。
=の右側は変数かもしれないし文字列かもしれない。
この場合は、文字列であることを明示するために引用符が必要です。
シンプルな if 文でも、かっこがあるときは引用符が必要だったのと同じです。

なんかやっとわかってきたぞ。

まとめ。

  • 文字列の代入は:=を使って引用符で囲む。
  • 文字列の長さを調べるのは strlen 関数。
  • if 文は文字列の比較。
  • 式の右辺は文字列として処理される。
  • if 文の中の式が複雑になった時はかっこで囲んで式の結果を判定させる。

それではテストに使ったソースコードです。


複数開かれている Chrome の中から必要なウインドウを探す【AutoHotkey】。

f:id:yasushiito:20190403184928p:plain

操作したい Chrome ウィンドウはどれ?

AutoHotkeyGoogle Chrome 操作すると何かと捗りますよね?
私は Chrome で表示されている AmazonYouTube のページに対してゴニョゴニョと操作することで楽させてもらっています。
AutoHotkey スクリプトから開いているウインドウを探して、ウィンドウタイトルの末尾が Google Chrome で終わるものを Chrome ウィンドウとして操作しています。
そこまでは良いですが、 Chrome は何枚でもウインドウを開けるので 見つけたウインドウが操作したいものである保証はないのが困ったところ。
そこで表示中の URL を取得して 操作したいウィンドウを探します。
ところが Chrome は何枚でもタブを開けるので見つけたウインドウが操作したい定時である保証はないのが困ったところ。
そこで Ctrl + tab でタブを順繰りに回しながら全てのタブで表示される URL を取得して操作したいウィンドウを探します。
これで確実に見つけることはできますが、開いているタブの枚数によってはとても遅い。
頻繁に使う機能では待っていられません。
もっと効率よく探すことはできないだろうか?

Chrome 拡張で解決する。

URL 取得系の chrome 拡張の中には 開いているタブ全ての URL をまとめて取得できるものがあります。
大抵は改行区切りでクリップボードに転送してくれるので、文字列検索で欲しいものがあるかないかだけでも判別できれば大幅に処理速度が改善します。
私は URL を取得する Chrome 拡張を二つインストールして、 一つは URL をまとめて取得できるもの、もう一つはカレントタブだけを取得するものと使い分けています。
これらはキーボードショートカットで起動できるようにしていますが、前者は ALT + SHIFT + C、 後者は ALT + C で割り当てています。
キーボードショートカット Ctrl の組み合わせを割り当てることもできますが、入力フォームで編集中の場合などに反応してくれないことがあるので、 ALT での組み合わせを推奨します。

ウィンドウの使い分け。

私は Chrome を2枚開いて使い分けています。
一つは Google documentを開いて音声入力するためのもので音声入力ウインドウと呼んでいます。
もう一つはブログを編集したりネットサーフィンするためのもので、作業用ウインドウと呼んでいます。
Autohotkey スクリプト側からは、 ウィンドウで開いているページの URL をまとめて取得して音声入力documentが開かれていれば音声入力ウィンドウと認識します。
また、 URL まとめて取得でGoogle ドライブのホームディレクトリを開いているウィンドウが見つかれば、それを作業用ウィンドウと認識します。
特定の URL を含んでいるかどうかで切り分けを行うので、ウィンドウの判別にはパラメータに特定の URL を渡す必要があります。
この URL は ini ファイルに保存されていて、その都度ロードして利用されます。

実際に使っているコードはこんな感じです。

カレントタブの URL を取得します。
Chrome だけでなく Firefox でも動作するように、どちらにブラウザの ALT +Cで取得します。

  
getbrowserurl(retfocus = False){
    ; キーボードショートカットでアドオンまたは Extension を呼び出して URL を取得する。
    ;調べたいブラウザを アクティブにしてから呼び出してください。
    ;
    ; Google Chrome ではURL をクリップボードにコピーで行う。
    ;https://chrome.google.com/webstore/detail/copy-url-to-clipboard/miancenhdlkbmjmhlginhaaepbdnlllc?hl=ja
    ;設定は ALT + C で選択中のタブのみをプレーンテキストでコピーするようにしておくこと。
    ;Firefox ではCopyTabTitleUrlアドオンを利用する。
    ;https://addons.mozilla.org/ja/firefox/addon/copytabtitleurl/
    ;ALT + C で URL のみをクリップボードにコピーできるように設定しておく。
    
    ;Chrome の設定ページなどChrome 拡張がコピペしてくれない URL があるので警告メッセージを設定しておく。
    Clipboard := "URL の取得に失敗しました。"
    Send,!c
    ;複数タブの URL をコピーできるタイプのエクステンションは改行コードを含むことがあるので削除しておく。
    Clipboard := RegExReplace(Clipboard, "\n", "")
    Clipboard := RegExReplace(Clipboard, "\r", "")
    return Clipboard
}

myahk/getbrowserurl.ahk at master · yasushiito/myahk · GitHub

作業用ウィンドウの探索です。
ウィンドウ見つけたらグローバル変数に保存して、以降の無駄な処理をスキップできるようにしています。
こちらは Chrome 専用です。
ALT + SHIFT + C で全てのタブの URL をまとめて取得します。

; Google Chrome で開かれた作業用ウインドウを URL で探す。
;第1パラメータで与えられた URL を開いているのが作業用ウィンドウである。
detectwork(url){
    global work
    IfWinExist, ahk_id %work%
        Return work
    work := 0
    ;念のためクリップボードをバックアップしておく。
    cb := Clipboard
    ;全てのウィンドウを調べる。
    WinGet, windows, list
    loop ,%windows%
    {
        ;ウィンドウタイトルの末尾を調べて Chrome だけに対して処理を行う。
        idstr := "ahk_id " . windows%A_Index%
        WinGetTitle,title,%idstr%
        pos := RegExMatch(title,"- Google Chrome$")
        if pos > 0
        {
            WinGet,wid,ID,%idstr%
            ;Chrome をアクティブにしてキーボードショートカットから URL を取得する。
            WinActivate,ahk_id %wid%
            ;Chrome Extension Copy all URLs を起動して ウィンドウで開いているすべてのタブの URL を取得する。
            Send, !+c
            ;作業ウィンドウを特定付ける URL が含まれてイルカ調べる。
            StringGetPos, pos, Clipboard, %url%
            if ErrorLevel = 0
            {
                work := wid
                Break
            }
        }
    }
    ;クリップボードにバックアップを返す。
    Clipboard := cb
    Return work

myahk/detectwork.ahk at master · yasushiito/myahk · GitHub

音声入力ウィンドウ探索です。
こちらも全く同様の作りですね。
いじってるグローバル変数が違うだけ。

; Google Chrome で開かれた音声入力エディタを URL で探す。
;第1パラメータで与えられた URL を開いているのが音声エディタのウィンドウである。
detecteditor(url){
    global editor
    IfWinExist, ahk_id %editor%
        Return editor
    editor := 0
    ;念のためクリップボードをバックアップしておく。
    cb := Clipboard
    WinGet, windows, list
    ;全てのウィンドウを調べる。
    loop ,%windows%
    {
        idstr := "ahk_id " . windows%A_Index%
        ;ウィンドウタイトルの末尾を調べて Chrome だけに対して処理を行う。
        WinGetTitle,title,%idstr%
        pos := RegExMatch(title,"- Google Chrome$")
        if pos > 0
        {
            WinGet,wid,ID,%idstr%
            ;Chrome をアクティブにしてキーボードショートカットから URL を取得する。
            WinActivate,ahk_id %wid%
            ;Chrome Extension Copy all URLs を起動して ウィンドウで開いているすべてのタブの URL を取得する。
            Send, !+c
            ;作業ウインドウを特定付ける URL を開いているWindows であれば作業用ウィンドウとする。
            StringGetPos, pos, Clipboard, %url%
            if ErrorLevel = 0
            {
                editor := wid
                Break
            }
        }
    }
    ;クリップボードにバックアップを返す。
    Clipboard := cb
    return editor
}

myahk/detecteditor.ahk at master · yasushiito/myahk · GitHub

ini ファイルの読み込みです。

これまでに試してみたこと。

URL バーからコピペする。
yasushiito.hatenablog.com

Chrome 拡張探して使う。
yasushiito.hatenablog.com



はてなブログの編集フォームのデザインが変わったのでエントリー開始スクリプトを修正した 改余分なバックアップをクリアする【AutoHotkey】。

f:id:yasushiito:20190403184928p:plain
Chrome のウインドウを判定する処理を変更したことによって発生した大規模修正の動作チェックを行っていたら、ブログの記事を新規作成するスクリプトが動かなくなってました。

タイトルをコピペできない症状でした。

これは大規模修正の際にバグを入れ込んだなと判断したのでソースコードとにらめっこ。
特におかしい所もなく、スリープの時間をいじるくらいしか問題箇所を特定できないくらい。
それで30分程度、あっちのスリープを足したり引いたり、クリップボードの内容を確認してみたり。
どうしても治らないので腰を据えて手動でキー送信してみたところ、 SHIFT + tabでタイトルにフォーカスが移らないことを発見。
よく見たら「0分前に保存されたバックアップの内容を挿入する」というような、これまでになかったメッセージが表示されていて、その横に「クリア」 というボタンが追加されていました。

f:id:yasushiito:20190910102019p:plain

タイトルにフォーカスを移すつもりが、こいつに移動させてコピペしていたようです。
そりゃコピペできるわけがない。

そうとわかれば解決法はとても簡単。
SHIFT + tab を2回送信してフォーカスをタイトルまで持っていくだけ。
つまんないことで悩んだな。
これにて一件落着…… と行けば良かったんですけどね。
今朝もう一度、動作確認のために編集フォームを開いてみたのですが、 今日はない!
昨日のメッセージは夢か幻だったか?

はてなブログの編集フォームをいったりきたりして動作の様子を観察してみると 編集した記事を保存しないままページを閉じた時に 現れることがわかりました。
トラブル発生時に書きかけの記事が ロストしないようにバックアップを保存する仕組みをはてなブログが備えているということですね。

つまり状況に応じてタイトルへのフォーカス移動が tab キー1回だったり2回だったり変化するわけです。
こいつは弱ったの。
スクリプト側からは画像認識でもしない限り、バックアップ復旧のメッセージがあるかないかなんてわからない。

そこで考えた作戦は、入力フィールドでなければペーストしても無視されるだろう作戦です。
1回 SHIFT + tab でフォーカスを移動させた後にダメ元でペーストして、念のためもう一度シフト+タブで移動させてペーストさせて 見るのです。
バックアップクリアのリンクに対して Ctrl + V を送ってもショートカットキーは無視されて何も起きません。
また、バックアップ復元のメッセージがなかったとしても、行き過ぎた先はプレビューボタンなので、やはり Ctrl + V は無視されます。
我ながら姑息な手を考えたものだ。

と実装したのは良いですが、 編集フォームに何らかの編集を加えるとバックアップ復元のメッセージは消えてしまいます。
消えてしまうと、フォーカスがストップする場所もも消えてしまうので tab キーで本文の入力フィールドに戻す時に失敗してしまいます。
編集して一呼吸の間にメッセージが消えるので、タイミング次第で失敗することがあります。
こいつは困ったぜ。

どうしても無理な時は禁断の画像認識を使うまでなんですが、出来る限り避けたいですね。

そこで次に考えたのが、タイトルの入力フィールドに enter キーを送っても無視されるだろう作戦です。

なんだよその作戦名は…。

本文の入力フィールドから SHIFT + tab でフォーカスを移動させて enter キーを送信します。
もしバックアップ復元のメッセージがなかったら、フォーカスはタイトルに移動しているはずです。
ここでエンターキーを送ると入力フォーカスは本文に戻されます。
また、バックアップ復元のメッセージがある場合、フォーカスをバックアップのクリアボタンに移動しています。
ここでエンターキーを送るとバックアップはクリアされて 入力フォーカスは失われます。
いずれの場合も 、この状況から SHIFT + tab キーを送信すれば、バックアップ復元メッセージは消えた状態でタイトルをフォーカスしている状態に なるんです。

ということで、ここで満を持して タイトルをペーストして tab で本文に入力フォーカスを移動させれば期待通りです。
バックアップ復元 Message のクリアは新規エントリーを開いた直後にやりましょうか。
スクリプトにするとこんな感じです。

    ; 新規エントリページを開く。
    Clipboard := "http://blog.hatena.ne.jp/" . hatena . "/" . hatena . ".hatenablog.com/edit"
    Send, ^v
    Send,{enter}
    Sleep 3000
    ;バックアップを復元するメッセージが表示されていることがあるので 復元ボタンに移動。
    ;メッセージがない場合はタイトルに移動して enter を空打ちして フォーカスを本部に移動させる。
    ;ここで SHIFT + tab 入力すれば必ず入力フォーカスがタイトルに移動しているので不確定要素がなくなる。
    Send,+{Tab}
    Sleep 100
    Send,{enter}
    Sleep 100

myahk/blogentry.ahk at master · yasushiito/myahk · GitHub


記事をアップロードする前に問題に気が付いたから助かった。


Chrome で開いているタブの URL を取得して作業ウインドウを判定する【AutoHotkey】。

f:id:yasushiito:20190403184928p:plain

これまでの経緯。

タイトルだけでは意味が分かりませんよね?
私は Google Chrome のウィンドウを2枚開いて、一枚は Google documentによる音声入力ウィンドウとして使い、もう一枚は調べ物をしたりブログ執筆などの作業ウィンドウとして使っています。

f:id:yasushiito:20190909102059p:plain

普段はこんな感じで二つの画面を行き来しながら作業しています。
1日のほとんどはこの二つのウィンドウで進んでいきますね。
作業の中でAuto HOT Key からChrome を操作することが結構あります。
例えば、 はてブしたり検索する時には作業ウィンドウを使います。
音声入力されたテキストを取り込むことも多いです。
Auto HOT Key から chrome を使い分けるためにウィンドウの判別処理を用意しています。
その判別処理が「音声入力document開いていれば音声入力ウィンドウ」「それ以外は作業ウィンドウ」といった具合に。

            pos := RegExMatch(title,"音声入力用")
            if pos > 0
            {
                WinGet,editor,ID,%idstr%
            }
            else
            {
                WinGet,work,ID,%idstr%
            }

myahk/detectchrome.ahk at master · yasushiito/myahk

問題がある。

それ以外なら作業Windowって……。
これだと 3枚以上の Chrome ウィンドウを開いたときに期待する処理になりません。
誰が見てもこのソースコードはまずいっていますよ。
過去ログを振り返ってみれば、問題のコードが作りこまれたのはこの辺ですね。
Chrome ウィンドウは何枚も開くようなものではないので実害はないのですが、こんないい加減な処理ではソースコードを眺めるたびに凹むので何枚開いても動くようにしたいです。

この問題を避けるために、 Ctrl + tab で一つ一つ開いているタブを確認して作業ウインドウを見つける処理も書いています。
しかし、今開いているタブの情報を使って何らかの処理をしたいような場合には、タブを切り替えるわけにはいきません。
タブを一切動かさずに全てのタブの情報を取得できれば良いのですけど……。

Chrome 拡張が使えるんじゃない?

以前に導入した Chrome Extension Copy All URLs+を使えば開いているタブの URL を改行区切りで一気に取得できます。

でも開いているタブの URL だけを取得したいこともある。
Copy all URL +は1ペアのキーボードショートカットと動作しか対応していないので、 全タブの情報取得に割り当ててしまうとカレントタブの URL だけを取得する手立てがなくなってしまいます。
状況に合わせて URL 取得の方法を選択できたらなあ。

それならもう一つ別の拡張を入れれば良いのでは?

その手があった。

URL をクリップボードにコピーというそのまんまのネーミングの Chrome 拡張があるので、これをインストールして ALT + C に割り当てて、カレントタブの URL 取得はこいつを使うことにします。

問題のコードが作りこまれたのはこの辺

そしてこれまで使っていたcopy all URLs+の方は ALT + SHIFT + C のショートカットを割り当てて、 ウィンドウで開いているタブの URL を全て取得できるようにします。

このエクステンション、これまで適当に使っていましたが、 オプションの意味がやっとわかりました。

f:id:yasushiito:20190909102158p:plain

  • Copy highlighted tabs only

これをチェックしておくとカレントタブの URL だけを取得します。
Chrome は Ctrl を押しながらタブをクリックすると複数の任意のタブを選択することができます。
その場合もこのオプションをチェックしてあれば、選択されたタブだけを URL 取得できます。

これをチェックしておくと全ての Chrome ウィンドウのタブの URL を取得します。
Chrome はいくつもウインドウを開けるのですが、このこのオプションのチェックが外れている場合は利用しているウィンドウのタブだけを URL の取得対象とします。
このオプションがチェックされていれば、すべてのウィンドウで開かれているタブを一切合切かき集めて URL 取得します。

今回の私は利用中のウィンドウのすべてのタブの URL を取得したいので、どちらもチェックを外しておきます。

これで ALT + C を入力すればカレントタブの URL を、 ALT + SHIFT + C を入力すれば全てのタブをの URL を取得できるようになりました。

作業ウインドウを判定する。

クリップボードを空にして Chrome ウィンドウに対して ALT + SHIFT + C を 送ります。
クリップボードの中身を確認して作業ウィンドウ特有のページの URL を文字列検索します。
私の場合は作業ウィンドウでは必ず Google ドライブのホームページを開いているので、その URL が見つかれば作業ウィンドウということになります。

今日のところはこれまでにしておきます。

ちなみにこの記事は入院前に書きためた1ヶ月以上前の内容です。



千年戦争アイギス ストーリーミッションは狂気の研究成果。

魔神と戦いたいがために続けていると言える千年戦争アイギスですが、新しくストーリーミッションが追加とされたら挑戦するのは当然です。
ストーリーミッション、イベントと違って期間内にどうにかしなければならないものではありませんので難易度もそれなりに高いと。
特に最近はその傾向が顕著で半ばクソゲーと 化しております。
こんな凶悪な敵を倒すのマジ無理。
幸いにも強力なブラックユニットガチャで迎えているので何とかなりますが無課金プレイヤーがクリアできるものなのだろうかこの難易度。
あの凶悪なデーモンは遠距離では魔法攻撃、ブロックされた時は強力な物理攻撃を連射するようです。
攻撃が物理か魔法か区別つけなかったのでコネさんを差し込んでみたらワンパンでキャーって言ったので 対策の打ちようがなく大混乱しました。
時間を止めている間にスキル発動させれば受けることができましたけどスマートなやり方とは思えないですね。
ガーディアンを差し込んでヒーラーでしっかり支えればギリギリ耐えられるようにはなっていますね。
途中で鍛冶屋のバフが入るので安定しますけど、確率の物理回避が入ってなければ危うかったかもしれませんね。
抱えることができれば、後は強力な攻撃をねじ込んで崩すだけです。
だけですとさらっと言っていますが、バリア付き装甲車の爆風で王子が死にそうになっているのは内緒です。
高速連射デーモンの攻撃を受けるユニットはまあまあいそうなので 大丈夫だと思います。