tmytのらくがき

個人の日記レベルです

Uno PlatformでmacOSアプリを作れるかな?

GitHubのissue*1を眺めていると、どうやらNuGetにXamarin Mac対応のバイナリが放流されているらしい?

NuGetのバイナリを手に入れて、展開してみると確かにxamarinmac20向けのバイナリが含まれているみたい。

$ unzip -l 2.0.512-dev.4178.nupkg | grep xamarinmac20
     4039  2019-11-27 03:05   build/xamarinmac20/uno.ui.targets
     4927  2019-11-27 03:11   build/xamarinmac20/Uno.UI.Tasks.targets
  6022624  2019-11-27 03:47   lib/xamarinmac20/Uno.dll
    84960  2019-11-27 03:47   lib/xamarinmac20/Uno.Foundation.dll
    24096  2019-11-27 03:18   lib/xamarinmac20/Uno.Foundation.pdb
   937116  2019-11-27 03:19   lib/xamarinmac20/Uno.pdb
  4546016  2019-11-27 03:47   lib/xamarinmac20/Uno.UI.dll
  1340020  2019-11-27 03:20   lib/xamarinmac20/Uno.UI.pdb
   196576  2019-11-27 03:47   lib/xamarinmac20/Uno.Xaml.dll
   712192  2019-11-27 03:18   lib/xamarinmac20/Uno.Xaml.pdb

macOSではまだHello Worldが動く程度らしいけど、一応ビルドはできるらしい。

ということはcsprojをいい感じに整えてあげれば好きなようにアプリがつくれる?気がしたのでとりあえずプロジェクトテンプレートにmacOSなプロジェクトを追加してみました。

github.com

これを使うとmacOS向けのプロジェクトがソリューションに増えます。

f:id:tmyt:20200130044454p:plain

これをmacでビルドするとiOSと似たような結果が得られて一安心。

f:id:tmyt:20200130044541p:plain

ただ、macOS向けのUno Platformは絶賛開発中なので2020年1月30日現時点で

  • TextBoxが未実装
  • Button, CheckBox, RadioButton は押しても反応しない
  • Selector系コントロールは例外が出る

という感じなのでパッチを書きつつ応援していきましょうね!

G8X ThinQ向け、別画面でブラウザを開くアプリをリリースしました

ダウンロード

今回は最初からPlayストアに公開しました。

play.google.com

機能

  1. デフォルトブラウザにしておくと、カバー画面有効時に別画面でURLを開きます
  2. カバー画面が使えない場合*1時はURLのハンドルを自動で無効にできます*2

機能1について

このアプリをデフォルトブラウザに設定しておくと、ほかのアプリ(ex. Twitter) でURLを開くときにこのアプリがブラウザの代わりになります。

ブラウザとして開かれたこのアプリは、即座に現在アクティブでない側の画面でブラウザを起動します。この時使われるブラウザは、アプリの設定にあるものが使われます。設定される前や、設定した後にそのブラウザが削除された場合は初回のみブラウザ選択画面が出ることがあります。

機能2について

カバー画面の有効/無効に合わせて、このアプリのブラウザの代わりになる機能を一時的に有効/無効を自動的に切り替える機能があります。この機能を有効にすると、Chrome Custom Tabsなどでアプリ内ブラウザを実装しているアプリはカバー画面が無効の時はアプリ内ブラウザが起動するようになります。

まとめるとこういう動作になります。

||自動切換え有効|自動切換え無効| |カバー画面有効|別画面でブラウザ起動|別画面でブラウザ起動| |カバー画面無効|アプリ内ブラウザ|同じ画面でブラウザ起動|

この機能はイベントを受け取るためだけのユーザー補助サービスとして実装されているので必要であれば有効にしてください。*3

設定

今回はランチャにアイコンが追加されてその画面から使用するブラウザの設定ができます。あとはデフォルトのブラウザに設定しておけばOKです。

*1:カバーがない、画面がOff、カバーを開ききってる

*2:ユーザー補助サービスを有効にすると使えます

*3:ユーザー補助サービスは筋悪いかな…とおもいつついったんこれでお願いします。

2019年買ってよかったもの

2019年も今日で終わりなので買ってよかったものをまとめておきます。

PC編

Lenovo C630

LenovoのARM64版Windows PC。リモートデスクトップだけで使っていると12~16時間ぐらいバッテリーで使えるのがとてもよい。 ARM64バイナリを実行する分には十分はやいし、x86エミュレーションをしても実用的な速度で動くのでARM64版Windowsは結構使える子ですよ?

国内モデルはOfficeが付いていて14万するのが難点。

