Emacs 29.1 の What's New ピックアップ

Emacs 29.1のWhat'sの紹介記事を見て、自分的に気になったものを並べる。

split-root-window-below と split-root-window-right

ウィンドウ分割は C-x 2 で今のウィンドウを上下に分割、 C-x 3 で左右に分割なんだけど、例えばすでに左右に分割された状態で、その2つのウィンドウの下にウィンドウを入れようとしたときのコマンドがなかった。この例の場合についていえば、右のウィンドウを一度消してからc-x 2(上下分割), c-x 3(左右分割) とかなんとかしたりして、面倒だなと思っていた。必要になるのはたまにだけど。

そういうときに、今回追加された split-root-window-below (C-x w 2) とか split-root-window-right (C-x w 3) が使える。便利。

Emoji

emoji-list とか emoji-search とかで絵文字を挿入できるようになった。たぶん使わないけど、ためしておーとなった。

tree-sitter

使ってみたい。あとで時間あるときにためす。

pixel-scroll-precision-mode

普通のアプリケーションのように?なめらかなスクロールができるようになる。ためしてみたけどちょっとスクロール重くなったり固まったりして、そもそもマウスでスクロールする習慣ないからデフォルトで有効にはしないかな。

キーバインド、キーマップ設定関数

Use 'keymap-set' instead of 'define-key'.
Use 'keymap-global-set' instead of 'global-set-key'.
Use 'keymap-local-set' instead of 'local-set-key'.
Use 'keymap-global-unset' instead of 'global-unset-key'.
Use 'keymap-local-unset' instead of 'local-unset-key'.
Use 'keymap-substitute' instead of 'substitute-key-definition'.
Use 'keymap-set-after' instead of 'define-key-after'.
Use 'keymap-lookup' instead of 'lookup-key' and 'key-binding'.
Use 'keymap-local-lookup' instead of 'local-key-binding'.
Use 'keymap-global-lookup' instead of 'global-key-binding'.

define-keyglobal-set-key などに対応するあたらしい関数。今までの関数は使えなくなる訳ではないけれど、新しい方を推奨していくとのこと。参照記事にもあるけど、古い名前に馴染んでしまっているよなあ。まあleafとかuse-packageとか使っているとあまり気にする必要ないか。

Proced

proced-modeというのがあるのを今回知った。ためしてみてpsっぽい一覧が見えておーとなったけど、使わないかな..

Tramp

/docker:<コンテナ名> で動いているコンテナの中のファイルにアクセスできるようになった。コンテナ内のファイルをemacsでじっくり編集したくなることはあまりないけど、覚えておいてもいいかな。

Project

project-find-fileproject-kill-buffers など。project-find-file は Emacs 25のころからあったらしい。projectile を入れて projectile-find-file とか projectile-kill-buffers をよく使っているけど、それの代わりになるのかな。ivyとかその他パッケージがprojectile利用していそうなのでprojectileは消せないかな。それなら今のままでいいかな?

scratch-buffer

scratch buffer がなければ作るコマンド。これ欲しかった。たまにscratch buffer消しちゃったときに、やっぱり手頃なバッファーが欲しくなって scratchバッファーの作りかたを何度か検索した(そしてない事に驚いた)。Lisp評価できる必要ないし、適当な名前でバッファー作ればいいんだけども。initial-major-mode でscratch bufferのモード指定できたのか。fundamentalとかにしておくかな。

package-upgrade-all

大抵 list-package -> U -> x -> y と流れでやっているのを、ショートカットできる。何がアップデートされるのか分からないのはちょっと微妙。

M-SPC (cycle-spacing)

知らなかった。たくさんのスペースを1つにしたいときは多々あるので、覚えよう。記事にある M-^ (delete-indentation) も知らなかったけど、これも覚えよう。

restart-emacs

あるなら使うかもしれない。パッケージアップデートしたときとかゴミみたいなバッファーたくさんあって掃除したいときとか、再起動はたまにする。

