mirror of
https://github.com/marcusDenslow/FERRUM.git
synced 2026-10-25 22:48:51 +01:00
306 lines
7.4 KiB
C
306 lines
7.4 KiB
C
/**
|
|
* countdown_timer.c
|
|
* Implementation of a focus timer that integrates with the shell status bar
|
|
*/
|
|
|
|
#include "countdown_timer.h"
|
|
#include "common.h"
|
|
#include "shell.h"
|
|
#include "themes.h"
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
|
|
// Global timer state
|
|
static struct {
|
|
BOOL is_active; // Whether timer is currently running
|
|
time_t end_time; // When timer will expire (in seconds since epoch)
|
|
char display_text[64]; // Current timer display text
|
|
char session_name[128]; // Optional session name
|
|
pthread_t timer_thread; // Timer thread ID
|
|
BOOL should_exit; // Flag to signal thread exit
|
|
BOOL is_temporarily_hidden; // Flag to hide timer while running external programs
|
|
} timer_state = {FALSE, 0, "", "", 0, FALSE, FALSE};
|
|
|
|
/**
|
|
* Update timer display text based on remaining time
|
|
*/
|
|
static void update_timer_display(void) {
|
|
if (!timer_state.is_active) {
|
|
strcpy(timer_state.display_text, "");
|
|
return;
|
|
}
|
|
|
|
// Calculate remaining time
|
|
time_t now = time(NULL);
|
|
int seconds_left = (int)difftime(timer_state.end_time, now);
|
|
|
|
if (seconds_left <= 0) {
|
|
// Timer has expired
|
|
if (strlen(timer_state.session_name) > 0) {
|
|
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
|
|
"Session '%s' complete!", timer_state.session_name);
|
|
} else {
|
|
strcpy(timer_state.display_text, "Timer complete!");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Format time as MM:SS
|
|
int minutes = seconds_left / 60;
|
|
int seconds = seconds_left % 60;
|
|
|
|
if (strlen(timer_state.session_name) > 0) {
|
|
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
|
|
"%s: %02d:%02d remaining", timer_state.session_name, minutes, seconds);
|
|
} else {
|
|
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
|
|
"Focus: %02d:%02d remaining", minutes, seconds);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Timer thread function
|
|
*/
|
|
static void *timer_thread_func(void *param) {
|
|
while (!timer_state.should_exit) {
|
|
// Update timer display
|
|
update_timer_display();
|
|
|
|
// Check if timer has expired
|
|
time_t now = time(NULL);
|
|
if (difftime(timer_state.end_time, now) <= 0) {
|
|
// Play a bell sound
|
|
printf("\a");
|
|
fflush(stdout);
|
|
|
|
// Update status bar with completion message
|
|
update_timer_display();
|
|
|
|
// Keep thread running to display completion message
|
|
}
|
|
|
|
// Sleep for 1 second
|
|
sleep(1);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Start a new countdown timer
|
|
*/
|
|
int start_countdown_timer(int seconds, const char *name) {
|
|
// Stop any existing timer
|
|
if (timer_state.is_active) {
|
|
stop_countdown_timer();
|
|
}
|
|
|
|
// Set timer parameters
|
|
timer_state.end_time = time(NULL) + seconds;
|
|
timer_state.is_active = TRUE;
|
|
timer_state.should_exit = FALSE;
|
|
timer_state.is_temporarily_hidden = FALSE;
|
|
|
|
// Set optional session name
|
|
if (name && *name) {
|
|
strncpy(timer_state.session_name, name, sizeof(timer_state.session_name) - 1);
|
|
timer_state.session_name[sizeof(timer_state.session_name) - 1] = '\0';
|
|
} else {
|
|
timer_state.session_name[0] = '\0';
|
|
}
|
|
|
|
// Initialize display text
|
|
update_timer_display();
|
|
|
|
// Create timer thread
|
|
int ret = pthread_create(&timer_state.timer_thread, NULL, timer_thread_func, NULL);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "Failed to create timer thread: %s\n", strerror(ret));
|
|
timer_state.is_active = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
// Detach thread so it cleans up automatically
|
|
pthread_detach(timer_state.timer_thread);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Stop the current countdown timer
|
|
*/
|
|
void stop_countdown_timer() {
|
|
if (!timer_state.is_active) {
|
|
return;
|
|
}
|
|
|
|
// Signal thread to exit
|
|
timer_state.should_exit = TRUE;
|
|
|
|
// Wait for thread to terminate (with timeout)
|
|
// Use a more portable approach since pthread_timedjoin_np is GNU-specific
|
|
|
|
// Set a timeout of 2 seconds
|
|
struct timespec timeout;
|
|
timeout.tv_sec = 2;
|
|
timeout.tv_nsec = 0;
|
|
|
|
// Sleep for the timeout period - the thread will keep running
|
|
// and clean itself up since we detached it
|
|
nanosleep(&timeout, NULL);
|
|
|
|
// Reset timer state
|
|
timer_state.is_active = FALSE;
|
|
timer_state.display_text[0] = '\0';
|
|
timer_state.session_name[0] = '\0';
|
|
}
|
|
|
|
/**
|
|
* Check if a timer is currently active
|
|
*/
|
|
BOOL is_timer_active() {
|
|
return timer_state.is_active;
|
|
}
|
|
|
|
/**
|
|
* Get the current timer display text
|
|
*/
|
|
const char *get_timer_display() {
|
|
if (timer_state.is_temporarily_hidden || !timer_state.is_active) {
|
|
return "";
|
|
}
|
|
return timer_state.display_text;
|
|
}
|
|
|
|
/**
|
|
* Temporarily hide the timer display
|
|
*/
|
|
void hide_timer_display(void) {
|
|
timer_state.is_temporarily_hidden = TRUE;
|
|
}
|
|
|
|
/**
|
|
* Restore the timer display after it was hidden
|
|
*/
|
|
void show_timer_display(void) {
|
|
timer_state.is_temporarily_hidden = FALSE;
|
|
}
|
|
|
|
/**
|
|
* Parse time string in format "XhYmZs" to seconds
|
|
*/
|
|
static int parse_time_string(const char *time_str) {
|
|
if (!time_str || !*time_str) {
|
|
return 0;
|
|
}
|
|
|
|
int total_seconds = 0;
|
|
char *str = strdup(time_str);
|
|
if (!str) {
|
|
return 0;
|
|
}
|
|
|
|
char *pos = str;
|
|
char *next_pos;
|
|
int value;
|
|
|
|
while (*pos) {
|
|
// Skip non-digits
|
|
while (*pos && !isdigit(*pos)) {
|
|
pos++;
|
|
}
|
|
|
|
if (!*pos) {
|
|
break;
|
|
}
|
|
|
|
// Parse number
|
|
value = (int)strtol(pos, &next_pos, 10);
|
|
if (pos == next_pos) {
|
|
break;
|
|
}
|
|
|
|
// Skip whitespace
|
|
pos = next_pos;
|
|
while (*pos && isspace(*pos)) {
|
|
pos++;
|
|
}
|
|
|
|
// Check unit
|
|
if (*pos == 'h' || *pos == 'H') {
|
|
total_seconds += value * 3600;
|
|
} else if (*pos == 'm' || *pos == 'M') {
|
|
total_seconds += value * 60;
|
|
} else if (*pos == 's' || *pos == 'S') {
|
|
total_seconds += value;
|
|
} else {
|
|
// No unit specified, assume minutes
|
|
total_seconds += value * 60;
|
|
}
|
|
|
|
// Move to next part
|
|
if (*pos) {
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
free(str);
|
|
return total_seconds;
|
|
}
|
|
|
|
/**
|
|
* Command handler for the focus timer command
|
|
*/
|
|
int lsh_focus_timer(char **args) {
|
|
if (!args[1]) {
|
|
// No arguments - show help
|
|
printf("Usage: focus_timer [start|stop] [duration] [session name]\n");
|
|
printf("Examples:\n");
|
|
printf(" focus_timer start 25m \"Coding session\" # Start a 25-minute timer\n");
|
|
printf(" focus_timer start 1h30m # Start a 1 hour 30 minute timer\n");
|
|
printf(" focus_timer stop # Stop the current timer\n");
|
|
printf("Current status: %s\n",
|
|
timer_state.is_active ? timer_state.display_text : "No active timer");
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(args[1], "stop") == 0) {
|
|
if (timer_state.is_active) {
|
|
printf("Stopping timer...\n");
|
|
stop_countdown_timer();
|
|
} else {
|
|
printf("No active timer to stop.\n");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(args[1], "start") == 0) {
|
|
if (!args[2]) {
|
|
printf("Error: Duration required. Example: focus_timer start 25m\n");
|
|
return 1;
|
|
}
|
|
|
|
int seconds = parse_time_string(args[2]);
|
|
if (seconds <= 0) {
|
|
printf("Error: Invalid duration format. Examples: 25m, 1h30m, 90s\n");
|
|
return 1;
|
|
}
|
|
|
|
const char *session_name = args[3] ? args[3] : "";
|
|
if (start_countdown_timer(seconds, session_name)) {
|
|
printf("Timer started: %s\n", timer_state.display_text);
|
|
} else {
|
|
printf("Failed to start timer.\n");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
printf("Unknown timer command: %s\n", args[1]);
|
|
return 1;
|
|
} |