tmytのらくがき

個人の日記レベルです

G8X ThinQのワイドモードを操作するアプリをTaskerに対応しました

G8X ThinQのワイドモードを無理やり有効にする例のボタンですが、Taskerに対応してくれないか?と要望をもらったので、Taskerに対応しました。

Taskerは特定のアプリが起動したときをはじめいろいろなタイミングで定型アクションを実行するツールなんですが、外部アプリをプラグインとして実行できるらしく、プラグインとして対応しました。

play.google.com

Taskerのアクションを選択する際に、プラグインを選ぶと"G8X WideMode"*1があるのでそこからこのアプリを呼び出せます。

Taskerに提供している機能は、ワイドモードを「ONにする」、「OFFにする」、「トグルする」の3種類です。プラグインの設定で動作を切り替えることができます。 例えば、「Kindleを起動したときにワイドモードをONにする」というような感じで使うことができます。便利ですね。

Tasker連携はバージョン1.1.0から利用可能です。アップデートしてご利用ください。

play.google.com

*1:ワイドモードを切り替えっていうキャプションにしたはず…

G8X ThinQ向け、ワイドモードボタンを増やすアプリを作ってみました

TL;DR

  • 右側アクティビティを強制的にワイドモードにするボタンをクイック設定パネルに出せるアプリです
  • 運が悪いと描画が崩壊する可能性があります
  • ダウンロードはここから。 Playストアからどうぞ

G8X ThinQのワイドモード不便ですよね

ごく一部というか実質Chromeしかワイドモードが使えなくて不便すぎるので、簡単なアプリを用意しました。 インストールするとクイック設定パネルに強制的にワイドモードに切り替えるボタンを追加できるようになります。

2019/12/13時点でPlayストアに未公開なので、不明なソースからのインストールを許可しないといけないのでリスクを理解している人向けです。

Playストアに公開しましした🎉

使い方

クイック設定パネルの設定を開きます

f:id:tmyt:20191213224747p:plain:w300

Wide Mode というアイコンが増えているので任意の場所に追加して、保存します。

f:id:tmyt:20191213225000p:plain:w300

ワイドモードにしたいアプリを右画面で起動して、クイック設定パネルの Wide Mode を押します。

f:id:tmyt:20191213224852p:plain:w300

ワイドモードになりました。

f:id:tmyt:20191213224900p:plain:w300

注意点

  • 内部APIを直接呼び出しているので時々描画が崩壊するかもしれません。
  • 最悪デバイスを再起動すればすべて元に戻ります。
  • 実績として、右側画面の画面分割と組み合わせたりしていじってたら描画が崩壊したことが1回、その後Chromeを正規手順でワイドモードにしたり戻したりしていたらSystemServerが再起動したことが1回あります。

LG G8X ThinQを契約なしで買ってきました

今日Softbankから発売になったLGのG8X ThinQを契約なしで買ってみました。屏風だしね。

Softbankの直営店に行って、端末だけ購入したいと伝えたらすぐに受付してくれました。とてもスムーズに購入できたので次回からも利用したいですね。

詳しいスペックその他は詳しいページにお譲りするとして、ぶっちゃけ55400円はめちゃくちゃ安いと思いますよ、FeliCaだし、Qiだし、SD855だし、RAM 6GBだし、防水だし、追加画面あるし、etc。

写真その他

しっぽ、絶対なくすと思うんです。間違いなく。磁石でついてるんですけど裏表あって微妙に不便だし、絶対なくすと思うんです(2回目)。

指紋認証もついてて、ディスプレイ内蔵なんですよ。かっこいいですね。若干認識制度があやしいか?っていう気もするんですけどだんだん慣れてきました。指の腹がぷにって押しつぶされるくらいに押さえつけてちょっと待たないとだめっぽいです。慣れればどうってことないです。慣れれば。

SIMロック解除コードが見つからないとか紆余曲折あったものの、無事SIMロックが解除できました。いつも通りとりあえずT-Mobile USAのSIMでローミングする様子を眺める。

あ~~~~~~びょうぶ~~~~~~~~~~~~~ たまんないですね。