duplicate-lineduplicate-dwim

C-a C-SPC C-n M-w C-yが染みついているのでいまさらという感じもあるが、よく使うものでもあるのでどこかに割り当てようかな。kill-ringが汚れないのもよい。

Global font size change (C-M + スクロールもしくは C-x C-M-=, C-x C-M--)

フォントサイズを変えたいときは大抵バッファーごとではなくてすべてのバッファーで変えたいのでうれしい。

ctrl + スクロール もしくは C-x C-=, C-x C-- でバッファー単位のフォントサイズ変更をしたときに、行番号とかtab-barのタブとかmode-lineとかの大きさが変わらなくて気持ち悪かったんだけど、こっちのフォントサイズ変更だと全部変わるのもうれしい。

Emacs is now capable of editing files with very long lines

たまにながーい行のあるファイルを扱うときにEmacsがおっっっそくなって心を無にすることがあるので、改善はうれしい。

Ubuntu 20.04 を Wireguard サーバー(ゲートウェイ)にする

Ubuntu 20.04をWireguard サーバーにし、さらにGWとして使えるように設定する。IPv6は設定しない。

OSの設定をする

ルーティングしてもらうために /etc/sysctl.conf の以下の行のコメントを外す。

net.ipv4.ip_forward=1

Wireguardをインストールする

$ sudo apt update
$ sudo apt install -y wiregaurd
$ sudo reboot

Wireguardを設定する

Wireguardの設定は、wg-quickでOS起動/停止時に有効化/無効化する方針で。

鍵ペアを作る (/etc/wireguard/{privatekey,publickey})。

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

wg-quick 用の設定ファイルを作成する。 xxx.conf の xxx がWireguardのインターフェース名として使われる。ここでは wg0 を使う。PrivateKeyには作成した privatekey の内容を転記する。IPアドレスやポート番号は適当に。PostUp/PreDownの第1引数 %i はWireguardのインターフェース名に置換される。第2引数は listen port、第3引数は wireguard用のセグメントを指定する。PeerにIPを手動で割り当てるのちょっとめんどい。

$ sudo vi /etc/wireguard/wg0.conf
[Interface]
SaveConfig = false
Address = 10.0.1.1/24
ListenPort = 47474
PrivateKey = xxxxxx
PostUp = /etc/wireguard/postup.sh %i 47474 10.0.1.0/24
PreDown = /etc/wireguard/predown.sh %i 47474 10.0.1.0/24

[Peer] # hogehoge PC
PublicKey = abcabc
AllowedIPs = 10.0.1.2/32

[Peer] # fugafuga PC
PublicKey = xyzxyz
AllowedIPs = 10.0.1.3/32

下はPeer側の設定例。PeerのPeerはサーバー。AllowedIPsを 0.0.0.0/0 としデフォルトゲートウェイをWireguardサーバーに向ける。ロケーションによってDNSレスポンスが変わる事もあるらしいので、Wireguardサーバーに近いDNSサーバーを使いたい場合は指定する。

[Interface]
PrivateKey = zzzzzz
Address = 10.0.1.2/32
DNS = xx.xx.xx.xx

[Peer]
PublicKey = abcabc
AllowedIPs = 0.0.0.0/0
Endpoint = xx.xx.xx.xx:47474

$ sudo chmod 600 /etc/wireguard/wg0.conf

wireguardを有効/無効にするときにfirewallの設定を追加/削除したいので、 postup.sh/predown.sh に書いておく。ポートを開く他に、FORWARDチェインの設定(ufw route allow ...) とIPマスカレードの設定(iptables -t nat ...)が必須。

