tmytのらくがき

個人の日記レベルです

WSL2のVHDを移動する

TL;DR

  • export/importすれば移動できます
CMD> wsl --export Debian debian.tar
CMD> wsl --unregister Debian
CMD> wsl --import Debian D:\wsl\Debian\ debian.tar
  • import後のデフォルトアカウントがrootになる
    • ファイル名を指定して実行でコマンドを実行した
debian config --default-user {your_name}

WSL2のVHDを移動したい

WSL2はWSL1と違ってext4フォーマットのrootfsをVHD形式でWindows側に保持してます。いろいろ使ってると案外大きくなっていって気が付いたら40GBも使ってました。

デフォルトでは %LocalAppData\Packages\{package_name} の中にVHDが作成されてまぁまぁ邪魔なのでほかのディスクへ移動できないかぐぐったらGitHubにその通りのIssueがあったので同じことをやります。 IssueはUbuntuですが、手元はWSL2はDebianで使ってるのでSnippetはDebianに変わっています。

github.com

まず移動。

> wsl --export Debian debian.tar
> wsl --unregister Debian
> wsl --import Debian D:\wsl\Debian\ debian.tar

export時点で40GBのtarが一時的にできて、unregisterでVHDが削除、その後importで40GBのtarから40GBのVHDが目的の場所にできてました。

export/import後に起動してみるとデフォルトアカウントがrootになっていたのでこれを修正。 デフォルトアカウントを変更するには debian config --default-user {your_name} を実行したらいいらしい。

コマンドプロンプトからだとなぜか動かなかったので、ファイル名を指定して実行からコマンドを実行してデフォルトユーザを変更。

うまくいきました。

Chromeの <input type="time"> の時計アイコンを消す

Stackoverflowで見つけました。

stackoverflow.com

最近のChrome<input type"time"> を使うと右端に時計アイコンが出ます。これを消したい。

input[type="time"]::-webkit-calendar-picker-indicator {
    background: none;
}

こうすると消えるみたい。大感謝…

ASP.NET CoreのTagHelperでいろいろするメモ

TagHelper

TagHelperを継承したクラスで、ProcessAsyncをoverrideしていろいろすると、出力をいろいろできる。

いろいろする

// ターゲットにするタグ名をここに付ける
[HtmlTargetElement("my:Example")] 
public class ExampleTagHelper : TagHelper
{
  // ViewContextオブジェクトを格納してほしい時に書く。
  // これがあると、TagHelperの内側からHttpContextとかにアクセスし放題になる
  [ViewContext]
  // 属性をBindしたくないときに付ける
  [HtmlAttributeNotBound]
  public ViewContext ViewContext { get; set; }

  // プロパティにバインドしたい属性を書く
  [HtmlAttributeName("hello")] 
  public string Hello { get; set; } = "";

  // これをoverrideしていろいろする
  public override Task ProcessAsync(
    // Bindした属性のリストとかはこっち
    TagHelperContext context,
    // Bindしてない属性のリストとか、出力するタグの設定とかはこっち
    TagHelperOutput output)
  {
    // 子要素のHTML-string はこれで読める
    var content = (await output.GetChildContentAsync()).GetContent();
    // タグを my:Example から div に変える
    output.TagName = "div";
    // 自己終了タグとして使いたいTagHelperなのに出力されるHTMLが自己終了だと都合が悪い時などにTagModeを変える
    // output.TagMode = TagMode.StartTagAndEndTag;
    // 属性を好きに付け替えたりできる
    output.Attributes.SetAttribute("style", "color: red");
    // 中身も好き勝手編集できる
    output.PreElement.SetHtmlContent("[1]");
    output.PreContent.SetHtmlContent("[2]");
    output.Content.SetHtmlContent($"[3] {Hello}");
    output.PostContent.SetHtmlContent("[4]");
    output.PostElement.SetHtmlContent("[5]");
    return Task.CompletedTask;
  }
}

このTagHelperをRazorからこんな感じで呼ぶ

<my:Example hello="world" />

そうすると、こう出力される。

