標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

Windows Terminal は管理者モードで直接起動できない(場合がある)

nyagos では管理者モードのコンソールを立ち上げる su という内蔵コマンドがある。これを Windows Terminal (wt.exe)に対応させたいと思ったが、どうしても妙なエラーが出て起動できない。

単なる自分のコードの不具合にも見えるが、PowerShell 単品でも簡単に再現できた。まず

powershell "start-process wt.exe"

だと普通のモードで Windows Terminal を起動できる。これを管理者モードで起動するように

powershell "start-process wt.exe -verb runas"

ではエラーになる。手続きが悪いかのように見えるが、wt.exe を cmd.exe に置き換えて

powershell "start-process cmd.exe -verb runas"

では、ちゃんと cmd.exe が管理者モードで起動する。なんでや!

そういうわけで仕方がないので、cmd.exe を間に噛ませて cmd.exe /c wt.exe ... で管理者モードを立ち上げるようにしたわけだが…

1日過ぎてから、ググってみたところ

あうあう(回避策も俺がやったのと対して変わらんみたいや…)

なお、スタートメニューの Windows Terminal アイコンを右クリック→「その他」→「管理者で実行」は大丈夫。よう分からん…

またバージョンタグが打たれていない最新版のバージョンを Go Modules で参照したい時

ブランチ名で指定したらよいようだ。

$ go get github.com/USER/REPOSITRY@master
go: github.com/USER/REPOSITRY master => v0.0.4-0.20200929012639-b0f19ff1ae2f
go: downloading github.com/USER/REPOSITRY v0.0.4-0.20200929012639-b0f19ff1ae2f

上に挙げた例では、レポジトリ github.com/USER/REPOSITRY には最新のタグとして v0.0.3 は存在するが、それに続くコミットにはまだタグがつけられていない。go get する時に @master をURLにつけてやったところ、存在しないタグ v0.0.4 +日付+コミットハッシュのついた形の指定に置き換えられた。

おそらく、その後更新されても多分自動で追随されることはないだろうが、とりあえずはこれで十分である。

追記

この go.mod を使ってるパッケージを参照している別のパッケージはどうなるねんと思ったら、go get -u でそちらのパッケージの go.mod も追随して、この新しいリビジョンを指すようになった。てっきり、コンフリクトエラーになるかと思ったのに、意外と賢い。

ReadConsoleInput の U+2000~U+2FFFの一部の文字に対する奇妙な挙動

そもそもの発端は、この issue:

∞ (U+221E) という文字が入力できないという話。どうも、Unicode の U+2000~U+2FFF の一部のコードを右クリックで Windows のコンソールにペーストすると、ReadConsoleInput で得られるデータが妙なことになるようだ(だが、それにも関わらず、CMD.EXE はうまいこと拾ってるんだよな)

それを確認するために、go-console-test という検証プログラムを作った。

まずは正常に入力される 「Ç (U+00C7)」を右クリックでペーストした結果:

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:0 VirtualScanCode:0 UnicodeChar:199 ControlKeyState:0}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:0 VirtualScanCode:0 UnicodeChar:199 ControlKeyState:0}

UnicodeChar フィールドに 199=0xC7 に対する KeyDown と KeyUp がペアで順に入ってくる。

次に問題の「∞ (U+221E)」。これがおかしい

KeyEventRecord: &{KeyDown:1 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:0 ControlKeyState:2}
KeyEventRecord: &{KeyDown:0 RepeatCount:1 VirtualKeyCode:18 VirtualScanCode:56 UnicodeChar:8734 ControlKeyState:0}

KeyDown の UnicodeChar がゼロになっているのだ。だが、KeyUp の時の UnicodeChar はちゃんと8734=0x221E が入っている。

報告者によると、⌂(U+2302)、⌐ (U+2310)、░(U+2591)、▒(U+2592)、▓(U+2593)も同じ傾向があるようだ。共通することは

  • KeyDown 時に UnicodeChar が 0、ControleKeyState=2 (左ALT)、VirtualKeyCode==0x12 (VK_MENU) というデータがくる
  • その KeyDown ~ KeyUp までに0個以上の謎の KeyDown イベントが入ることがある
  • 最後に来る KeyUp イベントについては、ちゃんと UnicodeChar に期待する Unicode が入ってくる。

とりあえず、エンドユーザには分からない話だから(彼は nyagos が1文字3バイト以上の UTF8文字を扱えないからだと勘違いしていて、なかなか失礼な話である)、とりあえず以下のようなアドホックな回避コードを go-tty にあてた。

  1. KeyDown された時に、左ALT (ContolKeyState=2) + VK_MENU (VirtualKeyCode==0x12) の時は、フラグをセットする
  2. KeyUp された時、普通はこれを無視するところだが、フラグがたっていて、かつ UnicodeChar != 0 の時はその UnicodeChar が入力されたと見なす。この後、当然だがフラグはクリアする