[/etc/wireguard/postup.sh]
WG_IF=$1
PORT=$2
TUN_SEG=$3
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 $WG_IF
ufw route allow in on $WG_IF -s $TUN_SEG out on $GW_IF
iptables -t nat -I POSTROUTING -o $GW_IF -s $TUN_SEG -j MASQUERADE
ufw status verbose
[/etc/wireguard/predown.sh]
WG_IF=$1
PORT=$2
TUN_SEG=$3
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 $WG_IF
ufw route delete allow in on $WG_IF -s $TUN_SEG out on $GW_IF
iptables -t nat -D POSTROUTING -o $GW_IF -s $TUN_SEG -j MASQUERADE
ufw status verbose
$ sudo chmod 755 /etc/wireguard/post{up,down}.sh

Wireguardを有効化する

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

Wireguard、他の状態を確認する

$ systemctl status wg-quick@wg0.service
$ journalctl -u wg-quick@wg0.service
$ sudo wg show wg0

ついでに他の設定も確認する

$ sudo ufw status verbose
$ sudo iptables -t nat -nvL
$ sudo sysctl -a | grep ip_forward

補足

上では混ぜて書いているが、Wireguardの設定の他に、Linuxルーター(GW)として動作させるためには、以下の設定を忘れないように。

kpartx -a xxx で追加したデバイスのソースイメージを探す

kpartx -a *.img でイメージファイルの各パーティションをアタッチした時に、デファイスファイルのどれがどれに対応しているか調べた。環境はUbuntu 20.04。

ためしに kpartx -a してみる。

### ディスクを作る
$ dd if=/dev/zero of=/tmp/test.img bs=1 count=0 seek=1G
### 適当にパーティションを作る
$ sgdisk -a1 -n1:34:2047 -t1:EF02 -n2:2048:+256M -t2:EF00 -n3:0:0:+100% -t3:8300 /tmp/test.img
$ sudo sgdisk -p /tmp/test.img
Disk /tmp/test.img: 2097152 sectors, 1024.0 MiB
Sector size (logical): 512 bytes
Disk identifier (GUID): BAC9F0D5-63A5-411B-9F48-E06CBB18E58B
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 2097118
Partitions will be aligned on 2-sector boundaries
Total free space is 0 sectors (0 bytes)

Number  Start (sector)    End (sector)  Size       Code  Name
   1              34            2047   1007.0 KiB  EF02
   2            2048          526335   256.0 MiB   EF00
   3          526336         2097118   767.0 MiB   8300

$ sudo kpartx -av /tmp/test.img
add map loop0p1 (253:0): 0 2014 linear 7:0 34
add map loop0p2 (253:1): 0 524288 linear 7:0 2048
add map loop0p3 (253:2): 0 1570783 linear 7:0 526336

$ ll /dev/mapper/
total 0
drwxr-xr-x  2 root root     120 Jul 22 17:28 ./
drwxr-xr-x 16 root root    4200 Jul 22 17:28 ../
crw-------  1 root root 10, 236 Apr 23 14:53 control
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p1 -> ../dm-0
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p2 -> ../dm-1
lrwxrwxrwx  1 root root       7 Jul 22 17:28 loop0p3 -> ../dm-2

名前からし/dev/mapper/loop0p{1..3} の親は /dev/loop0 であると推測できるが、一応確認してみる。

kpartx で接続された各パーティションは、device mapper によってマップされている。マッピングの様子は、たとえば dmsetup ls --tree で表示できる。

$ sudo dmsetup ls --tree
loop0p3 (253:2)
 └─ (7:0)
loop0p2 (253:1)
 └─ (7:0)
loop0p1 (253:0)
 └─ (7:0)

253:x や 7:0 はデバイスナンバーといい、major:minor の組で表わされる。 dev 下にあるデバイスファイルを ls -l で表示すると、オーナー/グループと日付の間に major, minor の形で表示される。たとえば dmsetup ls --tree の結果の loop0p3 を見ると、loop0p3 (dm-2) 自体は 253:2 で、親は 7:0 であるという。これは下の ls -l の結果と照合できる。