挙動の話

さて、この屏風。先行記事では、ワイドモードが一部のアプリしかできないとか書いてるじゃぁありませんか。たしかに、手元で確認したのはChromeだけです。Kindleすら広がらないのはいかがなものか。電書読ませろ。

そして、屏風芸人としてはまずは屏風にしないといけないな。ということで屏風にしてみたんですが、屏風にならないんですこれが。先行記事にあるように、ごくごく一部のアプリでしかワイドモードにできないそうです。 でも、どうしてもまずは屏風にしないといけない気がしたので頑張ったものがこちら。

ImageViewが2画面にわたって表示されていますね。期待した挙動です。完璧です。これを実現するにはこんなコードを書きます。

val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val setWideScreenMode = activityManager.javaClass.getDeclaredMethod("setWideScreenMode", Boolean::class.java)
setWideScreenMode.invoke(activityManager, true)

簡単ですねー。ただ、このコードを実行するのはお勧めしません。なぜなら、ロック画面の動作と、指紋認証の動作がおかしくなります。ロック/ロック解除を数回繰り返すと治ったりしますがお勧めしません。

なんでワイドモードになったりならなかったりするか

それは、ワイドモード対応アプリがハードコードされているからです!

えー…うそやん…えー…まじかよ…って15分ぐらい一人で騒いでいました。まじです。そのリストがこちらです。

  • com.android.chrome
  • com.lge.retailmode
  • com.lge.gallery.lduwidget
  • com.nhn.android.search
  • com.naver.whale
  • com.google.android.googlequicksearchbox
  • com.lge.epmobile

これ以外はワイドモードになりません。残念でした。じゃぁこれらのパッケージ名にしてあげればワイドモードが選べるのか?っていうと、選べました。

f:id:tmyt:20191207035355p:plain

つまりパッケージ名しか見てないということです。残念です。

おわり

画面が大きくて、SoCもはやくて、メモリもまぁまぁ多くて、全体的にポイント高いんですが、屏風という点ではDocomoの屏風に負けてしまうSoftbankの屏風でした。

Uno PlatformでDarkテーマを使うにはPre-releaseにする

Uno CalculatorがDark modeをサポートしたよ!っていうアップデートが配信されていたのでソースを読みながら同じようにしてもDarkにならんのやが…って1日考えてみた結果、Pre-releaseにしたら解決しました。という話です。

ThemeDictionariesが動かない

例えばこんな感じで書くと2019年12月2日現在のリリース版のUno Platformだと動きません。

<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary x:Key="Default">
          <SolidColorBrush x:Key="ThemeBrush" Color="DarkBlue" />
          <SolidColorBrush x:Key="BackgroundBrush" Color="Gray" />
        </ResourceDictionary>
        <ResourceDictionary x:Key="Light">
          <SolidColorBrush x:Key="ThemeBrush" Color="Red" />
          <SolidColorBrush x:Key="BackgroundBrush" Color="#ccccff" />
        </ResourceDictionary>
      </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>

これは、前にも話題にしたインラインなResourceDictionaryの動作に制限があるためで、ThemeDictionaries内のResourceDictionaryは解釈されません。

ただ、あくまでもインラインなResourceDictionaryが問題なのであって以前に話題に挙げたようにglobal-level*1な ResourceDictionaryに定義すれば2019年12月2日現時点のリリース版でもThemeDictionariesの中身は解釈されます。 ただ、この場合常にテーマはLightとして処理されます。

ThemeDictionariesは動く

ThemeDictionariesは動きます。ただUno.UIのPre-Release版が必要です。

しつこいようですが2019年12月2日現在のリリース版は2019年7月23日にリリースされた1.45.0というバージョンが最新です。 このバージョンではThemeDictionariesを解釈することはできるのですが、DarkやHighContractを使用することはできません。

Light以外のテーマを使えるようになるには、2019年10月9日にマージされた#1628というプルリクエストを含んだバージョンにする必要があります。 これが一体どのバージョンからなのか…というのは探すのが大変なのであきらめました…

