tmytのらくがき

個人の日記レベルです

Android 10以降で表示されるバッテリ残り時間を取得する

たぶんAndroid 10から追加されたバッテリーの残り時間をアプリで取得できたのでメモ。

Settings.Globalに格納されていて、ContentResolverでアクセスできる。キーはandroid.provider.Settingsに定義があったけれど、@hideされていたのでコピペして使った。

private val BATTERY_ESTIMATES_LAST_UPDATE_TIME = "battery_estimates_last_update_time"
private val TIME_REMAINING_ESTIMATE_MILLIS = "time_remaining_estimate_millis"
private val TIME_REMAINING_ESTIMATE_BASED_ON_USAGE = "time_remaining_estimate_based_on_usage"
private val AVERAGE_TIME_TO_DISCHARGE = "average_time_to_discharge"

private val AVERAGE_TIME_TO_DISCHARGE_UNKNOWN = -1
private val ESTIMATE_MILLIS_UNKNOWN = -1

data class Estimate(
    val lastUpdateTime: Long,
    val estimateMillis: Long,
    val isEstimateBasedOnUsage: Boolean,
    val timeToDischarge: Long,
)
fun getEstimate() = Estimate(
    Settings.Global.getLong(
        contentResolver,
        BATTERY_ESTIMATES_LAST_UPDATE_TIME, -1
    ),
    Settings.Global.getLong(
        contentResolver,
        TIME_REMAINING_ESTIMATE_MILLIS,
        ESTIMATE_MILLIS_UNKNOWN.toLong()
    ),
    Settings.Global.getInt(
        contentResolver,
        TIME_REMAINING_ESTIMATE_BASED_ON_USAGE, 0
    ) == 1,
    Settings.Global.getLong(
        contentResolver,
        AVERAGE_TIME_TO_DISCHARGE,
        AVERAGE_TIME_TO_DISCHARGE_UNKNOWN.toLong()
    )
)

Surface Duo向けアプリ2個をPlayストアに公開しておきました

Surface Duo向けのアプリ2個をPlayストアに公開しておきました。

Duo BrowserHelper

ひとつめ。G8X BrowserHelperのSurface Duo版。

Chrome Custom Tabs互換のブラウザアプリのふりをしてインテントを受け取って、常に別画面でブラウザを開くためのもの。Surface Duoの画面を折りたたんで、半画面で使っているときはCustom Tabsを起動するような仕組みになっています。

アイコンまでコピペなのでそのうちなんとかします。

play.google.com

スクショカッター

ふたつめ。Surface Duoのスクショを片画面分のサイズで切り抜くだけのもの。

Surface Duoは画面がどんな状態になっていても2枚つながったスクショを保存するので、半画面で使ってると空白のディスプレイ部分もくっついてくる。これを、スクショ通知の共有ボタンからこのアプリで開くことで簡単に半分サイズのスクショに編集できるというやつです。

名前がダサい?僕もそう思います。

play.google.com

startActivityの挙動

Surface DuoでのstartActivityの挙動をメモしたものです。基本的にはドキュメントに書いてある内容です。

定義

  • それぞれの画面はペインと呼びます。
  • ホーム画面が表示されているペインは空き状態と呼びます。
  • なにかアプリが起動しているペインは占有状態と呼びます。

挙動

  • ペインに表示されいてるアプリがstartActivityを呼ぶと、同じペインにアクティビティが表示されます
    • ただし、Intent.FLAG_ACTIVITY_NEW_TASKが設定されているかつ、もう一方のペインが空き状態の場合は空きペインに表示されます
      • ただし、もう一方のペインが空き状態でない場合は、呼び出したアクティビティと同じペインに表示されます

表にしておきます。

左側ペインにアプリAを起動して、異なるパッケージに属するアクティビティをstartActivityしたことを想定。

Flags 右側ペインの状態 起動場所
なし 空き
なし 占有
FLAG_ACTIVITY_NEW_TASK 空き
FLAG_ACTIVITY_NEW_TASK 占有

同じパッケージに属するアクティビティをstartActivityする場合はIntent.FLAG_ACTIVITY_MULTIPLE_TASKを付けないと、常に同じペインで表示されます。

Surface Duoを買いました

使い始めてもう2週間くらいになるのだけれども、Surface Duoを買いました。

