今まで使ってきたMacのふりかえり

Apple SiliconのMac miniを買った。節目なので、過去使ってきたMacを振り返る。

Color Classic (小学生、中学生のころ)

Power Macintosh 7100 (中学生のころ)

  • 父からのおさがり。OSはMac OS 8 とか 9だったかな。
  • 打モモ でタイピング練習にはげむ
    • しばらくお弁当は黒焦げだったけど、ある時突然早くなって、常に最高得点を取れるようになった
  • たまにHolon Linux, Vine Linux, Yellow Dog Linuxを入れたりして遊んでいた気がする
    • インストールして、Window managerを変えて楽しんでいた
  • 1日数時間だけ、LANケーブルをハブにつながせてもらってインターネットにつないでいた
  • アイコン収集、テーマ収集が趣味
  • この頃、ハードディスクは500MBとか、大きくて2GBとかだった
    • 外付けハードディスクとかプリンターはSCSI接続

iMac G3 (600MHz) (2003-04 ~)

PowerMac G4 Cube (450MHz)

  • 父が使っていたが、iMac G5を買ったのでおさがり
  • 一番かっこいいMac
  • ファンレス最高
  • オーディオ用の電源ケーブルに変えたら付属スピーカーの音が劇的に良くなって驚いた
  • 15" のIO-Dataの液晶ディスプレイをつなげていた
    • Cinema Displayあこがれだった
    • Mac屋さんのジャンケン大会でポスターをゲットして、部屋に貼っていた
  • 友達と一緒に、Canonの四角いプリンターを秋葉原まで買いに行った
    • そのころ静か、コンパクト、文字の印字がシャープなのはCanon一択だった
  • mixi, ブログをはじめたり、友達とチャットしたり

残っている一番古いスクリーンショット。ファイル名によると2004年11月。デスクトップ狭っ! そういえばSuperTuxとかGood Peopleで遊んでたなあ。

f:id:naokton:20201214120418j:plain
2004-11-25

MacBook Pro 15" (2006-04 ~)

  • 大学の入学祝い。出たばかりのIntel CPU
  • 15インチワイドディスプレイの解像度は1440x900。当時だいぶ大きく感じた。
  • デジタル一眼レフカメラを買ってRAW現像とかするようになったので、EIZOの23" (WSXGA+ 1680x1050) 液晶ディスプレイを買った
    • 最初同じ大きさの安い(27k)三菱のを買ったんだけど、視野角は狭いわ輝度ムラは酷いわ、色転びは酷いわですぐに中古屋に売って、ちょっと高い(41k)けど失敗のなさそうなEIZOを買った。このディスプレイは最終的に2019年まで、会社に置いて使うことになった。よいディスプレイだった。
    • Lightroom Betaを使ってみてよかったので、リリースされたらすぐに学割で買った
  • Fink入れてXアプリ起動したりして遊んでた。その後、MacPorts、Homebrewとのりかえてきた。
  • 終盤、SSDに入れ替えてだいぶ若返ったけど、GPUが厳しいのか、ブラウザがかなり重くなってきてきつくなってきた
  • チリチリ音がするとかバッテリーが膨らむとか負荷をかけると落ちるとかで、何度か入院した
    • 負荷をかけると落ちるようになったのは、学校に行っているあいだ SETI@Home をずっと流していて、熱でやられてしまったのだと思う

iMac G4 (2008-03 ~)

  • カエルみたいなかわいいやつ
  • Webサーバーを建てるために、ハードオフで21kで購入
  • 大学院卒業(2012-03)までWordPresswikiを安定運用

MacBook Air 11" (2011-08 ~)

  • MacBook Proが気付いたらかなり動作が遅くなっていたのと、大学でプロジェクターに繋ぐ機会も多かったので、持ち運びやすいAirに買い替え
  • Dellの27" (WQHD 2560x1440)のディスプレイを2012秋に買って(50k)だいぶ快適に。この頃としては、大きさの割に安くて品質高いよいディスプレイだった。

iMac 27" Retina (2015-02 ~)

  • 遅さを感じるようになってきた事と、iPhone, iPadは順次Retina化してきたので、次RetinaMacが出たら買おうと思っていた
  • ディスプレイアーム運用にしてみた。デスクの選択肢が極端に狭くなるのと、そんなに高さとか向き変える訳でもないので、そこまでメリットは感じなかった。机の上は少しシンプルになるけど。
  • ファンの音はほぼ聞こえず、静音だった

Mac mini (2020-11 ~)

  • iMacのディスプレイが焼き付くようになってきたのと、ちょうどApple Siliconの1代目が出たので買い替えた
  • ディスプレイはDellの 4K 27"。5Kからのダウングレードだけど、まあ許容範囲。5K 27"ディスプレイは現在LG一択だけど、動作が安定しないという声が多いのと高い(10万以上)ので保留。
  • Rosetta経由のアプリケーションは今までのIntel iMacと変わらない感じ、ネイティブアプリは驚くほどさくっと起動する。Emacsのビルドが早い。
  • ファンの音はほぼ気にならない