対応してるバージョンの境目を探すのも大変なので、とりあえず今回はUno.UIを2.1.0-dev.52というバージョンにNuGetから更新します。 このバージョンはプレリリース版としてマークされているので、NuGetの画面からプレリリースを含めるというところにチェックを付けると表示されます。

f:id:tmyt:20191202012036p:plain

Uno.UI以外はプロジェクトが生成されたときのデフォルトのままのバージョンを使ってビルドしても正しく動いているようです。

Uno.UIがプレリリース版に更新できたので、次のXAMLを適当な場所に保存します。ここではThemes/Styles.xamlという名前で保存しました。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
      <SolidColorBrush x:Key="ForegroundBrush" Color="DarkBlue" />
      <SolidColorBrush x:Key="BackgroundBrush" Color="Gray" />
    </ResourceDictionary>
    <ResourceDictionary x:Key="Light">
      <SolidColorBrush x:Key="ForegroundBrush" Color="Red" />
      <SolidColorBrush x:Key="BackgroundBrush" Color="#ccccff" />
    </ResourceDictionary>
  </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

これを使用するMainPage.xamlを次の内容で作成しました。

<Page
    x:Class="UnoApp2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

  <Grid Background="{ThemeResource BackgroundBrush}">
    <TextBlock Foreground="{ThemeResource ForegroundBrush}" Text="Hello, world !" Margin="20" FontSize="30" />
  </Grid>
</Page>

App.xamlにはUWPとUnoの互換性の都合でMergedDictionariesの設定を書いてあります。

<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
      <ResourceDictionary>
          <ResourceDictionary.MergedDictionaries>
              <ResourceDictionary  Source="Themes/Styles.xaml" />
          </ResourceDictionary.MergedDictionaries>
      </ResourceDictionary>
  </Application.Resources>
</Application>

ここまでをビルドして実行すると次の結果になります。期待した通りの結果ですね。

f:id:tmyt:20191202011938p:plain

ただ、Unoのテーマ実装ではまだ動的なテーマ変更に対応していません。おそらく#1766がマージされた日にはいろいろと解決するのでしょう…

github.com

*1:ルート要素がResourceDictionaryとなってるXAMLファイルをこう呼んでいるようです

せっかくなのでUno Platformの話がしたい

Uno Platformというものがある

UWPのコードをベースにして、自力でXAMLを解釈して、Xamarinの上でViewをいい感じに動かすっていうアプローチ。Xamarin.Formsのラッパーではない。

俺たちが期待してるUniversalはこれなんだよ、いいからMSは早く買収するんだ。という気持ちを抑えながら、公式ページで図解されている構造を見てみるとこういう感じ。

https://s3.amazonaws.com/uno-website-assets/wp-content/uploads/2019/08/21141002/diagram.png

あれこれなんか4年前に似たような図を描いたような気がする*1

簡単なコードなら普通に動くし、複雑なコードもUno Platformをターゲットに最初から作ればわりかしちゃんと動く。よくできている。

実際にWindows 10の電卓が移植されていて、Play StoreとかApp Storeからダウンロードできるので、とりあえず「すげーーー」って言うには電卓を入れるのがおすすめ。

Uno Calculator

Uno Calculator

  • nventive
  • 仕事効率化
  • 無料
apps.apple.com play.google.com

ただ、本物のUWPと、UWPに似せたAPIを提供したXamarinだとどうしても違うことがあるので現行のUWPのコードを突っ込めばそのまま動くか?と聞かれれば答えは”ほぼNo”という感じ。 実際にコピペしたら全然動かんかった。

implicit styleが動かんという話

さて、いろいろ動かないことはあるんだけども一番困ったimplicit styleが動かんというトピックを紹介しておきます。 アプリの見た目をそろえるときにこういう書き方をすることがあります。

<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <Style TargetType="TextBlock">
      <Setter Property="Foreground" Value="Red" />
    </Style>
  </Application.Resources>
</Application>

すべてのTextBlock の文字色が赤になることを期待しています。当然UWPで実行すると赤で表示されます。

