torazaemon2016’s blog

手書き文字認識メモ開発

Autohotkeyで 自分好みの ポモドーロタイマー 作成

仕事効率を上げるために、25分周期で動作するポモドーロタイマーというものがあります。

スマートフォンアプリなど、さまざまなものがありますが、自分好みになるようにAutohotkeyGUIで作ってみました。

keyword: autohotkey, Version2, V2, Pomodoro Timer, ポモドーロタイマー, Obsidian

WindowsでのPomodoro Timer

autohotkeyの機能を調べていた時に、このページから AutoHotKey で、Windows アプリの表示位置を指定して開く – リボン日記

lifeworknext.com

の「SPTimer.exe」は、とても使いやすく重宝していたのですが、OSのUpdateのいくつかののち、左下にタイトルバーの分身が表示されるようなったりして、使わなくなりました。

ObsidianでのPomodoro Timer

メインで使っているソフトである Obsidian において、プラグインで ポモドーロタイマーがあります。 でも、結構場所を取ることや、Obsidian以外のアプリを使う時には、困っていました。 (右ペイン、カレンダー下にポモドーロタイマーを設置)

Autohotkeyでの実装

時間設定だけして、時間経過後にポップアップや音が出るというAutohotkeyでのポモドーロタイマーの実装は、ググればいくつか出ますが、 Autohotkey V1での実装であったりしました。

よく探すと、

Reddit - ソーシャルの新たな中心地

に、Autohotkey V2で作られた出来の良いものがあります。

下図には、このahkのもの、Obsidianのプラグイン、SPTimer.exe、そして自作の4つを起動しているものです。 サイズ感の違いを見てください。

作ったもの

  • 上記 Reddit でのGroggyOtter さんのを参考に Autohotkey V2で Classではなく普通のGUI部品として、わかりやすさ・修正しやすさ優先で作成
  • SPTimerのように、小さくて画面にカウントダウンが出る
  • タイムアップしたらポップアップ表示や通知を行う+音も出る
  • Obsidianのdailyファイルに記録がされる

などの要求要件を考えて作ったものです。

画面右上の最前面に表示

起動すると、画面右上にタイマー表示します。

11/15でのUpdateで、上記の4つのタイマーの比較の図のものよりさらに小さくして、右上隅に配置するようにして、 ウィンドウのX(Closeボタン)を押せなくするように配置しました。

(最大化しているウィンドウをミスして消してしまうのを止める方法として、 Xボタンを無効化するようにその最前面にウィンドウを置いておくというテクニック 閉じるボタン隠すくん のダウンロード・使い方 - フリーソフト100 を知って、タイマーの置き場所にしました)

ツールバー上に常駐

また、画面右下のツールバーに赤の丸い時計アイコンが常駐します。

上記アイコンを右クリックするとメニューがでて、「Show/Hide」「Stop&Hide」「ReStart」させることができます。

  • 「Show/Hide」は画面上でのタイマー表示を表示/隠すだけで、タイマーは動いたままです。再度選択すると、消えた表示も戻り、タイマーは消えていた時間も減った形で表示されます。
  • 「Stop&Hide」は、時間計測を止めて表示も消えます。(停止)
  • 「ReStart」は、25分間を最初からスタートします。
  • 「Reload」は、このahkを読み直します(Reload)。結果として25分間のタイマーがスタートします。
  • 「Exit」は、常駐しているタイマーのAutohotkeyそのものを終了します。(ツールバーから消えます)

移動させるには

タイトルバーも表示しない形になっていますが、タイマーのウィンドウの上で左ボタンを押してドラッグすれば、移動させることができます。

タイマーの上で右ボタンを押す

表示を隠します。

ホットキー「F8」

プログラムの最初の方に、ホットキーとして「F8」を指定しています。

  • hotkey(F8)を押すことでも、Show/Hideや隠すボタンと同じように、画面から消えます。表示を隠すだけでを終了するだけでカウントダウンは続いたままで、時間がくるとポップアップや音が出ます。

  • 再度Hotkey(F8)を押すと、表示が戻ります。

継続のポップアップウィンドウ

時間が00:00になると、音が鳴って、画面中央に、以下のような継続するか終了するかのポップアップが表示されます。

