macOS 13 Ventura で全ての通知を閉じるスクリプト

macOSの通知で、個々の通知をクリックするまで残しておきたいケースでは、通知のスタイルを「通知パネル」にし、かつ「通知をグループ化」をオフにして利用します。
(私の場合は Slack の自分宛メッセージの通知などでこうしています。)

しかし、時には通知が(場合によっては自動的に)大量に発行され、画面に大量に溜まってしまう、なんていうこともあります。
こんな時に一気に閉じたくなりますがそういったボタンはなく、連打してもゆっくり消えるアニメーションが入るためになかなか閉じられずにストレスになります。。

そんな時のために一気に閉じる AppleScript を用意しておくと良いでしょう。
似たようなスクリプトはあちこちで見つかりますが、内部的な通知センターのUIの作りに依存しているため、OSのバージョンが異なったりすると動作しません。
以下は Ventura で動作を確認しているものです。

なお、各通知に対して下から順番に閉じるアクションを発行してもなぜか一部の通知が閉じずに残ったりする時があるので、何度かループしてやり直すという苦肉の策をとっています。

スクリプトエディタに下記を入力して、アプリケーションとして保存してダブルクリックで利用できます。なお初回実行時に許可を求められるので、保存したアプリケーションを「設定 > プライバシーとセキュリティ」の「オートメーション」と「アクセシビリティ」でアクセスを許可しておく必要があります。


activate application "NotificationCenter"
tell application "System Events"
	tell process "NotificationCenter"
		tell UI element 1 of scroll area 1 of group 1 of window "Notification Center"
			set loop to true
			
			repeat while loop
				set theWindows to every UI element as list
				set n to (count theWindows)
				set closedCount to 0
				repeat with i from n to 1 by -1
					try
						set theWindow to item i of theWindows
						set theActions to actions of theWindow
						repeat with theAction in theActions
							if description of theAction is "Close" then
								tell theWindow
									set closedCount to closedCount + 1
									perform theAction
									exit repeat
								end tell
							end if
						end repeat
					end try
				end repeat
				if closedCount = 0 then
					set loop to false
				end if
			end repeat
		end tell
	end tell
end tell

片手用Bluetoothキーボードを買ったのでカスタマイズツールを作ってみた

Amazonで、2つのノブの付いた片手用Bluetoothキーボードを買ってみました。Bluetooth付きのもの・USB専用のものや、6/9/12キー他、3ノブのものなど、色々種類があるようです。
いろんなブランド名で出品されており怪しげなのですが、念のためAmazon出荷のものを選んでみました。

片手キーボード (Bluetooth接続 3層レイヤー機能付き ノブ2個付き 9キー) 黒
片手キーボード (Bluetooth接続 3層レイヤー機能付き ノブ2個付き 9キー) 白


接続はUSB Type-Cです。Bluetooth対応版だとリチウムイオン電池を内蔵していて、USB接続中に充電されます(USB接続中はUSBキーボードとして振る舞います)。ノブ付きの片手キーボードデバイスは意外と高価なものが多いので、多少安価な部類に思います(それでもBluetooth版だと価格は上がりますが)。質感もアクリルパネルで悪くはないのですが、裏面がネジ露出でそのままだと置いた場所が傷だらけになるので、ゴム足なりビニールテープなりを貼る等の対策が必須です。
Bluetooth版だと3つのレイヤーに違うキーを割り当ててボタンで切り替えて使うこともできます。

しかし最大の欠点は設定ツールで、商品説明のGoogleDriveリンクで配布されている未署名のWindows用アプリしかなく、ちょっと怪しげな感じで作りも最低限のものです。商品名にはLinux/Macなどと書いてあるのですが、設定するにはWindowsが必須になってしまいます*1


そこでLinux/Mac、あるいはWinでもこのアプリを使わずに設定できるよう、どうやって設定されているのか調べてみました。
やり方は簡単(?)で、Windows(念のため環境が壊れてもいいように仮想環境でやりました)上でこのアプリを動かし、WiresharkでUSBキャプチャして設定時の通信内容からプロトコルを調べます。

*1:一旦設定さえしてしまえばツールがなくてもキー割当はデバイスが記憶してくれるのですが

続きを読む

