エンジニアのための最強ポモドーロタイマーをSketchyBarに実装してみた

## はじめに

最近は学習や仕事の経過を、
スマートに、しかも素晴らしく表示したいと思い、

SketchyBarでポモドーロ機能（Pomodoro Timer）を追加してみました！

## なぜ作ったのか？

ポモドーロ管理用にスマホアプリやタイマーアプリを使うことも多いですが、
実際にはスマホに触れた瞬間に通知やメッセージ、SNSを開いてしまい、
集中が途切れてしまうことがよくありました。

エンジニアとして、常にPCの上部（メニューバー）にポモドーロタイマーが常駐しているほうが、
作業フローを壊さず、理想的だと考えました。

この問題意識から、SketchyBar上にポモドーロタイマーを常設することを目指して今回作りました！



## SketchyBarって何？

[SketchyBar](https://github.com/FelixKratz/SketchyBar)は、macOSのメニューバーを自由にカスタマイズできるツールです。
アイコンやラベルを設定したり、スクリプト管理や消費表示も自由。

こんな感じ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184733/3c2fa8e1-36d3-47e0-a435-322dd8e320d7.png)

## 必要なもの

今回の実装には以下が必要です：
- SketchyBar（本体）
- jq（SketchyBarの状態取得に使用）
- **terminal-notifier**（タイマー終了時の通知を飛ばすため）

terminal-notifierはHomebrewでインストールできます。

```bash
brew install terminal-notifier
```

## 作った機能
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184733/09db04ee-e95d-432b-bcc5-0aceddbb9057.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184733/bf183b1e-ac1e-4a5a-9ed0-c71ca615954c.png)

### 1. Pomodoro Timer

- メニューバーの **中央（Center）**に
  - 【トマト（WORK）】
  - 【コーヒー（BREAK）】
  のボタンを表示
- クリックするとタイマーカウントを開始
- macOSの通知（terminal-notifier）も飛ぶ！

### 2. Pomodoro History

- 今日のポモドーロ成果をメニューバーの **左端（Left）**に表示
- 例： `🍅🍅☕️🍅`（とか）
- クリックするとその場でリフレッシュ

---

# 実装コード

ファイル構成はこんな感じ

## 📂 items/pomodoro_work.sh
```bash
#!/bin/bash
sketchybar --add item work_button center \
           --set work_button label="🍅" \
                 drawing=off \
                 click_script="NAME=work_button $PLUGIN_DIR/pomodoro.sh"
```

## 📂 items/pomodoro_break.sh
```bash
#!/bin/bash
sketchybar --add item break_button center \
           --set break_button label="☕️" \
                 drawing=off \
                 click_script="NAME=break_button $PLUGIN_DIR/pomodoro.sh"
```

## 📂 items/pomodoro.sh
```bash
sketchybar --add item pomodoro_launcher right \
           --set pomodoro_launcher \
                 icon="􀖈" \
                 icon.color=$YELLOW \
                 padding_left=0 \
                 padding_right=-5 \
                 click_script="$PLUGIN_DIR/pomodoro_launcher.sh"
```

## 📂 items/pomodoro_history.sh
```bash
#!/bin/bash
source "$HOME/.config/sketchybar/colors.sh"

POMO_HISTORY="$HOME/.config/sketchybar/pomodoro/.pomodoro_history"
today=$(date '+%Y-%m-%d')

output=""

if [ -f "$POMO_HISTORY" ]; then
    while IFS= read -r line || [ -n "$line" ]; do
        if [[ "$line" == "$today"* ]]; then
            if [[ "$line" == *"[WORK]"* ]]; then
                output+="🍅"
            elif [[ "$line" == *"[REST]"* ]]; then
                output+="☕️"
            fi
        fi
    done < "$POMO_HISTORY"
fi

if [ -z "$output" ]; then
    output="No Pomo"
fi

sketchybar --set pomo_history label="$output" label.color="$YELLOW"
```

## 📂 plugins/pomodoro.sh
```bash
#!/bin/bash
source "$HOME/.config/sketchybar/colors.sh"

WORK_MIN=50
BREAK_MIN=10

POMO_DIR="$HOME/.config/sketchybar/pomodoro"
MODE_FILE="$POMO_DIR/pomo_mode"
PID_FILE="$POMO_DIR/pomo_timer.pid"
HISTORY_FILE="$POMO_DIR/.pomodoro_history"

mkdir -p "$POMO_DIR"

BUTTON_WORK="work_button"
BUTTON_BREAK="break_button"

start_timer() {
    local duration="$1"
    local button="$2"
    local label="$3"

    local START_TIME=$(date '+%Y-%m-%d %H:%M:%S')

    (
      TIME_LEFT=$((duration * 60))
      while [ $TIME_LEFT -ge 0 ]; do
          MINUTES=$((TIME_LEFT / 60))
          SECONDS=$((TIME_LEFT % 60))
          TIME_STR=$(printf "%02d:%02d" $MINUTES $SECONDS)
          sketchybar --set "$button" label="$label $TIME_STR" label.color=$YELLOW drawing=on 
          sleep 1
          TIME_LEFT=$((TIME_LEFT - 1))
      done

      local END_TIME=$(date '+%Y-%m-%d %H:%M:%S')  # Save end time now

      # Send macOS notification after timer ends
      if [ "$label" = "🍅" ]; then
          terminal-notifier \
            -title 'Pomodoro'\
			-message 'Work Timer is up! Take a Break 🍅'\
			-appIcon "$HOME/.config/sketchybar/img/pomodoro.png"\
			-sound Funk
          echo "$START_TIME  $END_TIME  [WORK]  $WORK_MIN mins" >> "$HISTORY_FILE"
      elif [ "$label" = "☕️" ]; then
          terminal-notifier \
            -title 'Pomodoro'\
            -message 'Break is over! Get back to work ☕️'\
			-appIcon "$HOME/.config/sketchybar/img/pomodoro.png"\
			-sound Funk
          echo "$START_TIME  $END_TIME  [REST]  $BREAK_MIN mins" >> "$HISTORY_FILE"

      fi
      # Clean up PID file after timer naturally ends
      rm -f "$PID_FILE"

      # Reset UI after timer completion
      sketchybar --set $BUTTON_WORK label="🍅" drawing=on \
                 --set $BUTTON_BREAK label="☕️" drawing=on
      echo "none" > "$MODE_FILE"
    ) &
    echo $! > "$PID_FILE"
}

stop_timer() {
    # Stop and clean up timer process
    if [ -f "$PID_FILE" ]; then
        kill $(cat "$PID_FILE") 2>/dev/null
        rm -f "$PID_FILE"
    fi
    echo "none" > "$MODE_FILE"
    sketchybar --set $BUTTON_WORK label="🍅" drawing=on \
               --set $BUTTON_BREAK label="☕️" drawing=on
}

case "$NAME" in
  "work_button")
    current_mode=$(cat "$MODE_FILE" 2>/dev/null)
    if [ "$current_mode" = "work" ]; then
        stop_timer
    else
        stop_timer
        echo "work" > "$MODE_FILE"
        # 片方オだけオフにすると一度アイコンが真ん中に移動し、start_timerでアイコンの位置が変わりチラつく
        # そのチラつきへの対策で、一度全てオフにすることでチラつきを減らしている。
        sketchybar --set $BUTTON_WORK drawing=off \
                   --set $BUTTON_BREAK drawing=off
        start_timer $WORK_MIN "$BUTTON_WORK" "🍅"
    fi
    ;;
  "break_button")
    current_mode=$(cat "$MODE_FILE" 2>/dev/null)
    if [ "$current_mode" = "break" ]; then
        stop_timer
    else
        stop_timer
        echo "break" > "$MODE_FILE"
        # 片方オだけオフにすると一度アイコンが真ん中に移動し、start_timerでアイコンの位置が変わりチラつく
        # そのチラつきへの対策で、一度全てオフにすることでチラつきを減らしている。
        sketchybar --set $BUTTON_WORK drawing=off \
                   --set $BUTTON_BREAK drawing=off
        start_timer $BREAK_MIN "$BUTTON_BREAK" "☕️"
    fi
    ;;
  *)
    ;;
esac

```

## 📂 plugins/pomodoro_launcher.sh
```bash
#!/bin/sh

pomo_on() {
  sketchybar --set work_button drawing=on \
             --set break_button drawing=on \
             --set pomo_history drawing=on
}

pomo_off() {
  sketchybar --set work_button drawing=off \
             --set break_button drawing=off \
             --set pomo_history drawing=off
}

if [ "$1" = "on" ]; then
  pomo_on
elif [ "$1" = "off" ]; then
  pomo_off
else
  if [ "$(sketchybar --query work_button | jq -r '.geometry.drawing')" = "on" ]; then
    pomo_off
  else
    pomo_on
  fi
fi
```

---

# まとめ

- SketchyBarを使うと、自分の効率をスマートに経過できる
- Pomodoro Timerも、成果表示もすぐ実装できる
- 今までのポモドーロの履歴も`.pomodoro_history`に記録できる

```txt
2025-04-04 11:41:47  2025-04-04 12:31:47  [WORK]  50 mins
2025-04-04 12:41:47  2025-04-04 12:51:47  [REST]  10 mins
2025-04-04 13:11:47  2025-04-04 14:01:47  [WORK]  50 mins
2025-04-04 14:11:47  2025-04-04 15:01:47  [WORK]  50 mins
2025-04-04 15:11:47  2025-04-04 16:01:47  [WORK]  50 mins
2025-04-04 16:11:47  2025-04-04 17:01:47  [WORK]  50 mins
```