工事設計認証も通っているのですぐに日本ででるだろう。と思って待っていたのだけれども、一向にアメリカ以外の展開が発表されないので意を決して輸入に至りました*1

受け取り&開封

受け取り

本当はFedExのひとがうちまで配達に来てくれる…予定だったのだけども、せっかくなので関西空港での受け取りに挑戦。 FedExの営業所へ電話をして空港で受け取れないか?と尋ねたところ、ちょうど通関の許可が出たころでギリギリ間に合ったみたいでした。

用意ができたとの連絡を受けいざ空港へ…国際貨物と書かれたレーンを間違えないように進みます。

f:id:tmyt:20201019042751p:plain

一般人が使うのは一時立ち入りレーンなので一番右側の車線。

f:id:tmyt:20201019042903p:plain

しばらく行くと検問があって、右側2レーンぐらいが一時立ち入り向け。左側レーンは許可車両なので間違えないように進む。

f:id:tmyt:20201019043016p:plain

関係者以外立ち入り禁止とか書かれていてちょっとドキドキする。

f:id:tmyt:20201019043128p:plain

順番が回ってくると、検問のおじさんに、何しに来たの。と聞かれるので「FedExに荷物を取りに来た」と伝えると、一時立ち入りに必要な書類の記入をしてね。と紙を渡されるので書く。 一緒に、免許証を身分証として提出。退出予定時刻を伝えると、立ち入り許可証を受け取ってついに制限エリアの中へ!

FedExへ行くには国際郵便局前の信号を右折。僕は間違えて制限エリアの中をさまよいました。

国際郵便局前の信号を右折したらひたすら直進。突き当りを右折するとFedExの入り口と駐車場があるので、車をとめて荷物を受け取ります。 荷物受け取りに来たよ、と伝えると内線でここにかけて。といわれるので、内線で電話をしてトラッキング番号を伝えると奥から持ってきてくれます。

最後に、立ち入り許可証にハンコを押してもらって逆のルートで帰ればおしまい。

開封

BigAppleBuddy という、BestBuyとかで代わりに買ってきてくれるサービスを使ったのでそこのお手紙付き。

f:id:tmyt:20201019044140p:plain

本体、バンパー、ドキュメント類、充電器とケーブルが入ってました。SIMピンはドキュメント類の中に入ってました。

f:id:tmyt:20201019044745p:plain

Powered by Android

f:id:tmyt:20201019044322p:plain

楽天MobileのSIMを入れていたからか、言語設定の選択肢に日本語が出てきたのが印象的でした。

f:id:tmyt:20201019044426p:plain

セットアップ中も完全に日本語。

f:id:tmyt:20201019044455p:plain

早く日本でも普通に買えるようになってほしいね…

ちなみに、ちゃんと技適表示あります。

f:id:tmyt:20201019045157p:plain

設定その他

楽天MobileのAPNは自動設定されないので手動で設定。rakuten.jp と書いてあとは適当に設定。普通につながりました。 VoLTEはIMSを設定しても有効にならないみたい。4G LTE拡張モードのチェックが表示されないので使えなさそう。

そのほかの設定はあんまりやることもなくいつも通り普通のAndroidでした。

うちに届く2日前にミヤビックスからSurface Duo向けのOverlayほにゃららがタイミングよく販売スタートしたので、注文しておきました。 液晶はOverlay 9H Brilliant、背面はOverlay 9H Plusを貼っています。外側さらさらでとてもよい…

感想

サイズ感

2、3日使ってみたタイミングで折りたたんだ状態での手に持ったサイズ感が昔持ったことある気がする……ということになり、記憶をたどったところ2013年に発売された、Xperia Z Ultraとサイズ感が同じだ…ということに気付きました。

f:id:tmyt:20201019050433p:plain

Surface Duoのほうが少し幅広。縦は短い。数値で比較するとこう。

機種 サイズ
Surface Duo 145.2mm (H) x 93.3mm (W) x 9.9mm (T)
Xperia Z Ultra 179mm (H) × 92mm (W) x 6.5mm (T)

おんなじやんけ…案外サイズ感って覚えてるもんなんやな…と思った瞬間でした。Xperia Z Ultraの後継機が長年発売されないことに悲しんでいたところでのこれは個人的にうれしいポイントでした。

重さ