f:id:tmyt:20191130044459p:plain

ところが、Uno Platformでビルドすると次のメッセージが得られます。

Error reading response 1>MSBUILD : error : Generation failed: System.AggregateException: 1 つ以上のエラーが発生しました。 ---> System.InvalidOperationException: Generation failed for Uno.UI.SourceGenerators.XamlGenerator.XamlCodeGenerator. System.AggregateException: 1 つ以上のエラーが発生しました。 ---> System.Exception: Processing failed for file ***\UnoApp2.Shared\App.xaml ---> System.Exception: Implicit styles in inline resources are not supported (Application, Line 1:2) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.RegisterResources(XamlObjectDefinition topLevelControl) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.BuildApplicationInitializerBody(IndentedStringBuilder writer, XamlObjectDefinition topLevelControl) 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.InnerGenerateFile() 1>MSBUILD : error : 場所 Uno.UI.SourceGenerators.XamlGenerator.XamlFileGenerator.GenerateFile()

なんやかんや書いてますが、要するにx:Key が指定されていない暗黙的なリソースはサポートされてないですよ。ということらしいのです。めっちゃ困る。

これに関連したIssue(というかPR)をGitHubで探すとこれが見つかります。

github.com

ここの、 What is current behavior? に書いてあるのですが

Uno only supports global-level resources, including implicit styles

らしいのです。このglobal-levelがポイントです。

解決する話

先のPRから現状の実装ではglobal-levelならimplicit stylesが動く。と読み取れます。 じゃぁglobal-levelなstyleとはなんぞや。という話があり、ドキュメントなどから雰囲気をつかむと要するにこれです。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Style TargetType="TextBlock">
    <Setter Property="Foreground" Value="Red" />
  </Style>
</ResourceDictionary>

てきとうにXAMLを作って、ResourceDictionaryを書いて、その中ならimplicit styleと呼ばれているものを書きまくってもいいということらしいのです。実際動かすと動きます。

f:id:tmyt:20191130045520p:plain:w220 f:id:tmyt:20191130045903p:plain:w220

そんじゃこれ、UWPで動くんかというと動きません。実行結果はこれです。

f:id:tmyt:20191130044539p:plain

ではこれが、全部の環境で期待値となるXAMLはどうなるか。というと、App.xamlでResourceDictionaryとして読めばいい。

<!-- Themes/ImplicitStyles.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="TextBlock">
        <Setter Property="Foreground" Value="Red" />
    </Style>
</ResourceDictionary>

<!-- App.xaml -->
<Application
    x:Class="UnoApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Application.Resources>
    <ResourceDictionary Source="Themes/ImplicitStyles.xaml" />
  </Application.Resources>
</Application>

これで最初の画像と同じ結果が得られます(見た目変らないので画像省略)。

だんだん奇妙なXAMLになってきましたね。さすがはCross Platform…たまんないですね…

WPFの子ウィンドウとしてUnityを実行してみる

UnityってWPFの中に表示したりできひんよね?って聞かれたからドキュメント読んだらできるって書いてたからやってみたらできました。という話。

TL;DR

  • -parentHWND 0x**** で子ウィンドウにできる
  • HwndHostでUnityのプロセスを子ウィンドウとして起動するだけ

Unityを子ウィンドウで起動する

Windows向けにビルドしたUnityはコマンドライン引数で -parentHWND を付けて起動するとUnity Playerは子ウィンドウとして起動します。その時の画面幅などはSTARTUPINFOで指定されたものを使用します。と、Unityのドキュメントに書いてあります。

Windows スタンドアロンアプリケーションを別のアプリケーションに埋め込みます。これを使用する場合は、親アプリケーションのウィンドウハンドル (HWND) を Windows スタンドアロンアプリケーションに渡す必要があります。

これ、日本語だと特に書いてないんですけど、0xXXXXの形で16進数として渡すと成功します。 追記: 0xなしで10進でもOKでした

