ひつじTips

技術系いろいろつまみ食います。

[Unity]アプリのウインドウハンドルを確実に取得する方法

このQiita記事にめっちゃちゃんとまとまっていて(@kiruroboさん,素晴らしい記事本当にありがとうございます!!!),本エントリで紹介する方法はほぼこの「方法4」ではあるんですが,この「方法4」ではうまくいかないところがあったのでなんとかしましたよ,というのが本エントリになります.

qiita.com

諸事情により,アプリがバックグラウンドにいた場合でもウインドウハンドルを取得したく,その場合 GetActiveWindow() 関数を使用するQiita記事内の「方法1」「方法2」が使えません.

「方法3」でもいいのですが,ちょっとQiita記事内でも挙げられてる「問題点」が厳しくパス,「方法5」もちょっとめんどくてパス,というので,「方法4」たどり着きました.

Qiita記事には「方法4」の実装まで書かれていなかったので,具体的な実装を明記しつつ,問題点とそれの回避方法までまとめます.

無駄に長いので,答えが欲しい方は「4. 実装」の方にとんでください~

  • 方針
  • 問題
  • 問題の解決
  • 実装
  • 感想
続きを読む

Ubuntu16.04にpython3.7を apt install する方法(というか,apt レポジトリに登録されていないpythonのバージョンを導入する方法)

Ubuntu16.04 のデフォルトは python3 は 3.5ですが,新しいバージョンのが使いたくなるときがあるかと思います.(参考:python3.7のリリースノート

「Ubuntu16.04 python3.7 導入」とかを普通にググると,ソースからビルドしましょう系ばかり(下記「参考」参照)で,まぁそれはそれでいいんですけど,ちょっとめんどい...という感じです.

でも,ちゃんとPPAを管理してくれてる人がいて,そちらを使うとソース持ってきてビルドとかせずに,apt install でpython3.7を導入することができます!

(もうubuntu20.04も出ようか,というタイミングでアレですが,まだまだubuntu 16.04から抜けられない人もいるはず...)

やりかた

deadsnakes teamさんが作ってくれてるPPAを使います.

2020/3/7現在,確認すると2020/2/26にpython3.9とかも追加されていて,完全に死んでるようなところではなさそうです.

launchpad.net

(deadsnakesという名前が中二心をくすぐりますね!)

以下のようにコマンドをターミナルで実行すると,ubuntu16.04にpython3.7がapt installできます.

まず, add-apt-repositoryを以下のようにして導入(導入済みならスキップOK):

sudo apt update
sudo apt install software-properties-common

で,PPAを追加:

sudo add-apt-repository ppa:deadsnakes/ppa

最後に,pythonインストール:

sudo apt update
sudo apt install python3.7

これで,python3.7 が入ると思います!


ただし,python3 -Vで確認すると以前のバージョンのもののままだと思います.

このときは,aliasを変えるかシンボリックリンク先を変えちゃうのがよいのではないかと思います.

aliasの変更は,~/.bashrcalias python3='/usr/bin/python3.7'の行を追加する,

シンボリックリンク変更は,以下のようなコマンドで可能だと思います.

cd /usr/bin
sudo ln -nfs python3.7 python3

参考

websiteforstudents.com

ソースからビルドする方法

[Unity]Texture2Dのサイズを変更する方法

UnityのTexture2Dのサイズを変更したいとき「Texture2Dにresize関数がある!これで勝つる!」と思ったら,灰色になる(=色がなくなる)の,あるあるですよね.

Texture2Dのサイズを変更するのに,ググると以下の2つぐらいが出るかと思います.

light11.hatenadiary.com

kan-kikuchi.hatenablog.com

1つ目はRenderTextureを経由してリサイズする方法,2つ目はピクセル単位でCPUでループ回してサイズを変える方法,だ思うのですが,

今回はもう1つの(もっとカンタンではなかろうかと思われる)方法あったよ,というお話です.

結論

Graphics.ConvertTexture関数を使えばできます.

docs.unity3d.com

第一引数のsrcの方にサイズを変更したい元テクスチャを入れて,第二引数のdstには変更したいサイズのテクスチャを入れます.

例えば,このような関数を作れば便利に使えるかなと思います.

    static Texture2D ResizeTexture(Texture2D srcTexture, int newWidth, int newHeight) {
        var resizedTexture = new Texture2D(newWidth, newHeight);
        Graphics.ConvertTexture(srcTexture, resizedTexture);
        return resizedTexture;
    }

ただし,テクスチャのコピーになるので,パフォーマンスがシビアなところではご使用にお気をつけください.

具体例

例えば,以下のようなC#スクリプトで挙動を確認します.

これを適当なGameObjectにつけて実行すると,Spaceを押下したときに,テクスチャのWidth/Heightをプラス100してサイズ変更し,インスペクタで設定したオブジェクトのマテリアルのテクスチャを更新します.

public class TextureResizer : MonoBehaviour {
    public Texture2D targetTexture;
    public MeshRenderer targetMeshRenderer;

    private Material targetMaterial;

    void Start() {
        Debug.Log(string.Format("Width: {0}, Height: {1}", 
                                                 targetTexture.width, targetTexture.height));
        targetMaterial = targetMeshRenderer.material;
    }

    void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            targetTexture = ResizeTexture(targetTexture, 
                                                            targetTexture.width + 100, 
                                                            targetTexture.height + 100);
            targetMaterial.SetTexture("_MainTex", targetTexture);
            Debug.Log(string.Format("Width: {0}, Height: {1}", 
                                                     targetTexture.width, targetTexture.height));
        }
    }

    static Texture2D ResizeTexture(Texture2D srcTexture, int newWidth, int newHeight) {
        var resizedTexture = new Texture2D(newWidth, newHeight);
        Graphics.ConvertTexture(srcTexture, resizedTexture);
        return resizedTexture;
    }
}