これで一応、同文字が普通にペーストできるようはなった。go-tty に対するプルリクも受理され、マージいただけたようだ。先生もさぞ困惑されたことだろう。ちょっと無理やりな対応コードで申し訳ないことである(リジェクトされても仕方ないと思っていた)。

しかし、これ、どう考えても合理的な挙動に見えない。ReadConsoleInput に、何か、理解できていない仕様があるのだろうなぁ

追記

どうも、このような挙動がまったく起こらない環境もあるようだ。自分の手元の環境では、

  • Windows 8.1 :問題なし(KeyDown時にも ∞ の Unicode 出てる)
  • Windows10 1909:問題あり(KeyDown時に ∞ の Unicode 出ない=ゼロになる)

でも、Windows10 で問題が出ていない環境もあるそうなので、OS のバージョンとも決めつけられない。どういう条件で発生するんだろう。

追々記

得られた情報を総合すると:

  • Windows 8.1 : OK
  • Windows 10
    • 1803 : OK
    • 1909 : NG (KeyDown で UnicodeChar がゼロになってしまう)
    • 2004 : NG (KeyDown で UnicodeChar がゼロになってしまう)

どうも、1803~1909 の間で、ReadConsoleInput の挙動が変わった疑いがありますね。
( k_takata さん、情報ありがとうございました (← 追々々記:お名前間違えてすみません。直しました) )

2024.05.07 追記

Windows11 Pro 21H2 では直っている模様

$ go-console-test.exe
Hit ESCAPE key to stop.
KeyDown:0 UnicodeChar:13(0xD) VirtualKeyCode:13(0xD) VirtualScanCode:28(0x1C) ControlKeyState:0(0b0)
KeyDown:1 UnicodeChar:8734(0x221E) VirtualKeyCode:0(0x0) VirtualScanCode:0(0x0) ControlKeyState:0(0b0)
KeyDown:0 UnicodeChar:8734(0x221E) VirtualKeyCode:0(0x0) VirtualScanCode:0(0x0) ControlKeyState:0(0b0)
KeyDown:1 UnicodeChar:27(0x1B) VirtualKeyCode:27(0x1B) VirtualScanCode:1(0x1) ControlKeyState:0(0b0)

go-tty に Merge してもらった回避用Fix、削除してもらった方がいいような気もする一方、巷にこの回避コードがまだ必要な Windows バージョンも残っているかもしれないことを考えると下手に戻すのも危いかもしれない。

また、Windows10 ではUNIX型仮想端末入力機能が実装されたので、そっちを使えという考え方もある。むしろ、もう検証が難しい Windows 8 までの対応を考えると、今動いているコードを下手に触るな原則もある。

悩ましい… (たぶん、何もできない可能性が高い)

(.NET検証)FolderBrowserDialogを連続してShowDialogすると2回目以降は最前面に表示されない

件名そのまま。Microsoft のページは、いつ行方不明になるか分からないので、バックアップとして記事をあげておく。

この Q&A ページの要旨は以下のとおり:

  1. 1回目のフォルダー選択ウインドウを閉じた時点で、システムが他所のウインドウをアクティベートしてしまうため、2回目のフォルダー選択ウインドウがそれよりも背後になってしまう。
  2. 対応策としては1回目のフォルダ―選択ウインドウを閉じた後に、親ウインドウをアクティベートして、DoEvent で処理させればよいらしい。
  3. だが、親ダイアログの Load イベント中でフォルダー選択ダイアログを表示させている場合、その中でまだ開かれていない親ウインドウをアクティベートすることはできない。
  4. そこで、Load イベントでやっていたことを Shown イベントで行うようにした上で、親ウインドウ(というか自ウインドウか)のアクティベートを行うようにする。

実際やってみたところ、フォルダー選択ダイアログ2つを表示する場所を Loadイベントから Shown イベント中に移動させただけで直ってしまった。まぁ、親ウインドウをプログラムではなく、システムがアクティベートするから直ったというところだろうか。

ShellExecute でネットワーク上の実行ファイルを呼び出そうとするとエラーになる

結論から言うと

The operation was canceled by the user

というエラーが発生する時は、ネットワークドライブとか、シンボリックリンクなどを経由せず、確実なUNCパスを使えという話です。

