Windows Terminal起動時にIMEがオンになってしまう問題
Windows Terminal 起動時に自動でIMEがオンになってしまう問題に遭遇したので、対処方法を残す。

上記画像のように、起動時に「半角英数モード」であるもののIMEがオンになってしまった。 先人の情報を検索するに、Google日本語入力を利用している環境で発生する問題である雰囲気がある。
GithubのIssueも存在している。
原因を少し掘り下げ
IME周りの操作についての知識がないため、完全に予想でしかないけれども、v1.14.143で入った下記の修正の影響かもしれない。IMEの状態を AlphanumericHalfWidth にする変更であるが、Google日本語入力においては 半角英数 と 直接入力 が別の状態で存在しており、半角英数を指定されることでIMEがオンになってしまうと思われる。 (参考: Gboardヘルプ: Windows Terminal 起動時の 入力方法 に関する不具合
暫定対処
v1.13.11431.0 を利用する。
ただし、Microsoft Storeで自動更新される雰囲気があるので、自動更新を無効にしておかなければ、また同様の問題が発生するようになる。Microsoft Storeでは個別のアプリごとに自動更新を停止することはできない(参考リンク)ようなので、設定から全ての更新を無効化する必要がある。
Flutterを使ったファミコンエミュレータの開発 (HelloWorldまで)
概要
Flutter(Dart)の勉強をしようと考え、どうせなら以前から作りたいと思っていたファミコンエミュレータを書くことにした。 まだROMとしてはHello Worldを表示するテストROMしか動かせてはいないが、60FPSで描画出来るようになったので記事を書いた。

ファミコンエミュレータについて
先人の皆様がファミコンエミュレータを様々な言語や環境に移植しつつ、それに伴う知見をブログなどに残してくださっているので、それらの情報に従ったら基本的に作成できる。私の場合は、下記の記事を一番参考にさせてもらった。
CPU/PPUのそれぞれの仕組みから、HelloWorldを表示するまで必要な内容をわかりやすく説明してくださっている。 まずは記事に従ってROMに含まれるスプライトを表示してみるのが良い。実際に目に見えるものが出てくるとテンションが上がるのもある。
記事の最後にサンプルROMのアセンブラが乗っているので、自分のCPUが吐き出したオペコードと比較して何が間違っているかを確認してすすめることができた。
詳細なCPUの命令ごとの動作については下記を参考にさせてもらった。
レジスタのステータスフラグの意味や、各命令でしなければならないこと、アドレッシングモードの種類やサイクル数など、ここを見れば基本的に問題ないはず。 私の場合はオペコード毎の情報を格納したMAPを作るにあたって、このサイトに加えて、下記の2サイトも参考にした。
6502 Opcodes - NES Hacker Wiki
6502 instructions - Nesdev wiki
ただ、Hello Worldを動かすにあたっては全ての命令とアドレッシングモードを実装する必要はまったくなかったので、下記の命令とアドレッシングモードを実装するところまでで止めている。アドレッシングモード毎にFetchしなければならない数が決まっているのでそこと、Reset割り込みにおけるPCの初期値セットだけを実装し、頭からFetchしていくことで、必要な命令とアドレッシングモードを抜き出すことができた。(最終的にはJMPで無限ループになるので、そこまで抜き出す。)
# 命令 BNE DEY INX JMP LDA LDX LDY SEI STA TXS
# アドレッシングモード Implied Accumulator Immediate Relative Absolute AbsolutePageX
Flutterにおけるエミュレータ
エミュレータ実行スレッドを分ける
エミュレータ自体の実行はそれなりに処理負荷がかかるので別のIsolateで実行するようにした。
ゲーム実行中はずっと動かし続けるので compute 関数を使うのではなく、Isolate.spawn によってIsolateを立ち上げる。
emulatorReceivePort = ReceivePort(); Isolate.spawn(emulatorIsolateMain, emulatorReceivePort.sendPort);
emulatorIsolateMain 側でも ReceivePort を生成し、それをParent側に送信することで双方向の通信が出来るようにしている。
エミュレータ実行Isolate側では下記のコードのように常にParentからのイベントを待機しながら、エミュレータの1フレームごとの処理を行っている。
Timer.periodic(Duration(milliseconds: 16), _executeFrame);
await for (var message in childReceivePort) {
// メッセージが来たら処理を行う
}
1フレーム分の処理が完了したら、Isolate作成時に渡された sendPort を使って、メインIsolateに向けてフレームのピクセル情報を送信する。
画面への描画
エミュレータ側で RGBA 形式の Uint8List を作成し、それを ui.decodeImageFromPixels によって ui.Image 形式に変換させる。
(256x240はファミコンの画面サイズ)
Future<ui.Image> _convertFrameToImage(Uint8List pixels) { final c = Completer<ui.Image>(); ui.decodeImageFromPixels( pixels, 256, 240, ui.PixelFormat.rgba8888, c.complete, ); return c.future; }
その後 ui.Image を CustomPaint におけるCanvasに対して canvas.drawImage している。
当初は RawImage に直接 ui.Image をつっこんで、毎回 setState() {}により描画処理を発火させていたが、おそらく CustomPaint を使いrepaint させるほうがインスタンス化のコストが減り、低負荷なのではないかと考えてこのような形にした。(未検証なので実は RawImage でも軽かったりするかもしれない。)
class _EmulatorPageState extends State<EmulatorPageWidget> { _EmulatorController controller; @override void initState() { super.initState(); controller = _EmulatorController(); } Widget build(BuildContext context) { return Scaffold( body: Row( children: <Widget>[ Expanded( child: Container( width: double.infinity, height: double.infinity, color: Colors.black38, child: CustomPaint( painter: _EmulatorDrawPainter(controller), ) ) ), ] ), ); } } class _EmulatorController extends ChangeNotifier { ui.Image currentFrame; _EmulatorController(); // Isolate周りの処理は省略 /** * Isolateからフレーム更新のメッセージが届いた場合に発火する */ void _onRecvUpdateFrame(Uint8List framePixels) { _convertFrameToImage(framePixels).then((ui.Image image) { currentFrame = image; notifyListeners(); }); } } class _EmulatorDrawPainter extends CustomPainter { _EmulatorController controller; Paint paintObject; _EmulatorDrawPainter(this.controller): super(repaint: controller) { paintObject = Paint(); } @override void paint(Canvas canvas, Size size) { canvas.save(); if (controller.currentFrame != null) { canvas.drawImage(controller.currentFrame, Offset.zero, paintObject); } canvas.restore(); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } }
CustomPaint において描画をし直す = paint を再度呼び出すためには repaint オプションに ChangeNotifier や ValueNotifier を渡す必要がある。
これらのNotifierを使ってデータの更新があることを CustomPaint に通知すると、paint が再実行される。(ValueNotifier の場合はそのままデータを設定すると setter の働きによりそのまま通知が行われる。 ChangeNotifier の場合は notifyListeners() を実行すれば良い。
手を抜いた点と、今後の予定
PPUについてかなり手を抜いた。 パレット情報を使っていないし、1フレーム分のサイクルが走ったら、そのタイミングで全部描画するという実装にしている。 今後、実際のゲームROMを実行する段になって問題が出ると思うので、そのタイミングで実装する。
最終的にはMapper4のROMが動作するところまで実装したい。 その後、GameBoyColorのエミュレータ作成にもチャレンジしてみたい気持ちである。 (とりあえず吸い出し機とゲームは購入した)
PrometheusとGrafanaで複数台のサーバを監視する

趣味のサービスを運用しているが、何らかの監視をしたい。 しかし、DataDogやNewRelic、Mackrelのように有償のサービスを使うほどではない。 けれども、Zabbixはなんか嫌だったので、Prometheus + Grafanaで監視の仕組みを構築した。
構成
- 監視用サーバ Ubuntu16
- nginx
- docker
- Prometheus
- Grafana
- node_exporter
- 監視対象サーバ1 Ubuntu16
- node_exporter
- 監視対象サーバ2 Ubuntu16
- node_exporter
監視用サーバの設定
docker-compose.yml
PrometheusとGrafanaをDockerで構築する。
両方ともデータを永続化するために volumes を作成して、データが保存されるフォルダにマッピングさせている。
今回Grafanaはサブディレクトリ運用をするため、 GF_SERVER_ROOT_URL をこのように設定した。
version: '3'
services:
prometheus:
image: prom/prometheus:v2.17.2
ports:
- 9090:9090
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana
ports:
- 3000:3000
environment:
GF_SERVER_DOMAIN: "manage.example.com"
GF_SERVER_ROOT_URL: "https://manage.example.com/grafana"
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:
/etc/nginx/sites-enabled/default
nginxでリバースプロキシする。今回はHTTPSでかつBasic認証を設定した。(これらの設定はこの記事では対象としない)
ただしGrafanaをBasic認証を経由して使う場合は proxy_set_header で Authorization を空文字にしておく必要がある。
Authorization ヘッダーがGrafanaまで届いてしまうと Invalid username or password というエラーが出るためである。
{
listen 443 default_server ssl http2;
server_name manage.example.com;
ssl_certificate /etc/letsencrypt/live/manage.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/manage.example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers AESGCM:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_verify_client off;
auth_basic "Secret";
auth_basic_user_file /etc/nginx/global_htpasswd;
charset utf-8;
access_log /var/log/nginx/manage.access.log;
error_log /var/log/nginx/manage.error.log;
location /grafana/ {
proxy_set_header Authorization "";
proxy_pass http://localhost:3000/;
}
}
prometheus.yml
ターゲットとなるIPアドレスは各自の環境に合わせて設定すること。
global:
scrape_interval: 15s
external_labels:
monitor: 'codelab-monitor'
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 5s
static_configs:
- targets:
- prometheus:9090
- job_name: 'node'
scrape_interval: 5s
static_configs:
- targets:
- 192.168.0.2:9100
- 192.168.0.3:9100
- 192.168.0.4:9100
参考サイト
監視対象サーバの設定
NodeExporterはDockerではなくUbuntu上でそのまま動かす。 下記のサイトのやり方をそのまま踏襲した。
wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-amd64.tar.gz tar -zxvf node_exporter-0.18.1.linux-amd64.tar.gz sudo mv node_exporter-0.18.1.linux-amd64/node_exporter /usr/local/bin/ sudo vim /etc/systemd/system/node_exporter.service sudo systemctl daemon-reload sudo systemctl start node_exporter.service sudo systemctl enable node_exporter.service
/etc/systemd/system/node_exporter.service
[Unit] Description=Node Exporter [Service] Type=simple ExecStart=/usr/local/bin/node_exporter PrivateTmp=false [Install] WantedBy=multi-user.target
Grafanaでのデータソース追加
基本的なGrafanaの使い方はインターネット上の別の記事に任せるが、一点だけ注意する点として、URLを http://prometheus:9090 にする必要がある。Docker上のGrafanaがPrometheusにアクセスする場合、 localhost では駄目である。

CakePHP3をDocker上で簡単に動くようにする
概要
CakePHPを使って何かしらのウェブサービスを作ろうかと思った時、最初の環境構築が面倒。 ローカル環境でMySQLを建てるのも面倒くさいし、環境が汚れるのも嫌だし、最近だとやっぱりDockerを使って開発したい。 この記事では、CakePHP3のプロジェクトを作成してからDocker上でアプリを表示できるまでの手順を記す。
CakePHPプロジェクトの作成
まずはCakePHPプロジェクトの作成を行う。Dockerコンテナの中にcomposerを入れてそこでプロジェクトを作成する方法もあるが、Macにおいてコンテナ内とのファイルの共有処理はとても遅いため、今回はMacにcomposerを入れて、それを使ってプロジェクトを作成することとした。
composer create-project --prefer-dist cakephp/app app
Docker環境の準備
これからの作業は全てプロジェクトのディレクトリで行う。
mkdir app
まず、Dockerfileを作成する
cd app vim Dockerfile
中身はこんな感じ。
FROM php:7.3.11-fpm
# Install some libraries and extensions
RUN apt-get update && apt-get install -y \
libicu-dev \
unzip \
git \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install intl pdo pdo_mysql
# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN mkdir -p /app
WORKDIR /app
ベースとしているのは php:7.3.11-fpm イメージ。別のバージョンが使いたければ変えれば良し。
その後CakePHPを動かすために必要なPHPの拡張をインストールしている。
このイメージでは docker-php-ext-install を使うことで簡単に導入可能である。
その前段でインストールに必要なパッケージを apt で入れているが、同じ RUN 内で apt-get clean と rm -rf /var/lib/apt/lists/* することでDockerイメージを小さくしている。
続いて docker-compose.yml を準備する
vim docker-compose.yml
内容は以下の通り
version: "3"
services:
app:
build: .
depends_on:
- mysql
volumes:
- ./:/app
environment:
DEBUG: "true"
APP_DEFAULT_LOCALE: ja_JP
APP_DEFAULT_TIMEZONE: Asia/Tokyo
DATABASE_URL: mysql://root:root@mysql/database?encoding=utf8
web:
image: nginx
depends_on:
- app
ports:
- 8001:80
volumes:
- ./:/app
- ./.docker/nginx.conf:/etc/nginx/nginx.conf
mysql:
image: mysql:8.0.18
volumes:
- ./.docker/mysqld_add.cnf:/etc/mysql/conf.d/mysqld_add.cnf
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: database
CakePHPに環境変数を渡したい場合は app の environment に追記すれば良し。
今回はローカル開発ということで、DBパスワードも適当にしているが、公開する場合等は注意すること。
nginxの待ち受けるポートは 8001 にしているが、ここは自由に変えてまったく問題がない
続いて nginx と mysql 用のコンフィグファイルを配置する。
Dockerで使うファイルを置くディレクトリとして .docker フォルダを作成し、その中に置くこととする。
mkdir .docker vim .docker/mysqld_add.cnf vim .docker/nginx.conf
.docker/myqsld_add.cnf には下記のように書いておく。
MySQL8からログイン方法のデフォルト値が変わった関係で、この設定を入れておかないとPHP側から接続することができないようだ。これを mysqld コンテナの /etc/mysql/conf.d/ 以下にマウントすることで反映させている。
[mysqld] default_authentication_plugin=mysql_native_password
続いて .docker/nginx.conf の設定をする。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_rlimit_nofile 150000;
events {
worker_connections 65535;
multi_accept on;
use epoll;
}
http {
server_tokens off;
sendfile off;
etag off;
if_modified_since off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 10;
include /etc/nginx/mime.types;
default_type text/html;
charset UTF-8;
access_log /var/log/nginx/access.log;
types_hash_max_size 2048;
server_names_hash_bucket_size 64;
gzip on;
gzip_http_version 1.0;
gzip_disable "msie6";
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types text/plain text/css application/javascript application/x-javascript
text/xml application/atom+xml application/xml+rss application/xml
application/json text/json text/javascript+json text/javascript ;
server {
listen 80 default;
root /app/webroot;
location / {
try_files $uri /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
}
ここまででDocker周りの準備は完了。 さっそくビルドと起動をしてみる。
docker-compose build docker-compose up -d
確認
http://localhost:8001 にアクセスして下の画像のように各種状態が緑になっていたら問題なし。

Riot Gmaes Ecosystem Analyticsについて
RiotAPIを使っているサービスは、11月26日まで Riot Games Ecosystem Analytics (RGEA) に対応しなければならない。
そのようなメールが来ていたのでまとめてみる。
Riot Games Ecosystem Analyticsとは
ざっくり言うと、プレイヤーがサードパーティ製品をどのように使用しているかどうかをRiotが計測するためにAnalytics用のコードをサイトに突っ込んでくれという話であるようだ。
メールでそのスニペットが送られてくるので、それを受け取ったら30日以内に埋め込んで連絡してくれとある。
これをしない場合は、アクセスができなくなるらしい。
もしメールが来ていない場合は developer.riotgames.com で自身のメアドが正しいことを確認する必要がある。
実際の設定について
メールで送られてきたスニペットは上記のサイトのサンプルとほぼ同じであった。
<script type="text/javascript"> (function(t,l){ let w=window,d=document,s=d.createElement('script'),f=d.getElementsByTagName('script')[0]; w[t]=w[t]||function(){(w["_rgea"]=w["_rgea"]||[["uts",new Date()]]).push(Array.prototype.slice.call(arguments));w.RGEA&&w.RGEA.p();}; s.type='text/javascript'; s.async=true; s.defer=true; s.src=l; f.parentNode.insertBefore(s,f); })("rgea", "https://static.developer.riotgames.com/js/rgea.min.js"); rgea("propertyId", "RGEA****-****"); rgea("uid", "puuid"); // assuming the user is verified; encrypted puuid rgea("lolpid", "platformId"); // league platform id (na1, la2, euw1, kr, etc) rgea("lolaid", "accountId"); // assuming the user is verified; encrypted account id rgea("anonymous", false); // defaults to true </script>
uid と lolaid はそのページを開いているユーザとLoLアカウントが紐付いている場合に指定するようだ。
uid には puuid (Player Universally Unique Identifiers) 、 lolaid は accountId を渡す。
ユーザと紐ついていない場合のアクセスの場合 anonymouse に true を入れれば良さそう。
GCS(Google Cloud Storage)にファイルをバックアップする(最新N個のみ保持)
例えば運営しているサービスのデータベースのダンプをデータベースサーバで毎日とっているとして、 そのダンプファイルをどこか別のところにコピーしておかないと、結局サーバごと死んだときに復旧できなくなる。
適当なバックアップ用のサーバを用意して、そこにrsyncやscpしても良いのだが、 バックアップ用サーバの運用もめんどくさいので、今回はGoogleのクラウドストレージであるGoogle Cloud Storage(GCS)にバックアップ設定を行う。
この際、常に新しいバックアップファイルを別の名前でアップすると課金額が増えてしまうし、 逆に毎日同じファイル名で上書き保存していくと、数日前のものに戻したいという時に対応できない。 (そもそもそんな要求があるかは知らないが)
そこで、GCSのバージョニング機能と、ライフサイクル管理の機能を使う。
まず、バージョニング機能を有効にする。この操作はGUIからは現時点では行えないため、
gsutil コマンドを利用する。
gsutil コマンドのインストールは色々なサイトで解説されているので、ここでは説明しない。
(aptで無邪気に apt install gsutil とか打つと、全然関係ないツールが落ちてくるので注意)
バージョニングを有効にする
バージョニングはBucket単位で有効/無効の設定を行う。
gsutil versioning set on gs://<bucket_name>
ちなみに現在の設定は以下で確認できる
gsutil versioning get gs://<bucket_name>
ライフサイクル管理の設定を行う
バージョニングが有効の場合、ライフサイクル管理の設定がGUIから行えないので、
こちらも gsutil コマンドを利用する。
まずはライフサイクルを定義したJSONファイルを作成する 今回は、新しい3バージョンのみを保持する設定を行った。詳細なオプションは下記のページで確認できる
Buckets | Cloud Storage | Google Cloud
$ cat gcs_lifecyscle.json
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"numNewerVersions": 3
}
}
]
}
}
ここまで来たら、あとはgsutilを打つだけ
$ gsutil lifecycle set gcs_lifecyscle.json gs://<bucket_name>
ライフサイクルの反映はGoogleのドキュメントによると、反映に最大で24時間かかるとのことなので、気長にまとう。
Managing Object Lifecycles | Cloud Storage | Google Cloud
ファイルのアップロード
アップロードは下記のように簡単に行える。
gsutil cp backup/hoge.sql gs://<bucket_name>/backup.sql
ファイルの確認
バージョニングされたファイルを含めて見たいときはGUIから見れないようなので下記のように -a をつけた gsutil ls コマンドで確認する
gsutil ls -a gs://<bucket_name>/db/backup.sql
さらに詳細な情報(アップロードされた日付など)が見たい場合は、オプションに -l を追加すれば良い。
(ちなみに、もっと詳細な情報は -L で確認できる)
gsutil ls -la gs://<bucket_name>/db/backup.sql
tigで日本語が文字化けする
WSL(Windows Subsystem for Linxu)/Bash on Windowsにて開発環境を整えているときに、
aptで入れた tig で日本語が文字化けする現象にあたったのでメモ
ncursesのビルドは基本的に上記の手順通りにやればよかった。
私の環境だと configure: error: no acceptable cc found in $PATH とか ./configure: 18395: ./configure: make: not found とか言われたので、適当に必要なものをインストールした。
sudo apt-get install build-essential
ncursesのインストールが完了したあと、今度はtigをビルドした。
wget https://github.com/jonas/tig/releases/download/tig-2.4.1/tig-2.4.1.tar.gz tar xvzf tig-2.4.1.tar.gz cd tig-2.4.1 ./configure make sudo make install
これでまだ文字化けしていたので、下記の設定を.zshrc(または.bashrcとか)に入れたらうまくいった
export LC_ALL=en_US.UTF-8
WSL(Windows Subsystem for Linux/Bash on Windows)でvagrant sshができない問題とその対処
WSLで vagrant ssh しようとしたら Permission denied (publickey). と言われてログインできない事象に遭遇したので対処した。
まず、vagrant ssh 時に実行されるSSHコマンドを自分で書いて、どういうエラーなのかを確認したところ、下記のようになっていた。
% ssh vagrant@127.0.0.1 -p2222 -i .vagrant/machines/default/virtualbox/private_key @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0777 for '.vagrant/machines/default/virtualbox/private_key' are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored. Load key ".vagrant/machines/default/virtualbox/private_key": bad permissions Permission denied (publickey).
VMの鍵である .vagrant/machines/default/virtualbox/private_key のパーミッションが0777になっているため繋げられないとのことであった。
というのも、今回Vagrantを導入したリポジトリをWindowsのファイルシステム上( /mnt/c 配下 )にしているため、パーミッションがデフォルトではすべて0777になる。
結果として、生成された private_key も0777になり、SSHクライアントに怒られる。
解決策
最近のWSLではWindowsのファイルシステムについてもパーミッションを自由に設定できるようになったらしい。 ただしデフォルトではその機能が有効になっていないので、有効にする必要がある。
まず、 /etc/wsl.conf を下記の内容で作成する
[automount] options = "metadata"
設定後WSLを再起動する必要がある。 Windowsを起動させたまま再起動させるのはめんどくさそうだったので、普通にPCごとリブートさせた。
再起動後、private_keyのパーミッションを0600にしたら完了。普通に vagrant ssh できるようになった。
$ chmod 600 .vagrant/machines/default/virtualbox/private_key $ vagrant up $ vagrant ssh