これを実行したときのテクスチャをUnityEditor上で確認すると,もともと256x256だったのですが,356x356に変わっていることがわかります.

f:id:mu-777:20200222185048p:plain


また,コンソール出力を見ると,ちゃんとWidth/Heightが100ずつ増えていることがわかります.

f:id:mu-777:20200222014108p:plain

jupyter+matplotlibで3Dグラフを書くとき,ipywidgetsを使ってインタラクティブに数値を変える方法

f:id:mu-777:20200209150435g:plain

jupyter+matplotlibでグラフを書くときに,ipywidgetsのinteract関数を使って数値をインタラクティブに変更する方法がよく紹介されている(下記リンク参照)のですが,Axes3Dでグラフを作るときに結構ハマったので書いておく.

結論

上のgifを実現してるコードを載せる.

%matplotlib inline
# %matplotlib notebook  # notebookでは画が出ない

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact

fig = plt.figure()
ax = fig.gca(projection='3d')

def scatter(num_data):
    x = range(num_data)
    y = [np.sin(t/10.0) for t in x]
    z = [np.cos(t/10.0) for t in x]
    plt.gcf().gca(projection='3d').plot(x, y, z, 'o',color='C3')
    plt.show()

interact(scatter, num_data=(1, 300, 1))

キモは,interact関数に渡しているコールバック関数内で,pltから現在のAxes3Dを取得しなおしているところ.

interact関数を実行する前に作ったAxes3Dのaxは,コールバック関数内に渡せない(のか?? 下記「試してうまくいかなかったこと」では渡すことができてるようにも見えるが...).コールバック関数はstatic的に記述できるようにしておかねばならない.

なので,以下のように global 宣言を使っても期待通り機能する.

%matplotlib inline
# %matplotlib notebook

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact


def scatter(num_data):
    fig = plt.figure()
    global ax
    ax = fig.gca(projection='3d')
    x = range(num_data)
    y = [np.sin(t/10.0) for t in x]
    z = [np.cos(t/10.0) for t in x]
    ax.plot(x, y, z, 'o',color='C3')
    plt.show()

interact(scatter, num_data=(1, 300, 1))

試してうまくいかなかったこと

クラス化

これを実行すると,最初の初期値でのグラフが表示されるが,スライダーを動かしたら消える..

print(self._name, num_data, self._ax.get_zlabel())scatter関数に入っているが,ここは期待通りに,Interacterクラスの__init__で定義した "test" とか "aaaaa" が表示されているので,selfが渡っていないわけではないが..

