tmytのらくがき

個人の日記レベルです

C++/Win32からSurface Dialをさわってみたよ

TL;DR

  • C++/Win32からRadialControllerなんて触るもんじゃない
  • ABI::Windows::Foundation::Collectionsに対する実装はSDKに含まれない
  • MIDLコンパイラ/ns_prefixが必須

AzureaでSurface Dialを扱えるプラグインを書きました

Surface Studioと同時に発表されたSurface Dial、回せて、クリックできるだけのデバイスなのです。 これがMicrosoft Global Summitへ参加したタイミングでちょうど発売されて運よく入手することができたので、 UWP*1も、WPF*2も、やってる人がすでにいるので面白そうなのでC++から触ってみました。

ただただ触ってみるだけじゃ面白くないので、せっかくだから2010年にWindows Mobile 5/6向けにリリースしたTwitterクライアントでSurface Dialが使えるようになるプラグインを書きました。

Surface Dialを制御するCOMオブジェクトを実装して、さらにそれをJScriptから制御してるって感じです。

gist.github.com

デスクトップアプリからSurface Dialを操作する

Surface DialをはじめとするRadialControllerははWinRT経由でOSに統合されているので、UWPからならとても簡単に扱えます。たとえばこんな感じに。

var controller = RadialController.CreateForCurrentView();
controller.RotationChanged += (sender, e) => { }
controller.ButtonClicked += (sender, e) => { }

このAPIですが、シグネチャを見てわかるように、UWPのViewを必要としています。 しかし、デスクトップアプリからだとViewが存在しないのでウィンドウハンドルを使ってインスタンスを生成できるなにがしが必要になってきます。 といっても、.NETからならそれほど大変なものでもなく、COMオブジェクトを経由してインスタンスを生成することができます。*3

[System.Runtime.InteropServices.Guid("1B0535C9-57AD-45C1-9D79-AD5C34360513")]
[System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIInspectable)]
interface IRadialControllerInterop
{
    RadialController CreateForWindow(
    IntPtr hwnd,
    [System.Runtime.InteropServices.In]ref Guid riid);
}

これでインスタンスを取得すればだいたいどうにかなります。ですが、なかなかそれなりにつらいよ。ってことが以下で共有されています。

grabacr.net

ところがどっこい、C++だともう少したくさんめんどい。まず、ヘッダファイルとかはWindows SDK 14393に含まれています。 デスクトップC#だと自前定義していたIRadialControllerInteropもSDKで定義があります。なので、インスタンスの取得はこういう感じ。

// IRadialControllerIntropを取得して
Microsoft::WRL::ComPtr<IRadialControllerInterop> controllerIntrop;
Windows::Foundation::GetActivationFactory(
    HStringReference(RuntimeClass_Windows_UI_Input_RadialController).Get(),
    &controllerInterop);

// HWNDに対するIRadialControllerを取得する
Microsoft::WRL::ComPtr<ABI::Windows::UI::Input::IRadialController> controller;
controllerInterop->CreateForWindow(hwnd, IID_PPV_ARGS(&controller));

イベントハンドラの追加がめんどくさめなことを除けば、だいたい同じ感じでどうにかなります。

システム定義のアイコンを消したい

Surface Dialを長押ししたときにでるメニューは、デフォルトだとシステム定義のアイコンがいくつかならんでます。

https://i-msdn.sec.s-msft.com/en-us/windows/uwp/input-and-devices/images/windows-wheel/surface-dial-menu-offscreen.png

これを消したりするには、RadialControllerConfiguration::SetDefaultMenuItemsを呼び出すことでできます。 ちなみにMSDNに特に書いてないですが、カスタムアイコンを追加せずにシステムアイコンをすべて消そうとすると、呼び出しは失敗して システム設定された状態にリセットされます。どうやら仕様だそうです。

SetDefaultMenuItemsは、.NETの世界からだとIEnumerable<RadialControllerSystemMenuItemKind>を引数として取るので、 new List<RadialControllerSystemMenuItemKind>();とか適当にやればインスタンスが作れるのですが、C++だと微妙に話が違ってきます。

C++での引数はABI::Windows::Foundation::Collections::IVector<ABI::Windows::UI::Input::RadialControllerSystemMenuItemKind>*となっています。 ちなみに似たようなの3種類あります。

  1. ABI::Windows::Foundation::Collections::IVector
  2. Windows::Foundation::Collections::IVector
  3. Platform::Collections::Vector

似たようなのがいろいろあってめんどくさいですが、1はWinRT ABIでやり取りされる型のインターフェース。2はWinRT内部でやり取りされる型のインターフェース。3は2に対する実装。です。 2が指示されている場合は、3をインスタンス化して使えばOKですが、1が指示されているばあいは、対応する型を手書きする必要があるようです。*4