「はい」を押すと、休憩タイマー(5分)がスタートします。

また、Autohotkeyでのポップアップ以外にも、システムトレイの通知のところに、通知を出すことで、時間が来たことを知らせています。

休憩が終わったときのメッセージ。

Windowsで通知で画面を点滅させる設定

Windowsで警告時に画面を点滅させるか設定する方法 | エンジニアの備忘録 などを参考にして、Windowsの設定を行っておくと、タイマーの時間が00:00になり通知が発行されると画面が反転するので、音を消していたとしても気が付くことができます。

ahkスクリプト

autohotkeyそのもののインストールやその説明については、ググるなりしてください。

以下がahkでのプログラムです。 リスト表示の右上のほうに、クリップボードに保存ボタンがありますので、それでコピーして、エディタなどに貼り付けて、拡張子を .ahkとしたファイル名で保存してください。(例えば pomodorotimer.ahk)

autohotkeyがインストールされているなら、保存したファイルをダブルクリックすれば、タイマーが常駐し、カウントダウンがスタートします。

Update

  • 2025-11-17 関数化して、流れも整理しました。
  • 2025-11-19 MsgBoxもAlwaysOnTopしました。右ボタンでメニューをやめて、Trayでのメニューのみにしました。
; 2025-10-31 Pomodoro Timer by torazemon2016
; 2025-11-15 Update (Color表示、SystemTrayアイコン)
; 2025-11-17 関数化で作り直す
; 2025-11-19 AlwaysOnTopに

; 参考 "🍅 Pomodoro 🍅" By GroggyOtter
; https://www.reddit.com/r/AutoHotkey/comments/145kxh1/made_a_deal_with_a_user_on_the_sub_that_id_code/

#Requires AutoHotkey v2.0
#SingleInstance Force

;-------------------------------------------------------------------------
pomodoro_timer(25)  ; 25min 起動時に作成 左ボタンでドラッグで移動、右ボタン押すと終了メニュー出る
;-------------------------------------------------------------------------
pomodoro_timer(min)
{
    ;***ユーザによる設定値****************************
    toggle_hotkey := 'F8'   ; *** Hotキーの定義 ***

    pomodoro := min     ; 25(min)
    rest := 5           ;  5(min)
    interval := 1       ; 更新間隔(sec) (1秒毎に更新) 5秒毎とか10秒毎なら 5,10 (1.3とかしてデバッグも)
    ;pomodoro := 0.2    ; (for Debug) 0.2 = 12sec
    ;rest := 0.1        ; (for Debug) 0.1 =  6sec

    ; すごく小さく 右上に表示 (PCの画面の解像度により -100, w, hとも要修正)
    startx := A_ScreenWidth - 100   ;表示位置X (右)
    starty := 0                     ;表示位置Y (上)
    timer_w := 50   ; 表示の幅
    timer_h := 22   ; 表示の高さ

    ; サイズ12が小さくて大きくするなら、timer_w,timer_hやmake_gui()での値も大きく
    work_font := "s12 cBlack Norm"  ; フォントサイズs(12)、色c(Black)、書式(ノーマル)に設定 
    rest_font := "s12 cRed   Bold"  ; フォントサイズs(12)、色c(Red)、書式(Bold)に設定 

    work_color := "White"   ;Background Color
    rest_color := "Aqua"    ;

    work_end_message := '🍅 休憩時間になりました。`n🍅 休みましょう。`n🍅 タイマーを続けますか?'        ; 改行は `n
    rest_end_message := 'さあ時間です。頑張りましょう!'
    
    work_end_sound := "C:\Windows\Media\Ring06.wav"
    rest_end_sound := "C:\Windows\Media\Ring01.wav"
    
    logsavepath   := A_MyDocuments
    ;logsavepath   := "C:\Users\Username\Obsidian\daily"  ; Obsidian Daily file 

    log_work_start := "💻 Work start" ; 💻 🖥️ ⌨
    log_work_end   := "💻 Work end"
    log_rest_start := "🍵 Rest start" ; 🍵 (紅茶) ☕ (コーヒー)
    log_rest_end   := "🍵 Rest end"   ; ㊡ (まるの中に休の文字): Unicode U+32A1 もありかも
    
    ;***システム変数************************************
    static working := true                  ; 仕事中 true , 休暇中 false
    static timer_left := pomodoro * 60000   ; 分を秒*1000に
    static denominator := pomodoro * 60000  ; 分を秒*1000に
    static last_check := A_TickCount        ; 最後にチェックした時刻

    ;***main部*********************************************************************************
    static title := "AHK Timer"
    static timerGui := Gui('+AlwaysOnTop -SysMenu -ToolWindow -Caption +Border +E0x08000000' , title)   ; -Caption(タイトルバーなどなし)
    make_gui()
    show_gui()

    set_tray_and_hotkey()
    set_left_click()
    set_right_click()

    start_work()    ; プログラム起動と同時に開始
    
    ;***GUI部品作成と表示*******************************************************************
    make_gui()
    {
        timerGui.BackColor := work_color  ; 背景
        timerGui.SetFont(work_font)
        
        ; w40, w45 h5 なども、もとの大きさ(timer_w,timer_h)を変えたら好みでサイズ変更してください
        timerGui.timerText := timerGui.Add("Text", "x4 y1 w40 " , min . ":00")  ; w40 とすごく小さな表示に
        timerGui.timerText.SetFont(work_font)
        timerGui.timerBar := timerGui.Add("Progress", "x1 y+0 w45 h5 cGreen Vbar", 100) ; w45, h5 とすごく小さく。最後の100は100%とわかりやすいので
    }

    show_gui()
    {
        timerGui.Show("x" startx " y" starty " w" timer_w  " h" timer_h)    ; 表示
    }

    ;***Windowsシステム側への設定***************************************************************
    set_tray_and_hotkey()
    {
        ; 2025-11-12 常駐アイコン
        TraySetIcon("%SystemRoot%\System32\mmcndmgr.dll",15)    ; 赤い時計

        ;2025-11-12 システムトレイでのメニュー表示
        A_TrayMenu.Delete
        A_TrayMenu.Add "Show/&Hide", (*) => HideShowWindows()        ; &H キーボードのHをショートカット指定
        A_TrayMenu.Add "&Stop&&Hide", (*) => CloseWindow()       ; &S
        A_TrayMenu.Add "&ReStart", (*) => start_work()           ; &R
        A_TrayMenu.Add                                      ; -----
        A_TrayMenu.Add "Re&load", (*) => Reload()            ; &l
        A_TrayMenu.Add "&Exit", (*) => ExitApp()         ; 
        A_TrayMenu.Default := "Show/&Hide"

        Hotkey("*" toggle_hotkey, HideShowWindows)      ; Hotkeyで表示と隠す
        OnExit(stop_timer)                      ; アプリを終了するときに一応タイマー止めて終了へ
    }
    
    HideShowWindows(*)
    {
        if WinExist('ahk_id ' timerGui.hwnd)
            timerGui.Hide()
        else
            timerGui.Show()
    }

    CloseWindow(*)  ;   タイマーカウントダウンをやめて、表示も消す
    {
        stop_timer()
        timerGui.Hide()
    }

    set_left_click()
    {
        ; 左クリックダウン(0x201)メッセージを検知するコールバック関数を登録
        ; WM_LBUTTONDOWN が発生したとき、OnMessage_LButton関数が呼び出される
        OnMessage(0x201, OnMessage_LButton)
        OnMessage_LButton(wParam, lParam, msg, hwnd)        ; コールバック関数
        {
            ; ウィンドウをドラッグするためのメッセージを送信
            PostMessage(0xA1, 2, , , hwnd) ; WM_NCLBUTTONDOWN, HTCAPTION
        }
    }

    set_right_click()
    {
        ; 右クリックダウン(0x204)メッセージを検知するコールバック関数を登録
        ; WM_RBUTTONDOWN が発生したとき、OnMessage_RButton関数が呼び出される
        OnMessage(0x204, OnMessage_RButton)
        OnMessage_RButton(wParam, lParam, msg, hwnd)        ; コールバック関数
        {
            HideShowWindows()
        }
    }

    ;***タイマー関係************************************************************************
    start_work(*)           ; 起動最初やメニューからのスタートに対応するため用意
    {
        set_work()          ; 仕事モード
        start_timer()       ; ★タイマー開始★
    }

    start_rest(*)           ; 使ってない (TimeUp()で★タイマー停止★との対応がわかるようにするためそっちで直接書いている)
    {
        set_rest()          ; 仕事モード
        start_timer()       ; ★タイマー開始★
    }

    start_timer(*)
    {
        SetTimer(UpdateTime, interval * 1000)   ; タイマーを設定して、1秒毎に処理する関数(UpdateTime)を設定
    }

    stop_timer(*)
    {
        SetTimer(UpdateTime, 0)
    }

    ;***UpdateTime()が真のmain部*******************************
    UpdateTime()    ; タイマーカウントダウン(1秒ごと 呼び出されて、処理を繰り返す)
    {
        timerGui.timerText.Text := Format("{:02d}:{:02d}", Floor(timer_left / 60000), Floor(Mod(timer_left, 60000)) * 0.001)
        timerGui.timerBar.Value := (timer_left / denominator) * 100

        now := A_TickCount
        timer_left := timer_left - (now - last_check)
        ;MsgBox(now " " last_check " " now - last_check " " timer_left) ; for Debug
        
        if(timer_left < 0)
            TimeUp()

        last_check := A_TickCount   ; 次回更新に向けて時刻を記憶しておく
    }

    TimeUp()
    {
        timerGui.timerText.Text := "00:00"  ; 00:00 (timer_leftがマイナスになった場合でも00:01とか変に残るのを防ぐ)
        timerGui.timerBar.Value := 0        ; 0     (timer_leftがマイナスになってもバーが少し残る場合を防ぐ)

        stop_timer()    ; ★★★タイマー停止★★★

        msg := ""
        sound := ""
        log := ""
        if(working == true) ; 仕事中であったので仕事終わり
        {
            msg := work_end_message
            sound := work_end_sound
            log := log_work_end
        }
        else                ; 休憩終わり
        {
            msg := rest_end_message
            sound := rest_end_sound
            log := log_rest_end
        }
        user_notify(msg,sound,log)  ; ログ書いてから音鳴らして、(MsgBoxの)ポップアップ出すほうが良い

        ; Yes(Y)、No(N)    0x4 + Icon Question 0x20 + AlwaysOnTop(スタイル WS_EX_TOPMOST) 0x40000
        if (MsgBox(msg, title, 0x40024) = 'Yes')
        {
            if(working == true) ; 仕事中であったので仕事終わり
            {
                set_rest()      ;休暇モードへ start_rest()でもいいが★★★対応を見せるため
                start_timer()   ; ★★★タイマー開始★★★
            }
            else                ; 休憩終わり
            {
                set_work()      ; 仕事モードへ    start_work()でもいいが★★★対応を見せるため
                start_timer()   ; ★★★タイマー開始★★★
            }
        }
        else
        {
            CloseWindow()   ; タイマー表示終了
        }
    }

    set_work()          ; 仕事モード
    {
        working := true     ; 仕事モードに入る

        timerGui.BackColor := work_color  ; 背景
        timerGui.timerText.SetFont(work_font)
        timerGui.timerBar.Opt("+cGreen")

        timer_left := pomodoro * 60000
        denominator := pomodoro * 60000 
        last_check := A_TickCount

        write_log(log_work_start)   
    }

    set_rest()          ; 休憩モード
    {
        working := false    ; 休憩モードに

        timerGui.BackColor := rest_color  ; 背景
        timerGui.timerText.SetFont(rest_font)
        timerGui.timerBar.Opt("+cFuchsia")

        timer_left := rest * 60000
        ;denominator := rest * 60000
        denominator := pomodoro * 60000 ; Barの長さを25分のときと同じ量とする(バーの物理長さを同じ時間感覚に)
        last_check := A_TickCount

        write_log(log_rest_start)   
    }

    ;***ユーザへのメッセージ部************************************************
    ; 音を消している場合に対応 TrayTipで Windowsで警告時に画面を点滅させるか設定する方法
    ; https://engrmemo.jp/win/surface-notice-blink/
    user_notify(msg,sound,log)
    {
        TrayTip(msg,title,16)   ; SoundPlayで音出すので、TrayTipでは16:無音 (上のURL設定で->画面点滅)
        write_log(log)      ; ログ書いてから音鳴らして、(MsgBoxの)ポップアップ出すほうが良い
        SoundPlay(sound)
    }

    write_log(str)  ; 🍅 Unicode Character “🍅” (U+1F345)
    {           
        If WinExist("ahk_exe Obsidian.exe")
            WinActivate()

        Date := FormatTime(, "yyyy-MM-dd")
        Now := FormatTime(, "HH:mm")    ; Now := FormatTime(, "HH:mm:ss")
        FileAppend("`n" "- " Now " 🍅 Pomodoro " str "`n", logsavepath "\" Date ".md", "UTF-8")   
    }

}
;* END *

ahkファイルの最初の方に、個人の環境等で設定の変更が必要な変数などが固めてあります。

pomodoro_timer(min)
{
    ;***ユーザによる設定値****************************
    toggle_hotkey := 'F8'   ; *** Hotキーの定義 ***

    pomodoro := min     ; 25(min)
    rest := 5           ;  5(min)
    interval := 1       ; 更新間隔(sec) (1秒毎に更新) 5秒毎とか10秒毎なら 5,10 (1.3とかしてデバッグも)
    ;pomodoro := 0.2    ; (for Debug) 0.2 = 12sec
    ;rest := 0.1        ; (for Debug) 0.1 =  6sec

    ; すごく小さく 右上に表示 (PCの画面の解像度により -100, w, hとも要修正)
    startx := A_ScreenWidth - 100   ;表示位置X (右)
    starty := 0                     ;表示位置Y (上)
    timer_w := 50   ; 表示の幅
    timer_h := 22   ; 表示の高さ

    ; サイズ12が小さくて大きくするなら、timer_w,timer_hやmake_gui()での値も大きく
    work_font := "s12 cBlack Norm"  ; フォントサイズs(12)、色c(Black)、書式(ノーマル)に設定 
    rest_font := "s12 cRed   Bold"  ; フォントサイズs(12)、色c(Red)、書式(Bold)に設定 

    work_color := "White"   ;Background Color
    rest_color := "Aqua"    ;

    work_end_message := '🍅 休憩時間になりました。`n🍅 休みましょう。`n🍅 タイマーを続けますか?'        ; 改行は `n
    rest_end_message := 'さあ時間です。頑張りましょう!'
    
    work_end_sound := "C:\Windows\Media\Ring06.wav"
    rest_end_sound := "C:\Windows\Media\Ring01.wav"
    
    logsavepath   := A_MyDocuments
    ;logsavepath   := "C:\Users\Username\Obsidian\daily"  ; Obsidian Daily file 

    log_work_start := "💻 Work start" ; 💻 🖥️ ⌨
    log_work_end   := "💻 Work end"
    log_rest_start := "🍵 Rest start" ; 🍵 (紅茶) ☕ (コーヒー)
    log_rest_end   := "🍵 Rest end"   ; ㊡ (まるの中に休の文字): Unicode U+32A1 もありかも

起動キー(Hotley)、起動したときの表示位置、タイマー時間、表示メッセージ、再生する音、ログを記録するファイルのフォルダ、記録文字列などを変更してください。

ログファイルについて

仕事開始、仕事終了、休憩開始、休憩終了の記録を残すことができます。

ahkファイルの最初の設定部分に、保存するフォルダの指定があります。

logsavepath   := A_MyDocuments
;logsavepath   := "C:\Users\Username\Obsidian\daily"

初期は、マイドキュメントの下に、YYYY-mm-dd.md の名前のMarkdown形式で保存するようになっています。 Obsidianを想定しているので、Obsidianのdailyフォルダの場所などを指定してください。

ファイルには、以下のように記録されていきます。

- 20:16 🍅 Pomodoro 💻 Work start

- 20:41 🍅 Pomodoro 💻 Work end
  
- 20:41 🍅 Pomodoro 🍵 Rest start

- 20:46 🍅 Pomodoro 🍵 Rest end

おわりに

音とかメッセージなど、自分好みに変更して、やる気が出るように修正して、効率を上げてください。