Macのスリープが時々勝手に解除されるのを防ぐ

Macのスリープが時々勝手に解除されている?

前の記事でUSBファンを繋いだのですが、Macのスリープが数時間に1回、勝手に解除されてはまたスリープするという動作をしているらしく、時々ファン音がするのが気になるようになりました。ファン以外でも、USB HDDなどを繋いでいる場合も、勝手にスピンアップし始めて気になるというケースがままあるようです。

システム環境設定 > バッテリーで PowerNap を切ったりしたのですがそれだけでは改善せず。

この原因を調べてみました。なおOSバージョンはBig Surです。

調べ方は、 log show --last 10000 --style syslog | fgrep "Wake reason" で出てくる、以下のような行を見るというものです。調べてみると、さまざまな理由で起動してきている様子。

log show --last 10000 --style syslog | fgrep "Wake reason"
...
2021-07-11 07:51:29.506171+0900  localhost kernel[0]: (AppleACPIPlatform) AppleACPIPlatformPower Wake reason: EC.RTC (Alarm)
...
Wake reason = ARPT (Network)

無線LANを契機にスリープが解除されたときに出ます。
色々と対応方法はあるようですが、

sudo pmset -a tcpkeepalive 0

を実行してやるというのが有効なようです。(実行すると「スリープ中にFind My Macがうまく動かなくなるかもよ」と言ったメッセージが出ますが、どのみち移動してしまったらネットワーク接続できないので位置のレポートはできませんし、気にせず切ってしまいます。)

なお pmset は他にもスリープの動作に関して様々な設定ができるコマンドです。( 参考: man pmset , pmset で Mac の電源制御 - Qiita )

Wake reason = EC.RTC (Alarm)

タイマーで予約された起動です。
システム環境設定のバッテリー > スケジュールで予約されているもののほか、システムが勝手に予約していることもあります。
以下のファイルを見ると、どのプロセスがいつ起動を予約しているかを見ることができます。

/Library/Preferences/SystemConfiguration/com.apple.AutoWake.plist

今回の場合、「スクリーンタイム」が使用状況をレポートするためのエントリが2時間おきくらいに起動するよう設定しているのが見つかりました。
いちいちスリープ解除する必要なんてなさそうに思いますが、他の同一iCloudアカウントのデバイスとレポートを共有できるようなので、そのためなのでしょうか??
特に不要なので、システム環境設定 > スクリーンタイム > オプション で「オフにする」を実行します。
これだけではすぐに予約が解除されなかったため、上記のファイルを一度削除してから、バッテリー > スケジュール で予約を適当に ON→OFF と操作してファイルを再作成させ、予約が空になったことを確認します。

sudo rm /Library/Preferences/SystemConfiguration/com.apple.AutoWake.plist  # 一度削除


私の場合は上記で勝手に起動してくるのは収まって、静かになりました。バッテリーの持ち具合なんかにも影響しそうな気がします。

Macに外付けしたUSBファンの電源を自動でOn/Off

USBファンの電源を自動でOn/Off

夏になってきて暑くなってきました。そしてMacBook Proも負荷がかかるとCPU温度が上がり、サーマルスロットリングがかかって処理が遅くなる現象が起きるようになってきたため、外付けのUSB冷却ファンをつけてみました。


LiANGSTARノートパソコンスタンド 冷却ファンつき

確かにつけているとサーマルスロットリングは回避できるようになったのですが、手動でOnにするのをよく忘れるし、作業後にOffにするのも手間という課題が。
ちょっとした作業時は(多少とはいえ)音も気になるので止まっていて欲しいし、とはいえ熱くなってきたらスロットリングする前に稼働していて欲しい。

そんなわけで、熱やスリープ状態に応じてファンのOn/Offを自動化してみました。

制御に使ったのはUSBハブの、per-port power switchingというポートごとに電源をON/OFFできるという機能です。
これに対応しているものは結構少なく、情報もあまりありません。今のところ確実なのはSUGOI HUBという商品のようで、これはポート1, 2の2つみですが電源の制御が可能です。

システムトークス USB2-HUB4X-BK USB2.0ハブ

制御プログラム

制御に使うプログラムは以下です。Linux等でよく使われていますがlibusb(legacy)でMacでもちゃんと動作します。