データ上は250gって書いてました。それがいいかどうかはさておき。

見開き状態で片手で持ってると微妙に重いかもしれない?いやそうでもないかも?そもそも昨今の情勢で電車に乗らなくなってしまったのであまり立って使うこともなく…… ただ、閉じた状態で片手で持っているとちょっと重いかな。という気持ちになります。たぶん気持ちの問題です。キー入力するときは両手で持つのであまり気になりません。

そんなぐらいの重さ感。

画面

スペックシートにはAMOLEDと書いてます。ちなみにLGのパネルだそうです。Kernelソース*2にそう書いてました。たぶんあってると思います。 まぁまぁ高いだけあって左右の色味もわりとちゃんと調整されているような雰囲気。あまり詳しいことはわかりません。

片面だと3:4、両面だと3:2ということになっているので35mm版の写真がぴったりやんけ!!壁紙設定したろ!!って設定したら微妙に合わないんです。 なぜかというと、片面だと1800x1350、両面だと2700x1800というサイズの画面ということになってるので、両面だと3:2ということになってます。だいたいどこのメディアとかもそう書いてます。スペックにもそう書いてるのであってます。

ただこれは、正確ではなく内部的にはヒンジ部分を非表示の84px幅の画面として扱ってます。いわゆる縦長のノッチです。なので、壁紙に必要なサイズは 2784x1800 です。これは3.09:2になるので微妙に3:2より横長なんですよね。豆知識です。

電子書籍

もっともおすすめの使い方は電子書籍リーダーです。コミックを読むために存在していると思っています。SurfaceブランドにはSurface Bookという製品がありますが、Surface DuoのほうがさらにBookだな思いました*3

f:id:tmyt:20201019063210p:plain

試したアプリが偏っていますが結果を書き残しておきます。リクエストあったら何かの方法で伝えてください。

  • Kindle: 見開きで読めるすばらしい。ただし文字の本はヒンジで文字が隠れるのでダメ。
  • サイコミ: 見開きにならない。2画面にするとデバイスとしては横向きになってしまう。
  • ジャンププラス: 表紙とかの片面ページが中央揃えになってヒンジと被る。見開きOK。

そのほか

  • カメラはとりあえず写ります。最近の複数レンズ構成の高級スマートフォンと比べると大してきれいではないと思います。結局iPhoneがカメラとしては優秀だと思います。たぶん。
  • 背面に移るときのダブルタップはわりと時々失敗します。2回目やるとだいたい成功するのでそんなに気にしてません。
  • 折りたたんで片側モードの時にスクショを撮影するとなにもない反対側までを含めた大きい画像が保存されます。ちょっとおもしろい。微妙に不便なのでアプリ作りました f:id:tmyt:20201019052441p:plain
  • 空きディスプレイ(ホーム画面が表示されている状態を空いていると定義する)は自動で積極的に利用される。
    • 新しいタスク*4を起動すると空きディスプレイは積極的に埋められます。
  • プリインストールのMicrosoft Launcher以外の利用は考えない方がよさそう
    • Surface Duoの内部APIまでしっかり組み込まれているのがこれしかない
  • プリインストールのMicrosoft SwiftKey keybaord以外の利用は考えない方がよさそう
    • Launcherにおなじ。Gboardとかはキーボードを使ってない側のアクティビティもキーボードが開いているみたいなサイズになるので怪しい。
  • 見開き状態で懐中電灯を誤ってONにしてしまうと、強力な目つぶしを受けてしまうので十分注意すること。
  • バッテリーはけっこうもちます*5
  • 当初Facebook Messengerの挙動があやしかったのは10月のアップデートで修正されました。
  • いちおう3年はアップデートの面倒を見てくれるそうです。

あと気づいたら書き足します。

おしまい

18万といわれるといやーさすがにたけーわ…となるのはわかります。正直僕も高いと思います。さすがにありえんと思います。ただ、買ってよかったか。というと、これは買ってよかったなぁ。という電話になりました。

*1:ちなみに日本円で約18万円だそうです。恐ろしい…

*2:厳密にはGPLで公開されているKernelコードに含まれているdevice tree

*3:ちなみに表示されているのは。ふらいんぐうぃっち(1)です。 https://www.amazon.co.jp/dp/B00HUIL6M6/

*4:Intent.FLAG_NEW_TASK