簡単にC#から起動するにはこんな感じ。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        StartUnity();
    }

    void StartUnity()
    {
        var apppath = @"C:\doko\soko\unity-app.exe";
        var cmdline = $"-parentHWND {new WindowInteropHelper(this).Handle}";
        Process.Start(apppath, cmdline);
    }
}

実行すると、UnityがWPFの中で動いているような感じになりました。が、リサイズしてもウィンドウサイズが追従しないのでいまいちです。

f:id:tmyt:20190926065827p:plain

HwndHostをそれとなく実装していく

HwndHostを実装すれば子ウィンドウのサイズをWPFが面倒見てくれるようになります。Unityを子ウィンドウとして起動してくれるHwndHostを実装したクラスを書いてリサイズをいい感じに面倒見てもらいます。

class UnityHost : HwndHost
{
    private Process _childProcess;
    private HandleRef _childHandleRef;

    public string AppPath { get; set; }
    public HandleRef Child => _childHandleRef;

    protected override HandleRef BuildWindowCore(HandleRef hwndParent)
    {
        var cmdline = $"-parentHWND {hwndParent.Handle}";
        _childProcess = Process.Start(AppPath, cmdline);
        while (true)
        {
            var hwndChild = User32.FindWindowEx(hwndParent.Handle, IntPtr.Zero, null, null);
            if (hwndChild != IntPtr.Zero)
            {
                return (_childHandleRef = new HandleRef(this, hwndChild));
            }
            Thread.Sleep(100);
        }
    }

    protected override void DestroyWindowCore(HandleRef hwnd)
    {
        _childProcess.Dispose();
    }
}

static class User32
{
    [DllImport("user32.dll")]
    public static extern IntPtr FindWindowEx(IntPtr hParent, IntPtr hChildAfter, string pClassName, string pWindowName);
}

急にコードが増えた気がしますが気のせいです。BuildWindowCoreの中でUnityを起動して、できたウィンドウのハンドルを返してるだけです。 このままだと、起動するパスがどこか明示されていないのでXAMLから指定します。

<Window x:Class="WpfApp4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp4"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:UnityHost AppPath="C:\doko\soko\unity-app.exe" />
    </Grid>
</Window>

これでリサイズできるようになりました。

起動時にWPFのクライアントエリア全体にUnityが出ないようにする

ここまでのコードだと、XAMLでUnityHostをGridにいれてサイズを変えたりしていても、Unityを起動したときにクライアントエリア全体にUnityが一瞬表示されます。

f:id:tmyt:20190926070602p:plain

これを回避するにはUnityのドキュメントに書いてあったようにSTARTUPINFOを指定すればいいんですが、残念ながらそれはP/Invokeです。ということでP/Invoke使っていろいろ実装したものがこちらです。

gist.github.com

P/Invokeのせいでやたらと長くなった…やな感じ… 実際の処理はBuildWindowCoreを参照してください。STASRTUPINFO構造体を埋めて、CreateProcessを呼び出しているだけです。

おしまい

  • Unityプレイヤーを-parentHWNDコマンドライン引数を使って、WPFに埋め込んでみました。
  • UnityプレイヤーをHwndHostを使ってWPFの子ウィンドウとして管理しました

あるプロセスが読み込んでいる.NET Coreランタイムを特定したい

なんか表題のことがしたいらしく、よく聞いたら任意の.NET Coreプロセスが読み込んでいるcoreclr.dllのパスが知りたいという話でした。

coreclr.dllで.NET Core仮想マシンをホストしてるんだから、プロセスが読み込んでるモジュールを列挙したらわかるでしょ。とか思って、Processの情報を眺めたら、彼はそこに常にいたので、それを探したらお悩みは解決。

どうやらC#で解決したいらしいので、とりあえずpsapiからEnumしただけでおわり。

gist.github.com

EnumProcessesでプロセスを列挙してOpenProcessにPROCESS_VM_READとPROCESS_QUERY_INFORMATIONをつけてプロセスハンドルを手に入れて、EnumProcessModulesExでモジュールハンドルを列挙して、GetModuleFileNameExでモジュールのパスを取得する。そこにcoreclr.dllがあればそいつが.NET Coreプロセスだ!

という話でした。