Ryzen 9 3900X

ADMの12コア24スレッドCPU。メニーコアは大正義。Xamarin Androidのビルドをしたり、UWPのリリースビルドをしたときに明らかにパフォーマンスに効いてくるのが買ってよかったポイント。 Facebook MessengerのUWP版はXboxコントローラを接続するとCPUを100%使う不具合があるんですけど、24スレッドあると1個つぶされても何も気にならないというのもかなりいい。

最近は供給が増えてきた感じもするけど、手に入れづらいのが難点

カメラ/レンズ

SIGMA Art 135mm/f1.4

これはニコン用。年初に14-24/2.8を買ったので今年はもうレンズを買わないぞ…と心に決めていたのに、諸事情で135mmが必要になったのでこれは今年買った2本目のレンズ。

中心から端まできれいに解像するし、明るくてきれいにボケるのがやっぱりArtなんだなぁ…ははぁ…としょーもない感想を抱きました。 こんな感じのが撮れています。

f:id:tmyt:20191231234718p:plain

Hasselblad X1D II

中版ミラーレスカメラを買いました。中版すごい…さすがに奇麗だし、なに撮っても奇麗に写ってるなぁ…となってしまってよくない。もっとちゃんと写真撮らねば。という気持ちになる。 白飛び、黒飛びしたとおもっても結構ちゃんと階調が残っているのがDfと比べてすごいなぁと思ったところ。

レンズはXCD 90mm/3.2 を使ってます。いわゆる35mm版換算という表現をすると、71mmらしいです。こんな感じのが撮れています。

f:id:tmyt:20191231234753p:plain

小物

Aukey PA-D5

AukeyのPD充電器で、Type-Cが2ポート。ポートAは18W、ポートBは60Wもしくは45Wが出力される。

夏頃に出たAnkerのPowerPort Atom PD 2も2個ぐらいかって使っているのだけど、これは2個接続すると30Wx2になってしまうのでPC/iPadみたいな組み合わせにしたときにPCが充電されないところが難点。 Aukey PA-D5はその点で片ポートは45W確保されるので、どちらもちゃんと充電されるのがかなりおすすめポイント。

ただ、Type-Cの仕様に適合してるかとかは何も確認していないので気になる人は調査情報を待ってからにしたほうがいいと思います。

KOPI Mug

自動温めマグカップ。Qi充電器と共用の保温パッドが付属しているので、パッドに置いておくとマグカップに入れたものがずっとあったかいままになる便利グッズ。ようするに、ミニIHヒーターです。

これはかなり気に入っていて、コーヒーを入れてFF14をだらだらプレイして2時間とか3時間たっても中身はあったかいまま。とても優秀。まだ日本で売ってないかもしれない?ので、購入する場合はアメリカからの輸入になります。

自動車

マツダコネクトのAndroid Auto/Car Play対応レトロフィットキット

マツダ純正のマツコネをAndroid Auto/Car Playに対応させる改造パーツで、マツコネに接続したスマートフォンがナビになるやつ。 マツコネのナビが結構頭悪い*1ので、Google Mapsに置き換えたりができます。

ただ、Andorid Autoはタッチパネルが使えないのでちょっと不便。iPhoneをつなぐとちゃんとタッチパネルも使えるので便利ですよ。

まとめ

もっと家電買ったり、本棚かったりもしたんだけど省略しました。よかったなぁ、というのはこんな感じで2019年を締めたいと思います。2020年もよろしくお願いいたします。

*1:最近割とましになった

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

シーリングライトPanasonicのHH-XCB1283Aというモデルを使っているのですが、このモデルはリモコンが赤外線ではなくBluetoothになっているのでスマートフォンから操作できるのでとても便利です。なんですが、Bluetoothゆえいわゆる学習リモコンは全滅だしもちろんGoogle Assistantにも対応していないのでしかたなく力押しでGoogle Assistantに対応させました。

構成

今日は天気もよく、室温が高くて寝ているのか起きているのかわからない境目を漂っているときに、今回の構成を思いつきました。

f:id:tmyt:20191227192942p:plain

いろいろ起きた結果、USBで接続されたAndroidに対してadb shell input touchscreen tap x yでリクエストして、あらかじめ起動しておいたPanasonicの純正アプリを操作するだけ、簡単!天才!

アプリのどこを押すか決める

シーリングライトとの通信を取り持ってくれるAndroidをどうやって操作するか結構悩んだ*1結果、ADB経由でアプリを操作すれば解決やーんとなりました。 このアプリはPlay Storeで★1.9ととても微妙な評価なのですが、見た目はこんな感じです。