hub-ctrl.c

以下のようにインストール。libusb-compatは brew で導入します。

$ brew install libusb-compat
$ curl -O http://www.gniibe.org/oitoite/ac-power-control-by-USB-hub/hub-ctrl.c
$ gcc -Os `pkg-config --cflags --libs libusb` hub-ctrl.c

これで、SUGOI HUBを接続して ./hub-ctrl を実行すると

Hub #0 at 020:064
 INFO: individual power switching.
Hub #1 at 020:052
 INFO: individual power switching.
Hub #2 at 000:024
 INFO: individual power switching.

などと表示されます。このうちどれがSUGOI HUBかは、アップルメニュー > このMacについて > システムレポート の USB を開き、製造元IDが「NEC Corporation」になっているハブを探して、その場所IDを見ると分かります。今回はこんな感じになっており、 場所IDの後ろの部分が 64 と 064 が一致しているので、 Hub #0 が該当するものと分かります。

あとは以下のように、ハブ番号 0 を -h オプションに、電源をON/OFFしたいポートを -P オプションに指定して、-p 1 で on, -p 0 で off にできます。

hub-ctrl -h 0 -P 1 -p 0  # ポート1の電源off
hub-ctrl -h 0 -P 1 -p 1  # ポート1の電源on

本体のファン回転数に連動させる

さて、このポート1にUSBファンを接続して、熱くなってきたら自動でONになるようにしてみます。
MacのCPU温度やファン回転数の情報は、以下のように powermetrics コマンドを実行すると5秒ごとに取得・レポートさせることができます。

$ sudo powermetrics --samplers smc
...
**** SMC sensors ****

CPU Thermal level: 94
GPU Thermal level: 44
IO Thermal level: 44
Fan: 5054.41 rpm
CPU die temperature: 88.95 C (fan)
GPU die temperature: 76.00 C
CPU Plimit: 0.00
GPU Plimit (Int): 0.00 
GPU2 Plimit (Ext1): 0.00 
Number of prochots: 0

なかなか熱々。CPU温度のところに出ている (fan) という表示はファンで温度制御しているよ、というような意味でしょうか。さらに熱くなってサーマルスロットリング中は (power) などと出てきて、CPU Plimitに0以上の数値が表示されます。冷えている時は何も出ません。

これを使って、ファンを制御してみましょう。いろんな方法が考えられますが、本体ファンに連動させてみることにしました。一定以上の回転数で回っていたらonにするようにrubyでプログラムを書いてみました。on/offに幅が設けてあるのは閾値付近で頻繁にon/offが切り替わるのを避けるためです。

#!/usr/bin/ruby

`sudo true`  # パスワード入力を促すため
io = IO.popen("sudo powermetrics --samplers smc", "r")
while (line = io.gets)
  print line
  if line.match(/Fan: ([\d.]+)/)
    fan = $1.to_f
    if fan > 4200  # ON閾値
      system('hub-ctrl -h 0 -P 1 -p 1')
    elsif fan < 3800  # OFF閾値
      system('hub-ctrl -h 0 -P 1 -p 0')
    end
  end
end

これを実行しておくと、そろそろ熱くなってきたぞ、という頃合いに、自動的にUSBファンが周り始め、かなりの場合サーマルスロットリングを避けることができるようになりました(流石に全コアずっと100%で回り続けているような状況では放熱が追いつかないこともありますが、それでも性能劣化の度合いは緩和されているようです)。
音も本体ファンが高速に回っているような状況ならどのみち気にならないし、なかなか便利に使えています。

Macのテキストエディットやメモで入力される制御コード

macOS (Mojaveで確認) のテキストエディットやメモ、メールといった、標準テキスト入力を使ったアプリケーションでは、option や ctrl キーの組み合わせによって特殊な制御文字コードが入力される場合があります。これらは目に見えなかったり単なる改行に見えますが、処理するプログラムによっては思わぬところで文字化けや表示が崩れる原因になることがあるので、注意が必要です。

なお、通常の改行は LF (U+000A) です。

ctrl + enter (メモ帳などでは shift + enterも同様)

Unicode で定義された Line Separatpr (U+2028) が入力されます。
これは箇条書きなどの中で改行するために使われているようです。

ctrl + option + enter