*5:使用頻度にもよるけど1.5日ぐらい持つかもしれない

BottomSheetDialogFragmentの角を丸くする

backgroundつけても反映されないのでshapeApperanceOverlayを設定するのがいいらしい。

<style name="MyBottomSheetDialogTheme" parent="@style/ThemeOverlay.MaterialComponents.DayNight.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/MyModalStyle</item>
</style>
<style name="MyModalStyle" parent="Widget.MaterialComponents.BottomSheet.Modal">
    <item name="shapeAppearanceOverlay">@style/BottomSheet.CornerRadius</item>
</style>
<style name="BottomSheet.CornerRadius" parent="">
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">2dp</item>
</style>

VSCTをローカライズする

Visual Studio拡張でボタンとか追加する時はVSCTというXMLで配置先とか、ラベルとかを定義して、それがコンパイラでバイナリになって、リソースに押し込まれます。 で、それをローカライズするにはどうすればいいか、というとdocs.com のここに書いています。

docs.microsoft.com

この通りにいくらやってもうまくいかなくて、うまくいくcsprojができたので書き残しておきます。

AssemblyInfo.cs を編集

csprojを編集する前にまずAssemblyInfo.csにひとつ追加。これでフォールバックロケールの検索先が変わるそうです。

// これを追加する。この場合は en-us がデフォルト言語。それ以外をデフォルトにしたいときは、そのロケール文字列を指定するとOK
[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]

これを書いておかないと、デフォルトのフォールバックリソースにVSCTのコンパイル結果が含まれていないので、フォールバックリソースが使われたVisual StudioでVSCTが読み込まれません。

VSCTをローカライズする

VSCTのXMLローカライズ済み文字列定義を参照するみたいな賢いことはできないので、全ロケール分コピーします。

例えば、Menus.vsctというファイルがある状態で、en-USとja-JPに対応したい場合はMenus.en-US.vsct、Menus.ja-JP.vsct をコピーして作ります。それぞれ中身はよさげにローカライズします。

f:id:tmyt:20200908063511p:plain

コピー元のMenus.vsctは消しても消さなくても大丈夫。もし、カスタムツールに"VsctGenerator"を指定している場合は残してもいいし、使ってない場合は削除でOK。 カスタムツールを使っている場合でも、ビルドアクションは"なし"にしてOK。

Resxをローカライズする

Resxもローカライズします。しないとうまく動きません。Resxのローカライズはいつもと同じ感じで、Strings.resxをStrings.en-US.resxとStrings.ja-JP.resxにコピーしてそれぞれローカライズみたいな感じでOK*1

ただし、フォールバック言語(ここではen-US)も個別言語としてつくらないとだめ。ここでフォールバック言語用のresxは消してはいけない。消すと動かない。

f:id:tmyt:20200908063645p:plain

この時、Resxがプロジェクトにひとつもない場合は空で大丈夫なので追加しておく。追加しておかないとうまく動かない

csprojを調整する

ここでcsprojをアンロードして右クリック→編集する。そして、こんな感じにする。

<!-- vsct -->
<ItemGroup>
  <!-- PackageGuidsとかを自動生成するのであればこれが必要 -->
  <None Include="ExpandAllPackage.vsct">
    <Generator>VsctGenerator</Generator>
    <LastGenOutput>ExpandAllPackage1.cs</LastGenOutput>
  </None>
  <!-- 各言語版をコピーして作る -->
  <VSCTCompile Include="ExpandAllPackage.ja-JP.vsct">
    <!-- ResourceNameはすべての言語でMenus.ctmenuにする -->
    <ResourceName>Menus.ctmenu</ResourceName>
  </VSCTCompile>
  <!-- ほかの言語と同じ。対応する言語分編集する。 -->
  <VSCTCompile Include="ExpandAllPackage.en-US.vsct">
    <ResourceName>Menus.ctmenu</ResourceName>
  </VSCTCompile>
</ItemGroup>
<ItemGroup>
  <Content Include="Resources\ExpandAll.png" />