自分は VM 内の Windows の中で開発をよくするのですが、ツール類をいちいち VM にコピーするのが面倒なので、ネットワークドライブにパスを通して使っています。 が、一部のプログラムの起動が失敗します。条件を確認すると次のような場合のようです。

  • nyagos上のみ。CMD.EXE ではエラーにならない
  • GUI のプログラムである
  • ローカルディスクの実行ファイルではエラーにならない

nyagos では、GUI プログラムを起動する時は ShellExcute API を使います。試しに Lua の os.execute 関数を使って、CreateProcess API 経由で GUI プログラムを起動させようとすると、こちらは問題ありません。ShellExecute は Windowsエクスプローラーからクリックのと同じ起動方法なので、おそらくこれはセキュリティー的な制約なのでしょう。

これの回避方法が長い間分からなかったのですが、昨日、ようやくわかりました。その実行ファイルの真のフルパス(UNCパス)であれば、エラーが発生しないようです。

Go言語で真のフルパスを得るのは簡単で、"path/filepath".EvalSymlinks 関数を呼べば Ok です。

ということで、2年越しの issue をクローズできたのでした。めでたし、めでたし

Java版マインクラフトのデータを OneDrive へバックアップしよう

次のようなバッチファイルを OneDrive のフォルダーにおいて、実行する。 tar と Zstandard (Facebook の開発した圧縮プログラム)を使う

minecraft.cmd

move "%APPDATA%\.minecraft\screenshots\*.png" "%USERPROFILE%\OneDrive\画像\dotminecraft\."
tar -C "%APPDATA%" --exclude ".minecraft/backups/*.zip" -cvf - .minecraft | zstd > dotminecraft-%DATE:/=%.tar.zst

マインクラフトでは F2 キーで簡単にスクショが撮れる。が、このデータはずっとたまり続けてしまうので、バックアップ時に負荷になる。でも、ちゃんと残しておきたい。そこで画像ファイルだけは、OneDrive の別の画像フォルダーに逃しっぱなしにする。

また、.minecraft/backups には、マインクラフト自身がバージョンアップ前に取得するバックアップZIPがある。これが結構なサイズになるので、tar の --exclude オプションで排除しておく。

だが、exclude でちゃんと目的のファイルだけ除外されたのだろうか。zip という拡張子が指定されているので、関係ないファイルまで除外されていないだろうな。心配になったので、ドライランさせてログで比較だ。

tar -C "%APPDATA%" --exclude ".minecraft/backups/*.zip" -cvf - .minecraft >nul 2>exclude-test.log
tar -C "%APPDATA%" -cvf - .minecraft >nul 2>no-exclude-test.log
vim -d exclude-test.log no-exclude-test.log

OK だった!

Goで書いたファイルのコンバーターにファイル選択の GUI つける

仕事で使う月次データを日別に分解するコンバーターを Go で書いた。が、

.\month2daily [-o OUTPUT.zip] source.tsv

というコマンドラインのパラメータ、非開発者では使えんので。GUI が欲しい。

Walk 使おうかとも思ったが、manifest ファイルを作成しないといけないのがややこしいので、パス(今は要らんかもしれんけどね)

ローカルホストにウェブサーバー立てて、ブラウザでアクセス

  1. 元のコンバーターを、コマンドラインクライアント部分と、純粋にコンバートするだけの処理を別パッケージに分ける。
  2. net/http で、ウェブサーバーを立てる
    • get リクエストで、アップロード用のフォームを書いた html 出して、post リエクストでファイル受け取って、コンバートして返すだけ
  3. このままだと、本体を起動した後、ブラウザを別途起動するのが面倒くさいので、 https://github.com/toqueteos/webbrowser でブラウザも同時に起動するようにした。

OS標準のファイル選択ダイアログを使う

なんか、たかだかファイルの選択だけにウェブサーバーとブラウザを起動するのも大掛かりすぎると、作ってしまってから気がついた。こんなの OS標準のファイル選択ダイアログを呼んだ方が早いのでは( 判断が遅い!

syscall 使って自力で呼ぼうかと思ったが、既成のライブラリがあったので、それ使った → https://github.com/sqweek/dialog

成果物

zetamatta ちゃうけど、気にせんといてな

  • https://github.com/zat-kaoru-hayama/month2days/
    • main.go … コンバートロジック(ビジネスロジックだけ)
      • cmd/ … いろんな形式の実行ファイル
        • month2days/ … コマンドラインクライアント
        • month2daysweb/ … ウェブサーバー&ブラウザによるクライアント
        • month2daysgui/ … OSダイアログ出すクライアント
      • pkg/
        • webfilter/ … ファイルアップロード用のウェブ処理をやる部分を別途汎用パッケージ化して分離したやつ。でも、使わんやろなー

記事にしたけど、ほとんど自分用のメモやね