CR (U+000D) が入力されます。単独でも改行されますが、直後に enter を押すと CR+LF となってこれも一つの改行としてレンダリングされます。

ctrl + option + アルファベット

C0 Control Codeに定義された制御コードが入力されます(記号は英語キーボードの位置に準拠するようです)。以下に一覧があります。

C0 and C1 control codes - Wikipedia

参考: Unicodeにおける空白文字

空白文字の一覧は以下にあります。

Whitespace character - Wikipedia

Line Separatpr (U+2028) はHTMLだと < BR > に相当するものとのこと。
Paragraph Separatpr (U+2029) はHTMLだと < P > に相当するそうですが、入力する方法は見つかりませんでした。
(といっても、例えばWebブラウザからこれらのタグのレンダリング結果をコピーしても単なる LF になります。)

プログラミング言語によって、これらが改行扱いされるかは異なるようです。場合によっては、これらが改行として扱われることを想定していないコードにおいて脆弱性を引き起こす原因になることもあるとか。

GitHubのPull Reqest作成ページをコマンド1つで開く

何かコードを書いて、ブランチをGitHubにpushしたあと、そのブランチのプルリクエストをさくっと作りたかったので、1コマンドで現在のブランチからのPull Request作成ページを開くコマンドを作りました。(共有レポジトリを想定)

以下のコードを実行可能権限をつけてパスの通った場所に git-ghpr という名前で置き、

git ghpr

とするだけでOK(なおブラウザを開くopenコマンドはMacの前提です)。ghprは GitHub Pull Request の略のつもりです。

perl で何やらやっているのは、親の最も近いブランチ名からPull Request先のブランチを推定する処理です。常に master 等に出すと決まっている場合は、ORIGIN="master" 等として固定値を入れておくと良いでしょう。

#!/bin/bash                                                                     
set -e
ORIGIN_URL="$(git config --get remote.origin.url)"
case "$ORIGIN_URL" in
  "git@github.com:"*)
    GHPATH="${ORIGIN_URL#*:}"
    GHSRC="https://github.com/${GHPATH%.git}" ;;
  "ssh://git@github.com/"*)
    GHPATH="${ORIGIN_URL#*@}"
    GHPATH="${GHPATH#*/}"
    GHSRC="https://github.com/${GHPATH%.git}" ;;
  "https://github.com/"*)
    GHSRC="$ORIGIN_URL" ;;
  *)
    echo "origin url is not github"
    exit 1
esac
# Pull Request先を推定
ORIGIN="$(git describe --exclude \*/HEAD --all @~ | perl -pe 's/(?:heads|remotes|origin)\/|-\d+-g[\da-f]+$//g')"
GHURL="$GHSRC/compare/$ORIGIN...$(git rev-parse --abbrev-ref @)"
echo "$GHURL"
open "$GHURL"

pryがCtrl-Yでサスペンドしてしまう

Macのターミナル上でRubyのpryを使っている際、Ctrl-Kなどでカットした文字列を貼り付けようとCtrl-Yをタイプすると、サスペンドしてしまうことがあります。ちなみにirbだと発動しません。

$ pry
[1] pry(main)> [^Yをタイプ]
[1]+  Stopped                 pry
$ 

これはDSUSPという機能によるもの。Ctrl-Z(susp)が入力されたら直ちにジョブをサスペンドさせるのに対し、Ctrl-Y(dsusp)はこのキーで入力された制御文字をプロセスがreadしたタイミングでジョブをサスペンドさせるというもので、BSD互換の環境のみで利用可能な機能だそうです。

https://www.gnu.org/software/libc/manual/html_node/Signal-Characters.html


Mac上でsttyを使ってキー割り当てを調べてみると、

$ stty -a 
speed 9600 baud; 60 rows; 80 columns;
...
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
	eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
	min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T;
	stop = ^S; susp = ^Z; time = 0; werase = ^W;

dsusp = ^Y となっているのが分かります。


Liunx環境では発生しないこともあり、よくMacでだけハマって面倒なので、dsuspを無効化します。

無効化するには

stty dsusp undef

を実行すればOK。自動化するなら .bashrc とか .zshrc に

tty >/dev/null && stty dsusp undef

などと書いておけば良いでしょう。