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