[1]
<div>
[2]
[3] world
[4]
</div>
[5]

便利。

Windows 10 2004のエクスペリエンスっていう項目が気になったので探した

Windows 10 2004 May 2020 Updateにするとシステムのバージョン情報に"Windows Feature Experience Pack"というのが表示されるようになりました。らしい。この環境はInsider Fast Ringなので若干バージョンが違うけれども、2004でも同じことができます。

f:id:tmyt:20200620205541p:plain

Feature on Demand版のドキュメントを見てみると

This Feature on Demand package includes features critical to Windows functionality. Do not remove this package.

  • Capability Name: Windows.Client.ShellComponents~~~~0.0.1.0
  • Sample package name: Microsoft-Windows-UserExperience-Desktop-Package~31bf3856ad364e35~amd64~~.cab

と、いうことらしい…中身が気になったので探してみます。

おそらくこれはAppX形式だろう。ということで、PowerShellでパッケージを探してたのがこれ。

PS> Get-AppxPackage | Where-Object { $_.Version -eq "120.13701.0.0" }
Name              : MicrosoftWindows.Client.CBS
Publisher         : CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Architecture      : X64
ResourceId        :
Version           : 120.13701.0.0
PackageFullName   : MicrosoftWindows.Client.CBS_120.13701.0.0_x64__cw5n1h2txyewy
InstallLocation   : C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy
IsFramework       : False
PackageFamilyName : MicrosoftWindows.Client.CBS_cw5n1h2txyewy
PublisherId       : cw5n1h2txyewy
IsResourcePackage : False
IsBundle          : False
IsDevelopmentMode : False
NonRemovable      : True
Dependencies      : {Microsoft.VCLibs.140.00_14.0.27810.0_x64__8wekyb3d8bbwe}
IsPartiallyStaged : False
SignatureKind     : System
Status            : Ok

このパッケージは C:\Windows\SystemApps に入ってるらしいので、管理者権限のコマンドラインから様子を見てみることにします。

PS> dir

    ディレクトリ: C:\Windows\SystemApps\MicrosoftWindows.Client.CBS_cw5n1h2txyewy

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2020/06/14      7:20                AppxMetadata
d-----        2020/06/14      7:20                Assets
d-----        2020/06/14      7:20                InputApp
d-----        2020/06/14      7:19                pris
d-----        2020/06/14      7:20                ScreenClipping
d-----        2020/06/14      7:20                WindowsInternal.ComposableShell.Experiences.SuggestionUIUndocked
-a----        2020/06/13      7:19         877169 AppxBlockMap.xml
-a----        2020/06/13      7:19          58385 AppxManifest.xml
..省略..

AppXらしいデータが入ってました。中身が気になるのでAppxManifest.xmlを眺めてみます。

> Get-Content .\AppxManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" xmlns:uap6="http://schemas.microsoft.com/appx/manifest/uap/windows10/6" xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10" xmlns:wincap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/windowscapabilities" IgnorableNamespaces="mp uap uap3 uap4 uap6 uap10 wincap">
        <Identity Name="MicrosoftWindows.Client.CBS" Publisher="CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="120.13701.0.0" ProcessorArchitecture="x64"/>
        <mp:PhoneIdentity PhoneProductId="3898fac3-1f84-4040-83ce-ef30739c0a64" PhonePublisherId="6145b212-a274-4517-8abe-1e015f21ff03"/>
        <Properties>
                <DisplayName>Windows Feature Experience Pack</DisplayName>
                <PublisherDisplayName>Microsoft Windows</PublisherDisplayName>
                <Logo>Assets\StoreLogo.png</Logo>
        </Properties>
..省略..

こういうのがだらだら続くのだけれども、めっちゃ長いのでピックアップして。

どうやらInputMethod的ななにかだそうです。

