Introduction 
Welcome to the documentation for Tomat 2.13.0, a customizable, lightweight Pomodoro timer for Linux and macOS. It works from the command line and integrates with status bars.
To get started, have a look at Getting Started for a quick guide to install and setup Tomat.
Contents
This documentation includes the following sections:
- Getting Started: Quick start guide to install and use Tomat.
- Configuration: Overview of configuration options.
- Integration: Guides to integrate Tomat with various status bars and notification systems.
- Troubleshooting: Solutions to common issues.
- Configuration Reference: In-depth reference for all configuration options.
- CLI Reference: Comprehensive reference for all CLI commands and options.
Additional Resources
Support
- GitHub Issues: Report bugs or request features
- GitHub Discussions: Ask questions or share ideas
- Pull Requests: Contribute code improvements
Contributing
Contributions are welcome! Please see the Contributing Guide for details on how to get involved.
Getting Started
Tomat (“tomato” in Swedish 🇸🇪) is a Pomodoro timer for Linux and macOS. It consists of a background service (daemon) that manages the timer state and a command-line client to control the timer and query its status.
If you are new to the Pomodoro technique, it is a time management method that breaks work into intervals (typically 25 minutes) separated by short breaks. This approach helps improve focus and productivity by encouraging regular breaks to rest and recharge.
graph TD
A[25 min work] -->|Sessions 1-4| B[5 min break]
B --> A
A -->|Session 5| C[15 min long break]
C --> A
Installation
The easiest way to install Tomat on Linux or macOS is with the install script:
curl --proto '=https' --tlsv1.2 -sSf https://jolars.github.io/tomat/install | sh
You can also use Cargo or a package manager:
cargo install tomat
# Arch Linux (AUR)
paru -S tomat-bin
See the installation guide for more options including DEB/RPM packages, NixOS, and building from source.
Basic Usage
The next step is to start the daemon:
tomat daemon start
After that, you can start a Pomodoro session by calling
tomat start
You can check the current status of the timer with:
tomat status
Which by default returns a JSON object suitable for Waybar integration:
{
"text": "🍅 25:00 ⏸",
"tooltip": "Work (2/4) - 25.0min (Paused)",
"class": "work-paused",
"percentage": 0.0
}
See the CLI Reference for a full list of commands and options.
Background Service Setup
Most users will want Tomat to start automatically on login. This command installs a systemd user service on Linux or a LaunchAgent on macOS:
tomat daemon install
On macOS, installation loads and starts the service immediately. On Linux, enable and start it with:
systemctl --user enable tomat.service --now
Status Bar Integration
The last step is to integrate Tomat with your status bar. To for instance set up Waybar, simply add the following module to your Waybar configuration:
{
"modules-right": ["custom/tomat"],
"custom/tomat": {
"exec": "tomat status",
"interval": 1,
"return-type": "json",
"format": "{text}",
"tooltip": true,
"on-click": "tomat toggle",
"on-click-right": "tomat skip"
}
}
The documentation features integration guides for several popular status bars, including Waybar, Polybar, and others.
Configuration
Tomat can be configured via $XDG_CONFIG_HOME/tomat/config.toml on Linux or
~/Library/Application Support/tomat/config.toml on macOS. Here is a basic
example to get you started:
[timer]
work = 25.0
break = 5.0
long_break = 15.0
sessions = 4
auto_advance = "none"
See the Configuration Guide for a detailed explanation of all available configuration options
Architecture
Tomat uses a client–server architecture consisting of a daemon that runs in the
background and a command-line client that sends commands to the daemon via a
Unix socket. On Linux, this is normally $XDG_RUNTIME_DIR/tomat.sock; on macOS,
it is stored beneath the per-user temporary directory.
graph TD
A[Client] -->|Commands| B(Server)
B -->|Status| A
B -->|Notifications| C[Notification System]
B -->|Sound| D[Sound System]
The purpose of this is to avoid having the timer be tied to the lifetime of the calling process, which allows several different clients to interact with the same timer instance and also prevents the need to save and restore states on status bar restarts.
The daemon is also responsible for sending desktop notification, sound alerts, and calling hooks.
Installation Instructions
This section provides instructions on how to install Tomat, a customizable Pomodoro timer.
Install Script
The installer selects the appropriate release binary for Linux or macOS and
installs it to ~/.local/bin. If you prefer, download and inspect the script
before running it.
curl --proto '=https' --tlsv1.2 -sSf https://jolars.github.io/tomat/install | sh
Set TOMAT_INSTALL_DIR to change the destination, TOMAT_TAG to pin a
version, TOMAT_LIBC (gnu or musl) to override Linux libc detection, and
TOMAT_VERIFY_CHECKSUM=false to skip checksum verification.
Pre-built Binaries
You can also download a pre-built binary directly from the releases page. Binaries are available for multiple architectures and distributions:
- Generic Linux (x86_64, aarch64): glibc and static musl
.tar.gzarchives - macOS (Intel and Apple Silicon):
.tar.gzarchives - Debian/Ubuntu:
.debpackages - Fedora/RHEL/openSUSE:
.rpmpackages
The musl archives run without a system C library dependency. Because ALSA is not available in the static build, they support desktop notifications but not audio alerts.
Generic Binary
# Download and install (x86_64)
curl -L https://github.com/jolars/tomat/releases/latest/download/tomat-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv tomat /usr/local/bin/
# Or for ARM64
curl -L https://github.com/jolars/tomat/releases/latest/download/tomat-aarch64-unknown-linux-gnu.tar.gz | tar xz
sudo mv tomat /usr/local/bin/
macOS
# Apple Silicon
curl -L https://github.com/jolars/tomat/releases/latest/download/tomat-aarch64-apple-darwin.tar.gz | tar xz
sudo mv tomat /usr/local/bin/
# Intel
curl -L https://github.com/jolars/tomat/releases/latest/download/tomat-x86_64-apple-darwin.tar.gz | tar xz
sudo mv tomat /usr/local/bin/
Debian/Ubuntu Package
curl -LO https://github.com/jolars/tomat/releases/latest/download/tomat_amd64.deb
sudo dpkg -i tomat_amd64.deb
The DEB package includes the systemd service and shell completions.
RPM Package (Fedora/RHEL/openSUSE)
curl -LO https://github.com/jolars/tomat/releases/latest/download/tomat-x86_64.rpm
sudo rpm -i tomat-x86_64.rpm
The RPM package includes the systemd service and shell completions.
Package Managers
Arch Linux (AUR)
Tomat is available in the AUR as tomat-bin:
# Using your favorite AUR helper
paru -S tomat-bin
# or
yay -S tomat-bin
NixOS
If you are using NixOS, Tomat is available in the official packages:
{
environment.systemPackages = [
pkgs.tomat
];
}
You still need to set up the systemd service for automatic startup.
Home Manager
But if you’re using home manager, you’re in luck! Tomat is supported as a module:
{
services.tomat = {
enable = true;
settings = {
timer = {
work = 25;
break = 5;
};
};
};
}
Install from Crates.io
You can also install Tomat via Cargo from crates.io. If you don’t have Rust and Cargo installed, follow the instructions at https://www.rust-lang.org/tools/install first to set up your Rust environment.
cargo install tomat
Building from Source
Anoter way to install Tomat is to build it from source. First, clone the repository:
git clone https://github.com/jolars/tomat.git
cd tomat
Prerequisites
On Linux systems, audio notifications require ALSA development libraries. macOS uses Core Audio and needs no additional system package.
# Ubuntu/Debian
sudo apt-get install libasound2-dev
# Fedora/RHEL
sudo dnf install alsa-lib-devel
# Arch Linux
sudo pacman -S alsa-lib
Build and Install
Then, build and install Tomat using Cargo:
cargo install --path .
Note
Audio will be automatically disabled if ALSA is not available. The timer will still work normally, but with desktop notifications only.
Background Service Setup
Tomat can install the native user service—systemd on Linux or a LaunchAgent on macOS—so that the daemon starts automatically on login:
tomat daemon install
On macOS, this command loads and starts the LaunchAgent immediately. You can inspect it with:
launchctl print gui/$UID/io.github.jolars.tomat
On Linux, enable and start the installed systemd service with:
systemctl --user enable tomat.service --now
Alternative Manual Systemd Setup
If you prefer to set up the systemd service manually, you can copy the service file from the examples directory:
# Manual systemd setup (if you prefer)
mkdir -p ~/.config/systemd/user
curl -o ~/.config/systemd/user/tomat.service https://raw.githubusercontent.com/jolars/tomat/main/assets/tomat.service
systemctl --user daemon-reload
systemctl --user enable tomat.service --now
Configuration Guide
Tomat supports comprehensive configuration via a TOML file. On Linux, the path
is $XDG_CONFIG_HOME/tomat/config.toml (typically
~/.config/tomat/config.toml); on macOS, it is
~/Library/Application Support/tomat/config.toml. The TOMAT_CONFIG
environment variable can override either location.
The configuration file is organized into five main sections, which are documented in detail in the configuration reference.
Example
Here’s a minimal example of the [timer] section, showing the default values:
[timer]
work = 25.0
break = 5.0
long_break = 15.0
sessions = 4
Tip
Ready-to-use configuration files are available in the
examples/directory. Copy the complete example config to get started quickly.mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/tomat" cp examples/config.toml "${XDG_CONFIG_HOME:-$HOME/.config}/tomat/config.toml" # Then edit the config file as needed
Configuration Priority
Settings are applied in the following order, with later sources overriding earlier ones:
-
Built-in defaults
Standard Pomodoro timings (25min work, 5min break, 15min long break, 4 sessions).
-
Config file
Values from
$XDG_CONFIG_HOME/tomat/config.toml. -
CLI arguments
Flags passed to
tomat startcommand.
This means you can set your preferred defaults in the config file and still override them on a per-session basis using command-line flags.
Environment Variables
TOMAT_CONFIG- Path to the configuration file, overriding the platform default.
TOMAT_RUNTIME_DIR- Directory holding the daemon’s socket, PID, and state files. It takes
precedence over
XDG_RUNTIME_DIR, which in turn takes precedence over the platform default (/run/user/$UIDon Linux, a Tomat directory beneath the per-user$TMPDIRon macOS). The directory must be owned by you and must not be writable by anyone else; Tomat creates it with mode700when it does not exist yet, and refuses to modify a directory it did not create.
The daemon and its clients each resolve the runtime directory on their own, so
they have to agree. If you export TOMAT_RUNTIME_DIR from your shell profile,
run tomat daemon install from a shell where it is set: the generated service
file records the value so the service-managed daemon uses the same socket. See
Service Management.
Complete Configuration Example
Here’s a complete configuration file with all available options:
# $XDG_CONFIG_HOME/tomat/config.toml
# Timer durations and behavior
[timer]
work = 25.0 # Work session duration in minutes
break = 5.0 # Break duration in minutes
long_break = 15.0 # Long break duration in minutes
sessions = 4 # Number of work sessions before long break
auto_advance = "none" # Auto-advance mode: "none", "all", "to-break", "to-work"
# Sound notifications
[sound]
enabled = true # Enable sound notifications
system_beep = false # Use system beep instead of sound files
use_embedded = true # Use embedded sound files
volume = 0.5 # Volume level (0.0 to 1.0)
# Optional: Custom sound files (will override embedded sounds)
# work_to_break = "/path/to/custom/work-to-break.wav"
# break_to_work = "/path/to/custom/break-to-work.wav"
# work_to_long_break = "/path/to/custom/work-to-long-break.wav"
# Desktop notifications
[notification]
enabled = true # Enable desktop notifications
icon = "auto" # Icon mode: "auto" (embedded), "theme" (system), or "/path/to/icon.png"
timeout = 5000 # Notification timeout in milliseconds
urgency = "normal" # Urgency level: "low", "normal", "critical"
# Optional: Custom notification messages
# work_message = "Break time! Take a short rest ☕"
# break_message = "Back to work! Let's focus 🍅"
# long_break_message = "Long break time! Take a well-deserved rest 🏖️"
# Display formatting
[display]
text_format = "{icon} {time} {state}" # Text display template
Integration Guide
Tomat can be integrated with various status bars, notification systems, and service managers to enhance your productivity experience. This guide provides step-by-step instructions for setting up these integrations.
Status Bar Integration Guide
Tomat can be integrated with various status bars to display timer information in real-time. In this section, we provide configuration examples for popular status bars.
Waybar
Tomat is designed specifically for Waybar integration with rich JSON output and CSS styling support.
Basic Configuration
Add this to your waybar config (~/.config/waybar/config):
{
"modules-right": ["custom/tomat"],
"custom/tomat": {
"exec": "tomat status",
"interval": 1,
"return-type": "json",
"format": "{text}",
"tooltip": true,
"on-click": "tomat toggle",
"on-click-right": "tomat skip"
}
}
Styling
Add CSS styling (~/.config/waybar/style.css):
#custom-tomat {
padding: 0 10px;
margin: 0 5px;
border-radius: 5px;
}
#custom-tomat.work {
background-color: #ff6b6b;
color: #ffffff;
}
#custom-tomat.work-paused {
background-color: #ff9999;
color: #ffffff;
}
#custom-tomat.break {
background-color: #4ecdc4;
color: #ffffff;
}
#custom-tomat.break-paused {
background-color: #7dd3db;
color: #ffffff;
}
#custom-tomat.long-break {
background-color: #45b7d1;
color: #ffffff;
}
#custom-tomat.long-break-paused {
background-color: #74c0db;
color: #ffffff;
}
JSON Output Format
Tomat provides waybar-optimized JSON output:
{
"text": "🍅 24:30 ▶",
"tooltip": "Work (1/4) - 25.0min",
"class": "work",
"percentage": 2.0
}
Fields:
- text: Display text with icon and status symbols
- tooltip: Detailed information for hover
- class: CSS class for styling
- percentage: Progress percentage (0-100)
CSS Classes:
work/work-paused- Work session running/pausedbreak/break-paused- Break session running/pausedlong-break/long-break-paused- Long break running/paused
Visual Indicators:
- Icons: 🍅 (work), ☕ (break), 🏖️ (long break)
- State: ▶ (running), ⏸ (paused)
Polybar
Polybar
To integrate Tomat with Polybar, you can create a custom module that executes
the tomat status command to display the current timer status. Below is an
example configuration snippet that you can add to your Polybar configuration
file (usually located at ~/.config/polybar/config).
[module/tomat]
type = custom/script
exec = tomat status --output plain
interval = 1
click-left = tomat toggle
click-right = tomat skip
format-prefix = " "
format-foreground = #ffffff
i3status-rust
Tomat provides native support for i3status-rust with a dedicated output format:
[[block]]
block = "custom"
command = "tomat status --output i3status-rs"
interval = 1
json = true
[[block.click]]
button = "left"
cmd = "tomat toggle"
[[block.click]]
button = "right"
cmd = "tomat skip"
The i3status-rs format provides:
text- Display text with timer and status icons
short_text- Same as text (for abbreviated display)
state- Timer state mapping (Critical=work, Good=break, Info=paused)
i3status
For i3status compatible bars like i3bar, you can use a helper script. First, add this to your i3status config:
order += "read_file tomat"
read_file tomat {
path = "/tmp/tomat-status"
format = "%content"
}
Helper script:
#!/bin/bash
while true; do
tomat status --output plain > /tmp/tomat-status
sleep 1
done
i3blocks
i3blocks works perfectly with Tomat’s plain text output mode.
Simple Integration
[tomat]
command=tomat status --output plain
interval=1
With Click Support
[tomat]
command=tomat status --output plain
interval=1
signal=10
Add click handling with environment variables:
#!/bin/bash
# ~/.config/i3blocks/scripts/tomat-click
case $BLOCK_BUTTON in
1) tomat toggle ;; # Left click: toggle
3) tomat skip ;; # Right click: skip
esac
pkill -RTMIN+10 i3blocks # Refresh the block
Then set as the command: command=~/.config/i3blocks/scripts/tomat-click
Notification Systems
Tomat uses the notify-rust crate to send desktop notifications, which
supports native macOS notifications and a variety of notification daemons on
Linux. On macOS, allow notifications for Tomat when the operating system asks.
Below are instructions for integrating Tomat with popular Linux notification
systems.
Mako (Wayland)
Tomat works perfectly with mako out of the box using the embedded icon system:
# Default configuration works automatically
# Icon will be cached to ~/.cache/tomat/icon.png
For custom mako styling, add to ~/.config/mako/config:
[app-name="Tomat"]
background-color=#2d3748
text-color=#ffffff
border-color=#4a5568
default-timeout=5000
Dunst (X11)
For dunst notification daemon:
# Works with default configuration
# Uses embedded icon or theme icon as fallback
Custom dunst rules (~/.config/dunst/dunstrc):
[tomat]
appname = "Tomat"
background = "#2d3748"
foreground = "#ffffff"
timeout = 5000
Custom Notification Icons
To use a custom notification icon:
# ~/.config/tomat/config.toml
[notification]
icon = "/path/to/your/custom-icon.png"
Service Management
Tomat uses a daemon (server) to manage its timers in the background. Most users will want Tomat to start automatically when they log in. The built-in installer uses systemd user services on Linux and LaunchAgents on macOS.
Automatic Setup
Install and enable the service for the current platform:
tomat daemon install
Remove it again with:
tomat daemon uninstall
Pass --force to overwrite an existing service file without being asked; this
is required when the command runs without a terminal, such as from a
provisioning script.
The generated service file records TOMAT_RUNTIME_DIR if it is set in the
environment at install time, because the service manager starts the daemon with
a minimal environment and would otherwise put the daemon and its clients on
different sockets. Re-run tomat daemon install --force after changing the
variable.
Systemd
Setup
Install the service file and enable auto-start:
# Copy service file
curl -o ~/.config/systemd/user/tomat.service https://raw.githubusercontent.com/jolars/tomat/main/assets/tomat.service
# Enable auto-start
systemctl --user enable tomat.service
systemctl --user start tomat.service
If you prefer to create the service file manually
(~/.config/systemd/user/tomat.service):
[Unit]
Description=Tomat Pomodoro Timer Daemon
After=graphical-session.target
[Service]
Type=simple
ExecStart=%h/.cargo/bin/tomat daemon run
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Management
The service is managed using standard systemctl commands:
# Check status
systemctl --user status tomat.service
# View logs
journalctl --user -u tomat.service -f
# Restart service
systemctl --user restart tomat.service
# Disable auto-start
systemctl --user disable tomat.service
The systemd user manager is torn down at logout, so the service will not start at boot or before you log in graphically unless the account allows lingering:
loginctl enable-linger $USER
The unit uses Restart=on-failure, so tomat daemon stop (or
systemctl --user stop tomat.service) keeps the daemon stopped, while a crash
still brings it back.
launchd
On macOS, tomat daemon install writes
~/Library/LaunchAgents/io.github.jolars.tomat.plist, loads it into the current
GUI session, and starts the daemon. The agent uses KeepAlive with
SuccessfulExit set to false, so it restarts Tomat after a crash but leaves it
alone after tomat daemon stop.
Daemon errors are appended to ~/Library/Logs/tomat.log. Nothing rotates that
file, so truncate it yourself if a repeatedly failing daemon lets it grow.
Use launchctl to inspect or restart it:
# Check status and recent exit information
launchctl print gui/$UID/io.github.jolars.tomat
# Restart the daemon managed by launchd
launchctl kickstart -k gui/$UID/io.github.jolars.tomat
# Inspect daemon errors
tail -f ~/Library/Logs/tomat.log
Use tomat daemon uninstall to unload the LaunchAgent and remove its plist.
Troubleshooting Guide
This guide covers common issues and their solutions when using tomat.
- Installation Issues
- Configuration Problems
- Status Bar Issues
- Hook Issues
- Notification Issues
- Audio Problems
- Service Management Issues
- Daemon Issues
Getting Help
If you’re still experiencing issues, check existing issues on GitHub and the discussion board for similar problems and solutions.
If you cannot find a solution, please open a new issue or discussion.
Installation Issues
ALSA Library Missing on Linux
Problem
Audio notifications don’t work, or you get ALSA-related errors.
Solution
# Ubuntu/Debian
sudo apt-get install libasound2-dev
# Fedora/RHEL
sudo dnf install alsa-lib-devel
# Arch Linux
sudo pacman -S alsa-lib
Note: Audio will be automatically disabled if ALSA is not available. The timer will still work normally with desktop notifications only.
Cargo Install Fails
Problem
cargo install tomat fails with compilation errors.
Solution
Try the following steps:
- Update Rust:
rustup update stable - Check toolchain: Ensure you’re using Rust stable
- Install ALSA on Linux: See the ALSA section above. macOS uses Core Audio and does not need an extra audio package.
- Clear cache:
cargo cleanif building from source
Configuration Issues
Config File Not Loading
Problem
Configuration file is not being loaded or recognized.
Solution
Check that your config file is at the correct location:
$XDG_CONFIG_HOME/tomat/config.toml (typically ~/.config/tomat/config.toml).
Verify the path exists:
ls -l $XDG_CONFIG_HOME/tomat/config.toml
# Or: ls -l ~/.config/tomat/config.toml
Syntax Errors
Problem
Configuration file has TOML syntax errors.
Solution
TOML is whitespace-sensitive and requires proper quoting. Use a TOML validator or check that brackets, quotes, and equal signs are balanced.
Test your config:
# Using tomat to validate
tomat status
# Will show errors if config is invalid
Permission Denied
Problem
Cannot read configuration file due to permission errors.
Solution
Ensure the config file is readable by your user:
chmod 644 $XDG_CONFIG_HOME/tomat/config.toml
# Or: chmod 644 ~/.config/tomat/config.toml
Status Bar Integration Troubleshooting
General Issues
No Status Showing
Problem
The status bar does not display any tomat status.
Solution
-
Ensure daemon is running:
tomat daemon status # Should show "Daemon is running" -
Check that the PATH includes the directory where tomat is installed. See https://github.com/jolars/tomat/issues/21 for instance.
Waybar Integration Issues
Status Not Updating
Problem
Waybar shows outdated or no tomat status.
Solution
-
Check daemon status:
tomat daemon status # Should show "Daemon is running" -
Test status command directly:
tomat status # Should return JSON with current status -
Check waybar configuration:
{ "custom/tomat": { "exec": "tomat status", "interval": 1, // Update every second "return-type": "json" // Required for proper parsing } } -
Restart waybar:
killall waybar && waybar &
JSON Parsing Errors
Problem
Waybar shows parsing errors for tomat output.
Solution
-
Verify JSON output:
tomat status | jq . # Should show properly formatted JSON -
Check for daemon errors:
tomat daemon stop tomat daemon run # Run in foreground to see errors -
Check the disconnected status:
When the daemon is not running,
tomat statusreturns valid JSON with thedisconnectedclass. It does not write a connection error to the Waybar log.{ "text": "", "tooltip": "Tomat daemon is not running", "class": "disconnected", "percentage": 0.0 }
Styling Not Applied
Problem
Waybar shows tomat status but CSS styling doesn’t work.
Solution
-
Check CSS class names:
tomat status | jq .class # Should return: "work", "work-paused", "break", etc. -
Verify CSS selectors in waybar style:
#custom-tomat.work { background-color: #ff6b6b; } #custom-tomat.work-paused { background-color: #ff9999; } -
Test with simple styling:
#custom-tomat { background-color: red; /* Should always apply */ }
Hook Issues
Hook Not Executing
Problem
Configured hook command does not run when expected.
Solution
Verify the command exists and is executable:
which playerctl
ls -l /home/user/scripts/my-script.sh
Use absolute paths in your hook configuration:
[hooks.on_work_start]
cmd = "/usr/bin/playerctl"
Permission Denied
Problem
Hook fails with permission denied error.
Solution
Check that the command has execute permissions:
chmod +x /home/user/scripts/my-script.sh
Hook Timing Out
Problem
Hook command is killed before completing.
Solution
If your command takes longer than the default 5 seconds, increase the timeout:
[hooks.on_work_start]
cmd = "/home/user/scripts/slow-script.sh"
timeout = 10
Check if the command hangs when run manually. Ensure it doesn’t require interactive input.
No Error Output
Problem
Hook fails silently with no error messages.
Solution
Enable output capture to see error messages:
[hooks.on_work_start]
cmd = "/usr/bin/my-command"
capture_output = true
Working Directory Errors
Problem
Hook fails because working directory is invalid or inaccessible.
Solution
Verify the working directory exists and has correct permissions:
ls -ld /path/to/working/directory
Use absolute paths for both commands and working directories:
[hooks.on_work_start]
cmd = "/usr/bin/my-command"
cwd = "/home/user/project"
Hook Not Triggered
Problem
Hook is configured but never fires on the expected event.
Solution
Check that the hook name is spelled correctly (e.g., on_work_start, not
on_start_work). After changing your config, restart the daemon:
tomat daemon stop
tomat daemon start
Environment Variables Not Working
Problem
Hook command cannot access TOMAT_* environment variables.
Solution
Test your hook manually with environment variables:
TOMAT_EVENT=work_start TOMAT_PHASE=work /path/to/command
Verify your script or command is reading the correct variable names.
Notification Issues
No Desktop Notifications
Problem
Timer works but no notifications appear.
Solution
-
Check notification daemon:
# For mako (Wayland) ps aux | grep mako # For dunst (X11) ps aux | grep dunst # Test notifications notify-send "Test" "Notification test"On macOS, check System Settings → Notifications and allow notifications for the application macOS associates with Tomat.
-
Check tomat notification config:
[notification] enabled = true # Must be true -
Try different icon modes:
[notification] icon = "theme" # Try system theme icon
Icon Not Showing in Notifications
Problem
Notifications appear but without icons.
Solution
Different solutions for different notification daemons:
-
Mako: Use auto mode (default)
[notification] icon = "auto" # Uses cached embedded icon -
Dunst: Try theme mode
[notification] icon = "theme" # Uses system "timer" icon -
Custom icon:
[notification] icon = "/usr/share/icons/hicolor/48x48/apps/timer.png" -
Check icon cache location:
ls -la $XDG_CACHE_HOME/tomat/icon.png # Should exist if using "auto" mode
Audio Issues
No Sound Notifications
Problem
Timer works but no audio plays during transitions.
Solution
-
Check audio configuration:
# $XDG_CONFIG_HOME/tomat/config.toml [sound] mode = "embedded" # Must not be "none" -
Test system audio:
# Test if ALSA works aplay /usr/share/sounds/alsa/Front_Left.wav # Or try speaker-test speaker-test -t sine -f 1000 -l 1 -
Check volume levels:
- System volume (alsamixer, pavucontrol)
- Tomat volume in config (0.0-1.0)
-
Try different audio modes:
[sound] mode = "system-beep" # Use system beep instead
Wrong Audio Device
Problem
Audio plays on wrong device or not audible.
Solution
-
Check default ALSA device:
aplay -l # List audio devices cat ~/.asoundrc # Check ALSA configuration -
Use system beep as fallback:
[sound] mode = "system-beep"
Custom Sound Files Not Working
Problem
Custom sound files don’t play.
Solution
-
Check file paths and existence:
ls -la /path/to/your/sound.ogg -
Verify file format (must be supported audio format):
file /path/to/your/sound.ogg # Common formats: WAV, OGG, FLAC, MP3 -
Test file with system player:
aplay /path/to/your/sound.wav -
Use absolute paths:
[sound] mode = "embedded" work_to_break = "/home/user/sounds/work-done.ogg" # Absolute path
Service Management Troubleshooting
systemd Service Fails to Start
Problem
systemctl --user start tomat.service fails.
Solution
-
Check service file location:
ls -la $XDG_CONFIG_HOME/systemd/user/tomat.service -
Verify service file content:
cat $XDG_CONFIG_HOME/systemd/user/tomat.service # Should contain: ExecStart=%h/.cargo/bin/tomat daemon run -
Check service status:
systemctl --user status tomat.service journalctl --user -u tomat.service -f -
Reload systemd configuration:
systemctl --user daemon-reload systemctl --user restart tomat.service
Service Starts But Daemon Not Accessible
Problem
Service is running but tomat status fails.
Solution
-
Check if daemon is actually running:
ps aux | grep tomat -
Verify socket creation:
ls -la $XDG_RUNTIME_DIR/tomat.sock -
Check service logs:
journalctl --user -u tomat.service --no-pager
LaunchAgent Fails to Start
On macOS, inspect the loaded service and its error log:
launchctl print gui/$UID/io.github.jolars.tomat
tail -n 100 ~/Library/Logs/tomat.log
If the plist points to a binary that was moved or removed, reinstall it from the binary’s new location:
tomat daemon install --force
The macOS socket is beneath the per-user temporary directory:
ls -la "${TMPDIR}tomat-$(id -u)/tomat.sock"
Enable Debug Output
Problem
Need to see detailed daemon output for debugging.
Solution
Run daemon in foreground to see all output:
tomat daemon stop
tomat daemon run # Shows all debug output
Daemon Issues
Daemon Won’t Start
Problem
tomat daemon start fails or daemon exits immediately.
Solution
-
Check if already running:
tomat daemon status # If running, stop first: tomat daemon stop -
Check socket permissions:
runtime_dir="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}/tomat-$(id -u)}" ls -la "$runtime_dir"/tomat* # Should show socket and PID files with your user ownership -
Run daemon in foreground to see errors:
tomat daemon run # This shows all output directly -
Check runtime directory:
echo "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}/tomat-$(id -u)}"Linux normally supplies
$XDG_RUNTIME_DIR. macOS uses a Tomat directory beneath the per-user$TMPDIR. SetTOMAT_RUNTIME_DIRto override both. Tomat creates the directory with mode700when it is missing, but it never changes the permissions of a directory it did not create: if the daemon reports that the runtime directory is writable by other users, tighten it yourself withchmod go-wor pointTOMAT_RUNTIME_DIRsomewhere private.
Daemon Stops Unexpectedly
Problem
Daemon process dies or becomes unresponsive.
Solution
-
Check system logs:
journalctl --user -u tomat.service -f # If using systemd tail -f ~/Library/Logs/tomat.log # If using launchd on macOS -
Check for multiple instances:
ps aux | grep tomat # Kill any duplicate processes -
Clean up stale files:
runtime_dir="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}/tomat-$(id -u)}" rm -f "$runtime_dir/tomat.sock" "$runtime_dir/tomat.pid" tomat daemon start
Permission Errors
Problem
“Permission denied” when accessing socket or PID files.
Solution
-
Check file ownership:
runtime_dir="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}/tomat-$(id -u)}" ls -la "$runtime_dir"/tomat* # Files should be owned by your user -
Ensure runtime directory exists:
runtime_dir="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}/tomat-$(id -u)}" mkdir -p "$runtime_dir" chmod 700 "$runtime_dir" -
Restart daemon:
tomat daemon stop tomat daemon start
Command-Line Help for tomat
Tomat is a cross-platform Pomodoro timer with a daemon-based architecture, designed for command-line use and seamless integration with status bars. It uses a Unix socket for client-server communication, ensuring your timer state persists across client restarts and system suspend/resume.
Usage: tomat [OPTIONS] <COMMAND>
Options
-q,--quiet- Suppress routine output while preserving command results and errors
tomat daemon
Manage the tomat daemon, which runs in the background and maintains timer state. The daemon must be running for timer commands to work.
Usage: tomat daemon <COMMAND>
tomat daemon start
Start the tomat daemon as a background process. The daemon manages timer state and handles client requests via a Unix socket in the platform’s per-user runtime directory. Only one daemon instance can run at a time.
Usage: tomat daemon start
tomat daemon stop
Stop the running tomat daemon gracefully. This will terminate any active timer session. The daemon will clean up its socket and PID files.
Usage: tomat daemon stop
tomat daemon status
Check if the tomat daemon is currently running and report its process ID.
Usage: tomat daemon status
tomat daemon install
Install and enable the native tomat user service: a systemd user service on Linux or a LaunchAgent on macOS. This allows the daemon to start automatically on login and restart if it crashes.
Usage: tomat daemon install [OPTIONS]
Options
-f,--force- Force overwrite existing service file without prompting
tomat daemon uninstall
Stop and remove the native tomat user service. This removes the systemd service on Linux or LaunchAgent on macOS and disables automatic startup.
Usage: tomat daemon uninstall
tomat start
Start a new Pomodoro timer session with the specified durations. If no options are provided, uses defaults from the platform config file or built-in defaults (25min work, 5min break, 15min long break, 4 sessions). Custom durations only apply to the current session.
Usage: tomat start [OPTIONS]
Options
-w,--work <WORK>-
Duration of work sessions in minutes. If not specified, uses the value from the platform config file or the built-in default of 25 minutes.
-b,--break <BREAK_TIME>-
Duration of short breaks in minutes. If not specified, uses the value from the platform config file or the built-in default of 5 minutes.
-l,--long-break <LONG_BREAK>-
Duration of long breaks in minutes. Long breaks occur after completing the configured number of work sessions. If not specified, uses the value from the platform config file or the built-in default of 15 minutes.
-s,--sessions <SESSIONS>-
Number of work/break cycles before taking a long break. If not specified, uses the value from the platform config file or the built-in default of 4 sessions.
-a,--auto-advance <AUTO_ADVANCE>-
Control automatic phase transitions: all - Auto-advance through all phases none - Never auto-advance (pause at transitions) to-break - Auto-advance from work to break only to-work - Auto-advance from break to work only
If not specified, uses the value from the platform config file or the built-in default of ‘none’.
--sound-mode <SOUND_MODE>-
Control sound notifications: embedded - Use built-in audio files (default) system-beep - Use system beep (terminal bell) none - No sound notifications
If not specified, uses the value from the platform config file or the built-in default of ‘embedded’.
--volume <VOLUME>-
Set the audio volume for sound notifications, from 0.0 (silent) to 1.0 (maximum). Values outside this range will be clamped. If not specified, uses the value from the platform config file or the built-in default of 0.5.
tomat stop
Stop the current Pomodoro session and return the timer to idle state.
Usage: tomat stop
tomat status
Display the current timer status. Output format can be customized for different status bars (waybar, i3status-rs) or plain text. Text appearance can be customized using format templates.
Usage: tomat status [OPTIONS]
Options
-o,--output <OUTPUT>-
Output format: waybar, i3status-rs, or plain
Default value:
waybarPossible values:
waybar,i3status-rs,plain -f,--format <FORMAT>-
Customize the text display using placeholders: {icon} - Phase icon {time} - Remaining time (MM:SS) {state} - Play/pause symbol {phase} - Phase name {session} - Session progress
tomat watch
Continuously watch and output timer status updates. This maintains a single connection to the daemon and updates at the specified interval. Automatically exits when the daemon stops. More efficient than polling with ‘status’ command.
Usage: tomat watch [OPTIONS]
Options
-o,--output <OUTPUT>-
Output format: waybar, i3status-rs, or plain
Default value:
waybarPossible values:
waybar,i3status-rs,plain -f,--format <FORMAT>-
Custom text format (e.g. “{icon} {time}”)
-i,--interval <INTERVAL>-
Update interval in seconds
Default value:
0.25
tomat skip
Skip the current phase and immediately transition to the next phase (work → break → work → … → long break). The timer will start in the new phase if auto-advance is enabled, otherwise it will be paused.
Usage: tomat skip
tomat pause
Pause the currently running timer. Use ‘resume’ or ‘toggle’ to continue.
Usage: tomat pause
tomat resume
Resume a paused timer from where it left off.
Usage: tomat resume
tomat toggle
Toggle the timer state: pause if running, resume if paused. This is useful for waybar click handlers.
Usage: tomat toggle
Configuration Guide
Tomat supports comprehensive configuration via a TOML file. On Linux, the path
is $XDG_CONFIG_HOME/tomat/config.toml (typically
~/.config/tomat/config.toml); on macOS, it is
~/Library/Application Support/tomat/config.toml. Set TOMAT_CONFIG to
override the platform default.
The configuration file is organized into five main sections:
[timer]- timer durations and behavior
[sound]- for audio notification settings
[notification]- for desktop notification settings
[display]for output [display]- for output formatting
[hooks]- for custom commands triggered by timer events
Timer Settings
The [timer] section controls timer durations and phase transition behavior.
[timer]
work = 25.0
break = 5.0
long_break = 15.0
sessions = 4
auto_advance = "none"
Options
work-
Duration of work phase in minutes (default:
25.0) break-
Duration of short break phase in minutes (default:
5.0) long_break-
Duration of long break phase in minutes (default:
15.0) sessions-
Number of work sessions before a long break (default:
4) auto_advance-
Controls how the timer transitions between phases.
"none"(default)- Pause after every phase transition (requires manual resume)
"all"- Automatically continue through all phases without pausing
"to-break"- Auto-advance only from work to break/long-break
"to-work"- Auto-advance only from break/long-break to work
Note
Boolean values
trueandfalseare deprecated and will be automatically converted to"all"and"none"respectively.
Examples
To set a 30-minute work session, 10-minute break, 20-minute long break, with 3 sessions before a long break, and auto-advance from work to break, use the following configuration.
[timer]
work = 30.0
break = 10.0
long_break = 20.0
sessions = 3
auto_advance = "to-break"
Sound Settings
The [sound] section controls audio notifications when transitioning between
work/break phases. By default, tomat plays high-quality WAV files built into the
application. On Linux, this requires ALSA (Advanced Linux Sound Architecture);
on macOS, Tomat uses Core Audio without additional dependencies. If the audio
system is unavailable, Tomat falls back to the system beep or disables audio.
[sound]
mode = "embedded"
volume = 0.5
Options
mode-
Sound notification mode. Controls how phase transitions are announced.
"embedded"(default)- Use built-in audio files
"system-beep"- Use system beep (terminal bell)
"none"- No sound notifications
volume-
Audio volume level for embedded and custom sounds (0.0-1.0). Default:
0.5 work_to_break-
Path to custom sound file for work→break transitions. Overrides embedded sound. Optional.
break_to_work-
Path to custom sound file for break→work transitions. Overrides embedded sound. Optional.
work_to_long_break-
Path to custom sound file for work→long break transitions. Overrides embedded sound. Optional.
"enabled"-
Enable sound notifications.
Warning
Deprecated option. Use
mode = "embedded"instead. "system_beep"-
Use system beep.
Warning
Deprecated option. Use
mode = "system-beep"instead. "use_embedded"-
Use embedded sounds.
Warning
Deprecated option. Use
mode = "embedded"instead.
Examples
To use your own sound files, keep mode = "embedded" and specify paths to your
audio files. Custom sounds override the built-in ones:
[sound]
mode = "embedded"
work_to_break = "/home/user/sounds/work-done.ogg"
break_to_work = "/home/user/sounds/break-over.ogg"
work_to_long_break = "/home/user/sounds/long-break.ogg"
volume = 0.7
To disable all audio:
[sound]
mode = "none"
To use system beep only:
[sound]
mode = "system-beep"
Notification Settings
The [notification] section controls desktop notifications shown during phase
transitions. Notifications are displayed by the native notification system on
macOS or by a notification daemon such as dunst or mako on Linux.
[notification]
enabled = true
icon = "auto"
timeout = 5000
urgency = "normal"
Options
enabled-
Whether to show desktop notifications.
icon-
Controls notification icon.
"auto"(default)- Uses embedded icon, cached to
$XDG_CACHE_HOME/tomat/icon.png(mako-compatible) "theme"- Uses system theme icon (
"timer") <path>- Specify a file path (e.g.,
"/home/user/my-icon.png")
timeout-
Default:
5000 urgency-
Controls notification priority level.
"normal"(default)- Standard notification priority
"low"- Minimal interruption, typically shown without sound
"critical"- High priority, may bypass do-not-disturb settings
Note
macOS controls notification icons and display duration. The current macOS backend also ignores
urgency; these options continue to apply on Linux.
work_message-
The message shown when transitioning from work to break.
- Default
"Break time! Take a short rest ☕"
break_message-
The message sown when transitioning from break to work.
- Default
"Back to work! Let's focus 🍅"
long_break_message-
The message shown when transitioning to a long break.
- Default
"Long break time! Take a well-deserved rest 🏖️"
Examples
To disable notifications:
[notification]
enabled = false
To use a custom icon:
[notification]
icon = "/path/to/custom/icon.png"
To customize notification messages:
[notification]
work_message = "Break time! Step away from the screen."
break_message = "Back to work! Let's get things done."
long_break_message = "Long break! You've earned it."
Display Settings
The [display] section controls how timer status is formatted in the output.
[display]
text_format = "{icon} {time} {state}"
# text_format_idle = "" # Optional: format for idle state (defaults to text_format)
Options
text_format-
Template string for formatting timer display when timer is active (work/break phases). The field supports the following placeholders enclosed in curly braces
{}:{icon}- Phase icon (
🍅work/idle,☕break,🏖️long break) {time}https://github.com/jolars/dprint-plugin-badness/actions/runs/33058798128- Remaining time (e.g.,
25:00). When idle, shows upcoming work duration. {state}- Play/pause/stop symbol (
▶running,⏸paused,⏹idle) {phase}- Phase name (
Work,Break,Long Break,Idle) {session}- Session progress (e.g.,
1/4; empty for breaks and idle) - Default
"{icon} {time} {state}"- Example
"{phase}: {time} {state}"
text_format_idle-
Template string for formatting timer display when timer is idle (stopped). Uses the same placeholders as
text_format.If not specified, defaults to the value of
text_format. Set to empty string""to hide the widget when timer is idle.- Default
-
Same as
text_format(omit this option to use default) - Examples
-
# Hide widget when idle text_format_idle = "" # Show custom idle message text_format_idle = "⏹ Ready to start" # Use default (same as text_format) - simply omit the option: # text_format_idle = ... ``` ## Examples Minimal format (time only): ```toml [display] text_format = "{time}"
With session counter:
[display]
text_format = "[{session}] {icon} {time}"
Verbose format:
[display]
text_format = "{phase}: {time} {state}"
Show only icon when in idle:
[display]
text_format = "{icon} {time} {state}"
text_format_idle = "{icon}"
Icon Customization
The [display.icons] subsection allows you to customize the emoji/text symbols
used for different phases and states.
[display.icons]
work = "🍅" # Work/Idle phase icon (default: 🍅)
break = "☕" # Break phase icon (default: ☕)
long_break = "🏖️" # Long break phase icon (default: 🏖️)
play = "▶" # Playing state symbol (default: ▶)
pause = "⏸" # Paused state symbol (default: ⏸)
stop = "⏹" # Stopped/Idle state symbol (default: ⏹)
Options
work-
Icon shown during work sessions and idle phase (when using
{icon}placeholder).Default:
"🍅" break-
Icon shown during short break sessions.
Default:
"☕" long_break-
Icon shown during long break sessions.
Default:
"🏖️" play-
Symbol shown when timer is running (when using
{state}placeholder).Default:
"▶" pause-
Symbol shown when timer is paused.
Default:
"⏸" stop-
Symbol shown when timer is in idle state.
Default:
"⏹"
Examples
ASCII-only symbols:
[display.icons]
work = "W"
break = "B"
long_break = "L"
play = ">"
pause = "||"
stop = "X"
Alternative emoji set:
[display.icons]
work = "💼"
break = "🎮"
long_break = "🌴"
Minimal text symbols:
[display.icons]
work = "[W]"
break = "[B]"
long_break = "[LB]"
play = "▸"
pause = "❙❙"
stop = "■"
Hooks
Hooks allow you to execute custom commands when timer events occur. This enables integration with external tools, automation workflows, and custom notifications.
[hooks.on_work_start]
cmd = "playerctl"
args = ["pause"]
[hooks.on_break_start]
cmd = "playerctl"
args = ["play"]
Available Hooks
on_work_start- A work session starts
on_break_start- A break starts
on_long_break_start- A long break starts
on_pause- Timer is paused
on_resume- Timer is resumed
on_stop- Timer is stopped manually
on_complete- A phase completes naturally (auto or manual transition)
on_skip- User skips to next phase
Options
cmd-
Command to execute
- Example
"/usr/bin/notify-send"
args-
Command arguments (list of strings)
timeout-
Timeout in seconds (0 = no timeout)
- Default
5seconds
cwd-
Working directory
capture_output-
Capture stdout/stderr for debugging
- Default
false
Environment Variables
All hooks receive these environment variables:
TOMAT_EVENT- Event name (e.g.,
"work_start","pause") TOMAT_PHASE- Current phase (
"work","break","long_break") TOMAT_REMAINING_SECONDS- Seconds remaining in current phase
TOMAT_SESSION_COUNT- Current session number (1, 2, 3, …)
TOMAT_AUTO_ADVANCE- Auto-advance mode (
"none","all","to-break","to-work")
These can be used in scripts to customize behavior based on the timer state.
Examples
Pause music during work sessions:
[hooks.on_work_start]
cmd = "playerctl"
args = ["pause"]
timeout = 2
[hooks.on_break_start]
cmd = "playerctl"
args = ["play"]
Adjust screen brightness:
[hooks.on_pause]
cmd = "brightnessctl"
args = ["set", "30%"]
[hooks.on_resume]
cmd = "brightnessctl"
args = ["set", "100%"]
Log completed sessions:
[hooks.on_complete]
cmd = "sh"
args = ["-c", "echo \"$(date): $TOMAT_PHASE completed\" >> ~/tomat.log"]
capture_output = true
Custom notifications:
[hooks.on_work_start]
cmd = "notify-send"
args = ["🍅 Focus Time", "Let's get things done!", "-u", "critical"]
timeout = 3
[hooks.on_long_break_start]
cmd = "notify-send"
args = ["🏖️ Long Break", "You've earned it! Take 15 minutes."]
Execute custom scripts:
[hooks.on_work_start]
cmd = "/home/user/scripts/start-focus-mode.sh"
cwd = "/home/user/scripts"
timeout = 10
[hooks.on_stop]
cmd = "/home/user/scripts/end-focus-mode.sh"
cwd = "/home/user/scripts"
Security Considerations
Hooks execute with the daemon’s user privileges. Follow these security best practices:
- Only configure trusted commands: Hooks can execute any command your user can run.
- Use absolute paths: Prefer
/usr/bin/notify-sendovernotify-sendto avoid PATH manipulation. - Never run the daemon as root: Always use the
--usersystemd service. - Verify config file ownership: Ensure your config file is owned by your user.
- No shell injection: Commands are executed directly without a shell, preventing injection attacks.
- Timeout protection: Hooks are automatically killed after the timeout to prevent hanging processes.
Note: If an attacker controls your $XDG_CONFIG_HOME directory, they already
have code execution via shell rc files. Hooks don’t introduce new attack
vectors beyond standard Unix permissions.
Contributing
We welcome contributions to tomat! Here’s a guide to help you get started.
Quick Contribution Checklist
Before submitting any changes:
- Formatting:
cargo fmt -- --check(MUST exit with code 0) - Linting:
cargo clippy --all-targets --all-features -- -D warnings(MUST exit with code 0, no warnings allowed) - Compilation:
cargo check(MUST pass) - Tests:
cargo test(all integration tests must pass)
Getting Started
-
Fork the repository.
-
Clone your fork:
git clone <your-fork-url> -
Install prerequisites:
# Rust toolchain (specified in rust-toolchain.toml) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # ALSA development libraries (for audio) sudo apt-get install libasound2-dev # Ubuntu/Debian sudo dnf install alsa-lib-devel # Fedora/RHEL sudo pacman -S alsa-lib # Arch Linux -
You might also want to install task for easier task management. The following
taskcommands assume you have it installed.
Development Workflow
Essential Build Commands
Always run commands from the repository root.
# Quick development check (recommended)
task dev
# Individual commands
cargo check # Check compilation without building
cargo test # Run all tests (19 integration tests)
cargo clippy --all-targets --all-features -- -D warnings # Lint
cargo fmt # Format code
cargo fmt -- --check # Check formatting
# Build commands
cargo build # Development build
cargo build --release # Release build
Testing Your Changes
# Build and start daemon for testing
cargo build
./target/debug/tomat daemon start
# Test with short durations for fast feedback
./target/debug/tomat start --work 0.1 --break 0.05 # 6s work, 3s break
./target/debug/tomat status
./target/debug/tomat toggle
# Stop daemon when done
./target/debug/tomat daemon stop
Code Quality Standards
Mandatory Requirements
All code changes MUST pass these checks before commit:
- Zero clippy warnings:
cargo clippy --all-targets --all-features -- -D warnings - Proper formatting:
cargo fmt -- --check - All tests pass:
cargo test - Compilation success:
cargo check
Code Style
- Error handling: Uses
Box<dyn std::error::Error>for simplicity - Comments: Only add comments when they match existing style or explain complex logic
- Dependencies: Use existing libraries when possible, avoid adding new dependencies unless absolutely necessary
- Commit style: Use Conventional
Commits (
feat:,fix:,docs:,test:,refactor:)
Architecture Overview
Tomat is designed as a small, focused Rust project with a client-server architecture.
Module Structure
src/main.rsMain entry point, command parsing, high-level flowsrc/cli.rsCLI argument parsing withclapsrc/config.rsConfiguration system with timer, sound, and notification settingssrc/server.rsUnix socket server, daemon lifecycle, client request handling, PID file managementsrc/timer.rsTimer state machine, phase transitions, status output formatting, desktop notificationssrc/audio.rsAudio playback utilitiestests/integration tests
Communication Flow
Client CLI Commands
↓
Unix Socket ($XDG_RUNTIME_DIR/tomat.sock)
↓
Daemon Process (background)
↓
TimerState (Work/Break/LongBreak phases)
↓
JSON Status Output (optimized for waybar)
Key Design Decisions
Client-Server Architecture:
- Single binary with subcommands
- Daemon listens on Unix socket at
$XDG_RUNTIME_DIR/tomat.sock - PID file tracking at
$XDG_RUNTIME_DIR/tomat.pidwith exclusive file locking - Line-delimited JSON protocol for communication
Timer State Machine:
- Phases: Work → Break → Work → … → LongBreak (after N sessions)
- Two modes controlled by
--auto-advanceflag:false(default): Timer transitions to next phase but pauses, requiring manual resumetrue: Timer automatically continues through all phases
- Timer starts in paused work state
Testing Infrastructure
Integration Test Pattern
All tests use the TestDaemon helper struct for isolated testing:
#![allow(unused)]
fn main() {
// Start isolated test daemon with temporary socket
let daemon = TestDaemon::start()?;
// Send commands
daemon.send_command(&["start", "--work", "0.05", "--break", "0.05"])?;
// Get status
let status = daemon.get_status()?;
assert_eq!(status["class"], "work");
// Wait for timer completion
daemon.wait_for_completion(10)?;
}
Key testing features:
- Isolated environments: Each test uses
tempfile::tempdir()with customXDG_RUNTIME_DIR - Fast execution: Fractional minutes (0.05 = 3 seconds) for rapid testing
- Notification suppression:
TOMAT_TESTING=1env var disables desktop notifications - Automatic cleanup:
TestDaemonDrop impl kills daemon process
Test Categories
- Auto-advance behavior: Verify
auto_advance=falsepauses after transitions,auto_advance=truecontinues automatically - Timer control: Toggle pause/resume, stop/start
- Daemon lifecycle: Start, stop, status, duplicate detection
- Edge cases: Manual skip, fractional minutes
- Configuration: Timer, sound, and notification configuration parsing
- Icon management: Embedded icon caching and different icon modes
Adding New Features
Adding a New Command
- Add enum variant to
Commandsinsrc/main.rs - Add command handling in
handle_client()insrc/server.rs - Add match arm in
main()insrc/main.rs - Write integration tests in
tests/usingTestDaemon
Modifying Timer Behavior
- Update
TimerStatestruct insrc/timer.rsif new fields needed - Modify state machine logic in
next_phase(),start_work(), etc. - Update status output in
get_status_output() - Test both
auto_advance=trueandauto_advance=falsemodes
Adding Configuration Options
- Update appropriate config struct in
src/config.rs - Add default value functions
- Update
Defaultimplementation - Add comprehensive tests for new configuration options
- Update documentation and examples
Technical Implementation Details
Process Management
- Daemon lifecycle: SIGTERM with 5-second timeout, then SIGKILL
- PID file locking: Uses
fs2::FileExt::try_lock_exclusive()to prevent race conditions - Socket cleanup: Automatic cleanup of socket and PID files on graceful shutdown
Notification System
- Desktop notifications: Via
notify-rustwith embedded icon system - Icon caching: Embedded icon automatically cached to
~/.cache/tomat/icon.png - Mako compatibility: Default “auto” icon mode works with mako out of the box
Configuration System
- TOML-based: Configuration loaded from
~/.config/tomat/config.toml - Hierarchical: Built-in defaults → config file → CLI arguments
Debugging Tips
# Run daemon in foreground (see output directly)
cargo build && ./target/debug/tomat daemon run
# Test output (see println! statements)
cargo test -- --nocapture
# Check socket status
ss -lx | grep tomat
# Inspect PID file
cat $XDG_RUNTIME_DIR/tomat.pid && ps -p <PID>
# Check logs (if using systemd)
journalctl --user -u tomat.service -f
Backward Compatibility
When contributing, ensure:
- No breaking changes: Existing waybar configurations must continue to work
- CLI stability: Existing command-line interfaces are preserved
- Configuration compatibility: Existing config files remain valid
- API consistency: JSON output format remains stable for waybar integration
Release Process
The project uses automated semantic versioning:
- Conventional Commits: Commit messages determine version bumps
- Automated CI: GitHub Actions handle testing and releases
- Semantic Versioning:
feat:→ minor,fix:→ patch,BREAKING CHANGE:→ major
Changelog
2.13.0 (2026-08-27)
Features
2.12.0 (2026-08-27)
Features
- add global quiet flag (
90ee92a)
Bug Fixes
- return status when daemon is unavailable (
5ff2c6e), fixes #100 - resolve cargo audit vulnerabilities (
fa7d8f8) - bump memmap2 to 0.9.11 to resolve RUSTSEC-2026-0186 (
a5214c6)
2.11.0 (2026-02-23)
Features
- add
text_format_idlefor formatting output in idle (63c045b) - allow customizing icons in status text output (7db1fd3)
2.10.0 (2026-02-08)
Features
- add
--forceargument totomat daemon install(f66cdc5) - add overwrite prompt for
tomat daemon install(8fad194)
Bug Fixes
- load config when
tomat toggleinitializes timer (07a931d), closes #36 - modify systemd unit to enable hooks (b170bdd)
2.9.0 (2026-01-28)
Features
- add more logging for the server (c0aaa5b)
2.8.1 (2026-01-19)
Bug Fixes
- man pages, service unit, and completions in releases (598f024)
- open audio stream on demand (11bfd92)
2.8.0 (2026-01-15)
Features
- add
sound.modeoption (66b537a) - add hooks for end of states (494c8be)
- make 0 timeout for hooks mean no timeout (9c4514a)
Bug Fixes
- await resume for hooks if auto advancing (15ba349)
- execute skip hooks before phase transition (9ffc487)
- remove dead
on_completehook code (ae61f6e) - track cli reference and exclude from cargo build (4addeaa)
2.7.0 (2026-01-13)
Features
- add granular options for auto advancing (80d7f31)
- add support for customizing notification urgency (337ebd1)
- allow customization of notification messages (feb3b13)
Performance Improvements
- reduce size of sound alerts (755b791)
2.6.0 (2026-01-09)
Features
- add a system for calling hooks upon state changes (0573d57)
- reduce default
tomat watchinterval to 0.25s (5735a89)
Bug Fixes
- execute hook on work start (bf0b375)
2.5.0 (2025-11-29)
Features
Bug Fixes
- work around race conditions for spawning servers (c5b9113)
Performance Improvements
- reduce startup delay in server verification (0e02b9d)
2.4.0 (2025-11-04)
Features
Bug Fixes
- correctly get status output when paused (6006947)
2.3.0 (2025-10-31)
Features
- add
tomat daemon install/uninstall(f414020)
Bug Fixes
2.2.0 (2025-10-31)
Features
- add
--outputoption (8ccda87) - add
i3status-rsto output formats (0515bd9) - change default timer duration to 5 seconds (5d96283)
Bug Fixes
- fix lint errors (94b4175)
2.1.0 (2025-10-30)
Features
- add icon and enable it in notifications (70c047a)
- change summary field in notification to “Tomato” (e9e7c70)
- config: add configuration section for notifications (ff7f8ce)
- update logo (ee3559d)
2.0.0 (2025-10-29)
⚠ BREAKING CHANGES
- change
break-timetobreakin cli and config
Features
- change
break-timetobreakin cli and config (ef325fc) - enable sound notifications (bd28eb0)
- implement pause and resume commands (f63662b)
- improve timer precision (4cd6c16)
Bug Fixes
- avoid socket race condition (eefe788)
- don’t play audio notifications during testing (253df25)
- make timer persist after restarts (b0706c4)
- preserve timer progress across pause/resume cycles (333b03d)
- remove unused arguments from
tomat toggle(53c3065)
1.0.0 (2025-10-29)
Features
- add daemon, service file, and binary (baea76c)
- add notifications via
notify-rust(435e8dc) - add support for configuration through a TOML file (a3cb56d)
- add toggle subcommand (17f03c8)
- allow floats in durations (b61372f)
- enable full daemon control (68df527)
- setup basic package infrastructure (20401a8)
- support long breaks (91d544b)