Skip to content

samsonchim/solo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Solo

A native, minimalist TODO & reminder application for Fedora / GNOME, written in Rust.

Project layout

solo/
├── Cargo.toml                       # Cargo workspace
├── crates/
│   ├── solo-core/                   # shared model, JSON storage, DBus constants
│   │   └── src/{lib,model,storage}.rs
│   ├── solo-daemon/                 # background service  → binary: solo-daemon
│   │   └── src/{main,dbus,scheduler}.rs
│   └── solo-ui/                     # dashboard           → binary: solo
│       └── src/{main,proxy}.rs
├── data/
│   ├── org.solo.Solo.desktop        # application launcher (app grid)
│   ├── solo-daemon.desktop          # XDG autostart fallback
│   └── solo-daemon.service          # systemd user service (preferred)
├── data/icons/org.solo.Solo.svg     # scalable app icon (SVG)
├── gnome-extension/[email protected]/ # GJS panel extension
│   ├── extension.js
│   └── metadata.json
├── scripts/install.sh               # build + install everything into ~/.local
└── README.md

Prerequisites

You need the Rust toolchain plus the GTK4 / libadwaita / D-Bus development headers (the solo dashboard links against the system GTK4 and libadwaita libraries).

On Fedora:

sudo dnf install rust cargo gtk4-devel libadwaita-devel dbus-devel pkgconf-pkg-config

If your distro's rustc is older than what the crates require, install the toolchain via rustup instead: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

You also need a GNOME session (the daemon notifications and the panel extension target GNOME Shell 45–48, which is what current Fedora Workstation ships).


Installation

All install methods are per-user and require no root — everything lands under ~/.local and ~/.config.

Option A — one-shot install script (recommended)

From the repository root:

./scripts/install.sh

This will:

  1. cargo build --release (produces target/release/solo and target/release/solo-daemon).
  2. Install both binaries to ~/.local/bin/.
  3. Install the app launcher to ~/.local/share/applications/.
  4. Install and start the daemon as a systemd user service (~/.config/systemd/user/solo-daemon.service). If systemctl is unavailable it falls back to an XDG autostart .desktop file and launches the daemon for the current session.
  5. Copy the GNOME Shell extension to ~/.local/share/gnome-shell/extensions/[email protected]/.

The script prints the remaining manual steps when it finishes.

PATH note: make sure ~/.local/bin is on your PATH. On Fedora it usually is. If solo is "command not found", add this to ~/.bashrc and re-login: export PATH="$HOME/.local/bin:$PATH"

Option B — manual install (step by step)

If you'd rather do each step yourself:

# 1. Build release binaries
cargo build --release

# 2. Install the binaries
mkdir -p ~/.local/bin
install -m 0755 target/release/solo        ~/.local/bin/solo
install -m 0755 target/release/solo-daemon ~/.local/bin/solo-daemon

# 3. Install the app launcher (so "Solo" shows in the app grid)
mkdir -p ~/.local/share/applications
install -m 0644 data/org.solo.Solo.desktop ~/.local/share/applications/

# 4a. Install the daemon as a systemd user service (preferred)
mkdir -p ~/.config/systemd/user
install -m 0644 data/solo-daemon.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now solo-daemon.service

#     ── OR ──

# 4b. Use XDG autostart instead of systemd
mkdir -p ~/.config/autostart
install -m 0644 data/solo-daemon.desktop ~/.config/autostart/
solo-daemon &     # start it now for this session

# 5. Install the GNOME Shell extension
mkdir -p ~/.local/share/gnome-shell/extensions/[email protected]
cp -r gnome-extension/[email protected]/. \
      ~/.local/share/gnome-shell/extensions/[email protected]/

Enabling the GNOME panel extension

After the extension files are in place:

# Reload the extension list (Wayland: log out and back in; Xorg: Alt+F2 then type "r")
gnome-extensions enable [email protected]

Check it loaded:

gnome-extensions info [email protected]