<Application Id="InputApp" Executable="TextInputHost.exe" EntryPoint="WindowsInternal.ComposableShell.Experiences.TextInputUndocked.InputApp.App">
..省略..
  <Extensions>
    <!-- VS's manifest validation won't accept an empty Extensions section but it is required for
  fragment merging, so add a dummy protocol entry -->
    <uap:Extension Category="windows.protocol">
      <uap:Protocol Name="ms-inputapp" DesiredView="useMinimum">
        <uap:DisplayName>Input App</uap:DisplayName>
      </uap:Protocol>
    </uap:Extension>
    <uap3:Extension Category="windows.appExtension">
      <uap3:AppExtension Name="com.microsoft.windows.input.app" Id="InputApp" PublicFolder="Public" DisplayName="Input Experience">
      </uap3:AppExtension>
    </uap3:Extension>
  </Extensions>
</Application>

Windpws+Shift+Sで出てくるあれの実体がここに入っているそうです。

<Application Id="ScreenClipping" Executable="ScreenClippingHost.exe" EntryPoint="ScreenClippingHost.App">
..省略..
  <Extensions>
    <uap:Extension Category="windows.protocol">
      <uap:Protocol Name="ms-screenclip">
        <uap:DisplayName>ms-screenclip</uap:DisplayName>
      </uap:Protocol>
    </uap:Extension>
    <uap3:Extension Category="windows.appExtension">
      <uap3:AppExtension Name="com.microsoft.windows.app.screenclip" Id="ScreenClippingApp" PublicFolder="Public" DisplayName="Screen Snipping Experience"/>
    </uap3:Extension>
    <uap3:Extension Category="windows.appExtensionHost">
      <uap3:AppExtensionHost>
        <uap3:Name>com.microsoft.windows.protocoloverride</uap3:Name>
      </uap3:AppExtensionHost>
    </uap3:Extension>
  </Extensions>
</Application>

今のところこれくらいしか中身はないのだけれども、EntryPointの名前とかパッケージの名前とかを見るとそのうちいろいろな実体がここに移されてくるんだろうなぁ…という感じでした。

シーリングライトを力押しでGoogle Assistantに対応させる (その2)

4か月前に作ったこれを、毎日使っていたのですが、今日ふと電話置き場を眺めてみるとこんなことに。あらやだ。

f:id:tmyt:20200501005509p:plain

電池って4か月ちょいでこんなことになってしまうんですね、こわいこわい。

さて、我が家のシーリングライト危機が訪れました。解決方法として考えられたのは

  1. 別のAndroidを生贄にする
  2. バッテリーのないAndroidを手に入れる

1は、まだまだあるので大丈夫といえば大丈夫なんですが4か月ごとに電話が死んでいくとなるとそれはさすがに困ります。 2は、あまり数は無く、あっても大抵が産業用なのでかなり高い。

そこで3つ目の解決方法がふと思いつきました。Raspberry Pi 3にAndroidをインストールして、そこで今までのシステムを動かす。 全開の図はこんな感じにアップデートされます。

f:id:tmyt:20200501010401p:plain

かなりスマートになりました。adbを使っていた部分はもはやAndroidの内側になってしまったのでADBは不要になり、ローカルのコマンドを実行するだけになりました。

Raspberry Pi 3 でAndroid を実行する

この作戦はRaspberry Pi 3 でAndroid が起動してかつBluetoothが比較的まともに動くことが条件でした。 今回は2個試して、ちゃんと使えたLineageOS 16.0を使います。

konstakang.com

インストールはビルド済みイメージをダウンロードして、SDに書き込み、Raspberry Pi 3にカードを入れて起動すれば数分後にはAndroidの初回セットアップ画面が起動します。

セットアップはそこそこにすすめて、いつものAndroidと同じように開発者オプションを有効にして、ADBを有効にします。加えて、Rootアクセスも有効にしておきました。 これで基本的なセットアップはおしまい。

シーリングライトの専用アプリをインストールする

専用アプリがないとライトを操作できないので、専用アプリをどうにかしてインストールします。

Gappsをインストールする方法もあるのですが、このアプリはGappsなしでも動作するので ほかのAndroidバイスからapkをコピーしました。コピー方法は割愛。

Bluetoothも正しそうに動いてBLEでライトと通信できる状態になりました。

