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)
|
endif (NOT Slint_FOUND)
|
||||||
|
|
||||||
add_executable(focus src/main.cpp)
|
add_executable(focus src/main.cpp)
|
||||||
|
target_include_directories(focus PRIVATE "external")
|
||||||
target_link_libraries(focus PRIVATE Slint::Slint)
|
target_link_libraries(focus PRIVATE Slint::Slint)
|
||||||
slint_target_sources(
|
slint_target_sources(
|
||||||
focus ui/app-window.slint
|
focus ui/app-window.slint
|
||||||
|
|
|
@ -16,6 +16,12 @@ Simple Pomodoro timer.
|
||||||
- Ninja (or Make but you will need to adapt the commands)
|
- Ninja (or Make but you will need to adapt the commands)
|
||||||
- Git
|
- 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
|
### Steps
|
||||||
|
|
||||||
Fetch and setup the repository:
|
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 "app-window.h"
|
||||||
|
#include "slint_string.h"
|
||||||
#include "slint_timer.h"
|
#include "slint_timer.h"
|
||||||
#include "slint.h"
|
#include "slint.h"
|
||||||
|
#include "nlohmann/json.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CountdownState {
|
class CountdownState {
|
||||||
public:
|
public:
|
||||||
CountdownState(slint::ComponentHandle<ui::AppWindow> ui) : ui_(ui) {
|
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();
|
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 {
|
ui::SessionStep sessionStep() const {
|
||||||
|
@ -105,13 +118,17 @@ class CountdownState {
|
||||||
countdown--;
|
countdown--;
|
||||||
ui->global<ui::State>().set_countdown(countdown);
|
ui->global<ui::State>().set_countdown(countdown);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (countdown == 0) {
|
if (countdown == 0) {
|
||||||
timer->stop();
|
timer->stop();
|
||||||
ui->global<ui::State>().set_countdown_status(ui::CountdownStatus::NotStarted);
|
ui->global<ui::State>().set_countdown_status(ui::CountdownStatus::NotStarted);
|
||||||
// TODO make it customizable
|
std::string timerEndingScript = std::string(ui->global<ui::State>().get_timer_ending_script());
|
||||||
system("notify-send -t 0 \"Pomodoro\" \"task\" &");
|
if (timerEndingScript.empty()) {
|
||||||
system("paplay /usr/share/sounds/freedesktop/stereo/complete.oga &");
|
timerEndingScript = "notify-send -t 0 \"Pomodoro\" \"timer ended!\" && "
|
||||||
|
"gst-play-1.0 /usr/share/sounds/freedesktop/stereo/complete.oga &";
|
||||||
|
}
|
||||||
|
system(timerEndingScript.c_str());
|
||||||
goNextStep();
|
goNextStep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,24 +139,83 @@ class CountdownState {
|
||||||
slint::Timer timer_;
|
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)
|
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();
|
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);
|
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([&] {
|
ui->global<ui::State>().on_config_changed([&] {
|
||||||
countdown.reset();
|
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();
|
ui->run();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { State, SessionStep, CountdownStatus } from "./state.slint";
|
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 { Utils } from "utils.slint";
|
||||||
|
import { ComboBox } from "std-widgets.slint";
|
||||||
|
|
||||||
export component SettingsView inherits Rectangle {
|
export component SettingsView inherits Rectangle {
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
|
@ -40,5 +41,13 @@ export component SettingsView inherits Rectangle {
|
||||||
format-value(value) => { return Math.round(value); }
|
format-value(value) => { return Math.round(value); }
|
||||||
released(value) => { State.max-session-count = 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 <CountdownStatus> countdown-status;
|
||||||
in-out property <int> current-session;
|
in-out property <int> current-session;
|
||||||
in-out property <int> max-session-count;
|
in-out property <int> max-session-count;
|
||||||
|
in-out property <string> timer-ending-script;
|
||||||
|
|
||||||
callback config-changed();
|
callback config-changed();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue