Save configuration and allow custom script to execute when timer ends
This commit is contained in:
parent
8585f5741f
commit
da32449075
6 changed files with 24879 additions and 19 deletions
|
@ -22,6 +22,7 @@ if (NOT Slint_FOUND)
|
|||
endif (NOT Slint_FOUND)
|
||||
|
||||
add_executable(focus src/main.cpp)
|
||||
target_include_directories(focus PRIVATE "external")
|
||||
target_link_libraries(focus PRIVATE Slint::Slint)
|
||||
slint_target_sources(
|
||||
focus ui/app-window.slint
|
||||
|
|
|
@ -16,6 +16,12 @@ Simple Pomodoro timer.
|
|||
- Ninja (or Make but you will need to adapt the commands)
|
||||
- Git
|
||||
|
||||
You can provide a custom script to execute when the timer ends, the usual usage is to display a
|
||||
notification and play a sound, so if you don't provide your own script you also need:
|
||||
|
||||
- notify-send (notification's visual)
|
||||
- gst-play-1.0 (notification's sound)
|
||||
|
||||
### Steps
|
||||
|
||||
Fetch and setup the repository:
|
||||
|
|
24767
external/nlohmann/json.hpp
vendored
Normal file
24767
external/nlohmann/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load diff
110
src/main.cpp
110
src/main.cpp
|
@ -1,17 +1,30 @@
|
|||
#include "app-window.h"
|
||||
#include "slint_string.h"
|
||||
#include "slint_timer.h"
|
||||
#include "slint.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
class CountdownState {
|
||||
public:
|
||||
CountdownState(slint::ComponentHandle<ui::AppWindow> ui) : ui_(ui) {
|
||||
ui_->global<ui::State>().set_break_countdown_duration(60 * 5);
|
||||
ui_->global<ui::State>().set_focus_countdown_duration(60 * 25);
|
||||
ui_->global<ui::State>().set_max_session_count(4);
|
||||
reset();
|
||||
ui->on_start_stop([&]{
|
||||
if (sessionStep() == ui::SessionStep::Setup || sessionStep() == ui::SessionStep::Finished) {
|
||||
reset();
|
||||
goNextStep();
|
||||
start();
|
||||
} else if (sessionStep() == ui::SessionStep::Focus || sessionStep() == ui::SessionStep::Break) {
|
||||
toggleCountdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ui::SessionStep sessionStep() const {
|
||||
|
@ -105,13 +118,17 @@ class CountdownState {
|
|||
countdown--;
|
||||
ui->global<ui::State>().set_countdown(countdown);
|
||||
|
||||
|
||||
|
||||
if (countdown == 0) {
|
||||
timer->stop();
|
||||
ui->global<ui::State>().set_countdown_status(ui::CountdownStatus::NotStarted);
|
||||
// TODO make it customizable
|
||||
system("notify-send -t 0 \"Pomodoro\" \"task\" &");
|
||||
system("paplay /usr/share/sounds/freedesktop/stereo/complete.oga &");
|
||||
|
||||
std::string timerEndingScript = std::string(ui->global<ui::State>().get_timer_ending_script());
|
||||
if (timerEndingScript.empty()) {
|
||||
timerEndingScript = "notify-send -t 0 \"Pomodoro\" \"timer ended!\" && "
|
||||
"gst-play-1.0 /usr/share/sounds/freedesktop/stereo/complete.oga &";
|
||||
}
|
||||
system(timerEndingScript.c_str());
|
||||
goNextStep();
|
||||
}
|
||||
}
|
||||
|
@ -122,24 +139,83 @@ class CountdownState {
|
|||
slint::Timer timer_;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
std::string timerEndingScript = "";
|
||||
int focusDuration = 1500;
|
||||
int breakDuration = 300;
|
||||
int sessionCount = 4;
|
||||
};
|
||||
|
||||
Config loadJsonConfig(const std::string& configPath) {
|
||||
|
||||
std::ifstream file(configPath);
|
||||
Config config{};
|
||||
if (!file) {
|
||||
std::cerr << "Cant open config file: " << configPath << std::endl;
|
||||
return config;
|
||||
}
|
||||
auto jsonConfig = nlohmann::json::parse(file);
|
||||
file.close();
|
||||
|
||||
if (!jsonConfig.is_object()) {
|
||||
std::cerr << "Config file malformated: " << configPath << std::endl;
|
||||
}
|
||||
|
||||
if (jsonConfig["timerEndingScript"].is_string()) {
|
||||
config.timerEndingScript = jsonConfig["timerEndingScript"].get<std::string>();
|
||||
}
|
||||
if (jsonConfig["focusDuration"].is_number_integer()) {
|
||||
config.focusDuration = jsonConfig["focusDuration"].get<int>();
|
||||
}
|
||||
if (jsonConfig["breakDuration"].is_number_integer()) {
|
||||
config.breakDuration = jsonConfig["breakDuration"].get<int>();
|
||||
}
|
||||
if (jsonConfig["sessionCount"].is_number_integer()) {
|
||||
config.sessionCount = jsonConfig["sessionCount"].get<int>();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
void saveJsonConfig(const std::string& configPath, const Config& config) {
|
||||
|
||||
std::ofstream file(configPath);
|
||||
if (!file) {
|
||||
std::cerr << "Cant open config file: " << configPath << std::endl;
|
||||
return;
|
||||
}
|
||||
nlohmann::json jsonConfig;
|
||||
jsonConfig["timerEndingScript"] = config.timerEndingScript;
|
||||
jsonConfig["focusDuration"] = config.focusDuration;
|
||||
jsonConfig["breakDuration"] = config.breakDuration;
|
||||
jsonConfig["sessionCount"] = config.sessionCount;
|
||||
file << jsonConfig.dump(4);
|
||||
file.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
std::filesystem::create_directory(std::string(getenv("HOME")) + "/.config/focus");
|
||||
const auto configPath = std::string(getenv("HOME")) + "/.config/focus/config.json";
|
||||
|
||||
slint::ComponentHandle<ui::AppWindow> ui = ui::AppWindow::create();
|
||||
|
||||
Config config = loadJsonConfig(configPath);
|
||||
ui->global<ui::State>().set_timer_ending_script(slint::SharedString(config.timerEndingScript));
|
||||
ui->global<ui::State>().set_focus_countdown_duration(config.focusDuration);
|
||||
ui->global<ui::State>().set_break_countdown_duration(config.breakDuration);
|
||||
ui->global<ui::State>().set_max_session_count(config.sessionCount);
|
||||
|
||||
CountdownState countdown(ui);
|
||||
|
||||
ui->on_start_stop([&]{
|
||||
if (countdown.sessionStep() == ui::SessionStep::Setup || countdown.sessionStep() == ui::SessionStep::Finished) {
|
||||
countdown.reset();
|
||||
countdown.goNextStep();
|
||||
countdown.start();
|
||||
} else if (countdown.sessionStep() == ui::SessionStep::Focus || countdown.sessionStep() == ui::SessionStep::Break) {
|
||||
countdown.toggleCountdown();
|
||||
}
|
||||
});
|
||||
|
||||
ui->global<ui::State>().on_config_changed([&] {
|
||||
countdown.reset();
|
||||
config.timerEndingScript = ui->global<ui::State>().get_timer_ending_script();
|
||||
config.focusDuration = ui->global<ui::State>().get_focus_countdown_duration();
|
||||
config.breakDuration = ui->global<ui::State>().get_break_countdown_duration();
|
||||
config.sessionCount = ui->global<ui::State>().get_max_session_count();
|
||||
saveJsonConfig(configPath, config);
|
||||
});
|
||||
|
||||
ui->run();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { State, SessionStep, CountdownStatus } from "./state.slint";
|
||||
import { VText, VButton, VSlider, Palette } from "@selenite";
|
||||
import { VText, VButton, VSlider, VTextInput, Palette } from "@selenite";
|
||||
import { Utils } from "utils.slint";
|
||||
import { ComboBox } from "std-widgets.slint";
|
||||
|
||||
export component SettingsView inherits Rectangle {
|
||||
VerticalLayout {
|
||||
|
@ -40,5 +41,13 @@ export component SettingsView inherits Rectangle {
|
|||
format-value(value) => { return Math.round(value); }
|
||||
released(value) => { State.max-session-count = Math.round(value); }
|
||||
}
|
||||
|
||||
VTextInput {
|
||||
label: "Timer ending script";
|
||||
label-size: 1.25rem;
|
||||
label-alignment: TextHorizontalAlignment.center;
|
||||
placeholder: "Default";
|
||||
text <=> State.timer-ending-script;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export global State {
|
|||
in-out property <CountdownStatus> countdown-status;
|
||||
in-out property <int> current-session;
|
||||
in-out property <int> max-session-count;
|
||||
in-out property <string> timer-ending-script;
|
||||
|
||||
callback config-changed();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue