Carpe Diem

備忘録

screenshot-to-yoink でツールチップがスクリーンショットに含まれない

背景

以前以下の対応を行いましたが、

christina04.hatenablog.com

ツールチップがスクショできないという問題に遭遇しました。 期待する挙動は以下なのですが、

実際はこのようにツールチップが消えてしまいます。

そのため別の方法で実現する必要がありました。

環境

  • macOS 15.6.1

原因

以前の方法で使っているワークフローである

GitHub - Irvel/screenshot-to-yoink: Alfred workflow for capturing screenshots into the Yoink app

は内部でscreencaptureコマンドを使っていますが、これが起動する時間に数ms〜数十ms程度のラグがあり、その時間にツールチップが消えてしまうのが原因でした。

対応方法

全体スクリーンショットからトリミング

Cmd+Shift+3で画面スクリーンショットであれば保持されるので、それを最初に撮ってからその画像からCmd+Shift+4でスクリーンショットし直してリサイズする、というやり方です。

以前の仕組みのままできます。

ただし若干二度手間感があります。

ワークフローを使わず標準のスクリーンショットをベースにYoinkへ送る

ワークフローに頼らず、自前のスクリプトで解決するやり方です。
手順は以下です。

  1. 標準スクリーンショットのショートカットに戻す
  2. スクショの保存先を指定
  3. Yoinkへ送るスクリプトの用意
  4. LaunchAgentを用意して監視

順に詳細を説明します。

1. 標準スクリーンショットのショートカットに戻す

2. スクショの保存先を指定

スクショの保存先を指定します。

$ mkdir -p "$HOME/Screenshots"
$ defaults write com.apple.screencapture location "$HOME/Screenshots"
# フローティングサムネイルを無効化
$ defaults write com.apple.screencapture "show-thumbnail" -bool false
$ killall SystemUIServer

保存先は iCloud 同期配下だとOSや同期で再作成(birth変化)が起きることがあります。
なので~/Screenshots のようなローカル直下を推奨します。

3. Yoinkへ送るスクリプトの用意

次のようなYoinkへ送るためのスクリプトを用意します。

mkdir -p ~/.local/bin
cat > ~/.local/bin/screenshots2yoink.sh <<'SH'
#!/usr/bin/env bash
# minimal screenshots -> Yoink (once-only)
set -euo pipefail

DIR="$HOME/Screenshots"
OPEN_CMD=(/usr/bin/open -g -a Yoink)
types=(jpg jpeg png tif tiff heic)

wait_done() {
  local f="$1" last=-1 cur=0
  for _ in {1..20}; do
    cur=$(/usr/bin/stat -f %z "$f" 2>/dev/null || echo 0)
    [[ "$cur" -gt 0 && "$cur" -eq "$last" ]] && return 0
    last="$cur"; /bin/sleep 0.05
  done
}

yoinked_name() {  # name.ext -> name (yoinked).ext
  local f="$1" d b stem ext
  d="$(/usr/bin/dirname "$f")"; b="$(/usr/bin/basename "$f")"
  [[ "$b" == *" (yoinked)."* || "$b" == *" (yoinked)" ]] && { echo "$f"; return; }
  if [[ "$b" == *.* ]]; then stem="${b%.*}"; ext="${b##*.}"; echo "$d/${stem} (yoinked).${ext}"
  else echo "$d/${b} (yoinked)"; fi
}

shopt -s nullglob nocaseglob
for ext in "${types[@]}"; do
  for f in "$DIR"/*.${ext}; do
    [[ -e "$f" ]] || continue
    base="$(/usr/bin/basename "$f")"
    [[ "$base" == *" (yoinked)."* || "$base" == *" (yoinked)" ]] && continue
    wait_done "$f"
    new="$(yoinked_name "$f")"
    /bin/mv "$f" "$new" || continue
    /usr/bin/qlmanage -t -s 512 -o /tmp "$new" >/dev/null 2>&1 || true
    "${OPEN_CMD[@]}" -- "$new" || true
  done
done
SH
chmod +x ~/.local/bin/screenshots2yoink.sh

ポイント

  • wait_doneはサムネイルの作成を待つため
    • これがないとたまにサムネイル作成前にYoinkに送られ、サムネが見えない状態になることがある
  • yoinked_nameは一度Yoinkに送ったスクリーンショットを対象外とするためのリネーム

4. LaunchAgentを用意して監視

次のようにLaunchAgentを作成し、スクリーンショットが保存されるフォルダを監視します。

PLIST=~/Library/LaunchAgents/com.$USER.screenshots2yoink.plist
cat > "$PLIST" <<PL
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.$USER.screenshots2yoink</string>

  <!-- 監視対象(絶対パス) -->
  <key>WatchPaths</key>
  <array>
    <string>$HOME/Screenshots</string>
  </array>

  <!-- 実行コマンド -->
  <key>ProgramArguments</key>
  <array>
    <string>/bin/zsh</string>
    <string>-lc</string>
    <string>~/.local/bin/screenshots2yoink.sh</string>
  </array>

  <!-- ログ(必要なら有効化)
  <key>StandardOutPath</key><string>$HOME/Library/Logs/screenshots2yoink.out</string>
  <key>StandardErrorPath</key><string>$HOME/Library/Logs/screenshots2yoink.err</string>
  -->

  <key>RunAtLoad</key><true/>
  <key>StartOnMount</key><true/>
</dict>
</plist>
PL

有効化します。

$ launchctl unload "$PLIST" 2>/dev/null || true
$ launchctl load -w "$PLIST"

以上で準備はOKです。

動作確認

下図のようにツールチップのようなすぐ消えてしまうUXであっても、きちんとスクリーンショットできるようになりました。

まとめ

screenshot-to-yoink でツールチップがスクリーンショットに含まれない場合の対応として2通りの方法を紹介しました。

参考