$ ls -l /dev/loop* /dev/mapper/* /dev/dm-*
brw-rw---- 1 root disk 253,   0 Jul 22 17:28 /dev/dm-0
brw-rw---- 1 root disk 253,   1 Jul 22 17:28 /dev/dm-1
brw-rw---- 1 root disk 253,   2 Jul 22 17:28 /dev/dm-2
crw-rw---- 1 root disk  10, 237 Jul 22 06:10 /dev/loop-control
brw-rw---- 1 root disk   7,   0 Jul 22 17:28 /dev/loop0
brw-rw---- 1 root disk   7,   1 Jul 22 07:57 /dev/loop1
brw-rw---- 1 root disk   7,  10 Jul 20 15:34 /dev/loop10
brw-rw---- 1 root disk   7,  11 Jul 21 06:45 /dev/loop11
brw-rw---- 1 root disk   7,   2 Jul 22 07:57 /dev/loop2
brw-rw---- 1 root disk   7,   3 Jul 22 07:56 /dev/loop3
brw-rw---- 1 root disk   7,   4 May 17 16:33 /dev/loop4
brw-rw---- 1 root disk   7,   5 Jun 21 20:28 /dev/loop5
brw-rw---- 1 root disk   7,   6 May  6 09:28 /dev/loop6
brw-rw---- 1 root disk   7,   7 Jun 14 15:28 /dev/loop7
brw-rw---- 1 root disk   7,   8 Jun 29 23:38 /dev/loop8
brw-rw---- 1 root disk   7,   9 Jul 22 07:57 /dev/loop9
crw------- 1 root root  10, 236 Apr 23 14:53 /dev/mapper/control
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p1 -> ../dm-0
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p2 -> ../dm-1
lrwxrwxrwx 1 root root        7 Jul 22 17:28 /dev/mapper/loop0p3 -> ../dm-2

ループデバイスにアタッチされているものは、losetup --list コマンドで確認できる。

$ losetup --list /dev/loop0
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE     DIO LOG-SEC
/dev/loop0         0      0         0  0 /tmp/test.img   0     512

まとめると、今回の例では、 /tmp/test.img/dev/loop0 (7:0) にアタッチされており、その中のパーティション/dev/mapper/loop0p{1..3} にアタッチされている事がわかる。

kpartx -a でアタッチしたパーティションkpartx -d でデタッチできる

$ sudo kpartx -dv /tmp/test.img
del devmap : loop0p3
del devmap : loop0p2
del devmap : loop0p1
loop deleted : /dev/loop0

万が一、kpartx -a を何度もしたりアタッチしたファイルが何だったか忘れたなどの場合には、dmsetup, losetup を使って状況を確認、個別に削除できる。

補足

バイスのmajor番号は、/proc/device に一覧になっている。又、デバイス種ごとに番号は重複してもよいらしい。

$ egrep '^([A-C]| *[7] )' /proc/devices
Character devices:
  7 vcs
Block devices:
  7 loop

参考URL

vmdkをdockerコンテナ内でマウントする

Ubuntu cloud imageの初期状態のディスク内容を見たかったので、vmdkを直接マウントすることにした。ホストにいろいろインストールするのは避けたいので、dockerコンテナ内でやることにした。guestmount コマンドを使う。ホストはUbuntu 18.04。

  1. Ubuntu cloud-imageダウンロード

    $ wget https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.ova
    $ tar tvf focal-server-cloudimg-amd64.ova
    -rw-r--r-- root/root     10760 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.ovf
    -rw-r--r-- root/root       213 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.mf
    -rw-r--r-- root/root 539142656 2021-05-03 17:59 ubuntu-focal-20.04-cloudimg.vmdk
    $ tar xf focal-server-cloudimg-amd64.ova ubuntu-focal-20.04-cloudimg.vmdk
    
  2. コンテナ起動
    fuseが必要になるので、/dev/fuseをマウントすると共に、必要な権限を追加する(cap-add, security-opt) (ref)

    $ docker run -it --rm -v $(pwd)/ubuntu-focal-20.04-cloudimg.vmdk:/tmp/ubuntu-focal-20.04-cloudimg.vmdk \
      --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined \
      ubuntu:20.04 bash
    
  3. パッケージインストール
    ref

    # apt update && \
      DEBIAN_FRONTEND=noninteractive apt install \
        --no-install-recommends -y \
        libguestfs-tools qemu-utils linux-image-generic
    
  4. マウント
    guestmount は少し時間かかる。

    # guestmount -a /tmp/ubuntu-focal-20.04-cloudimg.vmdk -i --ro /mnt
    # ll /mnt
    total 88
    drwxr-xr-x 19 root root  4096 May  3 21:45 ./
    drwxr-xr-x  1 root root  4096 Jun 17 15:32 ../
    lrwxrwxrwx  1 root root     7 May  3 21:39 bin -> usr/bin/
    drwxr-xr-x  4 root root  4096 May  3 21:45 boot/
    drwxr-xr-x  5 root root  4096 May  3 21:42 dev/
    drwxr-xr-x 91 root root  4096 May  3 21:45 etc/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 home/
    lrwxrwxrwx  1 root root     7 May  3 21:39 lib -> usr/lib/
    lrwxrwxrwx  1 root root     9 May  3 21:39 lib32 -> usr/lib32/
    lrwxrwxrwx  1 root root     9 May  3 21:39 lib64 -> usr/lib64/
    lrwxrwxrwx  1 root root    10 May  3 21:39 libx32 -> usr/libx32/
    drwx------  2 root root 16384 May  3 21:45 lost+found/
    drwxr-xr-x  2 root root  4096 May  3 21:39 media/
    drwxr-xr-x  2 root root  4096 May  3 21:39 mnt/
    drwxr-xr-x  2 root root  4096 May  3 21:39 opt/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 proc/
    drwx------  2 root root  4096 May  3 21:42 root/
    drwxr-xr-x  3 root root  4096 May  3 21:42 run/
    lrwxrwxrwx  1 root root     8 May  3 21:39 sbin -> usr/sbin/
    drwxr-xr-x  6 root root  4096 May  3 21:42 snap/
    drwxr-xr-x  2 root root  4096 May  3 21:39 srv/
    drwxr-xr-x  2 root root  4096 Apr 15  2020 sys/
    drwxrwxrwt  2 root root  4096 May  3 21:42 tmp/
    drwxr-xr-x 15 root root  4096 May  3 21:41 usr/
    drwxr-xr-x 13 root root  4096 May  3 21:42 var/
    

見れた!

docker stop や docker-compose downが遅いとき

docker-compose downがなんか遅いなーと思っていたら、entrypoint.sh の書き方がよくなかった。 exec your-command としないと、 SIGTERM シグナルがプロセスに届かず、タイムアウト(10秒)を待って SIGKILL される。

確認

例としてflaskアプリをgunicornで動かしてみる。

ファイルの準備

Dockerfile

from python:3.9-slim
RUN pip install flask gunicorn
COPY app.py /

app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def konnnichiha():
        return "<p>Konnnichiha!</p>"

entrypoint.sh

#!/bin/bash
gunicorn -w 1 -b 0.0.0.0:5000 app:app

entrypoint-exec.sh

#!/bin/bash
exec gunicorn -w 1 -b 0.0.0.0:5000 app:app

テスト

コンテナを実行する。

$ chmod 755 entrypoint*
$ docker build -t flask-test .


$ docker run -d --name test-flask\
   -v $(pwd)/entrypoint.sh:/entrypoint.sh\
   --entrypoint '/entrypoint.sh'\
   test-flask
$ docker run -d --name test-flask-exec\
   -v $(pwd)/entrypoint-exec.sh:/entrypoint.sh\
   --entrypoint '/entrypoint.sh'\
   test-flask

exec版はentrypoint.shがいなくなっている。

$ docker top test-flask
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                935376              935355              0                   23:26               ?                   00:00:00            /bin/bash /entrypoint.sh
root                935446              935376              0                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app
root                935448              935446              0                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app

$ docker top test-flask-exec
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                935610              935591              2                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app
root                935658              935610              1                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app

終了してみると、execなしはタイムアウト待機分の10秒余計にかかる。

$ time docker stop test-flask
test-flask

real    0m10.228s
user    0m0.019s
sys     0m0.019s

$ time docker stop test-flask-exec
test-flask-exec

real    0m0.323s
user    0m0.020s
sys     0m0.020s

execありはシグナルを受け取ってgracefulに終了している。

$ docker logs test-flask
[2021-05-20 03:26:16 +0000] [7] [INFO] Starting gunicorn 20.1.0
[2021-05-20 03:26:16 +0000] [7] [INFO] Listening at: http://0.0.0.0:5000 (7)
[2021-05-20 03:26:16 +0000] [7] [INFO] Using worker: sync
[2021-05-20 03:26:16 +0000] [8] [INFO] Booting worker with pid: 8

$ docker logs test-flask-exec
[2021-05-20 03:26:22 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2021-05-20 03:26:22 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2021-05-20 03:26:22 +0000] [1] [INFO] Using worker: sync
[2021-05-20 03:26:22 +0000] [8] [INFO] Booting worker with pid: 8
[2021-05-20 03:27:02 +0000] [1] [INFO] Handling signal: term
[2021-05-20 03:27:02 +0000] [8] [INFO] Worker exiting (pid: 8)
[2021-05-20 03:27:02 +0000] [1] [INFO] Shutting down: Master

掃除。

$ docker rm test-flask test-flask-exec

しくみ

docker stopやdocker-compose downは、SIGTERM を送ってデフォルトの10秒 (ref https://docs.docker.com/engine/reference/commandline/stop/) たっても終了しない場合は、 SIGKILL が送信される。

PID 1が bash entrypoint.sh だと、シグナルが子プロセスに届かない。 exec commandcommand を実行すると command が PID 1になるので、シグナルを受け取れる。

exec commandcommand がシェルを置き換える。man bashより:

       exec [-cl] [-a name] [command [arguments]]
              If command is specified, it replaces the shell.  No new process  is
              created.  The arguments become the arguments to command.  (略)

なお、シグナルを受け取った後でシェルで他の処理をしたいときは、exec ではなく trap する

ref: https://docs.docker.com/engine/reference/builder/#entrypoint

他にもコンテナの終了が遅いときは、CMD/ENTRYPOINT の書き方とか、そもそも SIGTERM では終了しないプロセスとか、いくつか原因になりそうな事がある。 ref: https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop

M1 MacでKensingtonのマウスでダブルクリックできない問題が直った(完全には直ってはいなかった)

追記(2021-06-19): KensingtonWorks 2.2.10 on macOS 11.4 で完全に直ったっぽい。やったー

KensingtonのSlimBladeを使っているのだけど、M1 Macにつなぐとダブルクリックできない症状があった。どんなに早くダブルクリックしても、少しゆっくりダブルクリックしても、ただのクリックになってしまう。OSのダブルクリックスピードの設定を調整しても変わらない。ドライバである KensingtonWorks をアンインストールすればダブルクリックできるようになるので、KensingtonWorks(とmacOSの相性)の問題と思われる。

ドライバを入れないと2ボタンマウスになってしまい、上の方の2つのボタンが機能しなくなってしまうので、何かしらのドライバを入れたい。SteerMouse を使ってみたけれど、画面をつかんでスクロールする機能(Smart Scrollのgrab scroll)と相性が悪く断念した。SteerMouseにもgrab scrollの機能があるけれど、左右ボタン同時押しの設定と同時には使えなかった。grab scrollをよく使うので、ダブルクリックする時だけ隣に置いてあるトラックパッドを使う事にした。

先日バージョン2.2.8が出てインストールしてみたらダブルクリックできるようになっていた。アンインストーラーを実行しても残るゴミまできれいにしてからインストールした。もしかしたらそのせいかもしれない。

実は2.2.6が出た時にも同じことをして、その時もダブルクリックはできるようになったけど、ドラッグできなくなる問題が発生した。なのでその時は2.2.5に戻した。ちなみにドラッグできなかった時は、マウスボタンを押した際には何もイベントが発行されず、ボタンを上げた時にボタン押下とボタン離すイベントが発行されていた。それではドラッグできないね。

その後出た2.2.7は、面倒だったのでアンインストールせずインストーラーを実行しただけだったんだけど、特に挙動に変化はなかった(ダブルクリックできない)。

そして、2.2.8でボタンに設定を割り当てていったら、2.2.6の時と同じようにドラッグできず、ミドルクリックもできなくなった。もしかしたら2.2.6の時から挙動は変わっていないかもしれない。リリースノートにも2.2.7以降それ系の修正について記載はない。

完全?にアンインストールする手順

なぜドライバが残るのか、、そしてドライバが残っている状態ではKensingtonのマウスが完全に使えなくなったので(カーソルが動かない)、別のメーカーのマウスが手元にないと辛い。キーボードだけでもなんとかはなるけど。

  1. KensingtonWorks Uninstaller.app でアンインストールする
  2. Terminalで systemextensionsctl list を実行すると、com.kensington.tbwDKDriver が残っている。ただ再起動するだけでは消えない。

    % systemextensionsctl list
    1 extension(s)
    --- com.apple.system_extension.driver_extension
    enabled  active  teamID  bundleID (version)  name    [state]
    *    *   293UQF7R4S  com.kensington.tbwDKDriver (2.1.16/2.1.16)  KensingtonWorks DriverKit driver 11 [activated enabled]
    
  3. System Integrity Protection (SIP)を無効にする

    1. 電源を落とす
    2. 電源ボタン長押しで復旧モードで起動する
    3. 復旧モードでTerminalを開き、SIPを無効にする

      $ csrutil disable
      
    4. 再起動

  4. ドライバを消す

    % systemextensionsctl uninstall 293UQF7R4S com.kensington.tbwDKDriver
    Success
    
    % systemextensionsctl list
    1 extension(s)
    --- com.apple.system_extension.driver_extension
    enabled  active  teamID  bundleID (version)  name    [state]
    *    *   293UQF7R4S  com.kensington.tbwDKDriver (2.1.16/2.1.16)  KensingtonWorks DriverKit driver 11 [terminated waiting to uninstall on reboot]
    
  5. kensingtonと名前のつくものを探して消す。設定系の他にも、/Application 下に .Kensington*.app というような名前のアプリケーションが残っていて気持ちわるい。

    % sudo find /Applications /Library ~/Library -iname '*kensington*' 2>/dev/null | egrep -v 'Safari|CrashReporter'
    ...
    % sudo rm -rf ...
    
  6. SIPを有効にする(復旧モードで csrutil enable する)

インストール

きれいに削除できたら、KensingtonWorksを普通にインストールする。インストーラを実行すると再起動を促されるので再起動する。再起動後設定してみたらミドルクリックできなかったが、もう一度再起動したらミドルクリックできるようになった。

ボタン同時押しの設定はしない方がいい

2.2.8 (おそらく2.2.6から) ボタン同時押しの設定をすると、ボタン単押しが機能しなくなる(機能する場合もある)。

自分としては、トラックボールに欲しい機能の優先順位は

  1. ドラッグやgrab scroll
  2. ショートカット(Cmd+WCmd+[) (同時押しに割り当て)
  3. ダブルクリック

の順なので、2.2.5 に戻してダブルクリックを諦めるというのがよい。だけどまた綺麗にインストールしなおすのが面倒なので、とりあえず同時押しには何も割り当てない設定でしばらく過ごす事にする。ちなみにSmart Scrollのgrab scrollにはMiddle Click (button 3)を割り当てている。

f:id:naokton:20210318123115p:plain
今の設定

まとめ

M1 MacにKensingtonWorksを入れるとダブルクリックできなくなる問題は、おそらくバージョン2.2.6(2021-01-20リリース)で直った。直らない時は残ったゴミを完全に消してからインストールするとよいかも。そしてバージョン2.2.8(2021-03-03リリース)では、ボタン同時押しのアクションを設定すると、そのボタンを片方だけ押したときのアクションが効かなくなる事がある(おそらくバージョン 2.2.6の頃から)。

もしかしたら今後のmacOSのバージョンアップで直る可能性もなくはない? 今のバージョンはmacOS Big Sur 11.2.3。

AlfredのWorkflowでWebサイトのDOMを操作したりする

とあるサイトの検索フォームをAlfredから直接実行したかった。検索用のAPIが用意されていないので、ページのformからsubmitしないといけない。

偶然、AppleScriptからSafariのページでJavaScriptを実行できる事を知ったので、以下のような流れで実現できると思い、調査開始。

  1. Alfredで検索文字列をAppleScriptに渡す
  2. AppleScriptで、
    1. Safariに検索ページを表示
    2. do JavaScript でDOM操作(formに入力してsubmit)

今回はじめてAppleScriptに挑戦し独特な文法に困惑していたところ、JavaScript (Java Script for Automation; JXA) でも書ける事がわかって、そちらに避難した。プログラミングが初めてならApple Scriptの方が英語の文法そのままなので分かりやすいのかもしれないけど、、AppleScriptはまた次の機会に。

Alfredで検索文字列をApple Scriptに渡す

Templates > Essentials > Keyword to Script を選び、名前やキーワードを適当に設定する。ScriptのLanguageに /usr/bin/osascript (JS) を選ぶ。

f:id:naokton:20201221114423p:plain f:id:naokton:20201221114901p:plain f:id:naokton:20201221114935p:plain

スクリプト

Workflowの前ステップから渡ってきたキーワードを {query} 経由で受け取って処理を実行する。以下は例としてWikipediaのトップページから検索するようになっている。

function run(){
    let url = 'https://en.wikipedia.org/wiki/Main_Page'
    let word = "{query}"
    let app = Application("Safari")
    app.includeStandardAdditions = true

    app.activate()
    let tab = loadUrlNewTab(app, url)
    waitPageLoad(app, tab)
    searchWord(app, tab, word)
}

function loadUrlNewTab(app, url){
    let curWin = app.windows[0]
    curWin.tabs.push(app.Tab())
    curWin.currentTab = curWin.tabs[-1]
    curWin.currentTab.url = url
    return curWin.currentTab
}

function waitPageLoad(app, tab){
    ret = false
    do {
        ret = app.doJavaScript(
            "(function(){return document.readyState == 'complete'})()",
            { in: tab }
        )
        delay(0.2)
    }while(ret==false)
}

function searchWord(app, tab, word){
    app.doJavaScript(
        "(function(){\
            let form = document.getElementById('searchform');\
            let input = document.getElementById('searchInput');\
            input.value = '" + word + "';\
            form.submit();\
        })()",
        {in: tab}
    )
}

作るときは、Script Editorで実行(Cmd + R)して動きを確認しながら書いていった。

loadUrlNewTab はもう少し簡潔に書けるような気がするけど、とりあえず愚直に。

waitPageLoad は、これがないとページ読み込み完了より先にDOMを読みに行ってしまうので必要。

searchWord 中の getElementById は、ページに合わせて適当に修正する。

使い方

キーワード スペース 検索語句 -> Enter でタブが開いて、検索結果が表示される。 f:id:naokton:20201221122624p:plain

参考

ところでosascriptで使えるAppleScriptJavaScriptも、あまり流行っていないのか情報が少なくて苦労した。流行りの言葉だと、どうやって書くんだろうと思って調べると、大抵StackOverflowなどでやりたい事が既に質問されていたりするのだけど、それがない。ただし大変ありがたいことに、情報をまとめてすばらしい解説ページを作ってくれている方々がいたおかげで、AppleScript / JXA がどんな感じかなんとなく掴む事ができた。ありがたやありがたや。