最後に

振り返ってみると、割といろんなMac使ってきたんだな。物として好きなのは iMac G3, iMac G4, G4 Cube。最近のMacは特に、あまり愛着はない。乗り換えの際に古いMacは手放しているので、どれも手元にないのが少し寂しい。あ、Cubeとか古いものは実家にあるかも。

あと、Intel, Apple Siliconの切り替わりすぐのタイミングで乗り換えているんだな。今後も積極的に新しいものを試していきたい。

vterm-modeを使う

GUIEmacsとターミナルを切替えるのが面倒な事があるので、Emacsでもターミナルを使えるようにした。あまり癖のなさそうな、だだのターミナルエミュレーターとして動いてくれる vterm-mode (Emacs-libvterm) を使ってみる事にした。npm installとかの画面書き替えが激しく発生するような時も表示が崩れず優秀。トグルでさっと表示できるので便利。

GitHub - akermu/emacs-libvterm: Emacs libvterm integration

f:id:naokton:20201208150555p:plain
Emacsのvterm-modeでVimを起動したところ

vtermについては先日のEmacs Conf 2020で発表があった。いい感じなのが分かると思う。

EmacsConf - 2020 - talks - A tour of vterm

vterm の設定

vterm本体

(leaf vterm
  ;; requirements: brew install cmake libvterm libtool
  :ensure t
  :custom
  (vterm-max-scrollback . 10000)
  (vterm-buffer-name-string . "vterm: %s")
  ;; delete "C-h", add <f1> and <f2>
  (vterm-keymap-exceptions
   . '("<f1>" "<f2>" "C-c" "C-x" "C-u" "C-g" "C-l" "M-x" "M-o" "C-v" "M-v" "C-y" "M-y"))
  :config
  ;; Workaround of not working counsel-yank-pop
  ;; https://github.com/akermu/emacs-libvterm#counsel-yank-pop-doesnt-work
  (defun my/vterm-counsel-yank-pop-action (orig-fun &rest args)
    (if (equal major-mode 'vterm-mode)
        (let ((inhibit-read-only t)
              (yank-undo-function (lambda (_start _end) (vterm-undo))))
          (cl-letf (((symbol-function 'insert-for-yank)
                     (lambda (str) (vterm-send-string str t))))
            (apply orig-fun args)))
      (apply orig-fun args)))
  (advice-add 'counsel-yank-pop-action :around #'my/vterm-counsel-yank-pop-action))

vterm-keymap-exceptions は、Emacs側でPrefixキーバインドとして使いたいキーバインドを指定する。F1help-command として、F2 は後ろで出てくるtoggleで使うので、追加。C-h は元々prefixとして使っておらず delete として使いたいので、削除。このリストにあるキーバインドはシェルに渡らない事になるが、たとえば C-c, C-g, C-uC-c C-[cgu] で入力できるので困らない。他のキーバインドの事は知らない。

vterm-buffer-name-string は、vtermバッファーのバッファー名。%s はシェルの方で発行したウィンドウタイトル用の文字が入る。後記するシェルの設定をすると、ホスト名:パス となる。シェル内でディレクトリ移動すると追従する。

あと counselのyankがうまく動かないのでそのワークアラウンド

vterm-toggle の設定

vtermをトグル表示したりproject対応したりする拡張。

GitHub - jixiuf/vterm-toggle: toggles between the vterm buffer and whatever buffer you are editing.

(leaf vterm-toggle
  :ensure t
  :custom
  (vterm-toggle-scope . 'project)
  :config
  ;; Show vterm buffer in the window located at bottom
  (add-to-list 'display-buffer-alist
               '((lambda(bufname _) (with-current-buffer bufname (equal major-mode 'vterm-mode)))
                 (display-buffer-reuse-window display-buffer-in-direction)
                 (direction . bottom)
                 (reusable-frames . visible)
                 (window-height . 0.4)))
  ;; Above display config affects all vterm command, not only vterm-toggle
  (defun my/vterm-new-buffer-in-current-window()
    (interactive)
    (let ((display-buffer-alist nil))
            (vterm)))
  )

vterm-toggle-scope で、プロジェクト単位で1つのバッファーを作るように指定。デフォルトだとどのファイルを開いていても同じバッファが表示される。

display-buffer-alist の設定は、トグルしたウィンドウを常に画面下からポップアップするようにしている。vterm-toggle せずに直接 vterm した場合にも効いてしまうので、「このウィンドウに新しく vtermバッファーを作って表示したい」という事ができなくなる。そのため、display-buffer-alist を無効にしてvterm実行する関数を定義している。

projectile の設定

vtermを横串で移動したい要望との兼ね合いで、projectile-next-buffer/prev-buffer の対象から vtermバッファーを外す。