NodeJSを実行する

この仕組みはインターネット上で動作しているNodeJSサーバが、LANの内側で動作しているNodeJSプロセスにSocket.IO通してGoogle Assistantからのコマンドを転送する仕組みになっていました。 大きく書き直すのは大変、そしてAndroidはなんだかんだLinuxなのでNodeJSが動くでしょう。ということでAndroid上でNodeJSを実行できるようにします。

Androidで使えるNodeJSのバイナリを探してみたものの、今風のバージョンは見つからず、ソースからビルドするのもいまいちうまくいかない。 そこで、Termuxをインストールし、その中で apt install nodejs した結果インストールされたバイナリを使うことにしました。

TermuxはAndroid上で動作するLinux環境のようなものです。公式にAPKが配布されているのでダウンロードしてadb install しました。

termux.com

インストールが終わったら、Termuxを立ち上げてそこからはLinuxの世界なのでてきとうにNodeJSをインストールします。

$ apt update
$ apt install nodejs

これでNodeJSがインストールできました。使う時は /data/data/com.termux/files/usr/bin/node を参照するといいです。

おしまい

あとはこのNodeJS環境でこのプログラムを実行するだけで今まで通りシーリングがON/OFFできます。うれしいですね!

const socket = require('socket.io-client')('https://***.com');
const { exec } = require('child_process');
const util = require('util');

const X = [136, 248];
const Y = [189, 373, 555];

const on = `${X[0]} ${Y[0]}`;
const off = `${X[1]} ${Y[2]}`;
const dimm = `${X[0]} ${Y[2]}`;

socket.on('connect', () => {
  console.log('connect');
});
socket.on('execute', data => {
  const input = data.inputs[0];
  for(const execution of input.payload.commands[0].execution){
    if(execution.command === 'action.devices.commands.OnOff'){
      const arg = execution.params.on ? on : off;
      exec(`input touchscreen tap ${arg}`);
    }
    if(execution.command === 'action.devices.commands.BrightnessAbsolute'){
      const arg = execution.params.brightness ? on : dimm;
      exec(`input touchscreen tap ${arg}`);
    }
  }
  console.log(util.inspect(data, true, null));
});

一つだけ問題があって、HDMIに何もつながってないと起動しません。そして起動後HDMIを抜くと、

05-01 00:07:25.610   188   267 I hwc-drm-connector: force mode to 1280x720@0Hz
05-01 00:07:25.613   188   267 I hwc-drm-two: Unplug event @1596363841 for connector 29 on display 0
05-01 00:07:25.634  3546  3546 I HWC2    : Destroying display 0
05-01 00:07:25.637  3546  3546 E HWComposer: isConnected failed for display 0: Invalid display
05-01 00:07:25.637  3546  3546 E HWComposer: getPresentFence failed for display 0: Invalid display
05-01 00:07:25.637  3546  3582 E HWComposer: onVsync Failed to find display 0
05-01 00:07:25.637  3546  3546 E HWComposer: isConnected failed for display 0: Invalid display
05-01 00:07:25.637  3546  3546 E HWComposer: getPresentFence failed for display 0: Invalid display
05-01 00:07:25.651  3616  3639 I DisplayManagerService: Display device removed: DisplayDeviceInfo{"内蔵スクリーン": uniqueId="local:0", 1280 x 720, modeId 3, defaultModeId 3, supportedModes [{id=1, width=1280, height=720, fps=50.0}, {id=2, width=1280, height=720, fps=59.9402}, {id=3, width=1280, height=720, fps=60.0}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@40f16308, density 160, 54.186 x 53.788 dpi, appVsyncOff 1000000, presDeadline 16666667, touch INTERNAL, rotation 0, type BUILT_IN, state ON, FLAG_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}
05-01 00:07:25.684  3616  3631 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: ActivityManager
05-01 00:07:25.684  3616  3631 E AndroidRuntime: java.lang.IllegalArgumentException: Can't remove the primary display.
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at com.android.server.am.ActivityStackSupervisor.handleDisplayRemoved(ActivityStackSupervisor.java:4337)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at com.android.server.am.ActivityStackSupervisor.access$300(ActivityStackSupervisor.java:193)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at com.android.server.am.ActivityStackSupervisor$ActivityStackSupervisorHandler.handleMessage(ActivityStackSupervisor.java:4733)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:106)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at android.os.Looper.loop(Looper.java:193)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at android.os.HandlerThread.run(HandlerThread.java:65)
05-01 00:07:25.684  3616  3631 E AndroidRuntime:        at com.android.server.ServiceThread.run(ServiceThread.java:44)
05-01 00:07:25.685  3616  3703 I InputReader: Reconfiguring input devices.  changes=0x00000004
05-01 00:07:25.694  3616  3631 I Process : Sending signal. PID: 3616 SIG: 9
05-01 00:07:26.130   200   200 I lowmemorykiller: lmkd data connection dropped
05-01 00:07:26.131   200   200 I lowmemorykiller: closing lmkd data connection
05-01 00:07:26.152  3549  3597 W AudioFlinger: power manager service died !!!
05-01 00:07:26.152  3788  3803 W Sensors : sensorservice died [0xaba4dc00]
05-01 00:07:26.155   149   149 I ServiceManager: service 'telecom' died
05-01 00:07:26.155   149   149 I ServiceManager: service 'contexthub' died
(...省略)
05-01 00:07:26.166   149   149 I ServiceManager: service 'imms' died
05-01 00:07:26.166   149   149 I ServiceManager: service 'autofill' died
05-01 00:07:26.166   149   149 I ServiceManager: service 'profile' died