どうみてもiPhoneのスクショですが、Androidも見た目は全く同じ見た目です。これは1~6に電灯の状態*2をプリセットできる仕組みで、1がよく使うやつ、6が消灯、2~5が時々使うものです。とりあえず点灯と消灯ができればいいので、1と6を押すようにします。

ADB経由で任意の地点を操作するには、inputコマンドを利用すれば簡単です。たとえば、X:100, Y:100 地点をタップするには

$ adb shell input touchscreen tap 100 100

とか呼べばOKです。では今回どこを押せばいいのかというと、今回のADBを受けてくれるNextbit Robinでは次の通りでした。

状態 X Y
ON 267 439
OFF 804 1162

この値は後で使います。

Google AssistantからのWebHookを受けるサーバを書く

次に、WebHookを受け付けるサーバを書きます。ハンドリングは、actions-to-googleっていうパッケージを使えばいいらしいので使いました。OAuth2しないといけない風だったので仕方なく空実装を作りました。ADBを中継するサーバ(後で出てきます)とはSocket.IOを使っています。

const express = require('express');
const bodyParser = require('body-parser');
const util = require('util');
const { smarthome } = require('actions-on-google');

const expressApp = express();
const app = smarthome();
const server = require('http').createServer(expressApp);
const io = require('socket.io')(server);

app.onExecute((body, headers) => {
  io.sockets.emit('execute', body);
  return {
    requestId: body.requestId,
    payload: {
      commands: [{
        ids: [ '1' ],
        status: 'SUCCESS',
        state: { on: true, online: true },
      }],
    },
  };
});
app.onQuery((body, headers) => ({
  requestId: body.requestId,
  payload: {
    devices: {
      '1': { on: true, online: true },
    },
  },
}));
app.onSync((body, headers) => ({
  requestId: body.requestId,
  payload: {
    agentUserId: '1',
    devices: [{
      id: '1',
      type: 'action.devices.types.LIGHT',
      traits: [ 'action.devices.traits.OnOff' ],
      name: {
        defaultNames: ['シーリング ライト'],
        name: 'シーリング ライト',
        nicknames: ['シーリング ライト'],
      },
      willReportState: false,
    }],
  },
}));

const assistantApp = express().use(bodyParser.json());
const oauthApp = express().use(bodyParser.urlencoded({ extended: true }));

assistantApp.post('/fulfillment', app);

oauthApp.get('/authorize', (req, res) => {
  console.log('authorize');
  const uri = `${req.query.redirect_uri}?state=${req.query.state}&code=12345678`;
  res.redirect(uri);
});
oauthApp.post('/token', (req, res) => {
  console.log('token');
  res.send({
    token_type: 'Bearer',
    access_token: '12345678' + Date.now(),
    refresh_token: '12345678',
    expires_in: 86400 * 90,
  });
});

expressApp.use('/assistant', assistantApp);
expressApp.use('/oauth', oauthApp);

io.on('connection', () => { });
server.listen(process.env.PORT);

OAuth2がまったく中身のない実装になっている、うえに、リクエストはトークンの検証していないなどとても雑です。これ、エンドポイントをたたかれると普通に動いてしまうので、皆さんはちゃんと認可を実装しましょうね

ADBを呼び出すサーバを実装する

あとはこのサーバに接続して、ExecuteをSocket.IO経由で受け取り、接続されたAndroidに対してADBでリクエストをするサーバが必要です。これはsocket.io-clientパッケージを使えば一瞬で書けます。

const socket = require('socket.io-client')('https://ないしょだよ!!!!.example.com');
const { exec } = require('child_process');
const util = require('util');

const on = '267 439';
const off = '804 1162';

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(`adb.exe shell input touchscreen tap ${arg}`);
    }
  }
  console.log(util.inspect(data, true, null));
});

child_process.execでADBを呼び出しています。このプロセスは、Windows上のWSLで動くNodeJSから、Windows側のPATH環境変数に含まれているadb.exeを呼び出しているので".exe"まで含んでいます。Linuxなら不要です。

おわり

あとは、Google Assistantから呼んでもらえるようにいろいろを設定すればできあがり。

console.actions.google.com に新しいアクションを作って、WebHookサーバのURLを設定し、ADBを呼び出すサーバを実行し、Socket.IOの接続が確立できたのを確認したのちにGoogle Assistantに呼びかけます。

ねぇGoogle 電気つけて!!!!!!

きっとシーリングがつくはず…

*1:オートメーション的ななにかを使う?とかいろいろ

*2:明るさ、色など

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の屏風でした。