今回は1が指示されているので、適当に実装するなりなんなりして使えばおっけーです。

MIDLを要求される

ABI::Windows::Foundation::Collections::IVector<T> を実装したクラスを作った。Microsoft::WRL::Make<T>経由でインスタンスも作った。 これで完璧。とおもったらそうでもなく。ABI::Windows::Foundation::Collections配下のインターフェースに対応する実装はMIDLでの定義が必要です。 プロジェクトにIDLファイルを追加して、次のようなコードが必要です。この時、名前空間はIVectorの実装をインスタンス化する位置のものを指定します。

import "inspectable.idl";
import "Windows.Foundation.idl";
import "Windows.UI.Input.idl";

#define COMPONENT_VERSION 1.0

namespace DialWin32
{
    declare{
        interface Windows.Foundation.Collections.IVector<Windows.UI.Input.RadialControllerSystemMenuItemKind>;
    }
}

これをビルドすると、たぶん失敗します。プロジェクトを右クリックして、プロパティを開いて、MIDLの設定を少し変えます。

  • C/C++ -> General -> Additional Metadata Directories
    • C:\Program Files (x86)\Windows Kits\10\References\Windows.Foundation.FoundationContract\1.0.0.0*5
  • MIDL -> Advanced -> Prepend with ‘ABI’ namespace
    • Yes

これでビルドすると、IDLファイルのファイル名に _h.h を追加したファイルが生成されます。 このヘッダをincludeして、ABI::Windows::Foundation::Collections::IVector<T>の実装をMicrosoft::WRL::Make<T>で作ってあげれば やっとABI空間のIVector実装のインスタンスが手に入るわけです。

このあたりをやらないと

midlrt : error MIDL4034: Failed to load a dependency file. Windows.winmd (HRESULT:0x80070002 - The system cannot find the file specified. )

と言われたり、

error C2338: This interface instance has not been specialized by MIDL. This may be caused by forgetting a ‘*’ pointer on an interface type, by omitting a necessary ‘declare’ clause in your idl file, by forgetting to include one of the necessary MIDL generated headers.

と言われたりします。

それから、Prepend with 'ABI' namespaceを付けてないと、MIDLコンパイラが出力したヘッダファイルのコンパイルに失敗します。

おわりに

C++/Win32からSurface Dialを使うのは、IVectorさえ出会わなければそんなに大変じゃないです。IVectorもわかってしまえば大したことないです。 私のぐぐり力が低いばかりに、解決まで3日もかかってしまったのが非常にあれですが…

カスタムアイコンを追加するのはまだ試してないですが、たぶんWPFのつらいあれよりさらにつらそうだな…とおもいつつ、後日試してみます。

*1:http://blog.okazuki.jp/entry/2016/11/11/171706

*2:http://grabacr.net/archives/7141

*3:Windows classic samplesから引用 https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/RadialController/cs/winforms/RadialControllerInterop.cs

*4:Win2Dも手書きしてたのでたぶんそういうもんらしい

*5:たぶんもっとちゃんとした指定があるはずです…

SylphyHornのIssue #19の件

これはメモです。

TL;DR

  • SylphyHornというよりOpen.WinKeyboadHookというライブラリの問題
  • ToUnicodeExがKernel-mode Keyboard Bufferを変更するというSideEffectがある

問題

github.com

要約すると、Alt+Right押してAltを離したタイミングで♠が入力されるんだよね…というなぞ現象。

とりあえず調べてみたところ

この現象は、"Unicode対応ではないプログラムの言語"のロケールがEN-USの時に発生している。JA-JPの場合だと、Alt押しながらRight,RightのあとAlt離すで'B'が入力されるので同様の現象を確認可能。 すべての入力可能コントロールで起きるわけではなく、R#を有効にしたVisual Studioや、Explorer、Internet Explorerのアドレスバーで発生を確認。

Spy++でみてみると、SylphyHornを起動していない場合

  • WM_SYSKEYDOWN
  • WM_SYSKEYDOWN
  • WM_SYSKEYUP
  • WM_KEYUP

とメッセージが流れてくることに対して、SylphyHornを起動してると

  • WM_SYSKEYDOWN
  • WM_SYSKEYDOWN
  • WM_SYSKEYUP
  • WM_KEYUP
  • WM_CHAR

と謎のWM_CHARが出現。いったいこれはなんぞや。

原因はOpen.WinKeyboadHookにあるのでは

SylphyHornがキーフックする際に、Open.WinKeyboadHookというライブラリを使ってるのでこっちが原因じゃないの?って調べてみると、このライブラリの中のToUnicodeという関数のどこかがトリガーとなってWM_CHARが生成されてしまっているみたい。