</ItemGroup>
<!-- resx -->
<ItemGroup>
  <!-- 必須。ないとvsctがロードされない。リソースをコードから参照するときもこのリソースからバインディングが生成されるので消してはだめ -->
  <EmbeddedResource Include="Resources\Strings.resx">
    <Generator>ResXFileCodeGenerator</Generator>
    <LastGenOutput>Strings.Designer.cs</LastGenOutput>
    <!-- なんか適当に設定する。resxをresourcesに書き換えたものでOK。 -->
    <LogicalName>Strings.resources</LogicalName>
    <!-- ここには MergeWithCTOを書かない。書くとビルドできない。 -->
  </EmbeddedResource>
  <!-- 普通にローカライズするときと同じ。全言語作る。 -->
  <EmbeddedResource Include="Resources\Strings.en-US.resx">
    <!-- これをつけるとVSCTのコンパイル結果がこのresxとマージされる。ローカライズ済みリソースですべて必須。 -->
    <MergeWithCTO>true</MergeWithCTO>
    <!-- おなじ。resxをresourcesに書き換えたものでOK -->
    <LogicalName>Strings.en-US.resources</LogicalName>
  </EmbeddedResource>
  <!-- ほかの言語と同じ。対応する言語分編集する。 -->
  <EmbeddedResource Include="Resources\Strings.ja-JP.resx">
    <MergeWithCTO>true</MergeWithCTO>
    <LogicalName>Strings.ja-JP.resources</LogicalName>
  </EmbeddedResource>
</ItemGroup>

MergeWithCTOとLogicalNameは、resxの組のいずれかでOK。*2

編集したら、保存してプロジェクトを再読み込みする。あとはビルドすればおそらく複数言語に対応したVisual Studio拡張が出来上がっているはず。

ちなみに、VSCTが不要な場合はResxだけなのでこんな面倒なことをしなくてもローカライズできます*3

Visual Studioの言語を切り替えると、正しくローカライズされているように見える。

f:id:tmyt:20200908064008p:plain

*1:docs.comには、Strings.resxをStrings.en-US.resxにリネームするって書いてるけどここがたぶん間違い。

*2:たとえば、Strings.resxとBitmaps.resxがある場合は、Strings.resx(ja-JP, en-US)か、Bitmaps.resx(ja-JP, en-US)のいずれかでOK。ここではResxが1グループしかないので、こういうcsprojになっている。

*3:AssemblyInfo.csの編集もいらないしcsprojの編集もいらないし、ロケールのついてないresxがフォールバック先として読み込みもできる。

Visual Studioのソリューションエクスプローラのフィルタ機能を拡張する

tl;dr

  • ソリューションエクスプローラの表示を、Gitの中身を比較して変更があったファイルでフィルタする拡張を作った
  • フィルタする拡張機能は、HierarchyTreeFilterProvider を実装してVisual Studio側に公開するといい感じで動くらしい
  • フィルタ機能をソリューションエクスプローラに統合するときは、<Parent guid="guidSHLMainMenu" id="IDG_VS_TOOLBAR_PROJWIN_FILTERS" /> にするといい

Gitの状態でソリューションエクスプローラをフィルタしたい

Visual Studioの標準状態にはGitのunstagedなファイルのみ表示するフィルタはあって、これを使いたいからできればコミットしたくない。みたいな話を聞いた。 f:id:tmyt:20200908023344p:plain

普通にコミットして、git diff --stats origin/master 的なコマンドをSource Treeとかで実行したら十分なのでは?っていう話をしたら、それVSのフィルタ機能に統合してほしい。って言われたので作ってみたよ。

ソリューションエクスプローラのフィルタ機能を拡張する

まず、ソリューションエクスプローラのフィルタ機能を実現するにはHierarchyTreeFilterProviderというクラスを実装すればいい。 大まかにはdocs.com に書いてるのを読めばだいたい動く。

docs.microsoft.com

まず、HierarchyTreeFilterProviderを実装して、SolutionTreeFilterProvider属性を付けて、protectedで定義されているHierarchyTreeFilterを実装すればOK。

HierarchyTreeFilterProviderはフィルタの実装をnewするためのファクトリクラスで、フィルタとしての実体はHierarchyTreeFilterVisual Studioがフィルタを実行したい気分になると、HierarchyTreeFilter.GetIncludedItemsAsyncが呼び出されるので、このメソッドのなかにフィルタロジックを書いていけばOK。

次に、フィルタを呼び出すボタンをVSCTファイルに書く。基本的にはボタンをツールバーに追加する時と同じで、親として設定するグループを適切に設定する。適切というのは、

