strip unicode dashes, trim restating doc comments, untrack forbidden files
This commit is contained in:
@@ -8,7 +8,7 @@ fn main() {
|
||||
}
|
||||
|
||||
// On GNU targets, replace the WebView2Loader import library with the static
|
||||
// library so the loader is baked into the exe — no DLL to ship.
|
||||
// library so the loader is baked into the exe -- no DLL to ship.
|
||||
if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") {
|
||||
swap_webview2_to_static();
|
||||
}
|
||||
@@ -44,7 +44,7 @@ fn fix_resource_lib() {
|
||||
// archive signature). A .res file starts with 0x00000000.
|
||||
if let Ok(header) = std::fs::read(&lib_file) {
|
||||
if header.len() >= 4 && header[0..4] == [0, 0, 0, 0] {
|
||||
// This is a .res file, not COFF — re-compile with windres
|
||||
// This is a .res file, not COFF -- re-compile with windres
|
||||
let windres = "C:/Users/lashman/mingw-w64/mingw64/bin/windres.exe";
|
||||
let status = Command::new(windres)
|
||||
.args([
|
||||
|
||||
@@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A custom break activity defined by the user.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomActivity {
|
||||
pub id: String,
|
||||
@@ -13,7 +12,6 @@ pub struct CustomActivity {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
/// A single time range (e.g., 09:00 to 17:00)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TimeRange {
|
||||
pub start: String, // Format: "HH:MM"
|
||||
@@ -29,7 +27,6 @@ impl Default for TimeRange {
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule for a single day
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DaySchedule {
|
||||
pub enabled: bool,
|
||||
@@ -46,7 +43,6 @@ impl Default for DaySchedule {
|
||||
}
|
||||
|
||||
impl DaySchedule {
|
||||
/// Create a default schedule for weekend days (disabled by default)
|
||||
fn weekend_default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
@@ -304,14 +300,13 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Get the path to the config file (portable: next to the exe)
|
||||
// Portable: next to the exe
|
||||
fn config_path() -> Result<PathBuf> {
|
||||
let exe_path = std::env::current_exe().context("Failed to determine exe path")?;
|
||||
let exe_dir = exe_path.parent().context("Failed to determine exe directory")?;
|
||||
Ok(exe_dir.join("config.json"))
|
||||
}
|
||||
|
||||
/// Load configuration from file, or return default if it doesn't exist
|
||||
pub fn load_or_default() -> Self {
|
||||
match Self::load() {
|
||||
Ok(config) => config,
|
||||
@@ -327,7 +322,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load configuration from file
|
||||
pub fn load() -> Result<Self> {
|
||||
let config_path = Self::config_path()?;
|
||||
|
||||
@@ -345,7 +339,6 @@ impl Config {
|
||||
Ok(config.validate())
|
||||
}
|
||||
|
||||
/// Save configuration to file
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let config_path = Self::config_path()?;
|
||||
|
||||
@@ -357,7 +350,6 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate and sanitize configuration values
|
||||
pub fn validate(mut self) -> Self {
|
||||
// Break duration: 1-60 minutes
|
||||
self.break_duration = self.break_duration.clamp(1, 60);
|
||||
@@ -511,7 +503,6 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if a time string is in valid HH:MM format
|
||||
fn is_valid_time_format(time: &str) -> bool {
|
||||
let parts: Vec<&str> = time.split(':').collect();
|
||||
if parts.len() != 2 {
|
||||
@@ -536,29 +527,24 @@ impl Config {
|
||||
hours * 60 + minutes
|
||||
}
|
||||
|
||||
/// Check if a string is a valid hex color (#RRGGBB)
|
||||
fn is_valid_hex_color(color: &str) -> bool {
|
||||
color.len() == 7
|
||||
&& color.starts_with('#')
|
||||
&& color[1..].chars().all(|c| c.is_ascii_hexdigit())
|
||||
}
|
||||
|
||||
/// Get break duration in seconds
|
||||
pub fn break_duration_seconds(&self) -> u64 {
|
||||
self.break_duration as u64 * 60
|
||||
}
|
||||
|
||||
/// Get break frequency in seconds
|
||||
pub fn break_frequency_seconds(&self) -> u64 {
|
||||
self.break_frequency as u64 * 60
|
||||
}
|
||||
|
||||
/// Get snooze duration in seconds
|
||||
pub fn snooze_duration_seconds(&self) -> u64 {
|
||||
self.snooze_duration as u64 * 60
|
||||
}
|
||||
|
||||
/// Reset to default values
|
||||
pub fn reset_to_default(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
@@ -310,7 +310,6 @@ fn save_window_position(
|
||||
|
||||
// ── Dynamic Tray Icon Rendering ────────────────────────────────────────────
|
||||
|
||||
/// Parse a "#RRGGBB" hex color string into (r, g, b). Falls back to fallback on invalid input.
|
||||
fn parse_hex_color(hex: &str, fallback: (u8, u8, u8)) -> (u8, u8, u8) {
|
||||
if hex.len() == 7 && hex.starts_with('#') {
|
||||
let r = u8::from_str_radix(&hex[1..3], 16).unwrap_or(fallback.0);
|
||||
@@ -322,8 +321,6 @@ fn parse_hex_color(hex: &str, fallback: (u8, u8, u8)) -> (u8, u8, u8) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a 32x32 RGBA icon with a progress arc using the given accent/break colors.
|
||||
/// F10: Optionally renders a green checkmark when daily goal is met.
|
||||
fn render_tray_icon(
|
||||
progress: f64,
|
||||
is_break: bool,
|
||||
@@ -401,33 +398,31 @@ fn update_tray(
|
||||
break_color: (u8, u8, u8),
|
||||
goal_met: bool,
|
||||
) {
|
||||
// Update tooltip
|
||||
let tooltip = match snapshot.state {
|
||||
timer::TimerState::Running => {
|
||||
if snapshot.deferred_break_pending {
|
||||
"Core Cooldown — Break deferred (fullscreen)".to_string()
|
||||
"Core Cooldown -- Break deferred (fullscreen)".to_string()
|
||||
} else {
|
||||
let m = snapshot.time_remaining / 60;
|
||||
let s = snapshot.time_remaining % 60;
|
||||
format!("Core Cooldown — {:02}:{:02} until break", m, s)
|
||||
format!("Core Cooldown -- {:02}:{:02} until break", m, s)
|
||||
}
|
||||
}
|
||||
timer::TimerState::Paused => {
|
||||
if snapshot.idle_paused {
|
||||
"Core Cooldown — Paused (idle)".to_string()
|
||||
"Core Cooldown -- Paused (idle)".to_string()
|
||||
} else {
|
||||
"Core Cooldown — Paused".to_string()
|
||||
"Core Cooldown -- Paused".to_string()
|
||||
}
|
||||
}
|
||||
timer::TimerState::BreakActive => {
|
||||
let m = snapshot.break_time_remaining / 60;
|
||||
let s = snapshot.break_time_remaining % 60;
|
||||
format!("Core Cooldown — Break {:02}:{:02}", m, s)
|
||||
format!("Core Cooldown -- Break {:02}:{:02}", m, s)
|
||||
}
|
||||
};
|
||||
let _ = tray.set_tooltip(Some(&tooltip));
|
||||
|
||||
// Update icon
|
||||
let (progress, is_break, is_paused) = match snapshot.state {
|
||||
timer::TimerState::Running => (snapshot.progress, false, false),
|
||||
timer::TimerState::Paused => (snapshot.progress, false, true),
|
||||
@@ -474,7 +469,7 @@ pub fn run() {
|
||||
"break",
|
||||
tauri::WebviewUrl::App("index.html?break=1".into()),
|
||||
)
|
||||
.title("Core Cooldown \u{2014} Break")
|
||||
.title("Core Cooldown - Break")
|
||||
.inner_size(900.0, 540.0)
|
||||
.decorations(false)
|
||||
.transparent(true)
|
||||
@@ -554,7 +549,6 @@ pub fn run() {
|
||||
let break_c = parse_hex_color(&break_hex, (124, 106, 239));
|
||||
update_tray(&tray, &snapshot, accent, break_c, goal_met);
|
||||
|
||||
// Emit tick event with full snapshot
|
||||
let _ = handle.emit("timer-tick", &snapshot);
|
||||
|
||||
// F5: Screen dim window management
|
||||
@@ -616,7 +610,6 @@ pub fn run() {
|
||||
break_deferred_notified = false;
|
||||
}
|
||||
TickResult::BreakEnded => {
|
||||
// Restore normal window state and close break window
|
||||
handle_break_end(&handle);
|
||||
// F9: Close multi-monitor overlays
|
||||
close_multi_monitor_overlays(&handle);
|
||||
@@ -721,7 +714,7 @@ pub fn run() {
|
||||
.notification()
|
||||
.builder()
|
||||
.title("Break deferred")
|
||||
.body("Fullscreen app detected — break will start when you exit.")
|
||||
.body("Fullscreen app detected -- break will start when you exit.")
|
||||
.show();
|
||||
}
|
||||
let _ = handle.emit("break-deferred", &());
|
||||
@@ -883,7 +876,6 @@ fn close_break_window(app: &AppHandle) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle break start: either fullscreen on main window, or open a separate modal break window.
|
||||
fn handle_break_start(app: &AppHandle, fullscreen_mode: bool) {
|
||||
if fullscreen_mode {
|
||||
// Fullscreen: show break inside the main window
|
||||
@@ -899,7 +891,6 @@ fn handle_break_start(app: &AppHandle, fullscreen_mode: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle break end: restore main window state and close break window if open.
|
||||
fn handle_break_end(app: &AppHandle) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.set_always_on_top(false);
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicI32, Ordering};
|
||||
// ── MSVC Buffer Security Check (/GS) ────────────────────────────────────────
|
||||
//
|
||||
// MSVC's /GS flag instruments functions with stack canaries. These two symbols
|
||||
// implement the canary check. The cookie value is arbitrary — real MSVC CRT
|
||||
// implement the canary check. The cookie value is arbitrary -- real MSVC CRT
|
||||
// randomises it at startup, but for a statically-linked helper library this
|
||||
// fixed sentinel is sufficient.
|
||||
|
||||
@@ -44,15 +44,15 @@ pub unsafe extern "C" fn _Init_thread_header(guard: *mut i32) {
|
||||
loop {
|
||||
let val = guard.read_volatile();
|
||||
if val == 0 {
|
||||
// Already initialised — tell caller to skip
|
||||
// Already initialised -- tell caller to skip
|
||||
return;
|
||||
}
|
||||
if val == -1 {
|
||||
// Not yet initialised — try to claim it
|
||||
// Not yet initialised -- try to claim it
|
||||
guard.write_volatile(1);
|
||||
return;
|
||||
}
|
||||
// val == 1: another thread is initialising — yield and retry
|
||||
// val == 1: another thread is initialising -- yield and retry
|
||||
std::thread::yield_now();
|
||||
}
|
||||
}
|
||||
@@ -71,22 +71,22 @@ pub unsafe extern "C" fn _Init_thread_footer(guard: *mut i32) {
|
||||
// MinGW's libstdc++ exports the same operators but with GCC/Itanium mangling,
|
||||
// so the linker can't match them. We provide the MSVC-mangled versions here.
|
||||
|
||||
/// `std::nothrow` — MSVC-mangled `?nothrow@std@@3Unothrow_t@1@B`
|
||||
/// `std::nothrow` -- MSVC-mangled `?nothrow@std@@3Unothrow_t@1@B`
|
||||
/// An empty struct constant used as a tag for nothrow `new`.
|
||||
#[export_name = "?nothrow@std@@3Unothrow_t@1@B"]
|
||||
pub static MSVC_STD_NOTHROW: u8 = 0;
|
||||
|
||||
/// `operator new(size_t, const std::nothrow_t&)` — nothrow allocation
|
||||
/// `operator new(size_t, const std::nothrow_t&)` -- nothrow allocation
|
||||
/// MSVC-mangled: `??2@YAPEAX_KAEBUnothrow_t@std@@@Z`
|
||||
#[export_name = "??2@YAPEAX_KAEBUnothrow_t@std@@@Z"]
|
||||
pub unsafe extern "C" fn msvc_operator_new_nothrow(size: usize, _nothrow: *const u8) -> *mut u8 {
|
||||
let size = if size == 0 { 1 } else { size };
|
||||
let layout = std::alloc::Layout::from_size_align_unchecked(size, 8);
|
||||
let ptr = std::alloc::alloc(layout);
|
||||
ptr // null on failure — nothrow semantics
|
||||
ptr // null on failure -- nothrow semantics
|
||||
}
|
||||
|
||||
/// `operator delete(void*, size_t)` — sized deallocation
|
||||
/// `operator delete(void*, size_t)` -- sized deallocation
|
||||
/// MSVC-mangled: `??3@YAXPEAX_K@Z`
|
||||
#[export_name = "??3@YAXPEAX_K@Z"]
|
||||
pub unsafe extern "C" fn msvc_operator_delete_sized(ptr: *mut u8, size: usize) {
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A single day's break statistics.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DayRecord {
|
||||
@@ -16,7 +15,6 @@ pub struct DayRecord {
|
||||
pub natural_break_time_secs: u64,
|
||||
}
|
||||
|
||||
/// Persistent stats stored as JSON.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct StatsData {
|
||||
pub days: HashMap<String, DayRecord>,
|
||||
@@ -24,12 +22,10 @@ pub struct StatsData {
|
||||
pub best_streak: u32,
|
||||
}
|
||||
|
||||
/// Runtime stats manager.
|
||||
pub struct Stats {
|
||||
pub data: StatsData,
|
||||
}
|
||||
|
||||
/// Snapshot sent to the frontend.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StatsSnapshot {
|
||||
@@ -47,13 +43,11 @@ pub struct StatsSnapshot {
|
||||
pub daily_goal_met: bool,
|
||||
}
|
||||
|
||||
/// F10: Result of recording a completed break
|
||||
pub struct BreakCompletedResult {
|
||||
pub milestone_reached: Option<u32>,
|
||||
pub daily_goal_just_met: bool,
|
||||
}
|
||||
|
||||
/// F7: Weekly summary for reports
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WeekSummary {
|
||||
@@ -68,7 +62,7 @@ pub struct WeekSummary {
|
||||
const MILESTONES: &[u32] = &[3, 5, 7, 14, 21, 30, 50, 100, 365];
|
||||
|
||||
impl Stats {
|
||||
/// Portable: stats file lives next to the exe
|
||||
// Portable: next to the exe
|
||||
fn stats_path() -> Option<PathBuf> {
|
||||
let exe_path = std::env::current_exe().ok()?;
|
||||
let exe_dir = exe_path.parent()?;
|
||||
@@ -114,7 +108,6 @@ impl Stats {
|
||||
})
|
||||
}
|
||||
|
||||
/// Record a completed break. Returns milestone/goal info for gamification.
|
||||
pub fn record_break_completed(&mut self, duration_secs: u64, daily_goal: u32) -> BreakCompletedResult {
|
||||
let day = self.today_mut();
|
||||
let was_below_goal = day.breaks_completed < daily_goal;
|
||||
@@ -182,7 +175,6 @@ impl Stats {
|
||||
}
|
||||
}
|
||||
|
||||
/// F10: Check if current streak exactly matches a milestone
|
||||
fn check_milestone(&self) -> Option<u32> {
|
||||
let streak = self.data.current_streak;
|
||||
if MILESTONES.contains(&streak) {
|
||||
@@ -225,7 +217,6 @@ impl Stats {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get recent N days of history, sorted chronologically.
|
||||
pub fn recent_days(&self, n: u32) -> Vec<DayRecord> {
|
||||
let today = chrono::Local::now().date_naive();
|
||||
let mut records = Vec::new();
|
||||
@@ -243,7 +234,6 @@ impl Stats {
|
||||
records
|
||||
}
|
||||
|
||||
/// F7: Get weekly summaries for the past N weeks
|
||||
pub fn weekly_summary(&self, weeks: u32) -> Vec<WeekSummary> {
|
||||
let today = chrono::Local::now().date_naive();
|
||||
let mut summaries = Vec::new();
|
||||
|
||||
@@ -26,7 +26,6 @@ pub enum AppView {
|
||||
Stats,
|
||||
}
|
||||
|
||||
/// Snapshot of the full timer state, sent to the frontend on every tick
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TimerSnapshot {
|
||||
@@ -74,7 +73,6 @@ pub struct TimerSnapshot {
|
||||
pub is_long_break: bool,
|
||||
}
|
||||
|
||||
/// Events emitted by the timer to the frontend
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BreakStartedPayload {
|
||||
@@ -161,8 +159,6 @@ impl TimerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check idle state and auto-pause/resume accordingly.
|
||||
/// Returns IdleCheckResult indicating what happened.
|
||||
pub fn check_idle(&mut self) -> IdleCheckResult {
|
||||
if !self.config.idle_detection_enabled {
|
||||
// If idle detection disabled but we were idle-paused, resume
|
||||
@@ -218,7 +214,6 @@ impl TimerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// F2: Check if the foreground window is fullscreen (presentation mode)
|
||||
pub fn check_presentation_mode(&mut self) -> bool {
|
||||
if !self.config.presentation_mode_enabled {
|
||||
self.presentation_mode_active = false;
|
||||
@@ -230,7 +225,6 @@ impl TimerManager {
|
||||
fs
|
||||
}
|
||||
|
||||
/// Called every second. Returns what events should be emitted.
|
||||
pub fn tick(&mut self) -> TickResult {
|
||||
// Idle detection and natural break detection
|
||||
let idle_result = self.check_idle();
|
||||
@@ -322,7 +316,6 @@ impl TimerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// F1: Called every second for microbreak logic. Returns microbreak events.
|
||||
pub fn tick_microbreak(&mut self) -> MicrobreakTickResult {
|
||||
if !self.config.microbreak_enabled {
|
||||
return MicrobreakTickResult::None;
|
||||
@@ -416,7 +409,6 @@ impl TimerManager {
|
||||
self.snoozes_used = 0;
|
||||
}
|
||||
|
||||
/// F3: Advance the Pomodoro cycle position after a break completes
|
||||
fn advance_pomodoro_cycle(&mut self) {
|
||||
if !self.config.pomodoro_enabled {
|
||||
return;
|
||||
@@ -472,14 +464,14 @@ impl TimerManager {
|
||||
let past_half = total > 0 && elapsed * 2 >= total;
|
||||
|
||||
if past_half && self.config.allow_end_early {
|
||||
// "End break" — counts as completed
|
||||
// "End break" -- counts as completed
|
||||
self.has_had_break = true;
|
||||
self.seconds_since_last_break = 0;
|
||||
self.advance_pomodoro_cycle();
|
||||
self.reset_timer();
|
||||
true
|
||||
} else if !past_half {
|
||||
// "Cancel break" — doesn't count
|
||||
// "Cancel break" -- doesn't count
|
||||
// F3: Pomodoro reset-on-skip
|
||||
if self.config.pomodoro_enabled && self.config.pomodoro_reset_on_skip {
|
||||
self.pomodoro_cycle_position = 0;
|
||||
@@ -672,14 +664,12 @@ pub enum TickResult {
|
||||
BreakDeferred, // F2
|
||||
}
|
||||
|
||||
/// F1: Microbreak tick result
|
||||
pub enum MicrobreakTickResult {
|
||||
None,
|
||||
MicrobreakStarted,
|
||||
MicrobreakEnded,
|
||||
}
|
||||
|
||||
/// Result of checking idle state
|
||||
pub enum IdleCheckResult {
|
||||
None,
|
||||
JustPaused,
|
||||
@@ -687,7 +677,6 @@ pub enum IdleCheckResult {
|
||||
NaturalBreakDetected(u64), // duration in seconds
|
||||
}
|
||||
|
||||
/// Returns the number of seconds since last user input (mouse/keyboard).
|
||||
#[cfg(windows)]
|
||||
pub fn get_idle_seconds() -> u64 {
|
||||
use std::mem;
|
||||
@@ -712,7 +701,6 @@ pub fn get_idle_seconds() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
/// F2: Check if the foreground window is a fullscreen application
|
||||
#[cfg(windows)]
|
||||
pub fn is_foreground_fullscreen() -> bool {
|
||||
use std::mem;
|
||||
@@ -756,7 +744,6 @@ pub fn is_foreground_fullscreen() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// F9: Get all monitor rects for multi-monitor break enforcement
|
||||
#[cfg(windows)]
|
||||
pub fn get_all_monitors() -> Vec<MonitorInfo> {
|
||||
use winapi::shared::windef::{HMONITOR, HDC, LPRECT};
|
||||
|
||||
Reference in New Issue
Block a user