もっと調べてみるとToUnicodeExというWin32 APIを呼び出したことがトリガーとなっていることが分かった。そこでMSDN(英語)を読んでみると、下のほうにちょろっとだけ書いてあるこれ。

As ToUnicodeEx translates the virtual-key code, it also changes the state of the kernel-mode keyboard buffer. This state-change affects dead keys, ligatures, alt+numpad key entry, and so on. It might also cause undesired side-effects if used in conjunction with TranslateMessage (which also changes the state of the kernel-mode keyboard buffer).

ToUnicodeEx function (Windows)

しれっとびっくり大事件。SideEffectとかやめてよ、これいったいどうすればいいの。ということで今日はここまで。あくまでもメモです。

Twitterのレスポンスは人が理解できるものではなくなったのだよ

TwitterのRESTとStreamingのレスポンスがあまりにも難しすぎて忘れてしまうのでメモ。

  1. RESTでtweet_mode=extendedがついていない場合
    1. 添付画像のURLを含めて140文字以下の場合
      • 今まで通りtext, entities, extended_entities が設定される
    2. 添付画像のURLを含めて140文字を超える場合
      • textには全文表示URLを含んだものが設定される。
      • truncatedフィールドがtrueになる
      • entities, extended_entitiesに画像などは含まれず、全文表示URLのみが含まれる
  2. RESTでtweet_mode=extendedがついている場合
    1. 添付画像のURLを含めて140文字以下の場合
      • textはなくなる。代わりにfull_textが増える
      • truncatedフィールドはfalseになる
      • display_text_rangeフィールドが増えて、本文範囲が明示される
      • entities, extended_entities はこれまで通り
        • 含まれるindicesはfull_textに対して正しい値が格納される
  3. Streamingの場合
    1. 添付画像のURLを含めて140文字以下の場合
      • 今まで通りtext, entities, extended_entities が設定される
    2. 添付画像のURLを含めて140文字を超える場合
      • textには全文表示URLが含まれる
      • entities, extended_entitiesに画像は含まれず、全文表示URLのみが含まれる
      • extended_tweetフィールドが増える
        • ここにfull_text, display_text_range, entities(画像入ってる)がある
        • extended_tweet.entitiesはextended_entitiesと同様に複数枚画像に対応してる

つまるところ、全部のパターンをパースしようと思うと

  1. extended_tweet フィールドを見る
  2. full_text をみる
  3. text をみる

とかしないといけなくてくそめんどい

追記: entities を見るときは

  1. extended_tweet.entities をみる (extended_tweet.entitiesはextended_entitiesを包含しているので単体でOK)
  2. 画像はextended_entities、それ以外はentitiesをみる

で、いけます。こっちもめんどい。

結論

くそめんどい

Windows 10からAndroidのテザリングをONにしてみたい

追記(10/16 11:02): もう少しちゃんと書きました。

TL;DR

背景

Windows 10 Mobileだと、Bluetoothでペアリング済みでPC、電話ともにBluetoothがONになっているとWifiテザリングの設定を端末に触ることなくONにできる機能がありますよね。 macOSiOSでもできるあれです。あれのWindows版。VAIOXperiaのセットでも独自実装されてるやつがありますね。

でも今日はWindows 10の標準実装でMicrosoft拡張のMS-TCCです。詳しいことがEngadgetに書いてました。

japanese.engadget.com

ようするにBluetoothでMS-TCCというプロファイルをサポートしていれば、Windows 10Wifi設定からテザリングを有効にできますよ。という感じ。

MADOSMAをこの機能のためだけに比較的使ってたのですが、よく考えたらこれAndroidでもできるんじゃないかなぁ…と思い、調べてたらこの仕様ってちゃんとドキュメントあるんですね*1

下調べ

MS-TCCのドキュメントを読むとやらないといけないことと、必要な情報がいくつか。

  • 必要なもの
  • やらないといけないこと
    • SDPでGUID {232E51D8-91FF-4c24-AC0F-9EE055DA30A5} を広告
    • リクエストを処理してテザリングを有効にする
  • やったほうがいいこと
    • 端末が圏外かどうかとか見てエラー返す

必要なもののうち、ESSIDとパスフレーズWifiManager#getWifiApConfigurationをリフレクションで叩くと普通に読める。問題はBSSIDで、フィールドはあるけどここに入ってない。BSSIDは通常MACアドレスと同じなのでMACアドレスを読めばいい…のだけど、Android 6.0からWifiMACアドレスが読めなくなってしまった。ので今回はなぜかまだ正しい値を読めるip addrコマンドを実行して結果からMACアドレスを探すことに。