On Wayland (Fedora's default) you must log out and back in before GNOME Shell sees a newly added extension — there is no in-session shell reload on Wayland.


Verifying it works

  1. Daemon is running:

    systemctl --user status solo-daemon.service     # if installed via systemd
    # or, regardless of install method:
    gdbus introspect --session --dest org.solo.Daemon --object-path /org/solo/Daemon

    The introspection should list the org.solo.Daemon1 interface.

  2. End-to-end test — create a task that fires in one minute and lasts two:

    gdbus call --session --dest org.solo.Daemon --object-path /org/solo/Daemon \
      --method org.solo.Daemon1.AddTask "Test reminder" \
      "$(( $(date +%s) + 60 ))" 120
    • In ~60 s you should get a desktop notification "⏰ Test reminder".
    • The task title + a ticking countdown should appear in the top panel for 2 minutes.
    • Open the dashboard (solo) and you'll see the task listed.

Usage

Launch Solo from the app grid, or run solo in a terminal.

  • Add a task: fill in Title, Starts in (minutes from now) and Duration (minutes), then press Add task.
  • Edit: press the ✎ button on a row — its values load into the form; press Save.
  • Delete: press the 🗑 button on a row.
  • Close the window: the dashboard process exits, but the daemon keeps running, keeps firing reminders, and keeps updating the panel countdown.

Start time is entered as minutes from now to keep the UI minimal. See Development for how to switch to an absolute date/time picker.


How it runs (autostart & lifecycle)

  • On login: the systemd user service (WantedBy=default.target, After=graphical-session.target) starts solo-daemon automatically. With the XDG fallback, the GNOME session launches it from ~/.config/autostart.

  • On crash: the systemd unit has Restart=on-failure (2 s back-off).

  • On reboot: the daemon reloads tasks.json and reinjects all tasks. Tasks already notified keep their notified flag, so reminders are never re-fired for past events.

  • Manual control:

    systemctl --user start  solo-daemon.service
    systemctl --user stop   solo-daemon.service
    systemctl --user restart solo-daemon.service
    journalctl --user -u solo-daemon.service -f      # live logs

DBus contract

Service org.solo.Daemon, object /org/solo/Daemon, interface org.solo.Daemon1:

Member Kind Signature Notes
AddTask(title, start, duration) method (s x x) → s returns new task id
UpdateTask(id, title, start, duration) method (s s x x) → b true if found
DeleteTask(id) method s → b true if removed
ListTasks() method → s JSON array of tasks
ActiveTask() method → s JSON {title,start,end} or {}
ActiveTaskChanged signal (s x x) title, start, end (epoch s); empty title ⇒ none active
TasksChanged signal () emitted after any mutation

x = 64-bit signed integer (epoch seconds, UTC), s = string, b = boolean.


Data & storage

  • Tasks are stored as JSON at ~/.local/share/solo/tasks.json (XDG data dir).
  • Writes are atomic (temp file + rename) so a crash mid-write can't corrupt the list.
  • The daemon is the only writer; the UI mutates exclusively through D-Bus.

Example record:

[
  {
    "id": "0b6c…",
    "title": "Stand-up",
    "start_time": 1760000000,
    "duration_secs": 900,
    "notified": false
  }
]

Updating

Pull the latest code and re-run the installer:

git pull
./scripts/install.sh
systemctl --user restart solo-daemon.service   # if you used systemd

If you changed the extension, log out/in (Wayland) so GNOME Shell reloads it.


Uninstalling

# Stop & disable the daemon
systemctl --user disable --now solo-daemon.service 2>/dev/null || true
rm -f ~/.config/systemd/user/solo-daemon.service
systemctl --user daemon-reload

# Remove autostart fallback (if used)
rm -f ~/.config/autostart/solo-daemon.desktop

# Disable & remove the extension
gnome-extensions disable [email protected] 2>/dev/null || true
rm -rf ~/.local/share/gnome-shell/extensions/[email protected]

# Remove binaries and launcher
rm -f ~/.local/bin/solo ~/.local/bin/solo-daemon
rm -f ~/.local/share/applications/org.solo.Solo.desktop

# Remove data (optional — this deletes your tasks)
rm -rf ~/.local/share/solo

Troubleshooting

Symptom Fix
solo: command not found Add ~/.local/bin to PATH and re-login.
Dashboard shows "daemon unavailable" Daemon isn't running: systemctl --user start solo-daemon.service, or check journalctl --user -u solo-daemon.service.
No desktop notifications Ensure a notification daemon is active (GNOME provides one). Test with notify-send hello.
Panel countdown never appears Confirm the extension is enabled (gnome-extensions info [email protected]) and that you logged out/in on Wayland. Check journalctl --user -f for Solo: log lines.
Build fails on missing headers Install gtk4-devel libadwaita-devel dbus-devel (see Prerequisites).
cargo too old Use rustup (see Prerequisites) and cargo update.
App grid shows a generic/blank icon Run gtk-update-icon-cache -f ~/.local/share/icons/hicolor and re-login.

Notifications and/or panel countdown don't appear

These are both produced by the daemon's scheduler, so check it directly:

# 1. Make sure you're running the freshly built binary, not an old one.
systemctl --user restart solo-daemon.service

# 2. Watch the daemon log, then add a task that fires in ~5 seconds.
journalctl --user -u solo-daemon.service -f &
gdbus call --session --dest org.solo.Daemon --object-path /org/solo/Daemon \
  --method org.solo.Daemon1.AddTask "Smoke test" "$(( $(date +%s) + 5 ))" 60

Within ~5 s the log should print notifying — Smoke test and active task -> Smoke test ….

  • You see those log lines but no desktop popup: confirm a notification server is running (notify-send hi should pop). On GNOME it always is.

  • You see no log lines at all: the daemon isn't processing — confirm it's the new binary (systemctl --user restart) and that only one instance owns the bus name.

  • Panel stays empty. First confirm the daemon reports an active task while one is running:

    gdbus call --session --dest org.solo.Daemon --object-path /org/solo/Daemon \
      --method org.solo.Daemon1.ActiveTask
    • Returns ('{"title":"…","start":…,"end":…}',) ⇒ daemon is fine; the issue is the extension. Re-copy extension.js, ensure it's enabled (gnome-extensions info [email protected]), and log out/in on Wayland so the Shell reloads it. Watch journalctl --user -f for Solo: lines.
    • Returns ('{}',) while a task should be active ⇒ daemon side: check the task's start time hasn't already passed its duration.

    The panel polls ActiveTask() once per second, so no D-Bus signal needs to be delivered to the Shell for it to work.

Historical bug (fixed): the notification call is blocking and used to be invoked on the async runtime, which could panic and kill the scheduler loop — taking notifications and the panel signal down together. It now runs on a dedicated blocking thread.


Development

Run the pieces directly without installing:

cargo run -p solo-daemon          # terminal 1: background service (logs to stderr)
cargo run -p solo-ui              # terminal 2: dashboard window

Type-check / lint everything:

cargo check
cargo clippy --all-targets

Notable extension points:

  • Absolute date/time picker: the two adw::SpinRows in crates/solo-ui/src/main.rs encode start time as minutes from now. Replace them with a gtk::Calendar + time entry and convert to epoch seconds before calling AddTask.
  • SQLite instead of JSON: crates/solo-core/src/storage.rs is the only module that touches the filesystem. Swap its body for rusqlite and nothing else needs to change.
  • Dependency versions assume the current ecosystem (relm4 0.9 / gtk4 0.9 / libadwaita 0.7 / zbus 5). Run cargo update if your distro pins different minors.

Requirement → code map

Requirement Where it lives
Rust + GTK4/relm4/libadwaita, follows system dark mode crates/solo-ui (adw::StyleManager)
Local persistence in ~/.local/share/solo/ crates/solo-core/src/storage.rs
Daemon auto-starts, survives reboot, reinjects tasks data/solo-daemon.service, crates/solo-daemon/src/main.rs
Closing the dashboard keeps the daemon alive Separate processes; UI never owns the daemon
Native notifications via DBus crates/solo-daemon/src/scheduler.rs (notify-rust)
Daemon exposes active-task state on DBus crates/solo-daemon/src/dbus.rs (ActiveTaskChanged)
GNOME top-panel live countdown gnome-extension/[email protected]/extension.js

License

MIT.

About

Linux Based Custom ToDo and Reminder app using Rust

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages