Art and Server
Art and Server
Shinya Koyano
Buy on Leanpub

目次

序論

この本を英語で書いた方が良いと思う。しかし、残念ながらわたしには英語を十分に扱える能力がない。

アートとは何でしょうか? わたしにはわかりません。あなたはどういう意味だと思いますか?

”アートは取り扱いが難しい”と言われることが多いんじゃないでしょうか。でも、もし”アートは取り扱いが難しいから扱わない”とするならば、それは”人間は取り扱いが難しいから扱わない”というのと似るように感じます。アートを扱う必要があるのは我々の宿命です。

他者ののサーバーを借りる場合

そのサーバーの管理者が定めたガイドラインに従う必要があります。そのガイドラインは管理者が自由に決められるので借りる側はそれに合わせる必要があります。だから時に折り合いがつかないことがあります。これはやむを得ないことです。

自分のサーバーを持つ

自分が管理者になるので他者の作ったガイドラインとは無縁になります。

わたしはこの方法を実践してみることにしました。この本はその記録です。

実際は一つのサーバーが孤立して存在するのは難しいので、そう単純ではありません。でも、ともかくやってみることにしました。

自己紹介

わたしについて少し話しておいた方が話が伝わりやすいのではないかと思います。

わたしの本名は小谷野慎也です。オンラインネームはmoliと名乗っています。

わたしは基本的にアーティストです。主に絵を描きます。絵を描いているのをストリーミングすることが多いです。音楽も作ります。プログラミングは好きですが、まともなものが作れるほどの能力はありません。

サーバー管理は今回が初めてです。

GNU/LinuxデスクトップのXubuntuなどを使っていた経験は少しあります。

わたしの主な収入源はコミッションとサポーターによるものです。

わたしのソーシャルメディアなどののリンクをここで紹介するべきなのかもしれませんが、NSFWアートが含まれるものが多いので、現段階では差し控えます。Leanpubについてもう少し理解してから考えましょう。

計画

自分でサーバをホストし、既存のサービスの代替となるソフトウェアを実行する。

  • Discord
  • 各種ソーシャルメディア
  • ウェブサイト
  • ストリーミング
  • 送金

別に、わたしはこれらのサービスが嫌いなわけではありません。そのガイドラインと折り合いがつかなかったり、将来サービスが終了したりした場合の保険を用意したいだけです。

構成

デスクトップコンピュータとサーバーコンピュータの2台でシステムを構成します。

デスクトップコンピュータ

絵を描いたり、音楽を作ったり、その他日常的なことをするためには必要です。

サーバー構成要素としては、仮想端末でサーバーコンピュータにSSHで接続するために使います。

サーバーコンピュータ

サーバーとして動作させるための各種ソフトウェアはGNU/Linuxのものが多いので、それが必要です。

基本的に電源を切らずに安全に運用できる必要があるので、低電力・低発熱のものが望ましい。

ディスプレイは机の上のスペースを食うので、ヘッドレスサーバーとして使用したい。

Debian

コマンドについて知りたいことがあったらDebian Wikiを検索するのがよい。

ロケール

  • [ArchLinuxのロケールの解説]https://wiki.archlinux.jp/index.php/%E3%83%AD%E3%82%B1%E3%83%BC%E3%83%AB

以下のようにして設定すると簡単なようだ。

1 sudo dpkg-reconfigure locales

ハードウェア

ルーター

WSR-6000AX8P

各種コンピュータをネットワークに接続するために使用する。他のルーターでもあまり違いはないと思われる。

デスクトップ

iMac, macOS Sequoia 15.1.1

ペンタブレット

Wacom Intuos Pro Medium

**重要な注意点として、これはそのままではiMacには接続できない。**Intuos ProはUSB-Aでコンピュータに接続する必要がある。しかし、iMacはUSB-Cのポートしかないので、変換機が必要になる。(Bluetoothで接続することもできるが、事前にIntuos Proを充電するためにUSB-AでiMacから給電する必要があるので、いずれにしてもまずUSB-AでiMacと接続するしかない)

USB-C ハブ

iMacにさまざまなものを接続するために必要になる。必要なものをリストする。

  • USB-A(一つでは足りなくなるかもしれないので、2-3は必要)
  • HDMI(デュアルディスプレイにするために必要だった)
  • MicroSDカード(Raspberry Pi OSを書き込むために必要)

サーバー

**公式の専用のケースも一緒に購入したが、これは使わない方がいい。**Raspberry Pi Zero 2 Wの基板がケースの溝に硬く挟まってしまうので、一度ケースに入れてしまうと、取り出すのに非常に苦労する。取り外すのは不可能ではないが、ドライバーのようなものをテコにして基盤を持ち上げる必要があり、破損する危険性があると感じた。MicroSDカードを抜き差しするためには、絶対にケースから基盤を取り外す必要があるため、OSを再インストールするときに困ったことになる。

公式のケースを使うのはやめて、裸のまま使うか、適当なプラスチックの箱か何かを用意してそれに入れておけば十分だろう。

ハードウェアのセットアップ

Raspberry Piを購入してもそれだけで動作させることはできない。

何が必要なのかわからなかったので、デスクトップ環境を動作させるために必要なパーツを含めて全て用意した。今思えば必要なかった。

自分で必要なパーツを買い揃える自信がなかったので、近所の大型電気量販店に行って店員さんに全ての必要なパーツを選んでもらい購入した。

ヘッドレスサーバー

Raspberry PiはRaspberry Pi Imagerを使ってMicroSDカードにOSを書き込む時にWifi経由でSSH接続するための設定をすることができる。

なので、Raspberry Piをヘッドレスサーバーとして運用するだけなら、必要なパーツは電源だけである。

オペレーティングシステム

OSのドキュメントは全く読んでいない。

Raspberry Pi OS(64-bt)デスクトップ環境付き をインストールした。しかし、実際にはデクストップ環境は自分には必要なかった。それどころかPGPを使用する際に問題になったので(後述)、次回からはデスクトップ環境なしでインストールするべきだろう。

Paspberry Pi OSはdebianベースのOSなので、こちらのドキュメントも参考になりそうだが、全く読んでいない。

自分の場合aptを利用するだけなので、debianに精通している必要があまりない。

サーバーの総合的なセットアップ手順

セットアップには順序が重要である。

まず、ルーターを設置する。そしてインターネットに接続する設定をする。(詳細は忘れた)

次に、iMacをルーターに有線接続する。(有線の方が高速なので)

また、iMacをWifiでもルーターに接続できるようにしておく。(Raspberry Pi Imagerのため)

すると、ルーターのDHCPによってiMacがネットワークに接続できるようになる。

iMacはMicroSDカードの読み書きができないので、USB-CハブをつなぎMicroSDカードを扱えるようにする。

MacにRaspberry Pi Imagerをインストールする。

Raspberry Pi ImagerでMicroSDカードにRaspberry Pi OSを書き込む。(この時推奨の64-bit, デスクトップ環境ありを選択したが、デスクトップなしの方がよいと思う。理由は後述)

  • この時iMacのWifiの設定を自動で取得してくれる。
  • ホスト名を覚えておく(後でアクセスするために必要になる)
  • SSHの設定をする(後でアクセスするために必要になる)

自分はこの時、ディスプレイとBluetoothのキーボード・マウスを揃えて単体でも使えるようにして起動したが、別に必要なかったと思う。(当時、Wifi経由のSSH接続だけで十分かどうか確信が持てなかった)

OSの書き込みが完了したら、Raspberry PiにMicroSDカードを差し込み、電源を繋ぐ、それだけで起動する(電源ボタンなどはない)。

ここまでの設定をしてきたのであれば、MacからsshでRaspberry Piに接続できるはずだ。

systemd

サービスの管理はsystemdが行うので、これを押さえておく必要がある。sshdやnginx、prosodyとかも皆サービスである。

独自のサービスを作成する

/etc/systemd/systemfoo.serviceという名前でUnitファイルを作る。

 1 [Unit]
 2 Description=Foo Service
 3 
 4 [Service]
 5 Type=simple
 6 Restart=always
 7 ExecStart=/home/user1/foo.sh
 8 
 9 [Install]
10 WantedBy=multi-user.target

より短い例。

1 [Unit]
2 Description=Foo Service
3 
4 [Service]
5 ExecStart=/home/moli/Desktop/foo.sh
6 
7 [Install]
8 WantedBy=multi-user.target

Description=は識別子なので独自のものを。

Type=は通常simpleでよい。(デフォルトsimple)

Restart=はプロセスが終了した時に再起動するかどうかを指定する。デフォルトはno。普通はalwaysを選択することになるだろう。

ExecStartは実行される引数付きのコマンド。

WantedBy=はランレベル。multi-user.targetでいいだろう。ターゲットは/usr/lib/systemd/system/multi-user.targetのようになっている。

systemctl daemon-reloadでサービスの設定ファイルを再読み込みする

systemctl

システムの状態を表示
sudo systemctl status
失敗したサービスを表示
sudo systemctl --failed
サービスを起動
sudo systemctl start foo.service
サービスを停止
sudo systemctl stop foo.service
サービスの状態を表示
sudo systemctl status foo.service
サービスを自動起動に設定
sudo systemctl enable foo.service
サービスの自動起動を無効
sudo systemctl disable foo.service
Unitファイルのバックアップファイルを表示
sudo systemctl cat foo.service

journalctl

ログを表示する。

-x
メッセージカタログでログを拡張する
-e
ページャの末尾にジャンプする
-u unit
指定されたunitに一致するメッセージを表示する
-t identifier
識別子でフィルタリングする(systemd-catのメッセージをフィルタするのに使える)

単にjournalctlとすると全てのログが表示されるので、grepなりなんなりで表示を絞り込んでもいい。

systemd-cat

ジャーナルに直接メッセージを送ることができる。

-t identifier
識別子を指定する。指定しないと実行ファイル名になる
1 systemd-cat -t FOO echo hello

GNU Bash

Bashとそれに関連するソフトウェアはとても多い。小さくまとめたいと思うのだけど…。

基本

ループ。

1 while true; do
2     echo hello;
3     echo world;
4 done

条件。

1 if true; then echo 'A'; fi

複合コマンド。

1 ( echo 'foo'; echo 'bar' ) | cat -n
2 
3 # 中括弧を使うと現在のコンテキストで実行される
4 { echo 'foo'; echo 'bar' } | cat -n

コプロセス

1 echo foo >&${COPROC[1]}
2 echo bar >&${COPROC[1]}
3 echo baz >&${COPROC[1]}
4 cat <&${COPROC[0]}

関数。

 1 f1 ()
 2 {
 3     # ローカル変数
 4     local x='hello'
 5     echo $x
 6 }
 7 
 8 
 9 x='world'
10 f1
11 echo $x

変数。

1 x='hello'

変数を削除。

1 unset -v x y

関数を削除。

1 unset -f f

コマンド置換。

1 echo $( ls )
2 
3 # または
4 
5 echo `ls`

プロセス置換。

1 cat -n <(ls -l)

シェル変数

HOME
ホームディレクトリ
PATH
シェルがコマンドを検索するディレクトリのリスト。コロンで区切る

.コマンドとsourceコマンド

.は現在のシェルコンテキストでコマンドを実行する。source.の別名である。

GNU core utilities

env

env

コマンド名をPATHから検索してくれるので、コマンドの絶対パスがわからないときに使われることが多い。

1 #!/usr/bin/env python3

あまり使うこともないと思うが、名前らしく環境変数を設定することができる。

また、コマンドの引数を処理させるためには-Sオプションが必要である

1 #!/usr/bin/env -S foo=bar cat -n
2 
3 foo
4 bar
5 baz

OpenSSH

導入

サーバーにアクセスするにはこれを使う。

Raspberry Piは初期設定でパスワード認証によってsshで接続できるようになっている。

ローカルネットワークでsshで接続するには次のようにする。

1 ssh user@host.local

sshにはいくつか認証方式がある。

  • パスワード認証
  • キーによる認証

キー認証の方がより安全な方式のようなので、まず、パスワード認証でログインできることを確認したら、キー認証に切り替えて、パスワード認証によるログインを無効化する。

具体的には以下のような手順になる

  1. クライアントの方でssh-keygenを使って公開鍵と秘密鍵のペアを生成する
  2. サーバーに公開鍵を登録する。手動でやるか、ssh-copy-idを使う
  3. sshdの設定ファイルを編集しパスワード認証を無効化する
  4. ssh-agentやscpなどの便利なコマンドを使う

全体を把握するためにWikibooksを読むのもいいかもしれない。

設定したのが少し前のことなので、大まかにしか書いてない、後で書き直す。

ssh

ssh

sshクライアント。

パスワード認証にせよ鍵認証にせよこれを使うことになる。

1 ssh user@host

あとは道なりに進んでゆけば良い。

ssh-keygen

鍵を生成する。

1 ssh-keygen

特にオプションなどは必要ないと思う。パスフレーズを尋ねられるので設定する。

公開鍵を登録する

サーバーの~/.sshがsshの設定ファイルを入れるディレクトリなのでここに鍵を入れる。鍵はテキストファイルなので端末からコピーアンドペーストすればよい。

ssh-copy-idを使うこともできる。

sshd

設定はファイルは/etc/ssh/sshd_configである。

パスワード認証を無効化するには設定ファイルにPasswordAuthentication noと記述する。

サービスとして実行されれいるので、設定ファイルの内容を反映するためにはsudo systemctl restart sshd.service として再起動する。

ssh-agent

クライアントの方で使うと以後の鍵の入力を省略できるようになる。 ssh-addで秘密鍵を登録する。

1 ssh-agent bash
2 ssh-add ~/.ssh/key

複数のクライアントから同じサーバーに接続する必要がある場合は便利かもしれないが、自分の場合はあまり使っていない。

sftp & scp

クライアントからサーバーにファイルを転送するのにこれらが必要だ。sftpでもscpもどちらでもよさそうだが十分試してないので、後で調査する。

sftp

1 sftp moli@pi.local
ヘルプを表示する
help
sftpを終了する
bye
exit
リモート ディレクトリを移動する
cd
リモート ディレクトリをリスト表示する
ls
ローカルにファイルを転送する
get foo.txt
ローカル ディレクトリをリスト表示する
lls
ローカルファイルを転送する
put foo.txt

DDNS

自分の場合固定IPではなかったのでDDNSに登録する必要があった。

選択肢はいくつかあったが自分はNO-IPのProプランを使うことにした。

  • ルーターがNO-IPに対応していたこと(必須ではない)
  • 独自のドメインを設定することができること(IndieWebに対応するために必要)

しかし、月に約15ドルもかかるので、かなり厳しい。もっとどうにかならないだろうか。

独自ドメイン

上記のサイトを参考にしつつ選んでみた。

この記事とは直接関係しないが、価格が手頃だったのでレジストリ.xyzから直接購入することにした。

しかし、自分がちゃんと設定方法を理解していないだけかもしれないが、DDNSを設定するとサブドメインを使うことができないようなので、後々NO-IPから別のドメインを設定せざるを得なくなったりと、使いにくい感がある。固定IPならば問題はなさそうなのだけど。

結果としては、特に難しいこともないのだが、初めてドメインを購入するとなるとWHOISについて悩むことになるだろう。

WHOIS

WHOISはドメインの所有者に連絡を取るための公開データベースを検索するための仕組みだ。

ここにはドメインを取得する時に記入した個人の住所、電話番号、メールアドレスが含まれる。

WHOISは公開されており誰でも検索できるので、普通にやると住所や電話番号が全世界に公開されてしまうようになっている。個人の場合には安全のために望ましくないと考えるのが常識だろう。

しかし、WHOISはその時代遅れな仕様が改められない。

対策として、レジストラがドメインの所有者にWHOIISに登録することでドメイン所有者の個人情報を隠すWHOISプライバシーなどと呼ばれる仕組みが、どのレジストラにも備わっているようだ。

ただし、WHOISプライバシーを有効にした場合、もしレジストらとドメイン所有者が紛争になった場合、ドメイン所有者に勝ち目がなくなるといった問題もあるようだ。わたしは法律に詳しくないのでなんとも言えない。

わたしは住所を公開するわけにもいかなかったので、WHOISプライバシーを有効に設定している。

考察

DDNSにせよ、ドメインにせよ、ISPにせよ、自分と異なったポリシーを持っている他者であるわけだ。これらを利用しなければならない時点で、完全に独立したサーバーとは言えない。

インターネットを利用する以上、完全に独立しているというのは無理だということが明らかになったと思う。

他者と協調しつつ、自分の権利を確保してゆくというふうに立ち回るほかない、そう思われる。

nginx

HTTPサーバーとリバースプロキシ。

aptでインストールすれば特に問題はないようだ。

同様の用途としてCaddyを使うようになってしまったので、今後使うかどうかはわからない。

ただし、使うつもりがあるなら、Certbotを設定するより前に入れておいた方がいい。Certbotはnginxの設定ファイルを書き換えてTSLが有効になるようにするからだ。

TLS

TLS

Wikipediaのページを読んでも、全く意味がわからない。

二者間の通信のセキュリティを担保するために第三者の認証局を入れるという方式のようだ。

とにかく、これを入れないとWebサーバーやXMPPサーバーが使えないので、なんとかして設定する必要がある。

認証局を選ぶ

無料で証明書を発行してくれる認証局。大抵の場合はここが使われるようだ。

証明書には有効期限(13ヶ月くらい)があるので、証明書の取得と更新を自動で行うプログラムを入れるのが一般的なようだ。

Certbot

Let’s Encryptがおすすめしているクライアントプログラムがこれなので、これを使う。

使い方は忘れたが、Webページの指示通りにやっていけば問題なかった。

XMPP

Discordの代替として見たときに、グループチャットとファイル共有は可能だが、画面共有はできない。(音声通話は可能なようだが、設定していない)

画面共有がしたいならJitsi Meetなどを使うという手もあるが、ストリーミングで代用できる部分も多いので、今は検討しないことにした。

サーバー

設定の仕方は忘れたが、ドキュメント通りにやっていけばできるはずだ。

チャット、ファイル共有、グループチャットなどには、それぞれポート開放とCertbotによる証明書の発行が必要になる。

証明書の更新

1 sudo prosodyctl --root cert import /etc/letsencrypt/live/

自動更新を設定するべきなのだろうけど、そこまで手が回っていない。

クライアント

vCardというプロフィールを設定するためのモジュールが上手く動作しなかったので他のクライアントで設定する必要があった。それ以外はあまり問題なく使えている。

Caddy

ウェブサーバーとリバースプロキシ。

いくつか特徴がある。

  • 証明書を自動で取得してくれる
  • 一つの実行ファイルで完結しているので、好きな場所に配置できる
  • 設定ファイルをカレントディレクトリから読み込むのでディレクトリによって動作を変えられる
  • ドキュメントがわかりやすい

デメリットとしては。

  • サービスにしたければ手動で登録する必要がある
  • nginxのような大量のアクセスに対してどの程度の性能があるのかよくわからない

今のところ大量のアクセスを処理しなければならない状況にはないので、自分にはCaddyが適していると思う。

今のCaddyfileはこんな感じ。あまり綺麗ではないので後で整理したい。

 1 moli-green.xyz {
 2     root * /home/moli/Public/www
 3     file_server
 4 }
 5 
 6 moli-stream.zapto.org {
 7     reverse_proxy :8080
 8 }
 9 
10 :80 {
11     root * /home/moli/Public/onion
12     file_server browse
13 }

インストール

静的バイナリをインストールすることにする。

1 curl -OL https://github.com/caddyserver/caddy/releases/download/v2.9.1/caddy_2.9.1_linux_arm64.tar.gz
2 tar xzf caddy_2.9.1_linux_arm64.tar.gz

このバイナリ一つで使用するとこができる。

署名の検証方法はよくわからなかった。

Caddyをサービスにする

まあ、あとはドキュメントの通りでうまくいく。

設定ファイルを編集する。

1 sudo vim /etc/caddy/Caddyfile
2 sudo systemctl reload caddy

静的サイトの場所はどこにでも配置できる。(/var/www/html, /srv が例に上がっているが、標準的なディレクトリとして例示しているだけだろう)

API

caddyの管理エンドポイントはCADDY_ADMIN環境変数で設定できる。

 1 # 管理エンドポイントのために使用されてないポートを探す
 2 ss -ant | grep 2222
 3 
 4 # caddyの管理エンドポイントを設定
 5 export CADDY_ADMIN=localhost:2222
 6 
 7 # Caddyを起動(runの方がいいかもしれない)
 8 ./caddy start
 9 
10 # 構成ファイルを作成
11 jq -n '{apps: {http: {servers: {foo: {listen: [":2015"], routes: [{handle: [{handler: "static_response", body: "hello!"}]}]}}}}}' > config.json
12 
13 # 構成ファイルをロード
14 curl localhost:2222/load -H 'Content-Type: application/json' -d @config.json
15 
16 # 構成を表示
17 curl localhost:2222/config/ | jq
18 
19 # 構成を変更
20 curl localhost:2222/config/apps/http/servers/foo/routes/0/handle/0/body -H 'Content-Type: application/json' -d '"second."'
21 
22 # IDを設定
23 curl localhost:2222/config/apps/http/servers/foo/routes/0/handle/0/@id -H 'Content-Type: application/json' -d '"msg"'
24 
25 # IDを使用して構成を変更
26 curl localhost:2222/id/msg/body -H 'Content-Type: application/json' -d '"something..."'
27 
28 # Caddyを停止
29 ./caddy stop
30 
31 # Caddyを前回の構成で起動
32 sudo -E ./caddy run --resume &

GET/config/[path]

1 curl http://localhost:2019/config/ | jq

JSON Config Structure /apps/http/servers

1 curl -s http://localhost:2019/config/apps/http/servers | jq '.'
* * *

構成をエクスポート。

1 curl -s http://localhost:2019/config/apps/http/servers | jq '.'

Caddyfile

グローバルオプション

トップレベルの括弧はグローバルオプションを意味する

1 {
2     ...
3 }
1 email moli_green@runbox.com

HTTP3を設定するにはこうする。

1 servers :443 {
2     protocols h1 h2 h3
3 }

細かく設定すると、こんな感じになる。

1     log hoge {
2         level debug
3         format console
4         output file /home/moli/work/s/log {
5             roll_size 1mb
6             roll_keep 2
7     }
8     }           roll_keep_for 1m
9     

しかし、最低限の設定でも十分だと思われる。

1     log {
2         output file /home/moli/work/s/log
3     }

ディレクティブ

1 encode zstd gzip
1 header foo "bar"

HTST

httpの接続をhttpsにアップグレードする。max-ageはブラウザがそれを記憶する秒数。

preloadはGoogleのサービスを使うので、適切な資格が必要なようだ。

以下は、有効期間が5分の例(最終的に2年に設定するのが目標なようだ)

1 header Strict-Transport-Security "max-age=300; includeSubDomains"
* * *

リクエストの最大サイズを指定する。(POSTリクエストも含まれるようなので、添付できるファイルサイズの上限でもある)

1     request_body {
2         max_size 1MB
3     }
1 reverse_proxy localhost:50000 {
2         header_up Host {http.request.host}
3     }

beader_upはバックエンドに送るヘッダーを操作する。 Header Valueの形になっている。{http.request.host}はリクエストヘッダーに含まれるホスト名を示す。なので、この例の指定はホスト名にホスト名を設定しているだけなので、何も設定しなくても、違いはない。

1 tls {
2     protocols tls1.2 tls1.3
3 }

Owncast

ストリーミングするならPeerTubeなどの選択肢もあったが、インストールが難しそうだったので、別のソフトウェアを探していたところ、これを見つけた。

非常に簡単にインストールできる。

インストールスクリプトを実行するだけである。単体の実行ファイルとしてインストールされる。Caddyを内蔵しているのでHttpサーバーの設定なども必要ない。ホームディレクトリにインストールするならばルート権限も必要ない。

しかし、わたしのサーバーコンピュータはRaspberry Pi Zero 2 Wなので性能が足りなかった。(しばらく動かしているとダウンしてしまう)もう少し、性能の高いマシンであれば全く問題ないと思うので、サーバーマシンを買い替えたら導入したい。

CLIP STUDIO PAINT

名前が長いので以下CSPと略す。

複数ページ管理機能を使うためにはEXを購入する必要がある。

わたしはこのCSPを使い慣れているので、ペイントソフトに関してはCSPが基準になっている。

漫画を描く

CSPの使い方ではなく、総合的な話になってしまう。

3D

doc

Painter

ワコム タブレットの設定

わたしの場合、CLIP STUDIO PAINTのペンによるパンの操作に慣れていたので少し設定を変更した。

  1. Wacom Centerを開く。
  2. ペンの設定 -> アプリケーション -> Corel Painter 2023を追加
  3. 下サイドスイッチを”パン/ズーム…”に変更
ペンをタブレットから浮かせて下サイドスイッチを押した状態
パン ペンをタブレットにつけてしたサイドスイッチを押した状態
ズーム

という動作になる。

CLIP STUDIO PAINTとは少し動作が異なるが、個人的にはなんとか慣れることができそうに思う。

設定のリセット

Shiftを押しながらPainterを起動する。

ショートカット

CLIP STUDIO PAINTと違うところ、または、個人的に要注目の部分だけ取り上げる

1-0キー
ブラシの透明度を10-100%の範囲で10%刻みに変更する(例えば3なら30%の透明度)

TC# Ncat

HTTPサーバーにアクセスする。-CオプションはLFをCRLFに変換する。

 1 moli@pi:~/Public $ ncat -C localhost 8000
 2 GET / HTTP/1.0
 3 
 4 HTTP/1.0 200 OK
 5 Content-Type: text/plain; charset=utf-8
 6 Server: Caddy
 7 Date: Wed, 15 Jan 2025 07:46:29 GMT
 8 Content-Length: 3
 9 
10 hi.

接続を待ち受ける。デフォルトのポートは31337番。(端末を二つ開いて実行する)

1 ncat -l

先ほどのポートに接続する。

1 ncat localhost

適当にテキストを書き込むと、待ち受け先に出力される。

Curl

ネットワークからファイルを転送する。多様なプロトコルに対応している。

1 moli@pi:~/Desktop $ curl https://moli-green.xyz
2 hello www.
3 foo.

ポートを指定するならこう。

1 curl https://moli-green.xyz:443/

ファイルを保存する。

1 curl -o index.html https://moli-green.xyz
-s
サイレントモード
-H
HTTPヘッダーを追加。@filenameでファイルを使用。@-で標準入力を使用。
1 curl -s -H "foo: 1" -H "bar: 2" localhost:8001

GnuPG

キーをリスト表示
gpg --list-keys
公開鍵をエクスポート
gpg -a --export Shinya
-a , --armor オプションはASCIIフォーマットで鍵を出力する(デフォルトはバイナリ)
秘密鍵のエクスポート
gpg -a --export-secret-keys Shinya
公開鍵の削除
gpg --delete-keys Shinya
秘密鍵の削除(秘密鍵を先に削除する)
gpg --delete-secret-keys Shinya
公開鍵のインポート
gpg --import Foo.pub
暗号化
gpg -o t.gpg --encrypt -r Shinya t.txt
-r, --recipient 受け取り手(IDを引数にとる。IDは名前の一部などでもよいようだ)
復号化
gpg -o t.txt --decrypt t.gpg
署名
gpg -o t.sig -s -r Shinya t.txt
署名を検証
gpg --verify t.sig
分離署名を作成
gpg --detach-sig foo.txt
gpg --detach-sig foo.txt

iMacにGnuPGをインストールする

Mac版ビルドへのリンクが公式にあるのでそれを使う。

Lynx

テキストブラウザ。

HTTP

見てみると想像以上に複雑化している。Wikibooksの記述を見るくらいが精一杯のところだろう。

リクエストの種類についてはMethod Definitionsに記述があるが、通常GET以外はほとんど使わないだろう。

1 moli@pi:~ $ ncat -C moli-green.xyz 80
2 GET / HTTP/1.0

Anvil

Python

標準ライブラリ

言語リファレンス

インストール

実行

インタラクティブモード

PYTHONSTARTUP環境変数に指定したファイルが先に実行される。いつも使うモジュールはここで読み込んでおくと良い。

文字列

1 >>> 'foo bar baz'.split()
2 ['foo', 'bar', 'baz']

クラス

 1 >>> class Foo:
 2 ...     '''this is Foo'''
 3 ...     x = 3
 4 ...     def f(self):
 5 ...         return self.x
 6 ...         
 7 >>> o = Foo()
 8 >>> o.f()
 9 3
10 >>> help(Foo)

インスタンス作成時に初期化したいならinitメソッドを定義する。

 1 >>> class Foo:
 2 ...     '''this is Foo'''
 3 ...     def __init__(self, x):
 4 ...         '''initialize'''
 5 ...         self.x = x
 6 ...     def f(self):
 7 ...         return self.x
 8 ...         
 9 >>> o = Foo(4)
10 >>> o.f()
11 4
12 >>> help(Foo)

継承。

1 >>> class Bar(Foo):
2 ...     pass
3 ...     
4 >>> o = Bar(4)
5 >>> type(o)
6 <class '__main__.Bar'>
7 >>> o.f()
8 4
9 >>> help(Bar)

例外

1 >>> try:
2 ...     1 / 0
3 ... except ZeroDivisionError:
4 ...     print('error!')
5 ...     
6 error!

独自の例外クラスを定義するにはExceptionクラスから派生させる。

例外を投げるにはraise Exception()とする。

また、最後にキャッチされた例外を取得するにはsys.exception()とする。

 1 >>> import sys
 2 >>> class FooError(Exception):
 3 ...     pass
 4 ...     
 5 >>> try:
 6 ...     raise FooError()
 7 ... except Exception:
 8 ...     e = sys.exception()
 9 ...     print(type(e))
10 ...     
11 <class '__main__.FooError'>

例外オブジェクトを取得するにはasで変数を指定できる。こちらを使うべきだろう。

また、Exceptionクラスはコンストラクタで任意のタプルを受け取り、e.argsのように取得できるので、データを受け渡す必要があるならこれを使うとよい。

1 >>> try:
2 ...     raise Exception('foo', 'bar')
3 ... except Exception as e:
4 ...     print(type(e))
5 ...     print(e.args)
6 ...     
7 <class 'Exception'>
8 ('foo', 'bar')

クリーンアップ処理が必要ならfinally句を使う。

1 >>> try:
2 ...     pass
3 ... finally:
4 ...     print('done')

with

 1 >>> class Foo:
 2 ...     def __enter__(self):
 3 ...         return 'hi'
 4 ...     def __exit__(self, exc_type, exc_val, exc_tb):
 5 ...         print(exc_type, exc_val, exc_tb)
 6 ...         return True
 7 ...         
 8 >>> with Foo() as x:
 9 ...     print(x)
10 ...     raise Exception('foo')
11 ...     
12 hi
13 <class 'Exception'> foo <traceback object at 0x105745080>

ファイル

エンコーディング

Pythonはテキストモードでシステムのエンコーディングを用いる。これは特定の環境で問題を引き起こすので、utf8に固定してしまう方が望ましい。これはpythonの起動オプションか環境変数で設定できる。

Utf8モードが有効かどうかは以下のようにして確認できる。

1 >>> import sys
2 >>> sys.flags.utf8_mode
3 0

起動オプションで設定。

1 python3 -X utf8

環境変数で設定。

1 env PYTHONUTF8=1 python3

実際の実行可能スクリプトは以下のようになるだろう。

1 cat ./utf8.py 
2 #!/usr/bin/env PYTHONUTF8=1 python3
3 
4 import sys
5 print(sys.flags.utf8_mode)
* * *

io.TextIOBase

1 >>> with open('foo.txt', 'wt') as f:
2 ...     f.write('hello')
1 >>> with open('foo.txt', 'rt') as f:
2 ...     s = f.read()
3 ...     print(s)
4 ...     
5 hello

io.RawIOBase

 1 >>> with open('foo.bin', 'wb') as f:
 2 ...     b = (99).to_bytes()
 3 ...     f.write(b)
 4 ...     
 5 1
 6 >>> with open('foo.bin', 'rb') as f:
 7 ...     b = f.read()
 8 ...     i = int.from_bytes(b)
 9 ...     print(i)
10 ...     
11 99

実際にバイト列を読み書きするにはもう少し調べてみる必要があるだろう。

pip

Pythonパッケージマネージャ。

インストール済みのパッケージを表示する
python -m pip list
パッケージを検索する
WebブラウザでPyPIにアクセスして行う
パッケージをインストールする
python -m pip install requests
パッケージの情報を表示する
python -m pip show requests
パッケージをアップデートする
python -m pip install --upgrade requests

importlib

Pythonインタプリタの中でモジュールをリロードする時に使う。

いつも使うので~/.zshrcの中でPYTHONSTARTUPに設定して常時読み込まれるようにしておく。

1 import aecpic
2 from importlib import reload
3 reload(aecpic)

pdb

Pythonデバッガー。

モジュールのインポートなどは必要ない。組み込み関数breakpoint()をコードに挿入するだけで自動的にデバッガーが立ち上がる。

h, help
デバッガーコマンドを表示する
d, down
下のフレームに移動
u, up
上のスタックフレームに移動
a, args
関数の引数を表示する
s, step
ステップ実行
l
ソースコードを表示
c, continue
実行を継続
p
式の値を表示する
pp
pと同様だが 表示にpprintを使う
q, quit
デバッガを終了する

main

1 if __name__ == '__main__':
2     print('hi')

doctest

1 cat t.py
2 def add2(x):
3     '''
4     >>> import t
5     >>> t.add2(3)
6     5
7     '''
8     return x + 2
9 python3 -m doctest t.py

pydoc

1 python3 -m pydoc t

HTML形式でドキュメントを書き出す。

1 python3 -m pydoc -w t

ローカルHTTPサーバーを起動しドキュメントを開く。

1 python3 -m pydoc -b

os

環境変数を取得する

1 >>> import os
2 >>> os.environ['HOME']

subprocess

1 import subprocess
2 r = subprocess.run(['ls', '-l'], capture_output = True)
3 r.stdout
4 
5 r = subprocess.run(['curl', 'https://moli-green.xyz'], capture_output = True)
6 r.stdout

tempfile

1 import tempfile
2 f = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
3 f.name
4 # '/tmp/tmpb30bxpet.png'
5 f.close()

json

1 >>> x = {'foo': 3, 'bar': [4, 5, 6]}
2 >>> j = json.dumps(x)
3 >>> j
4 '{"foo": 3, "bar": [4, 5, 6]}'
5 >>> o = json.loads(j)
6 >>> o
7 {'foo': 3, 'bar': [4, 5, 6]}

多分、特定のインターフェースをサポートしていないオブジェクトはシリアライズできないようだけど、よくわからない。

パターンマッチと併用するのが良いだろう。

1 >>> match o:
2 ...     case {'foo': x, 'bar': y}:
3 ...         print(x)
4 ...         for i in y:
5 ...             print(i)
6 ...

組み込みクラスとマッチさせた方がセキュリティ的には良いようだが。まあ、型が合わないと後々実行時エラーが発生してわかりそうではある。

1 >>> match o:
2 ...     case {'foo': str() as x, 'bar': list(y)}:
3 ...         print(x)
4 ...         for i in y:
5 ...             print(i)
6 ... 

Requests

1 import requests

GET

1 r = requests.get('https://moli-green.xyz')

ヘッダを追加するならこう。

1 h = {'foo': 'bar', 'baz': 'hoge'}
2 r = requests.get('https://moli-green.xyz', headers=h)

POST, PUT

* * *

mimetypeを取得するにはこう。

1 >>> r = requests.get('https://moli-green.xyz/img.webp')
2 >>> r.headers['content-type']
3 'image/webp'

mimetypes

拡張子からファイルタイプを判別する。

 1 >>> import mimetypes
 2 >>> mimetypes.guess_type('https://moli-green.xyz/img.png')
 3 ('image/png', None)
 4 >>> mimetypes.guess_type('https://moli-green.xyz/img.jpg')
 5 ('image/jpeg', None)
 6 >>> mimetypes.guess_type('https://moli-green.xyz/img.jpeg')
 7 ('image/jpeg', None)
 8 >>> mimetypes.guess_type('https://moli-green.xyz/img.webp')
 9 ('image/webp', None)
10 >>> mimetypes.guess_type('https://moli-green.xyz/img.avif')
11 ('image/avif', None)
12 >>> mimetypes.guess_type('https://moli-green.xyz/img.j2k')
13 (None, None)
14 >>> mimetypes.guess_type('https://moli-green.xyz/img.jp2')
15 ('image/jp2', None)
16 >>> mimetypes.guess_type('https://moli-green.xyz/img.qoi')
17 (None, None)

pillow

画像処理ライブラリ。(PILのフォーク)

画像の読み込みと保存。

1 >>> from PIL import Image
2 >>> img = Image.open('img.png')
3 >>> img.save('img.webp')

pillowには他の画像形式に対応するためのプラグインが公開されていることもあるようなので、PyPIで探してみるといいだろう。

パターンマッチ

キャプチャーパターンには*によるアンパックが使える。これはパターンマッチの機能というわけではなく、式リストの機能だと思われる。

1 >>> match input().split():
2 ...     case ["foo", *objs]:
3 ...         for i in objs:
4 ...             print(i)

_は何にでも一致する。

|は or。

1 >>> match input():
2 ...     case 'foo' | 'bar':
3 ...         print('ok')
4 ...     case _:
5 ...         print('other')

マッチしたものを変数に取得したければasを使う。

1 >>> match input().split():
2 ...     case ["foo", ("bar" | "baz") as x]:
3 ...         print(x)

辞書型へのパターンマッチの例

1 >>> d = dict(x=2, y=3, z=[10, 20, 30])
2 >>> match d:
3 ...     case {'x': 2 as x, 'z': z, **o}:
4 ...         print(x, z, o)

dataclasses

argpase

大変複雑だが、最低限次のことを覚えておけばいいだろう。

1 >>> import argparse
2 >>> p = argparse.ArgumentParser()
3 >>> p.add_argument('-f', '--foo', help='Foo!', action='store_true')
4 >>> p.add_argument('url')
5 >>> arg = p.parse_args('https://example.com'.split())
6 >>> print(arg.foo)
7 False
8 >>> print(arg.url)
9 https://example.com

Python パッケージ

仮想環境

仮想環境を作る

1 python3 -m venv tutorial-env

仮想環境をアクティベート

1 source tutorial-env/bin/activate

仮想環境のアクティベートを解除

1 deactivate

パッケージを作る

TestPyPI

Pythonパッケージのアップロードテスト用のリポジトリ

Twine

PythonパッケージをPyPIなどにアップロードするためのコマンド。

Hatch

ビルドシステム。

pipx

Hatch

設定

設定を表示

1 hatch config show

設定を変更

1 hatch config set template.name "Shinya Koyano"

設定を初期状態に戻す

1 hatch config restore

プロジェクトの初期化

注意、パッケージ名はすべて小文字にするのが無難。

1 hatch new 'foo'

既存のディレクトリを初期化するには次のようにした方がいいかもしれない。

1 hatch new moli5 .

実際的なコマンドラインアプリケーションのプロジェクトを初期化するにはこうだろう。

1 hatch new --cli foo .

ライセンス

CLI

コマンドラインインターフェースを導入することもできる。デフォルトでClickモジュールが使われるようだ。

1 hatch new --cli foo

__init__.py__about__.py

__init__.pyパッケージのためのファイル。

__about__.pyはHatchがバージョン管理情報を書き込むためのファイルと思われる。

メタデータの記述方法

PyPAに仕様が書かれれいるので、これを参照する。ぱっと見ではよくわからないので、一つ一つ検討する必要があるだろう。

まあ、デフォルトのままでも特に問題はないように思われる。

依存関係

pyproject.toml

1 dependencies =[
2     "cowsay",
3 ]

環境

hatchは特定のPython仮想環境を自動的にインストールして起動するようになっている。デフォルトでは現在のPythonのバージョンが用いられる。

1 hatch shell
2 exit

特定の環境で実行するには。

1 [envs.foo.scripts]
2 show = 'echo "hello."'

そしてhatch run foo:show

バージョン(管理)

tool.hatch.versionで指定したファイルからバージョンを読み込む。

hatch veersion "0.0.2"のように、コマンドでバージョンを更新できる(ファイルに書き込まれる)。

フォーマットと静的解析

1 hatch fmt

テスト

デフォルトでpytestが使われるようだ。

1 # tests/test_1.py
2 
3 import moli2.foo
4 
5 def test_add3():
6     assert moli2.foo.add3(2) == 5
1 hatch test

ビルド

1 hatch build

src/project/以下のファイルがwheelに含まれると思うのだが、よくわからない。

公開

まず、TestPyPIに公開するにはこうする。

APIトークンを使う場合は、ユーザー名は__token__として、パスワードにAPIトークンを設定する。

1 hatch publish -r test

キャッシュの削除

hatchを使用すると__pycache__/.pytest_cache/などのディレクトリが作られる。これらがあるとreuse lintでもgitでも問題になる。おそらく解決策はgit cleanで、あらかじめ削除しておくことだろう。(.gitignoreに記述しておくとなおよいだろう)。ただ、まだ試してはいない。

SPDX

ライセンスを表記するための共通形式らしい。

SPDX-FileCopyrightTextについて

SPDX 3.0の仕様書には言及がないが、SPDX 2.3の仕様書には言及がある。

どうやら、REUSEという関連団体の仕様に定義があるようだ。またREUSEはプロジェクト検証用のツールを用意しているので、Hatchの場合はこれを使えばよさそうだ。

なぜSPDX 3.0でこの記述が削除されたのかわからないので、若干不安だが、まあ、多分、設定しておいても悪影響はないだろう。

なぜこのようなライセンスに関わる部分が不明瞭なのか不思議だ。ライセンスを明瞭にするのがSPDXの目的だと思うのだが。

reuse-tool

順を追って解説する。

ファイルに著作権情報とライセンスを付与する。

ライセンス識別子はSPDX License Listに記述されている。

1 pipx run reuse annotate -c 'Shinya Koyano <moli_green@runbox.com>' -l 'MIT' ./foo.py

次にライセンステキストをダウンロードする。

1 pipx run reuse download --all

ライセンスを検証する。

1 pipx run reuse lint

pipx

uv

初期化。スクリプトが存在していればヘッダが追加される。スクリプトが存在していなければ、シンプルな初期ファイルが作成される。

1 uv init --script foo.py

スクリプトにパッケージを追加する。

1 uv add --script foo.py 'cowsay'

スクリプトを実行する。

1 uv run ./foo.py

最後にreuse-toolを使ってSPDX形式のライセンスを付与する。

* * *

pipxでインストールできるパッケージの作り方。

1 uv init --lib foo
1 uv sync # 仮想環境を作成する
2 sourcd .venv/bin/activate
3 python -c 'import foo; foo.cli()' -- [something option for the cli interface]

Magika

インストール

1 python -m pip install magika==0.6.1rc1

pathlib

1 >>> from pathlib import Path
2 >>> p = Path('./img32.webp')

使い方

1 >>> from magika import Magika
2 >>> magika = Magika()
3 >>> p = Path('./img32.webp')
4 >>> r = magika.identify_path(p)
5 >>> print(r.output.label)

Zsh

設定ファイル。

1 ~/.zshrc

Zshの組み込みコマンドのManページを表示する

1 man zshbuiltins

Zshのプションを設定するにはsetoptを使う。AUTO_CDAUTO_PUSHDは設定しておいた方がZshの利便性を享受できるだろう。オプションを無効化するにはunsetoptを使う。

1 # 変数
2 foo=3
3 
4 # 配列
5 
6 man zshoptions
7 
8 zmodload

sudo

-E
既存の環境変数を保持する

GNU Screen

Ed

gawk

Tor

オニオンサービスを実行する

/etc/tor/torrcがTorの設定ファイル。

1 HiddenServiceDir /var/lib/tor/hidden_service/

このHiddenServiceDirhostnameというファイルにオニオンサービスのURLが書かれている。次のようにして確認する。

1 sudo cat /var/lib/tor/hidden_service/hostname

caddyにこのURLを記述しても機能しないので80番ポートのデフォルトはオニオンサービスにしている。

git

ユーザー設定

1 git config --global user.name 'Shinya Koyano'
2 git config --global user.email 'moli_green@runbox.com'
3 git config --global user.signingkey ''

基本的な使い方

 1 git init
 2 
 3 git add .
 4 git commit
 5 
 6 git clone [paht or URI]
 7 
 8 git status
 9 
10 cat .gitignore 
11 *.hoge
12 
13 git diff
14 
15 git mv foo bar
16 
17 git log
18 
19 git pull
20 
21 git push

MacにおけるGPG署名時のエラーについて

コミットしようとすると次のようなエラーになる。

1 error: gpg failed to sign the data fatal: failed to write commit object

Macに入っていたGitを使っていたが、それをやめ、HomebrewのGitを使うようにする。

それでもまだエラーが出るが、メッセージが変化している。

1 gpg: signing failed: Inappropriate ioctl for device

これは~/.zshrcexport GPG_TTY=$(tty)とすることで解決できた。

これによってMacからでも自分のGitサーバーに署名付きでコミットできるようになった。

Forgejo

Caddyのリバースプロキシを通そうと思ったがうまくいかない。

二要素認証を有効にしているとプッシュできない。この問題の解決方法はわからない。

Homebrew

Vim

インストール

1 brew install vim

設置ファイル

$HOME/.vimrc

タブ

tabstopなどの設定が影響するようだが、いまいちよくわからない。

しかし、いちいちスペースを入力するのは、あまりに面倒なので設定してみた。しばらくこれで様子を見よう。

1 syntax enable
2 set tabstop=4
3 set expandtab
4 set autoindent

テキストオブジェクト

yab( ... )の全体をコピーできる。

端末通信

まず、以下のようにして端末内でプロセスを起動する。

1 :term python

ソースファイルでyapyabなどとして、実行したいコードの部分をレジスタに入れる。

次のEXコマンドで端末のプロセスにコードを送信することができる。(@"は無名レジスタを指すようだ)

1 :call term_sendkeys("python", @")

また、@@で直前のEXコマンドを繰り返すことができる。タイプの手間が省けることもあるだろう。

実際の使用は以下のようになるだろう。

1 term python
2 yap
3 :w|call term_sendkeys("python", @")
4 
5 " more edit
6 yap
7 @@

tmux

インストール

brew install tmux

操作

セッションを開始
tmux new
プレフィックスキー
C-b
ヘルプを表示
C-b ?
コマンドプロンプト
C-b :
セッションからデタッチ
C-b d
最後のセッションにアタッチ
tmux attach
指定したセクションにアタッチ
tmux attach -t foo
セッションをリスト表示
tmux list-session
tmux ls
tmuxを完全に終了する
:kill-server
ウインドウを作成する
C-b c
ウインドウを切り替え
C-b [number]
ウインドウを水平方向に分割
C-b %
ウインドウを垂直方向に分割
C-b "
ペインを移動
‘C-b [up, down, left, right]’
ツリーモード
C-b s
C-b w
C-b choose-tree

ウインドウレイアウト

レイアウトを順番に選ぶ
C-b space
水平に均等に並べる
C-b M-1

Mac標準のターミナルではメタキーが無効になっているので、設定で有効にする。

* * *

コピーモード

コピーモード開始
C-b [
選択開始
C-Space
選択範囲をコピーし、コピーモードを終了する
C-w
貼り付け
C-b ]

Alacritty

HTML

CSS

calc

@media

color

@font-face

https://developer.mozilla.org/ja/docs/Web/CSS/font-feature-settings

JavaScript

テンプレートリテラル

1 {
2   let x = 3;
3     let s = `foo ${x}`;
4     console.log(x);
5 }

例外

1 try {
2     throw "hi";
3 } catch(e) {
4   console.log(e);
5 } finally {
6   console.log("bye.");
7 }

ループ

基本的にfor ofを使っておけばよさそうだ。

1 {
2   let a =["foo", "bar", "baz"];
3   a.foo = 3;
4   
5   for (const i of a) {
6     console.log(i);
7   }
8 }

分割代入

角かっこはは省略できない。

1 {
2   let a, x, y;
3   a = [1, 2];
4   [x, y] = a;
5 
6   console.log(`x = ${x}, y = ${y}`);
7 }

関数

関数名はつけてもつけなくてもいい。関数名をつけた場合は関数内から参照できるようになる。

1 {
2   const f = function foo (x=0) {
3     return x + 2;
4   }
5   
6   console.log(f(3));
7   console.log(f());
8 }

アロー関数式

thisを持たないなど、関数とは少し違う。まあ、高階関数用の無名関数として使うにはこれの方が便利だろう。

1 {
2   let f = x => x * 2;
3   f(3);
4   
5   let f2 = (x, y) => x * y;
6   f2(2, 4);
7   
8   (x => x ** 2)(3);
9 }

オプショナルチェーン

.の代わりに?.を使う。プロパティが存在しない場合にエラーになる代わりにundefinedが返るようになる。

1 {
2   let x = { "foo": 1, "bar": 2 };
3   console.log(typeof x);
4   console.log(`${x?.foo} ${x?.baz}`);
5 }

正規表現

1 {
2   const re = /f.o/;
3   const s = "foo fox";
4   s.match(re);
5 }

map

1 {
2   a = [3, 4, 5, 6];
3   a.map(x => x + 10);
4 }

オブジェクト

1 {
2   const Foo = {
3     name: "foo"
4   };
5   const o = Object.create(Foo);
6   console.log(o?.name);
7   console.log(typeof o);
8 }

プロミス

setTimeout

1 setTimeout(() => console.log('hi'), 1000 * 5);
* * *
 1 {
 2   const p = new Promise(
 3     (resolve, reject) => {
 4       setTimeout(() => {
 5         resolve("OK!");
 6       }, 4000);
 7     });
 8   
 9   p.then((resolve) => {
10     console.log(resolve);
11   });
12 }

async function

プロミスの完了を待つ関数。なので、通常の関数と同じように使える。

 1 {
 2   function f1() {
 3     return new Promise(
 4       (resolve) => {
 5         resolve('ok');
 6       });
 7   }
 8   async function f2() {
 9     const r = await f1();
10     console.log(r);
11   }
12 
13   f2();
14 }

モジュール

querySelector, querySelectorAll

DomノードをCSSセレクタの構文で選択する。

1 {
2   const a = document.querySelectorAll("a");
3   for (i of a) {
4     console.log(i.title);
5   }
6 }

フェッチ API

クライアントサイド Web API

TypeScript

Firefox DevTools

フォントサイズを変更する

about:configdevtools.toolbox.zoomValueの値を大きくする。

matrix

サーバー

Element

インストールしようかと思ったが手を広げすぎな気がする。

Accessibility

jq

GNU Readline

PostgreSQL

Godot アセットパイプライン

3Dモデル

Blenderでモデルを作成しglTF 2.0形式でエクスポートしたものを使うのが推奨されている。これでいいと思う。(Blenderから直接読み込むのも試してみたがMacのセキュリティの関係でうまくいかないようだ)

glTFにはテキスト形式*.gltfとバイナリ形式*.glbの二種類あるようだが、テキスト形式の方がGodotとは相性が良さそうなので、テキスト形式を使おう。

Synapse

インストール

Pyplを使ってインストールした。(デビアンパッケージでのインストールがうまくいかなかったので)

設定

起動

1 cd ~/synapse
2 source env/bin/activate
3 synctl start
4 deactivate

man

Glicol

ショートカット

コメントアウト(トグル)
ctrl + / command + /
Rub or Update
command + Enter

演算子

:
多分オシレーターを定義
>> ~
多分モジュレーターを定義

Glicolのコンソールを開く

cmmand + shift + j

ヘルプを参照する

help("sin")

sin

サイン波のオシレーター。周波数は-1.0から1.0の範囲。

他にもオシレーターがある。

saw
ノコギリ波
squ
矩形波
tri
三角波

mul

入力信号に数値を掛ける。0.0から1.0の範囲。

add

入力信号に固定値を足す

seq

ノートスペースで区切って並べる。ノートは1小節を分割する。

`_``はノートをさらに分割する。

ノートの値はMidiノート番号である。(60番がC3かC4だと思われる)

sawsynth 1 2

ノコギリ波のシンセサイザー

1はアタックで、2はディケイ

choose

シーケンスから一つ選ぶ。(おそらくランダム)

1 o: seq ~a ~a ~a 50 >> sawsynth 0.1 0.5 >> mul 0.2
2 ~a: choose 40 60 0 0 0 0

sp

今はサンプルを用意するのが面倒なので飛ばす

imp

インパルス信号を生成する。1.0が一小節なのでその範囲でだろう。

1 o: imp 0.5  q >> sawsynth 0.1 0.4 >> mul 0.2

FM

特にFMシンセサイザーに特化した機能があるわけではないが、オシレーターの周波数をもジュレートすることで同じことができる。

1 o: sin ~fm >> mul 0.2
2 ~fm: sin 1.2 >> mul 300 >> add 500

noiz

ホワイトノイズ。シード値を取るが あまり変化はなさそう。

1 o: noiz 1 >> mul 0.1

lpf

ローパスフィルタ。カットオフ周波数, Q値

Typeface

Inconsolata

等幅書体。プログラミングなどに向いているようだ。

Alacritty

VI mode

1 control + shift + space

OAuth

Request-oauthlib

The AT Protocol SDK for Python

インストール

1 python -m pip install atproto

アプリパスワード

使い方

1 >>> from atproto import Client
2 >>> client = Client()
3 >>> client.login('username', 'application_password or password')

自分の識別子。

1 >>> client.me.did

ポストのリストを取得。

1 >>> def showPosts():
2 ...     posts = client.app.bsky.feed.post.list(client.me.did, limit=10)
3 ...     for uri, post in posts.records.items():
4 ...         print(uri, post.text)
5 ...
6 >>> showPosts()

Flask

SQLite

データ型

  • text
  • numeric
  • integer
  • real
  • blog
  • any

SQLiteの重要な特徴として: 互換性のある型の値が挿入された場合は型変換を行った上で挿入する。しかし、データの損失なしに変換できない場合はテキストとして挿入する。

ROWID

データベースの行にはIDが必要になることもある。SQLiteでは全ての行に自動的にrowidという一意の値が割り振られるので、これを使えばよい。

1 select rowid from foo;

SQL

データ型

データベースソフトウェアによってまちまちなようなので、各種ソフトウェアのドキュメントを参照するしかなさそうだ。

テーブルを作る

1 create table foo (x text, y int)

テーブルに挿入

1 insert into foo values('bar', 100);

テーブルを削除

1 drop table foo;

検索

1 select * from foo where x = 2;

スワップファイルのサイズを増やす

これを使う。

 1 # メモリとスワップサイズのサイズを確認
 2 free -h
 3 
 4 # スワップファイルのサイズを編集
 5 sudo vim /etc/dphys-swapfile
 6 
 7 # スワップファイルの使用を停止
 8 sudo dphys-swapfile swapoff
 9 
10 # スワップファイルを新しいサイズで作成
11 sudo dphys-swapfile swapon
12 
13 # システムを再起動する(このままだと起動中のプロセスが新しいスワップファイルのサイズを認識しないため)
14 sudo shutdown -r now

Mitraのビルドでは2048MBに設定したところビルドに成功した。

Nushell

Zellij

プリセット

Unlock firstを選ぶ。そうしないとcontrolキーをZellijが使ってしまうので、vimなどが機能しなくなる。

Zig

Raspberry Pi 用にクロスコンパイル

1 zig build-exe -target aarch64-linux-gnu main.zig

ActivityPub

IPFS

Ren’py

Blender

Godot

今はGodotで使用するモデルを作るのが目的である。だから、自ずと条件は決まってくる。

  1. ポリゴン数のコントロールが可能であること。

360度画像の作り方

“cycles“を選択すると、カメラのプロパティでパノラマタイプが選択できるようになる。

Geometry Nodes

Instance on Points Node

doc

Godot

GDScript

Globals

EditorScript

とりあえず、言語を試すにはこの設定がよいか。

2D メインツールバー

q選択モード

w移動モード。選択モードとの違いはよくわからない

e回転モード。

sスケールモード。シフトで縦横比を維持できるようだ。

四角いボタン。ノードが重なっている時に選択メニューを表示する。

ピボット設定ボタン。ピボットがオブジェクトの中心位置になる。(そこからオフセットになる)

g, space。パン。

rルーラーモード。定規が表示される。

shift + sスマートスナップモード。他のオブジェクトと整列させるために使うようだ。

shift + gグリッドスナップモード。グリッドにスナップする。G

三つの点。スナップする対象を切り替える。

command + lロック。

command + gグループ化。親ノードと子ノードをグループ化する。一つのノードを選択すると全体が選択される。

スケルトン。今のところ時期尚早。

ビュー。時期尚早だが、だいたい見ればわかる。

Node2D

canvasitem

canvasitemはすべての2Dオブジェクトの抽象基底クラスらしい。

Visible可視不可視。

Light Mask時期尚早。

Sprite2D

Godotのファイルシステムに画像を読み込んで、Textureで読み込む。

Node

-Node

重要だと思うのだが、例をが見つからないのでよくわからない。

CanvasLayer

レイヤーが必要ならこれを使う。主に背景だろう。

数値が大きいほど手前にくる。常にデフォルトのCanvasLayerが存在しているようで、デフォルト値は0

ビューポートとキャンバス変換

Transformのことかと思うけど、よくわからない。

ライトとシャドウ

時期尚早だろう。

スプライトアニメーション

時期尚早。

パーティクル

時期尚早。

CharacterBody2D

2Dボディを動かすためのクラス。

プロジェクト -> 設定 -> インプットで入力方法を設定しておく。

以下のようなものをScriptに設定する。

 1 extends CharacterBody2D
 2 
 3 @export var speed = 300
 4 
 5 func get_input():
 6     var input_direction = Input.get_vector("ui_left", "right", "up", "down")
 7     velocity = input_direction * speed
 8     
 9 func _physics_process(delta: float) -> void:
10     get_input()
11     move_and_slide()

CollisionShape2D

多分、これが衝突オブジェクト。子にSprite2Dを持たせれば良いだろう。

衝突

内訳には4つのオブジェクトがあるようだ。

StaticBody2D

衝突可能な静的オブジェクト。床などに使うだろう。

RigidBody2D

freezeプロパティを有効にすると物体を固定できる。そうするとStaticBody2Dと違いはなくなる。

CharacterBody2D

自分は物理の影響を受けないが、他のオブジェクトには物理の影響を与える。名前の通りキャラクター用。

velocityが速度ベクト

CollisionShape2D

基本的に、衝突にはこれを使えばよい。

Input

入力を処理するためのメソッド(オーバーライドして使う)

シーン

どうやら、シーンはGodotにおける基本構成単位のようだ。

具体的にはファイルシステム -> 新規シーンとしてシーンを作成する。

作成したシーンは別のシーンから、シーンブラウザの「子シーンをインスタンス化」でインスタンス化することができる。(おそらくスクリプトからも可能だろうが、まだ知らない)

1 var s = preload("res://foo.tscn")
2 var i = s.instantiate()
3 add_child(i)

シーンのインスタンス化

positionは親からの相対位置になるようだ。

最後に、ノードは解放しないとリソースリークになるのだろう。

1 var s = preload("res://foo.tscn")
2 
3 func genSI() -> void:
4     var i = s.instantiate()
5     var p = get_parent()
6     p.add_child(i)
7     i.position = Vector2(1000, 0)
8     print(i.position)
9     #i.queue_free()

Godot インターフェース

後で。

Godot 通知

_notificationに全ての通知が届くようだが、_physics_processはなぜか取得できない。とりあえず、オーバーライドしておけば動くので、とりあえずこれでよしとしよう。

_initは最初に呼び出されるようだ。

_readyはUIで設定したプロパティを初期化した後に呼び出されるので、通常の初期化はこれを使うべきだろう。

_physics_processはフレームごとに繰り返し処理を行う部分に使うのに最適なそうだ。

とりあえず、_ready_physics_processを覚えておくだけで十分だろう。

インプット

プロジェクトの設定で設定したキーバインドを取得するには以下のようにすればよい。

1 if Input.is_action_pressed("down"):
2     print("down")

シグナル

基本的には、UIのノード -> シグナルにそのノードが発行可能なシグナルがリストアップされているので、そこから選ぶ。受信者とその受信メソッドも自動的に作成される。

スクリーンの外に出ているかどうか、シグナルで判定する

これは、UIで子ノードとして追加しする。

まだ試していない。

スプライト

テクスチャ

画像を読み込んだ場合これが生成される

Godot 3D

blender

*.glbフォーマットで出力しておけば良さそうだ

Node3D

3D関係のクラスはここから継承されているそうだ。

StaticBody3D

デフォルトでピン留めされているようなもの

CollisionShape3D

衝突はこれだけでいいと思うのだが。どうなんだろう。

MeshInstance3D

定義済みのプリミティブシェイプ、または、独自のシェイプを読み込んで表示することができる。ただし、*.glb形式は読み込めない。*.obj形式なら読み込める。どの形式がベストなのかはよくわからない。

どうやら、MeshInstance3D*.obj形式しか対応していないようだ。

*.glb形式を使いたい場合は、シーンとして読み込ませる必要がある。また、そちらの方が表現力が高いので、そうするべきだと思われる。

StandardMaterial3D

CharacterBody3D

キャラクター用のボディ。基本的に物理の影響を与えるが受けないとされているようだが、正確にはそうでもなく、ある程度影響は受ける。

*.glbファイルの読み込み

ファイルシステムに*.glbファイルを読み込んでから、シーンにドラッグ&ドロップすることで挿入できる。ノードタイプはNode3Dと表示されているが、詳しいことはわからない。

移動

Basis

DirectionalLight3D

指向性のあるライト。とはいえ、画面全体を照らすので、太陽光のために使うものなようだ。

Marker3D

常に(非表示状態でも)マーカーを表示するだけ。それ以外はただのコンテナである。

Camera3D

カメラ。詳細を追求すれば膨大だが、特に何も設定しなくても問題なく使える。

Godot Command line

スクリプトを実行するには-sオプションを使う。しかし、スクリプトとして実行可能にするためにはSceneTreeを継承する必要があるようだ。試してみたところ、Nodeは継承していない。これはNode2DNode3Dは継承しているのだろうか。

GDScript

マニュアルから多少順番が前後している。

export

@export系の注釈は値をインスペクターで編集可能にする

1 @export var n = 3
2 @export var s: String
3 @export_range(0, 100) var r

@onready

ノードの読み込みが終わるまで変数の初期化を遅延する(_ready)

1 @onready var child = get_node("foo")

ドキュメントコメント

シャープ二つで始める

1 ## x + 2
2 func add2(x: int) -> int:
3     ## add2
4     return x + 2

アサーション

1 assert(1 == 0, "ERROR!")

シグナル

シグナルを定義

1 signal hoge

シグナルによって呼び出されるメソッドを定義

1 func _on_hoge(args) -> void:
2     print("called signal: hoge", args)

シグナルを接続。呼び出し。

1 self.connect("hoge", _on_hoge)
2 var s: Signal = Signal(hoge)
3 s.emit([1, 2, "cat"])

呼び出し可能オブジェクト

Godotの関数は基本的にメソッドなのでselfを渡す必要があるようだ。

1 var x = Callable(self, "add2")
2 assert(x.call(3) == 5)

変数

変数はvarで宣言する。

静的変数にはstatic修飾子をつける。これはクラススコープになる。

1 var x = 1
2 var x: int = 1
3 static var y = 2

定数はconst修飾子で宣言する

1 const x: int = 1

列挙型

自動的に値が割り振られる定数のようなもの。クラスのトップレベルでないと宣言できないようだ。

1 enum e {foo, bar, baz}

関数

大体、思った通りに動く。関数は常にselfを持つので指定する必要はない。

 1 ## x + 2
 2 func add2(x: int) -> int:
 3     ## add2
 4     return x + 2
 5 
 6 func add1(x: int) -> int: return x + 1
 7 
 8 func add3(x = 1): return x + 3
 9 
10 func fcf(f: Callable) -> int:
11     return f.call(1)

if

まあ普通

for

pythonとほぼ同じ

match

pythonと同じ

クラス

スクリプトファイルは無名クラスである。

ファイルの先頭で名前とアイコンを設定できる。

クラスに名前をつけるとノードの追加パネルで参照できるようになる。

1 @icon("res://icon2.png")
2 class_name MyClass

継承

1 extends Node

スーパークラスを参照するときはsuperキーワードが使える

コンストラクタは_init

* * *

load, preload

Godotのファイルシステムからスクリプトを読み込むときには必要になるらしいが、今のところ使用しなくても動作している。あとで、検証。

* * *

ツール

シーンを実行しなくてもエディタ(2D, 3D)などから実行できる。

1 @tool
2 extends Sprite2D
3 
4 func _process(delta: float) -> void:
5     if Engine.is_editor_hint():
6         self.rotation += PI * delta
7     else:
8         self.rotation -= PI * delta
9     

エクスポート

ドキュメントコメント

##二つで開始するだけ。ファイルの先頭および、各種メンバーに設定できる。

静的型付け

キャストが必要になるケースがあるようだが、まあ、それは実際に直面するまで。

## フォーマット文字列

基本的に%sだけ覚えておけばいいと思う。

1 f = func():
2     const x: String = "foo %s bar"
3     const y: String = x % 2
4     print(y)
5     print("%s, %s, %s", [22, "foo", self])
6 f.call()

デバッグ

複数インスタンスを同時にデバッグすることもできるようだ。あとで検証したい。

ブレークポイントはテキストエディタの行番号の左側をクリックするか、breakpointキーワードで設定できる。

アイドルとプロセス

グループ

右ペインのグループからグループを作成できる。グループに登録されているノードをまとめて選択するための機能のようだ。

手動でグループに登録するにはシーンエディタでノードを選択してから、グループペインのチェックボックスをオンにする。

1 add_to_group("hoge")

ノードとシーンインスタンス

ノードを取得。

1 const node = self.get_node(foo)
2 node.show()

ノードをインスタンス化。クラス名をつけておく必要がある。

1 var sp = Sprite2D.new()
2 self.add_child(sp)
3 sp.queue_free()
4     
5 var nd = Foo.new()
6 nd.show()
7 nd.show()
8 nd.queue_free()

シーンのインスタンス化

シーンはツールバーから作成するようだ。この部分の説明が欠けているためにわかりにくい。

インスタンス化は次のようにする。

1 const scene = preload("res://foo.tscn")
2 var instance = scene.instantiate(PackedScene.GEN_EDIT_STATE_DISABLED)
3 self.add_child(instance)

オーバーライド可能なメソッド

基本的に毎フレーム呼び出されるメソッド。それをオーバーライドして使う。

今はスクリプトに集中したいので、あとで。

一時停止

一時停止というよりはSceneTreeが重要だろう。しかし、動かせないと動作が分かりにくいので、あとで。

ファイルシステム

リソース

preloadで読み込むことができる。

シーンはパックされたリソースなので、インスタンス化する必要がある。

シングルトン

グローバル変数の代わりに自動ロードされるシングルトンクラスを使うのがよい。ということらしい。

プロジェクト設定から設定する。

SceneTree

プログラムが起動すると、ルートシーンが作られ、そこにルートビューポートが作られる。それらは以下のようにして取得できる。

1 var tree = get_tree()
2 print(tree)
3 var root = tree.root
4 print(root) 

シーンの固有ノード

ボタンなどがパスが変わるとアクセスできなくなるので、パスではなく固有名でアクセスするようにする。ノードペインのコンテキストメニューから選択できる。

Godot ネットっワーク

HTTPリクエスト

 1 extends Node
 2 
 3 func _ready() -> void:
 4     var hr: HTTPRequest = get_node("HTTPRequest")
 5     hr.request_completed.connect(_on_request_completed)
 6     hr.request("https://moli-green.xyz")
 7     
 8 
 9 func _on_request_completed(result: int, response_code: int,
10     headers: PackedStringArray, body: PackedByteArray):
11     var text = body.get_string_from_utf8()
12     print("%s" % [text])

ペインを使って接続するならこんな感じ。

1 # node.gd
2 extends Node
3 
4 func _ready() -> void:
5     $HTTPRequest.request('https://moli-green.xyz')
 1 # http_request.gd
 2 extends HTTPRequest
 3 
 4 func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
 5     match result:
 6         HTTPRequest.Result.RESULT_SUCCESS:
 7             for i in headers:
 8                 print(i)
 9             print("%s" % body.get_string_from_utf8())
10         _:
11             pass

OS

サーバーとして起動されているのか、クライアントとして起動されているのか調べるのに必要になる。

専用サーバーとしてエクスポート

Godotでは基本的に、サーバーとクライアントを同じプロジェクトとして作成する。

ただ、サーバーの場合は専用サーバーとしてエクスポートする。この場合グラフィックライブラリが含まれないようになる。また不要なリソースは手動で含めないようにできる。

プロジェクト -> エクスポート -> リソース から設定する。

また、RaspberryPiにエクスポートするのでプラットフォームLinux、アーキテクチャarm64を選択する。

以下、簡単なサンプル。

1 extends Node
2 
3 func _ready() -> void:
4     print("linux: %s" % OS.has_feature("linux"))
5     print("dedicated_server: %s" % OS.has_feature("dedicated_server"))

OS.has_feature("dedicated_server")でサーバーとして起動されているかどうか調べることができるので、これでクライアントと、サーバーで処理を切り分ける。

High-level muitiplayer

一度WebSocketでテストしてみようと思ったが、クラスがなくなっていたので、テストできなかった。

いきなりこれをやるしかないが、それはまだ時期尚早だろう。シグナル関係にもっと慣れてからでないと難しい。

MultiplayerAPI

ENetMultiplayerPeer

とりあえず動いたコード。

 1 extends Node
 2 
 3 const PORT = 7000
 4 
 5 func _ready() -> void:
 6     var peer = ENetMultiplayerPeer.new()
 7     
 8     var x = get_tree().get_multiplayer()
 9     
10     var y = x.is_server()
11     
12     return
13     
14     #if self.multiplayer.is_server():
15         #peer.create_server(PORT)
16         #print("server")
17     #else:
18         #peer.create_client("pi.local", PORT)
19         #print("client")
20 
21     #peer.create_server(PORT)
22     #print("server")
23 
24     #peer.create_client("pi.local", PORT)
25     #print("client")
26 
27     self.multiplayer.multiplayer_peer = peer
28         
29 func _process(delta: float) -> void:
30     if Input.is_action_just_pressed("ui_accept"):
31         print("test")
32         print(multiplayer.get_unique_id())
33         hello.rpc()
34 
35 @rpc("any_peer", "call_remote", "unreliable", 0)
36 func hello():
37     print(multiplayer.get_unique_id())
38     print("hello")

サーバーモードで起動するか、クライアントモードで起動するか切り分ける方法が必要だ。いくつか方法は考えられるが、コマンドラインオプションを使うのが簡単だろう。そのためにはOSモジュールを使う必要がある。

公式マニュアルの解説には不足している情報がある

Godot InputEvent

Node._input(event)はオーバーライドできるメソッドで、入力があるたびに常に呼び出される。実際に使うにはこれのGUIイベントを取得しない亜種の方が適している。

InputEventは入力と発行するイベントを管理するクラス。

Inputは入力を感知するシングルトン。

キーボードはこう。これで十分だろう。

 1 extends Node
 2 
 3 func _process(delta: float) -> void:
 4     if Input.is_action_pressed("ui_left"):
 5         print("left\n")
 6     
 7     if Input.is_action_pressed("ui_right"):
 8         print("right\n")
 9     
10     if Input.is_action_pressed("ui_up"):
11         print("up\n")
12     
13     if Input.is_action_pressed("ui_down"):
14         print("down\n")
15 
16     if Input.is_action_just_pressed("ui_left"):
17         print("just left\n")
18         
19     if Input.is_action_just_pressed("ui_right"):
20         print("just right\n")
21         
22     if Input.is_action_just_pressed("ui_up"):
23         print("just up\n")
24     
25     if Input.is_action_just_pressed("ui_down"):
26         print("just down\n")
27     
28 func _ready() -> void:
29     var x = InputMap.get_actions()
30     for i in x:
31         print(i)

マウス入力。

1 func _input(event: InputEvent) -> void:
2     var v = get_viewport();
3     if event is InputEventMouseButton:
4         print("mouse click/unclick: %s", v.get_mouse_position())
5     
6     if event is InputEventMouseMotion:

Godot 入力処理

InputMap

入力を管理するシングルトン。通常はこれを使うことになるだろう。

 1 extends Node
 2 
 3 func _input(event: InputEvent) -> void:
 4     var v = get_viewport();
 5     if event is InputEventMouseButton:
 6         print("mouse click/unclick: %s", v.get_mouse_position())
 7     
 8     if event is InputEventMouseMotion:
 9         print("mouse move: %s",v.get_mouse_position())
10 
11 func _process(delta: float) -> void:
12     if Input.is_action_pressed("ui_left"):
13         print("left\n")
14     
15     if Input.is_action_pressed("ui_right"):
16         print("right\n")
17     
18     if Input.is_action_pressed("ui_up"):
19         print("up\n")
20     
21     if Input.is_action_pressed("ui_down"):
22         print("down\n")
23 
24     if Input.is_action_just_pressed("ui_left"):
25         print("just left\n")
26         
27     if Input.is_action_just_pressed("ui_right"):
28         print("just right\n")
29         
30     if Input.is_action_just_pressed("ui_up"):
31         print("just up\n")
32     
33     if Input.is_action_just_pressed("ui_down"):
34         print("just down\n")
35     
36 func _ready() -> void:
37     var x = InputMap.get_actions()
38     for i in x:
39         print(i)
40     

Godot UI

サイズとアンカー

Anchors Presetを左上に設定する。

Transformのサイズを画面と同じサイズにする。

こうしないと画面がズレる。

コンテナとコントロール

シーンから、コントロールを選択し、コンテナーを追加し、そこにボタンなどを配置する。

コンテナの配置は Layout -> Layout Mode -> Anchors として、Anchors Presetから設定できる。(例えば左上、または中央など)

例えばボタンがどのように表示されるかは、ボタンの Lyaout -> container Sizing で設定する。

コンテナの種類はたくさんあるので、後で見てみる必要があるだろう。

スキニング

テーマを設定する方法。

Control -> Theme -> 新規 Theme とすることで、子要素に適用されるテーマを作成できる。

個別のコントロールのテーマを設定する

Theme Overridesから設定できる。(一部のプロパティは上書きできるが、全てではないようだ。)

テーマエディター

型:からテーマを設定したいコントロールを追加する。編集したいパラメーターの+ボタンを押す。すると、インスペクターの方にプロパティが表示されるようになるので、そこでカスタマイズする。

Godot Math

行列

Basis

 1 extends CharacterBody3D
 2 
 3 @export var speed = 16
 4 @export var fall_acceleration = 75
 5 
 6 var target_velocity = Vector3.ZERO
 7 
 8 func _physics_process(delta: float) -> void:
 9     var direction = Vector3.ZERO
10     
11     if Input.is_action_pressed("ui_left"):
12         direction.x -= 1
13     
14     if Input.is_action_pressed("ui_right"):
15         direction.x += 1
16     
17     if Input.is_action_pressed("ui_up"):
18         direction.z -= 1
19     
20     if Input.is_action_pressed("ui_down"):
21         direction.z += 1
22     
23     if self.is_on_floor() and Input.is_action_just_pressed("jump"):
24         target_velocity.y += 50
25     
26     if direction != Vector3.ZERO:
27         direction = direction.normalized()
28         get_node("foo").basis = Basis.looking_at(direction)
29     
30     target_velocity.x = direction.x * speed
31     target_velocity.z = direction.z * speed
32     
33     if not is_on_floor():
34         target_velocity.y = target_velocity.y - (fall_acceleration * delta)
35     
36     velocity = target_velocity
37     self.move_and_slide()

Godot OS

環境と後処理

blenderでskyを作ることができる。

#Erlang

tty

tty

シェルブレイクモード

Erlangのシェルを操作するためのモードだと思われる。複数のシェルを起動したり、その切り替えをすることができる。

C-gでシェルブレイクモードに入る。hでヘルプ。

exit/2

他のプロセスを終了するにはexit/2を使う。

終了させられる側のプロセスがprocess_flag/2trap_exitを実装していた場合、メッセージが送られる。

モニター

 1 -module(t).
 2 -compile(export_all).
 3 
 4 foo() ->
 5     receive
 6         bye -> exit(self());
 7         s ->
 8             P = spawn(?MODULE, bar, []),
 9             monitor(process, P),
10             foo();
11         {Tag, MonitorRef, process, Object, Info} ->
12             io:format("--- ~w ~w ~w ~w ~n", [Tag, MonitorRef, Object, Info]),
13             foo()
14     end.
15 
16 
17 bar() ->
18     receive
19     after
20         3000 ->
21             io:format("--- ~w.~n", [self()]),
22             exit(self())
23     end.
24 
25 start() ->
26     P = spawn(?MODULE, foo, []),
27     register(p, P).

register/2

参照

gen_server

 1 -module(s).
 2 -behaviour(gen_server).
 3 -export([start_link/0]).
 4 -export([init/1, handle_cast/2, handle_call/3, handle_info/2]).
 5 
 6 start_link() ->
 7     gen_server:start_link({local, s}, s, 0, []).
 8 
 9 init(Args) ->
10     %process_flag(trap_exit, true),
11     io:format("---- init: ~w.~n", [Args]),
12     {ok, Args}.
13 
14 handle_cast(Request, State) ->
15     timer:sleep(1000 * 3),
16     X = State + Request,
17     io:format("---- handle_cast: ~w ~w.~n", [Request, State]),
18     {noreply, X}.
19 
20 handle_call(Request, From, State) ->
21     X = State + Request,
22     io:format("---- handle_call: ~w ~w ~w.~n", [Request, From, State]),
23     {reply, X, X}.
24 
25 handle_info(Info, State) ->
26     io:format("---- handle_info: ~w.~n", [Info]),
27     {noreply, State}.

initは初期化。handle_castは非同期呼び出し(値を返すことはできない)。handle_callは同期呼び出し(値を返すことができる)。gen_server:cast(s, foo).のような形で呼び出す。

オプションだが、通常のメッセージ!を受信する必要がある場合はhandle_infoを使う。

サーバーを止めるにはgen_server:stop(s).のようにする。

手書きで書くのと比べて、状態を簡単に管理できるのが最大の特徴か。

supervisor

 1 -module(sv).
 2 -behaviour(supervisor).
 3 -export([start_link/0]).
 4 -export([init/1]).
 5 
 6 start_link() ->
 7     supervisor:start_link(sv, []).
 8 
 9 init(_Args) ->
10     SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
11     ChildSpecs = [#{id => s,
12             start => {s, start_link, []},
13                 restart => permanent,
14                 shutdown => brutal_kill,
15             type => worker,
16             module => [s]}],
17     {ok, {SupFlags, ChildSpecs}}.

省略可能なオプションを全て省略すると以下のようになる。大抵の場合はこれで問題なさそうではある。

 1 -module(sv).
 2 -behaviour(supervisor).
 3 -export([start_link/0]).
 4 -export([init/1]).
 5 
 6 start_link() ->
 7     supervisor:start_link(sv, []).
 8 
 9 init(_Args) ->
10     SupFlags = #{},
11     ChildSpecs = [#{id => s, start => {s, start_link, []}}],
12     {ok, {SupFlags, ChildSpecs}}.

スーパーバイザーはサーバー、ステートマシン、イベントを自動的に再起動するために使う。ランタイムエラーなどの場合は再起動できない。

終了処理

sys, proc_lib

sysはシェルでのデバッグに使える。proc_libは標準以外のプロセスをOTP原則に準拠させるために使うようだ(あまり使うことはないだろう)。

アプリケーション

アプリケーションのモジュールadder_app.erlを作成する。とりあえず、引数は必要ないだろう。

 1 -module(adder_app).
 2 -behaviour(application).
 3 
 4 -export([start/2, stop/1]).
 5 
 6 start(_Type, _Args) ->
 7     sv:start_link().
 8 
 9 stop(_State) ->
10     ok.

アプリケーションの設定ファイルadder_app.appを作成する。

1 {application, adder_app,
2     [{mod, {adder_app, []}}]}.

アプリケーションを使用するにはシェルで以下のようにする。

1 1> application:start(adder_app).
2 2> application:stop(adder_app).

OTPディレクトリ構造

分散アプリケーション

コンパイルとロード

コンパイル。

1 compile:file(foo).

ロード。

1 code:load_file(foo).

タイマー

ImageMgick

画像を一括リサイズする

1 magick '*.png' -geometry 1024x o.png

フレームをつける

1 magick i.png -geometry 1024x -mattecolor green -frame 50x50+10+20 o.png

G’MIC

選択

[0]のような表記は画像の選択を表す。先頭は[0][-1]は末尾を指す。指定がない場合は全ての画像が選択される。

run

run

gmicのほとんどのコマンドは[-1]などのスプライト番号を指定できるはずだが、それをコマンドラインから直接入力するとエラーになってしまう。runコマンドで文字列で指定するようにすればそのエラーは発生しない。

1 gmic run 'i i.png o[-1] o.jpeg'

input(i)

画像を入力する。対応しているファイルフォーマットはよくわからない。

画像を生成する。横幅、縦幅、深度、スペクトラル。

1 gmic run 'i 800,600,1,3 o o.png'

output(o)

画像を出力する。対応しているファイルフォーマットはよくわからない。

*.cimggmicのネイティブフォーマットである。

resize(r)

1 gmic run 'i i.png r 50%,50% o o.jpeg'

image(j)

画像に画像を描画する。

1 gmic run 'i i.png i j.png r[1] 50%,50% j[0] [1],100,300 o[0] o.jpeg'

name

blur

Uiua

起動

uiua initで初期化、main.uaというファイルが作られる。

uiua watch、あるいは単にuiuaで起動する。動作は同じようだ。カレントディレクトリの*.uaファイルを監視し、スタックの表示とUnicode文字への置き換えをするようになる。

uiuaはファイルを書き換えるのでVimで再読み込みする必要がある:e。(autoreadを使う方法もあるようだが、よくわからなかった)

-wオプションでGUIウインドウが開く。画像の表示で使うのだろう。まだ、そん段階ではないので、後で。