%matplotlib inline
# %matplotlib notebook

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact


class Interacter(object):
    def __init__(self):
        self._name = "test"
        self._fig = plt.figure()
        self._ax = self._fig.gca(projection='3d') 
        self._ax.set_zlabel("aaaaa")

    def scatter(self, num_data):
        print(self._name, num_data, self._ax.get_zlabel())
        x = range(num_data)
        y = [np.sin(t/10.0) for t in x]
        z = [np.cos(t/10.0) for t in x]
        self._ax.plot3D(x, y, z,  'o',color='C3')
        plt.show()
        
interacter = Interacter()
interact(interacter.scatter, num_data=(1, 300, 1))

これ地味に解せない...

2Dグラフのサンプルの拡張

これも上と同様に,最初はグラフが表示されるが,スライダーを動かしたら消える. print(ax.get_xlabel(), num_data) では,期待通りに ax.set_xlabel("aaa") と設定した文字列"aaa"が表示されるにも関わらず...

%matplotlib inline
# %matplotlib notebook

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact

fig = plt.figure()
ax = fig.gca(projection='3d') 
ax.set_xlabel("aaa")

def scatter(num_data):
    x = range(num_data)
    y = [np.sin(t/10.0) for t in x]
    z = [np.cos(t/10.0) for t in x]
    print(ax.get_xlabel(), num_data)
    ax.plot3D(x, y, z,  'o',color='C3')
    plt.show()

interact(scatter, num_data=(1, 300, 1))

感想

matplotlib 難しすぎて使うたびにググってる気がする...ソース読めばこの挙動の理由がわかる気もするがめんどい...

ホモグラフィ変換行列の導出方法についてまとめ

f:id:mu-777:20200202190729p:plain

ちょっとホモグラフィ変換について調べたので,そのまとめ(という名のいろんなページの情報集約)

モグラフィ変換とは

平面から平面へ写像する変換,ぐらいの理解.直感的には以下のサイトの下の方のデモアプリがわかりやすい.

shogo82148.github.io

アフィン変換は,平行移動と回転・拡縮(スケール)・せん断(スキュー)の組み合わせまで.ホモグラフィ変換はそれを拡張し,台形のような変換まで可能.

結局は写像なので,以下のような同次変換で表すことができる.

 \boldsymbol{x}' = H\boldsymbol{x} \quad  \Leftrightarrow \quad \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = H\begin{bmatrix} x \\ y \\ 1 \end{bmatrix}


ただし, \boldsymbol{x}' は変換後の点, \boldsymbol{x} は変換前の点とする.

モグラフィ変換行列  H の導出

方針

モグラフィ変換行列 H'を一般的に考えると,点の変換は以下のように表せる.

 \begin{bmatrix} X' \\ Y' \\ W' \end{bmatrix} 
= 
\begin{bmatrix}
h'_{00} & h'_{01} & h'_{02}  \\
h'_{10} & h'_{11} & h'_{12}   \\ 
h'_{20} & h'_{21} & h'_{22}   
\end{bmatrix} 
\begin{bmatrix} x \\ y \\ 1 \end{bmatrix}

 
\begin{bmatrix} x' \\ y'  \end{bmatrix} 
= 
\begin{bmatrix} X'/W' \\ Y'/W' \end{bmatrix}


ここで, H'を定数倍した, sH' sは定数)での変換を考えると,結局 X',  Y',  W' s倍されるだけになる.

 \begin{bmatrix} x' \\ y'  \end{bmatrix} 
= 
\begin{bmatrix} sX'/sW' \\ sY'/sW' \end{bmatrix} 
= 
\begin{bmatrix} X'/W' \\ Y'/W' \end{bmatrix}


なので,この一般的なホモグラフィ変換行列はスケールに不定なので,これから考えるホモグラフィ変換行列 H h_{22}を1として考える.つまり,

 H = \begin{bmatrix}