<Parent guid="guidSHLMainMenu" id="IDG_VS_TOOLBAR_PROJWIN_FILTERS" />

こう書く。このIDG_VS_TOOLBAR_PROJWIN_FILTERSはどこから出てきたのはは気にしてはいけない。こう書け。と先のdocs.comにも書いてあるのでこう書く。気にしてはいけない。

Gitの状態にアクセスする

LibGit2Sharpを使ってアクセスした。Visual Studioが開いているソリューションは DTE.Solution.FullNameプロパティにアクセスすると得られるので、ここからディレクトリを上方向にたどった最寄りの.git をソリューションが参照すべきGitリポジトリとした。

このパスを、LibGit2Sharpで開いて、Diff.Compare<TreeChanges>を呼び出す。差分がある(Add, Delete, Modifiedなど)ファイルのみを返すのでこの一覧をソリューションエクスプローラに表示すべきファイルとして使った。 この一覧ができてしまえばあとは、_hierarchyCollectionProvider.GetFilteredHierarchyItemsAsync を呼び出して、コールバックにフィルタすべきかどうかを判定する項目が入っているので、CanonicalNameに含まれているパスをチェックすればOK。

LibGit2SharpをVS拡張から使う

LibGit2Sharpはlibgit2のC#バインディングで実行にはネイティブ実装が必要。で、本来はNuGetから自動でインストールされてbinに出力されるけれども、VSIXの場合はVSIXに明示的に含めるようにしないとだめ。なのでcsprojを編集して次を書き足しておく。

<ItemGroup>
  <PackageReference Include="LibGit2Sharp.NativeBinaries" GeneratePathProperty="true">
    <Version>2.0.306</Version>
  </PackageReference>
  <Content Include="$(PkgLibGit2Sharp_NativeBinaries)\runtimes\win-x64\native\git2-106a5f2.dll">
    <Link>lib\win32\x64\git2-106a5f2.dll</Link>
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    <IncludeInVSIX>true</IncludeInVSIX>
  </Content>
  <Content Include="$(PkgLibGit2Sharp_NativeBinaries)\runtimes\win-x64\native\git2-106a5f2.pdb">
    <Link>lib\win32\x64\git2-106a5f2.pdb</Link>
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    <IncludeInVSIX>true</IncludeInVSIX>
  </Content>
  <Content Include="$(PkgLibGit2Sharp_NativeBinaries)\runtimes\win-x86\native\git2-106a5f2.dll">
    <Link>lib\win32\x86\git2-106a5f2.dll</Link>
    <IncludeInVSIX>true</IncludeInVSIX>
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
  <Content Include="$(PkgLibGit2Sharp_NativeBinaries)\runtimes\win-x86\native\git2-106a5f2.pdb">
    <Link>lib\win32\x86\git2-106a5f2.pdb</Link>
    <IncludeInVSIX>true</IncludeInVSIX>
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
  <Content Include="Resources\SolutionFilter.png" />
</ItemGroup>

これで、NuGetからLibGit2Sharp.NativeBinariesがインストールされて、x86, x64 Windows向けバイナリがVSIXに含まれるようになる。

ベースブランチを選択する

どこかをDiffの基準点にする必要がある。今回は、WPFの非表示のWindowとContextMenuを動的に生成して、ボタンを押したときにポップアップメニューが表示されるようにした。

f:id:tmyt:20200908025438p:plain

ソリューションエクスプローラのこのツールバーにボタンを追加する方法は探しても見つからないけれども、VSCTの親設定にこう書けば追加できる。

<Parent guid="guidSharedMenuGroup" id="IDG_VS_PROJ_TOOLBAR2" />

これをどうやって探すかはdocs.comに詳しくはないけど書いてある。

docs.microsoft.com

SharedCmdPlace.vsctこのファイルに定義されているのが定義済みのコマンドバーなどなどの場所で、このファイルはこのあたりに入っていた。 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VSSDK\VisualStudioIntegration\Common\Inc

この中から、ソリューションエクスプローラツールバーを探すと、IDG_VS_PROJ_TOOLBAR2がみつかる。

おしまい

成果物はGitHubVisual Studio Marketplaceに公開してあります。ローカライズもうまくいかなくてだいぶ悩んだけどそれはまた別の機会に…

github.com

marketplace.visualstudio.com