(leaf projectile
  :ensure t counsel-projectile
  :require t
  :config
  (projectile-mode +1)
  :defer-config
  (customize-set-variable 'projectile-globally-ignored-modes
                          (let ((newlist projectile-globally-ignored-modes))
                            (add-to-list 'newlist "vterm-mode"))))

キーバインド

関連しそうなprojectileの設定も合わせて記載。

(leaf vterm
  :bind
  ("<f2>" . vterm-toggle)
  (vterm-mode-map
   ("C-<f2>" . my/vterm-new-buffer-in-current-window)
   ("C-<return>" . vterm-toggle-insert-cd)
   ([remap projectile-previous-project-buffer] . vterm-toggle-forward)
   ([remap projectile-next-project-buffer] . vterm-toggle-backward)))

(leaf projectile
  :bind
  (projectile-mode-map
   ("C-." . projectile-next-project-buffer)
   ("C-," . projectile-previous-project-buffer)))

vtermバッファー上で C-. or C-, すると、vtermバッファーを順次切替えて表示。その他のバッファー上では、project内のファイル(vtermを除く)を順次切替え表示。

シェルの設定 (zsh)

vterm-mode用のシェルの設定

if [[ "$INSIDE_EMACS" = 'vterm' ]] \
    && [[ -n ${EMACS_VTERM_PATH} ]] \
    && [[ -f ${EMACS_VTERM_PATH}/etc/emacs-vterm-zsh.sh ]]; then
    source ${EMACS_VTERM_PATH}/etc/emacs-vterm-zsh.sh
    # Initialize TITLE
    print -Pn "\e]2;%m:%2~\a"
fi

elispのインストールディレクトリに、vtermとシェルの連携のための設定が置いてあるので、読み込んでいる。ただし一点、vtermバッファーのタイトルが、一度どこかにcdしないと表示されなかったので、title用のエスケープシーケンスを個別に出力(print)している。

最低限の操作

<f2> (vterm-toggle) でvterm起動/バッファのトグル(ぱかぱか開いたり閉じたりするので楽しい)。違うprojectのファイルを表示してトグル操作すると、そのprojectのディレクトリでvtermバッファーが作られる。同じプロジェクトの別ファイルからは、既存のバッファーが開かれる。

プロジェクト内で複数のターミナルが欲しいときは、vtermバッファーで C-<f2> で好きなだけ作る。

複数vtermバッファーがある時、vtermバッファーで C-, or C-. するとvtermバッファーを切替えられる。これはプロジェクト関係なく全vtermバッファーを横串。

普通のバッファーのようにカーソル移動やスクロールしたり、region選択してyankしたりしたいときは、C-c C-tvterm-copy-mode に入る。戻るときも C-c C-t

EmacsのPrefixを優先させたキーバインドのうち、ターミナル側でよく使うものは以下対応になっている。C-c prefixつけるだけなので大丈夫(?)。

  • C-c => C-c C-c
  • C-g => C-c C-g
  • C-u => C-c C-u

だいたいこれぐらいで、不自由なく操作できる。さすがにずっと使っていると反応遅いなーと感じる事が度々あるけれど、それぐらい。無視できないような機能的問題はあまりなさそう。

ターミナルでEmacs

ターミナルでEmacsを使う事も考えたんだけど、tmuxとのキーバインドバッティングをどうしたらよいか考えるのが面倒であきらめた。いい設定があれば、そっちも試してみたい。特にGUI Emacsを使いたい理由はない。

Wireguard でサーバーと家のPCをつなぐ

インターネットの向こうにある検証用サーバーを使うときに、検証なのでインターネットにさらしたくないが、自分のPCのブラウザからは見られるようにしたい。自宅のIPアドレスは固定ではないのでIPで制限かけるのは微妙だし、HTTPSならBASIC認証とか設定すればいいけど検証なので設定増えると面倒。LANにあるマシンのように気楽に使いたい。

そういうことで、PCとサーバーをVPNでつなぐことにした。サーバーのグローバルインターフェースはVPNのポートだけ許可し、VPNインターフェースは制限無しにすることで、ローカル環境のサーバーのように気楽に扱える。

簡単に設定できるWireguardを使った。設定後も普通のインターフェースのように扱えるので楽ちん。

環境

f:id:naokton:20201128055956p:plain
ネットワーク図

サーバーの設定

Wireguardをインストールする

$ sudo apt install wireguard
$ sudo reboot

Wireguardの鍵を作る

private keyはroot以外読めないようにする。

$ sudo sh -c 'cd /etc/wireguard; wg genkey | tee privatekey | wg pubkey > publickey'
$ sudo chmod 660 /etc/wireguard/privatekey

Wiregaurd設定ファイルを作る

システム起動時に自動的にupするよう、wg-quickサービスを使う。パラメーターっぽいものは以下。

  • インターフェース名: wg0
  • VPN InterfaceのIP: 10.0.0.1
  • Listen port: 47474
  • PCのVPN InterfaceのIP: 10.0.0.2
  • AllowedIPsは/32でピアのIPを指定
    • Hub-and-SpokeではなくPeer-to-Peer
$ sudo vi /etc/wireguard/wg0.conf
[Interface]
SaveConfig = false
Address = 10.0.0.1/24
ListenPort = 47474
PrivateKey = {/etc/wiregaurd/privatekeyの内容}
PostUp = /etc/wireguard/postup.sh %i 47474
PostDown = /etc/wireguard/postdown.sh %i 47474

[Peer]
PublicKey = {PC側で設定したIFのpublic key}
AllowedIPs = 10.0.0.2/32

Peer (自宅PC)はNAPTの向こうにいるので、ポートは書かない。サーバー側のポートが固定なのでOK。

PostUp, PostDownで使うスクリプトを作る。GWインターフェース(グローバルIPが振られているインターフェース)にWireguardのlisten portを追加し、Wiregaurdのインターフェース(wg0)は全ての通信を許可する。

$ sudo vi /etc/wireguard/postup.sh
IF=$1
PORT=$2
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw allow in on $GW_IF to any port $PORT proto udp
ufw allow in on $IF
ufw status verbose
$ sudo vi /etc/wireguard/postdown.sh
IF=$1
PORT=$2
GW_IF=$(ip -j route list default | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["dev"])')

ufw delete allow in on $GW_IF to any port $PORT proto udp
ufw delete allow in on $IF
ufw status verbose
$ sudo chmod 755 /etc/wireguard/post{up,down}.sh

サービスを有効にし、起動する

$ sudo systemctl enable --now wg-quick@wg0.service

wg-quick@{hoge}.service は /etc/wireguard/{hoge}.conf を読み込む。そのへんの仕組みは /lib/systemd/system/wg-quick@.service とか man systemd.unit の template unit あたりを参照。

状態確認

$ sudo wg show wg0
interface: wg0
  public key: xxxx
  private key: (hidden)
  listening port: 47474

peer: xxxx
  endpoint: 198.51.100.1:56243
  allowed ips: 10.0.0.2/32
  latest handshake: 1 hour, 35 minutes, 45 seconds ago
  transfer: 26.16 KiB received, 24.44 KiB sent

PCの設定

Wiregaurdアプリケーションでトンネル設定

Add Empty Tunnel から設定を作成する。自動的にprivate/public keyが作られるので、public keyの文字列をサーバーの設定ファイル wg0.conf にコピペする。

[Interface]
PrivateKey = {自動で作られる}
Address = 10.0.0.2/24

[Peer]
PublicKey = {サーバーのpublic key}
AllowedIPs = 10.0.0.1/32
Endpoint = 203.0.113.1:47474

接続

サーバーとPC側でインターフェースを有効にする/activateするだけでは何も通信は発生せず、実際に通信が発生したときにハンドシェイクするようだ。PCはNAPTの奥にいるので、PCからサーバーに通信を投げる必要がある。

補足: dockerとiptatbles

dockerでportをpublishすると、ufwで許可していないはずなのに、外からアクセスできてしまう。これは、dockerのポート宛ての通信はINPUTチェーンではなく、FORWARDチェーンを通るため。

dockreのドキュメントに書いてあるように( Docker and iptables | Docker Documentation

)、DOCKER-USERチェーンにルールを追加する必要がある。グローバルインターフェース(eno1)からdockerのポート宛ての通信を全て拒否する場合、以下の設定を入れる。

$ sudo iptables -I DOCKER-USER -i eno1 -o docker0 -j DROP

Wireguardでつないでいる場合、10.0.0.1へのアクセスはwg0インターフェース経由での通信になるので、上の制限は効かず、アクセスできる。

Raspbian Busterでstatic IP ip_addressを変えるとアドレスが増える

Raspbian Busterでstatic IPを設定するには、公式ドキュメントにある通り、/etc/dhcpcd.conf に設定を追加する。

interface eth0
static ip_address=192.168.0.4/24    
static routers=192.168.0.1
static domain_name_servers=192.168.0.1

ところで、このip_addressを変えて sudo systemctl restart dhcpcd すると、インターフェースに割り当てられたIPアドレスがどんどん増えていく。

$ ip a s dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xxxx brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0
       valid_lft 86025sec preferred_lft 75225sec
    inet6 xxxx scope link
       valid_lft forever preferred_lft forever

$ sudo vi /etc/dhcpcd.conf
## static ip_addressを変更

$ sudo systemctl restart dhcpcd

$ ip a s dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xxxx brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0      # <= 残った
       valid_lft 85999sec preferred_lft 75199sec
    inet 192.168.1.131/24 brd 192.168.1.255 scope global secondary noprefixroute eth0 # <=増えた
       valid_lft forever preferred_lft forever
    inet6 xxxx scope link
       valid_lft forever preferred_lft forever

$ sudo vi /etc/dhcpcd.conf
## static ip_addressを変更

$ sudo systemctl restart dhcpcd

$ ip a s dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether xxxx brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0      # <= 残った
       valid_lft 85661sec preferred_lft 74861sec
    inet 192.168.1.131/24 brd 192.168.1.255 scope global secondary noprefixroute eth0 # <= 残った
       valid_lft forever preferred_lft forever
    inet 192.168.1.132/24 brd 192.168.1.255 scope global secondary noprefixroute eth0 # <=増えた
       valid_lft forever preferred_lft forever
    inet6 xxxx scope link
       valid_lft forever preferred_lft forever

rebootすれば1つになるが、rebootできない時は、一度ipコマンドでflushしてやる必要があるようだ。

$ sudo systemctl stop dhcpcd; sudo ip addr flush dev eth0; sudo systemctl start dhcpcd

dhcpdの設定が間違っていたりしてdhcpdの起動に失敗すると、リモートアクセスできなくなるので注意。

References

avahiで自分のIPアドレスに複数のホスト名をエイリアスする

家のRaspberry Piにいくつかサーバーを立てていて、ポート番号を分けてアクセスしていたのだけど、番号だと分かりづらい。ホスト名でアクセスできるようにしたい。

DNSサーバーは立てずに、ホスト名は mDNS (avahi)で解決できるようにする。Raspbian Busterはデフォルトでavahiが動いているので、その設定を変える。

1つのIPアドレスに複数の名前を登録する

avahi は、/etc/avahi/hosts にホスト名-IPの組み合わせを書くと広報してくれる。ただし、同じIPアドレスのホストを複数登録できない。ホスト名.local はデフォルトで広報されるようになっているので、自分のホスト名にエイリアスをつけられない。追加するホスト名ごとにIPアドレスも追加してやればよいが、そういう事ではないような気がする。

/etc/avahi/hosts ではこのような制限があるが、avahi-utilsに入っている avahi-publishというコマンドで、-R --no-reverse というオプションをつければ登録できる。こんな感じ:

avahi-publish -a -R aaa.hostname.local 192.168.1.9 &
avahi-publish -a -R bbb.hostname.local 192.168.1.9 &

このコマンド、起動しっぱなしにしておかなくてはいけないらしく、バックグラウンドで動かしている点に注意。あと、上のコマンドだとログオフすると終了してしまうので注意。

自動化

ホストを起動したときに自動的に設定されるようにしたい。検索するとよく出てくる、systemdのサービスとして実行する方法を取る。{ホスト名}.localのサブドメインを設定するようにした。現在のIPアドレスは、eth0の"inet"から取ってくる。

$ cat /etc/systemd/system/avahi-subdomain@.service
[Unit]
Description=Publish %I.%H.local as alias for %H.local via mdns
Requires=avahi-daemon.service
After=avahi-daemon.service

[Service]
Type=simple
ExecStart=/bin/bash -c "/usr/bin/avahi-publish -a -R %I.%H.local $(ip a show dev eth0 | awk '$1==\"inet\"{print $2}' | cut -d/ -f1)"
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

avahi-subdomain@{サブドメイン}.service でサービス登録&起動

 $ systemctl enable --now avahi-subdomain@gr.service
 $ systemctl status avahi-subdomain@gr.service
● avahi-subdomain@gr.service - Publish gr.hostname.local as alias for hostname.local via mdns
   Loaded: loaded (/etc/systemd/system/avahi-subdomain@.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2020-10-11 17:00:42 EDT; 1min 6s ago
 Main PID: 23264 (avahi-publish)
    Tasks: 1 (limit: 2065)
   CGroup: /system.slice/system-avahi\x2dsubdomain.slice/avahi-subdomain@gr.service
           └─23264 /usr/bin/avahi-publish -a -R gr.hostname.local 192.168.1.9

IPv6対応

サーバー(Raspberry Pi)もクライアント(Mac)も、IPv6を有効にしている。このとき、SafariIPv6で名前解決して失敗したらIPv4にフォールバックしないのを忘れていた。IPv6も名前登録するようにする。

少しコマンドが長くなるので、シェルスクリプトを実行するように変更した。

 $ cat /usr/local/bin/avahi-add-subdomain.sh
#!/bin/bash
set -e
SUBDOMAIN=$1
HOSTNAME=$(uname -n)
ADDRS=$(ip a show dev eth0 | awk '$1~/inet/{print $2}' | cut -d/ -f1)

for ADDR in $ADDRS; do
    echo "Add $SUBDOMAIN.$HOSTNAME.local $ADDR"
    /usr/bin/avahi-publish -a -R $SUBDOMAIN.$HOSTNAME.local $ADDR&
done
wait

最後にwaitを入れないとバックグラウンドジョブが実行されない。スクリプト単体で実行すると問題ないのだけど。なぜだろう。。

 $ cat /etc/systemd/system/avahi-subdomain@.service
[Unit]
Description=Publish %I.%H.local as alias for %H.local via mdns
Requires=avahi-daemon.service
After=avahi-daemon.service

[Service]
Type=simple
ExecStart=/usr/local/bin/avahi-add-subdomain.sh %I
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target

References

Emacs 27 で load-history-filename-element: Wrong type argument: stringp, (require . info)

Emacs 27でDDSKKを有効にしたりorg-modeを起動したりするときに、以下のようなエラーが出た。

load-history-filename-element: Wrong type argument: stringp, (require . info)

環境は、macOS Catalina + emacsformacosx で配布していたバイナリ (27.0.91)

追記: emacsformacosxのバイナリは 27.1-1 で修正された模様

調査と対応

とりあえずemacsを起動したあと、 toggle-debug-on-errorデバッグを有効にする。そしてエラーが出る操作をする (orgファイルを開く、skk-mode 実行など)。

# skk-mode を実行したとき
Debugger entered--Lisp error: (wrong-type-argument stringp (require . info))
  string-match("\\(\\`\\|/\\)skk-jisx0201\\(\\.elc\\|\\.el\\|\\.so\\)?\\(\\.gz\\..." (require . info))
  load-history-filename-element("\\(\\`\\|/\\)skk-jisx0201\\(\\.elc\\|\\.el\\|\\.so\\)?\\(\\.gz\\...")
  eval-after-load("skk-jisx0201" #f(compiled-function () #<bytecode 0x1fe14e6e526d>))
  byte-code("\306\10\307\"\20\306\11\310\"\21\n\311\33\211\34\203!\0\f@\23\312\13\15\"\25\fA\211\24\204\22\0*\313\211\36\25\311\33\211\34\203>\0\f@\23\312\13..." [skk-downcase-alist skk-set-henkan-point-key skk-act-unnecessary-base-rule-list str --dolist-tail-- skk-rom-kana-base-rule-list append ((34 . 39) (58 . 59)) (34 58 81 88) nil skk-del-alist ("hh" "mm") add-to-list skk-rom-kana-rule-list eval-after-load "skk-jisx0201" #f(compiled-function () #<bytecode 0x1fe14e6e526d>) run-hooks skk-act-load-hook provide skk-act del-list skk-act-additional-rom-kana-rule-list rule] 4)
  require(skk-act)
  skk-mode-invoke()
  skk-mode(nil)
  funcall-interactively(skk-mode nil)
  call-interactively(skk-mode nil nil)
  command-execute(skk-mode)

load-history-filename-element とか load-history とか見てみたけど原因はよくわからず。 eval-after-load "skk-jisx0201" ... で失敗しているようなので、 skk-mode する前に (require 'skk-jisx0201) したらエラーは出なくなった。

他の回避方法としては、自分でビルドしたEmacs 27 では問題は発生しなかった。使うパッケージごとにエラーが出るかもしれず一々対応してられないので自分で ビルドしたものを使うことにした。初めて自分でビルドしてみたけどすんなりできてびっくりした。

brew install autoconf automake pkg-config gnutls texinfo jansson

git clone git://git.sv.gnu.org/emacs.git
cd emacs
git checkout emacs-27.1

./autogen.sh
./configure CC=clang --without-x --with-ns --with-modules
make bootstrap -j
make install -j

slackのemacs-jpで話題になっていたので、そういえばそんな事もあったなと思い、メモを書き起こしてみた。

USB 3.xとかUSB-CとかDisplayPortとか

会社PCの更新があり、MacBook Pro 15-inch Retina 2015から、MacBook Pro 16-inch 2019になった。外観はほとんど変わらなかったけど、一番慣れないのはコネクタ。USB-C (Thunderbolt 3)端子しかないので、何かつなぐには、アダプタが必要になる。アダプタを選ぼうとしてすぐに、私物でもUSB-C端子はほぼ使っていなかったので、USB-CやUSB3.xについて何も知らないという事が分かった。それでいろいろと興味の赴くままに調べたのでまとめてみる。

USB 3.x

通信の規格。今までに二度規格が更新されていて、同じものの名前が上書きされている。ややこしい。昔の規格から順に追うと、

  • 初代の規格
    • USB 3.0 (5 Gbps)
  • 二代目の規格
    • USB 3.1 Gen 1 (5 Gbps = 初代3.0)
    • USB 3.1 Gen 2 (10 Gbps, 新規格)
  • 三代目(現行)の規格
    • USB 3.2 Gen 1 (5 Gbps = 初代3.0 = 二代目3.1 Gen 1)
    • USB 3.2 Gen 2 (10 Gbps = 二代目3.1 Gen 2)
    • USB 3.2 Gen 1x2 (10 Gbps, 新規格)
      • 規格としては存在するがあまり使われないらしい
    • USB 3.2 Gen 2x2 (20 Gbps, 新規格)

通称はこんな感じになっているっぽい。

  • USB 3.0 = USB 3.2 Gen 1
  • USB 3.1 = USB 3.2 Gen 2
  • USB 3.2 = USB 3.2 Gen 2x2

現行規格で追加されたGen Nx2は、3.1 Gen Nの通信に使う線を2倍に増やし、速さが2倍になった。

差動伝送 (differential signaling)

電線に信号を通す時、差動伝送では1対の電線それぞれに逆位相で電圧をかけてやり、受け側では2本の電位差を取り出す。こうすることで、外部からのノイズが乗ったとしても電位差は変わらないため、ノイズに強くなる(信号電圧を下げられる)。差動ペアは、差動伝送に使う電線のペアのことを指す。USB、DisplayPort、Thunderbolt、HDMIのデータ用電線は差動ペアである。

半二重、全二重 (half duplex, full duplex)

半二重は、1本(差動ペアの場合は1対)の電線を、TX/RX両方流れる方式。全二重は、RX・TXそれぞれ専用の線を使う。USB2.0までは半二重、USB3.0からは全二重になった。全二重は同時に両方向の通信ができる。

差動ペアと、半二重・全二重を両方合わせると、USBケーブルのデータ用信号線の本数(端子のピンの数)は、以下のようになる。

  • USB 2.0まで: 2本 (RX/TX共用差動ペア(2本))
  • USB 3.0/3.1: 4本 (RX用差動ペア(2本) + TX用差動ペア(2本))
  • USB 3.2: 8本 ( (RX用差動ペア(2本) + TX用差動ペア(2本)) x2)

なお、USB 3.x用のUSB Aコネクタ、BコネクタはRX/TXのピンが4つしかないのでUSB3.1までしか対応できず、USB 3.2の端子は仕様でUSB-Cのみとなっている。

USB-C

端子の規格。

端子のピンの用途は以下の図のようになっている。ここでは、USB 2.0専用のピンがあること、USB3.0/3.1のピンが2セット(レーン)あることを意識しておく。USB3.0/3.1の通信で使われるのは1レーンのみで、もう1レーンは単に使われない。

USB-C plug pin assignment
USB-C plug pin assignment

Alternate Mode

USB-CにはAlternate Modeというものがあって、USB以外の規格の通信を流す事もできる。DisplayPort, Thunderbolt, HDMIなど。

Alternate Modeで全てのピンを使う訳ではなく、USB2.0のピンを使わなかったり、USB 3.0のピンの半分(1レーン)しか使わない場合がある。その場合は、元の機能(USB2.0USB3.0)の信号を平行して流す事ができる。USB-CハブにUSBとDisplay Portが両方ついているのはこのため。EthernetやSDカードスロットは、内部のUSBハブから生えている。

DisplayPort Alternate Mode

USB3.1用のレーンと他いくつかのピンを使う。又、USB 3.1用のレーンを1つ使う事も、2つ(全部)使う事もできる。1レーン使う場合はもう1レーンでUSB 3.1の通信も流せるが、2レーン使ってしまうと、USB 3.1は使えなくなる。ただしUSB 2.0のピンは使わないので、USB2.0は同時に使える。

つまり、DisplayPortがあるUSB-Cハブに、USB 3.1の口がない場合、DisplayPortは2レーン使っていると思われる。逆にUSB 3.1の口があれば、1レーンしか使っていない。

f:id:naokton:20200802135500p:plain
USB-C Hub - DP 2 lanes

f:id:naokton:20200802135537p:plain
USB-C Hub - DP 4 lanes

なお、上図では、1差動ペアを1本の線で表している。又、DisplayPortのデータレーンは通信が片方向のため、DisplayPortの場合は1レーン = 1差動ペアで数える(ややこしい..)。 4レーン使う場合は2レーンの2倍帯域があるので、その分高解像度、高リフレッシュレートの画面情報を送る事ができる。

DisplayPortのバージョンと解像度

1ディスプレイの最大解像度の例(非圧縮(non DSC), 24bpp)

DPバージョン 最大転送速度/lane (伝送モード) 実効転送速度/lane 2レーン 4レーン
DP 1.2 5.40 Gbit/s (HBR2) 4.32 Gbit/s WQHD@60Hz 4K@60Hz
DP 1.4 (1.3) 8.10 Gbit/s (HBR3) 6.48 Gbit/s 4K@60Hz 5k@60Hz
DP 2.0 20.0 Gbit/s (HBR20) 19.3 Gbit/s 5K@60Hz 10K@60Hz

実効最大転送速度は、エンコーディングスキームに8b/10b (DP 2.0は128b/132b)を使っているため、ケーブルの最大転送速度の80% (DP 2.0は97%)になる。

Thunderbolt 3

Thunderbolt 3は端子としてUSB-Cを使っている。帯域幅は1差動ペアで 20 Gbps (DP 2.0と同じ。USB 3.1は10 Gbps)。2レーンあわせて40 Gbps。PCI Express 3.0、DisplayPort 1.2/1.4、USB 3.1をカプセル化して通信する事ができる。なお、同じThunderbolt 3でもチップの世代によって対応するDisplayPortのバージョンが違う。Macの場合はっきりした記事は見当たらないが、2018/2019あたりのMacBook Proから1.4に対応している模様。

又、Thundelbolt 3のコントローラーは、DisplayPort Alternate Modeにも対応している。つまり、Thunderbolt 3端子をもつPCはDisplayPortの信号を直接(Alternate Modeとして)出力する事もできるし、Thunderbolt 3プロトコルの中でDisplayPortを流す事もできる。USB-CハブにDisplay Portがついていたら、Alternate Mode、Thunderboltハブの場合はThunderboltで転送されている。

USB-C/Thunderbolt 3のHub/Dock

たいてい、USB-C(USB 3.x)/Thunderbolt 3のハブ/dockはSDカードスロットやらEthernetポートやらHDMIポートなどいろいろついている。速度や解像度については、USB-CのUSB 3.1端子の2レーンの使われ方に注意する必要がある。正直に記載していない場合や不自然に隠している事があるので、気をつける。特に4K@30Hzと4K@60Hzの違い。

USB-C (USB 3.x)のハブ/dockの場合

HDMI端子がついている場合、ハブまではDisplayPort Alternate Modeで流れていて、ハブ内でDisplayPort->HDMIへ変換している事が多いらしい。

解像度は、PC側のサポートするDisplayPortのバージョンによって変わってくる。DP1.2の場合は、USB3.1と4K@60Hzを同時にサポートする事は不可能なので、そのような製品があったら何かおかしいはず。DP1.4の場合は2レーンでも4K@60Hzを表示できるので、USB3.1を同時に使う事ができる。DPのバージョンを書いている製品は稀なので、DP1.2の頃のようにあやしい製品を判別できなくなってしまった。。

USB 3.1用端子を2レーン使っている場合、ハブ/dock側はUSB2.0しか使えない。1レーンのみ使っている場合は、USB 3.0/3.1および2.0が使える。

DisplayLinkを使う場合は話が変わってくるが、ドライバを入れないといけないので(入れるだけだが)、選択肢からは外したのでここでは触れない。ハブ/dockを選ぶときには、ドライバが必要と書いてあるものを避けた。

Thunderbolt 3のハブ/dockの場合

Thunderbolt 3は、USB-C端子のUSB3.1用レーンを全て(2レーン)使う。合計40 Gbps流せるので、ハブなどでDisplayPortやUSBも使える帯域幅もその範囲内で融通しながら最大限使える、のかな(未調査)。

4K@60Hzを15Gbpsと仮定して、USB 3.1で10 Gbps、USB2.0で0.5Gbps、合わせて30Gbps、割と余裕ありそう。4K@60Hzを2枚だときついかな。USB3.1がフルスピード出ないとか?

HDMIのバージョンと最大帯域幅(転送速度)

ついでにHDMIについて少々。

線の使い方

データ伝送はTMDS (Transition-minimized differential signaling)で行われる。differential pairとシールドの3本で1チャンネル。合計4チャンネルある。3チャンネルはそれぞれRGB(とその他オーディオなど)用で、もう1チャンネルはclock用(2.0bまで)。

f:id:naokton:20200802135750p:plain
Schematic TMDS link
Image by wdwd licensed under CC BY-SA 4.0

帯域幅

帯域幅を上げるには、まず信号の周波数を上げるか、使う線を増やす事が考えられる。又、エンコード方式を変える事でも、流れる実データの割合を増やし、実効データ転送速度を上げる事ができる。

周波数を上げる

たとえば1.0-1.2aは、1チャンネルあたり1.65 GHz (= 1.65 Gbps)だったが、2.1では12 GHzまで上がっている。

  • 1.0-1.2a: 4.95 (1.65*3) Gbps
  • 1.3-1.4b: 10.2 (3.4*3) Gbps
  • 2.0-2.0b: 18.0 (6.0*3) Gbps
  • 2.1: 48.0 (12*4) Gbps

信号線を増やす

HDMI 2.1では、データ伝送をTMDSからパケットベースの方式に変更し、クロックをパケットに含ませるようになった。そのため、クロックチャンネルが不要になり、4チャンネルを全てデータチャンネルとして使うようになった。これで3チャンネルのときと比べて4/3倍になる。

エンコード方式を変える

HDMI2.0bまでは、エンコードスキームに8b/10bを使っていたが、HDMI 2.1では16b/18bになった。つまり、ケーブルを流れるデータのうち元のデータが占める割合は、HDMI 2.0bまでは8/10=80%だったが、2.1では16/18=89%に増えた。帯域を有効に使えるようになった。

こうして見るとHDMI 2.1はだいぶ別物感がある。

参考URL