h_{00} & h_{01} & h_{02}  \\
h_{10} & h_{11} & h_{12}   \\ 
h_{20} & h_{21} & 1  
\end{bmatrix} 
= 
\frac{1}{h'_{22}}
\begin{bmatrix}
h'_{00} & h'_{01} & h'_{02}  \\
h'_{10} & h'_{11} & h'_{12}   \\ 
h'_{20} & h'_{21} & h'_{22}  
\end{bmatrix}


ここで求めるべき値は H の8つの要素となる.1点の変換につき, x,  yの2式が立てられるので,4点の変換がわかればホモグラフィ変換行列は求めることができるはず.

その4点を,変換前と変換後それぞれ  \boldsymbol{x_i},  \boldsymbol{x'_i}(ただし, i \in [0, 1, 2, 3])とし,それらは既知として,以下のような式を導出することで, H を求める.

 
\boldsymbol{y}(\boldsymbol{x_i}, \boldsymbol{x'_i}) 
= 
A(\boldsymbol{x_i}, \boldsymbol{x'_i})
\begin{bmatrix}
h_{00} \\ h_{01} \\ h_{02}  \\
h_{10} \\ h_{11} \\ h_{12}  \\ 
h_{20} \\ h_{21}
\end{bmatrix}


式の展開

あらためて,元のこの式を \boldsymbol{x_0} から   \boldsymbol{x'_0}への返還として展開すると,

 \boldsymbol{x_0}' = H\boldsymbol{x_0}

 \Leftrightarrow 
\begin{bmatrix} x'_0 \\ y'_0 \\ 1 \end{bmatrix} 
= 
\frac{1}{W'_0} \begin{bmatrix} X'_0 \\ Y'_0 \\ W'_0 \end{bmatrix}
= 
\frac{1}{h_{20} x_0 + h_{21} y_0 + 1} 
\begin{bmatrix} 
h_{00} x_0 + h_{01} y_0 + h_{02} \\ 
h_{10} x_0 + h_{11} y_0 + h_{12} \\ 
h_{20} x_0 + h_{21} y_0 + 1 
\end{bmatrix}


とできる.よって,

 
(h_{20} x_0 + h_{21} y_0 + 1) \begin{bmatrix} x'_0 \\ y'_0 \\ 1 \end{bmatrix} 
= 
\begin{bmatrix} 
h_{00} x_0 + h_{01} y_0 + h_{02} \\ 
h_{10} x_0 + h_{11} y_0 + h_{12} \\ 
h_{20} x_0 + h_{21} y_0 + 1 
\end{bmatrix}


第3式は両辺が同じ値になるので,第1, 2式を整理すると,

 
\begin{bmatrix} 
h_{20} x_0 x'_0+ h_{21} y_0 x'_0+ x'_0 \\ 
h_{20} x_0 y'_0 + h_{21} y_0 y'_0 + y'_0  
\end{bmatrix} 
= 
\begin{bmatrix} 
h_{00} x_0 + h_{01} y_0 + h_{02} \\ 
h_{10} x_0 + h_{11} y_0 + h_{12} 
\end{bmatrix}

 \Leftrightarrow 
\begin{bmatrix} 
x'_0 \\ 
y'_0  
\end{bmatrix} 
= 
\begin{bmatrix} 
h_{00} x_0 + h_{01} y_0 + h_{02} - h_{20} x_0 x'_0 - h_{21} y_0 x'_0\\ 
h_{10} x_0 + h_{11} y_0 + h_{12} - h_{20} x_0 y'_0 - h_{21} y_0 y'_0
\end{bmatrix}


これを整理すると以下のようにでき,欲しい式のカタチになってきた!

 \Leftrightarrow 
\begin{bmatrix} 
x'_0 \\ 
y'_0  
\end{bmatrix} 
= 
\begin{bmatrix} 
x_0 && y_0 && 1 && 0    && 0     && 0 && - x_0 x'_0 && - y_0 x'_0\\ 
0    && 0     && 0 && x_0 && y_0 && 1 && - x_0 y'_0 && - y_0 y'_0
\end{bmatrix} 
\begin{bmatrix}
h_{00} \\ h_{01} \\ h_{02}  \\
h_{10} \\ h_{11} \\ h_{12}  \\ 
h_{20} \\ h_{21}
\end{bmatrix}


残りの3点の変換も同様に考えると(というかコピペして並べると)以下の式になる.

 
\begin{bmatrix} 
x'_0 \\ 
y'_0 \\ 
x'_1 \\ 
y'_1 \\ 
x'_2 \\ 
y'_2 \\ 
x'_3 \\ 
y'_3 
\end{bmatrix} 
= 
\begin{bmatrix} 
x_0 && y_0 && 1 && 0    && 0     && 0 && - x_0 x'_0 && - y_0 x'_0 \\ 
0    && 0     && 0 && x_0 && y_0 && 1 && - x_0 y'_0 && - y_0 y'_0 \\
x_1 && y_1 && 1 && 0    && 0     && 0 && - x_1 x'_1 && - y_1 x'_1 \\ 
0    && 0     && 0 && x_1 && y_1 && 1 && - x_1 y'_1 && - y_1 y'_1 \\
x_2 && y_2 && 1 && 0    && 0     && 0 && - x_2 x'_2 && - y_2 x'_2 \\ 
0    && 0     && 0 && x_2 && y_2 && 1 && - x_2 y'_2 && - y_2 y'_2 \\
x_3 && y_3 && 1 && 0    && 0     && 0 && - x_3 x'_3 && - y_3 x'_3 \\ 
0    && 0     && 0 && x_3 && y_3 && 1 && - x_3 y'_3 && - y_3 y'_3 
\end{bmatrix} 
\begin{bmatrix}
h_{00} \\ h_{01} \\ h_{02}  \\
h_{10} \\ h_{11} \\ h_{12}  \\ 
h_{20} \\ h_{21}
\end{bmatrix}


ここまできたらあとは右辺行列の逆行列を求めて連立方程式を解けば,ホモグラフィ変換行列が求まる.

なんか頑張れば解析的に逆行列求まらないかなぁと思いつつ,sympyでシンボリック計算をしてみたが終わらない...

実装例

OpenCV

この計算をそのままやってるのがOpenCVgetPerspectiveTransform関数で,実際のソースは以下のようになっている.

srcに変換前の4点,dstに変換後の4点を入れると,ホモグラフィ変換行列を計算して返してくれる.

cv::Mat cv::getPerspectiveTransform(const Point2f src[], const Point2f dst[], int solveMethod)
{
    CV_INSTRUMENT_REGION();

    Mat M(3, 3, CV_64F), X(8, 1, CV_64F, M.ptr());
    double a[8][8], b[8];
    Mat A(8, 8, CV_64F, a), B(8, 1, CV_64F, b);

    for( int i = 0; i < 4; ++i )
    {
        a[i][0] = a[i+4][3] = src[i].x;
        a[i][1] = a[i+4][4] = src[i].y;
        a[i][2] = a[i+4][5] = 1;
        a[i][3] = a[i][4] = a[i][5] =
        a[i+4][0] = a[i+4][1] = a[i+4][2] = 0;
        a[i][6] = -src[i].x*dst[i].x;
        a[i][7] = -src[i].y*dst[i].x;
        a[i+4][6] = -src[i].x*dst[i].y;
        a[i+4][7] = -src[i].y*dst[i].y;
        b[i] = dst[i].x;
        b[i+4] = dst[i].y;
    }

    solve(A, B, X, solveMethod);
    M.ptr<double>()[8] = 1.;

    return M;
}

github.com

連立方程式の行列を作って,solverで解いて,結果を返していることがわかる.

これを見てもやはり解析的に逆行列を出すことは難しいのかな...

Unity

凹みさんが,Unityでのホモグラフィ変換を実装している

tips.hecomi.com

凹み先生によると,

入力座標が (0.0, 0.0)、(1.0, 0.0)、(1.0, 1.0)、(0.0, 1.0) と出来るため 8 次元連立一次方程式は割りと綺麗に解くことが出来ます

とのことで,実装は以下のような感じ.

    float[] CalcHomographyMatrix()
    {
        var p00 = v00.viewPosition;
        var p01 = v01.viewPosition;
        var p10 = v10.viewPosition;
        var p11 = v11.viewPosition;

        var x00 = p00.x;
        var y00 = p00.y;
        var x01 = p01.x;
        var y01 = p01.y;
        var x10 = p10.x;
        var y10 = p10.y;
        var x11 = p11.x;
        var y11 = p11.y;

        var a = x10 - x11;
        var b = x01 - x11;
        var c = x00 - x01 - x10 + x11;
        var d = y10 - y11;
        var e = y01 - y11;
        var f = y00 - y01 - y10 + y11;

        var h13 = x00;
        var h23 = y00;
        var h32 = (c * d - a * f) / (b * d - a * e);
        var h31 = (c * e - b * f) / (a * e - b * d);
        var h11 = x10 - x00 + h31 * x10;
        var h12 = x01 - x00 + h32 * x01;
        var h21 = y10 - y00 + h31 * y10;
        var h22 = y01 - y00 + h32 * y01;

        return new float[] { h11, h12, h13, h21, h22, h23, h31, h32, 1f };
    }

github.com

手計算したのだろうか...掃き出し法とかもう覚えとらんよ...

まぁ確かにホモグラフィ変換の用途として,プロジェクションに使う場合だと画像やレンダリング結果を歪ませるだけだし,画像中の歪んだものを補正する系だと射影先はスクリーン座標の端から端なので,射影元/射影先のどちらかはだいたいスクリーン座標系の(0, 0),(0, 1),(1, 0),(1, 1)なんだよなぁ.

一般的なホモグラフィ変換が使われるのは,カメラの内パラキャリブレーションとかSfMとか,結構マニアックなとこぐらいなのかな.

参考

2019年12月時点のゲーミングノートPCまとめ

ゲーミングノート買おうと思って調べてみた.

調べるのは,海外勢のLenovo,HP,ALIENWARE(デル)と,国内BTO税のGTune(マウスコンピュータ),パソコン工房ドスパラ

どうしよう..絶対2070で事足りるんやが,オタク特有のとりあえず全部買う・一番いいやつ買う的な精神のせいか,2080欲しくなってしまう...

RTX2080

17インチディスプレイと,メモリ32GBの条件

メーカ/機種名 CPU ストレージ 値段(税込) 備考
Lenovo
Legion Y740
i7-9750H 512GB(SSD) 29.1万 2080だけどMax-Qデザイン,英語キーボード
HP
OMEN 17-cb0004TX
エクストリームモデル
i9-9880H 1TB(SSD) 34.0万 none
ALIENWARE
AREA-51M スプレマシー VR
i9-9900K 1TB(SSD) 36.2万 2080はファクトリオーバークロック
7万引きのセール中
GTune
NEXTGEAR-NOTE i7950GA1
i9-9900K 512GB(SSD)
+1TB(HDD)
42.1万 none
GTune
NEXTGEAR-NOTE i7950SA1
i7-9700K 512GB(SSD)
+1TB(HDD)
42.2万 メモリオプション変えると,i7950GA1より高くなる罠
パソコン工房
LEVEL-17FG102-i9K-VORVI
i9-9900K 500GB(SSD)
+1TB(HDD)
38.1万 none
パソコン工房
LEVEL-17FG102-i7K-VOPVI
i7-9700K 500GB(SSD)
+1TB(HDD)
36.5万 none

備考

RTX2070

17インチディスプレイと,メモリ32GBの条件

メーカ/機種名 CPU Storage 値段(税込) 備考
Lenovo
Legion Y740
i7-9750H 512GB(SSD) 24.1万 2070だけどMax-Qデザイン,英語キーボード
ALIENWARE
AREA-51M プレミアム VR
i7-9700 1TB(SSD) 33.5万 2070はファクトリオーバークロック
GTune
NEXTGEAR-NOTE i7941PA1
i7-9750H 1TB(SSD)
+2TB(HDD)
26.4万 12/18までのセール中
ドスパラ
GALLERIA GCR2070RNF
i7-9750H 1TB(SSD) 25.3万 メモリ/ストレージが無料アップグレード中

備考

  • HPで32GBメモリを選択できなかった
  • パソコン工房でRTX2070の17インチディスプレイのモデルがなかった

libcurl is built without the https-proxy support となってネットワークにつながらないときの解決策

HTTPS_PROXY環境変数は普通

HTTPS_PROXY=https://<proxy-host>:<proxy-port>

というふうに, https という通信プロトコルになってると思うが,それを

HTTPS_PROXY=http://<proxy-host>:<proxy-port>

http すると上手くいく.

意味がわからん..

github.com