やらないといけないことについては、BluetoothAdapter#listenUsingRfcommWithServiceRecordBluetoothソケットを作るとSDPは広告できるし、リクエストを処理してテザリングを有効にするのはWifiManager#setWifiApEnabledをこれまたリフレクションで叩くと実現できそう。

できました

そんなこんなでやってみたら動いたのでGitHubにソースとかおいておきました。

github.com

ところで、MS-TCCのドキュメントを読んでいただければわかるとおもいますが、この仕様、テザリングをONにすることはできるのにOFFにすることはできないんですよ。なのでテザリングの自動OFF機能がついてる機種以外はONになりっぱなし。最後OFFだけ手動でやってください。

いまのところAPKなしでソースだけなので各自ビルドしてください。あとBluetoothがOFFだった時のエラー処理が何も入ってないのでたぶんやばいです。 BluetoothがOffだったりOn/Offされたときにおかしくならないようにして、APKにしました。GitHubのReleasesからどうぞ*2

これできたからRobinのテザリングWindows 10からリモートでONにできるようになったのでますますMADOSMAの出番が減ってしまいそうだなーって…

ドメインレジストリが逮捕されてしまったら

追記 (8:39 AM): よく読んだらレジストリでした。

背景

僕はKA.TJというドメインを101domainで購入し、優先度の低いメールアドレスのために運用していました。 このドメインは、今年の4月が有効期限となっていたので2月に更新手続きをしていました。

ところが、いつまでたってもPending Approbal(保留中)という状態から一向に進みません。 サポートチケットを何回か作って、どうなってるんだ、レジストリをせっついてくれ。と言っていたのですが、まったく状況は改善しません。 まぁそんなに急いでなかったので割と放置してたというのもあるんですが、ぶっちゃけ放置してました。

ちなみにTJドメインタジキスタンドメインです。タジキスタンは2016年10月11日現在の外務省海外安全情報によると”レベル2: 不要不急な渡航は止めてください”ですね。

事件

そういやあれ、いまだにPendingだしいい加減払い戻ししてもらいたいなぁ…と最近ふと思い出し、そのうちサポートにまた問い合わせようかな。 と思っていたところ、たまたま今朝チャットサポートが使える時間に思い出したのでチャットサポートに問い合わせてみることに。

またレジストリせっついとくって言われるだけかなー。と思いきやここで驚きの返答が。

22:14 Corey K: The TJ registry has been unavailable to the world. Apparently the people running the registry were arrested.
22:15 Yutaka: !? It is really?
22:15 Corey K: Apparently
22:17 Corey K: I recommend you open a support ticket, our account manager that handles .TJ will respond

ざっくりこういうかんじ。

僕「なーなーなんでいまだにPendingなん
サポート「TJレジストリが世界的に利用できなくなってる。たぶんだけどレジストラを運用してた人間が逮捕されたっぽい
僕「まじすか
サポート「まじっぽい

学び

Bash on Windowsのtmuxセッションが終了して悲しくならないために

Bash on Windowsって、bash.exeがホストしてるLinuxサブシステムで動いてます。複数のbash.exeを起動してもそこはなんかいい感じになっていて*1プロセスごとにインスタンスを作ってLinuxサブシステム用のプロセスをそのインスタンスの中で起動するという動きだそうです。

で、bash.exeが終了した時点でセッションへの参照がデクリメントされて、最終的にセッションへの参照が0個になった時点でLinuxサブシステムが終了したみたいな動きになります。困ったものです。

通常困らないのですが、tmux起動してその中で作業やっていて、ウィンドウ閉じちゃったりするとLinuxサブシステムごと終了してtmuxインスタンスはなかったことに。結果悲しみに包まれるということが起こりえます。

そこで先行研究もせずに、COMパワーを駆使してLinuxサブシステムのインスタンスを作るだけ作ってほったらかし。あとはタスクトレイに常駐する。というツールをこしらえておきました。

github.com

Bash on Windowsでウィンドウ閉じるとtmux終了して悲しみに包まれる人たち向けです。

*1:システムのデフォルトのセッションを使ってる

IIJmioのKDDIプランSIMが届いたよ!

IIJmioKDDI回線が10月スタートしています。昨日やっと受け取ることができたので手持ちの端末にいろいろ刺してみました。

契約はSMS付き契約で、音声はありません。音声通話できるかは未確認です。

端末 通信できたか 備考
Nextbit Robin × 設定変えたりしてもだめ
Xperia Z4(KDDI版) CPA設定でAPNを入力する
Nexus 5X
iPhone SE docomoと同じプロファイルをインストール
MADOSMA Q501 × 14936.1000、圏外
NuAns NEO × 14393.189、圏外
Zenfone 5 × そもそも電波つかまない
Xperia Z Ultra KDDI版+UKROM+Unlocked

Moto G4 PlusあたりのDSDSな端末でところですね