DisplayがなくなったのでDisplayManagerがDisplayを削除して…するとActivityManagerが対応したディスプレイを削除…しようとしたけどPrimaryなので削除できない!!といったあとシステムがダウンしていったのでした。悲しい。

その結果、部屋の真ん中にRaspberry Piが放置されることに…

f:id:tmyt:20200501012531p:plain

G8X WideMode 1.1.2は動きません

おしらせです。

G8X WideMode ver1.1.2というのを今朝6時ごろリリースしたのですが、ワイドモードスイッチが動作しないバグを埋め込みました。 10時頃に修正版をPlayストアにアップロードして現在*1配信処理中です。

そろそろいけるかな?と思っていたのですがまだ配信されていないのでAPKを置いておきます。 1drv.ms

ついでに、アプリの名前が"WideMode for LG" になりました。兄弟機でもぼちぼち動くようなので名前を変えた次第です。中身は変わりません。

*1:2020/3/28 15:00

X1D IIからAndroidでRAWをダウンロードしてみる

専用アプリがなぜかiPadにしかなくて、せめてiPhoneには出してよ…とおもいつつ、いろいろあれこれした結果がこちらです。

github.com

FontAwesome ProのフォントファイルはさすがにGitに乗せられないので、FreeのOTFを入れて参照するフォントを変えるか、ProのDuotoneを入れるとたぶん動きます。

Unoを真面目に使ってみた

Uno Platform、実際に使った話をあまりきかない*1のでちょっくら真面目に使ってみるかってことで使ってみたのがこちらのソースコードになります。

感想

  • 案外動く
  • 仮想化が怪しい
  • TcpClientがまともに動いたのはびっくりしました

ほぼ同じコードで動いていて、違うコードを書かないといけなかったのは

  • Bluetooth LEの接続/通信
  • Wifiの自動接続
  • ファイルの書き出し先の決定(iOSのフォトとか、Androidの外部ストレージとか)

で、それ以外は同じコードでできた。まぁまぁいい感じだと思いました。

開発中だし仕方ないんだけど、怪しい箇所はちらほらあって

  • ActualWidthが取れない
  • CommandBarのボタンがなぜか押せない(iOS)
  • ListViewのImageのSourceにBindingすると仮想化の闇に飲まれる(Android)
  • ScrollViewerのZoomを使うと中の要素のレイアウトが壊れる(iOS)

とか確認しました。

*1:Hello Worldしかみんなやらない問題