X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=teatimer.c;h=c58f7896aab1cb699932eae919c5eddf43915571;hb=4f00ec2b0006b2eb6170ae2d29a9e1305dfea294;hp=881a8c0fbf2e9c37666b42fd6a36102229ad64c8;hpb=8336b0374b6cb7d97dbc95ad45c667df67c0593d;p=teatimer.git diff --git a/teatimer.c b/teatimer.c index 881a8c0..c58f789 100644 --- a/teatimer.c +++ b/teatimer.c @@ -1,7 +1,8 @@ /* * Trivial Tea Timer * - * (c) 2002, 2010 Martin Mares + * (c) 2002, 2010, 2013 Martin Mares + * (c) 2021 Jiri Kalvoda * * GPL'ed */ @@ -9,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,26 +20,115 @@ #define UNUSED __attribute__((unused)) static guint second_timer; -static char old_text[8]; -static GtkWidget *win, *hbox1, *timebox, *togglebutton1; +static char old_text[16]; +static GtkWidget *win, *hbox1, *vbox1, *timebox, *namebox, *togglebutton1; +static PangoFontDescription *timebox_font; +static bool autoresize_timebox_font = false; +static int timebox_font_size; static time_t alarm_time; -static char *run_cmd; +static char *run_notify; +static char *default_name = "Tea"; static int expired; +static int notify_pid = 0; +static gint notify_watch = 0; +static int kill_notify_by = 0; static void -it_tolls_for_thee(void) +kill_notify(void) { - if (run_cmd) + if (kill_notify_by && notify_pid) { - if (!expired) + kill(notify_pid, kill_notify_by); + notify_pid = 0; + } + if (notify_watch) + { + g_source_remove(notify_watch); + notify_watch = 0; + } +} + +static void +quit(void) +{ + kill_notify(); + gtk_main_quit(); +} + +static int // return pid of new process or 0 if failed +expand_and_exec(char *cmd) +{ + GError *err = NULL; + gint argc; + gchar ** argv = NULL; + int pid; + + g_shell_parse_argv(cmd, &argc, &argv, &err); + GString ** expanded_argv = malloc(sizeof(expanded_argv[0])*argc); + if (!expanded_argv) + return 0; + gchar ** expanded_argv_gchar = malloc(sizeof(expanded_argv_gchar[0])*(argc+1)); + if (err) + { + fprintf(stderr, "teatimer: Unable to parse command: %s\n", err->message); + g_error_free(err); + return 0; + } + if(!expanded_argv_gchar) return 0; + for (int i=0; imessage); - g_error_free(err); + j++; + g_string_append_c(expanded_argv[i], '%'); } + else + if (argv[i][j]=='%' && argv[i][j+1]=='n') + { + j++; + const gchar * name = gtk_entry_get_text(GTK_ENTRY(namebox)); + g_string_append(expanded_argv[i], name); + } + else + g_string_append_c(expanded_argv[i], argv[i][j]); + } + expanded_argv_gchar[i] = expanded_argv[i]->str; + } + expanded_argv_gchar[argc]=NULL; + g_spawn_async(NULL, expanded_argv_gchar, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err); + g_strfreev(argv); + for (int i=0; imessage); + g_error_free(err); + return 0; + } + return pid; +} + +static void +notify_exit(GPid pid, gint status UNUSED, gpointer data UNUSED) +{ + if (notify_pid == pid) + notify_pid = 0; +} + +static void +it_tolls_for_thee(void) +{ + if (run_notify) + { + if (!expired) + { + if(notify_watch) + g_source_remove(notify_watch); + notify_pid = expand_and_exec(run_notify); + notify_watch = g_child_watch_add(notify_pid, notify_exit, NULL); expired = 1; } } @@ -48,7 +139,7 @@ it_tolls_for_thee(void) static gint on_second_timeout(gpointer data UNUSED) { - char buf[8]; + char buf[16]; time_t now = time(NULL); int delta = alarm_time - now; char *sign = ""; @@ -58,9 +149,12 @@ on_second_timeout(gpointer data UNUSED) sign = "-"; delta = -delta; } - if (delta >= 6000) - delta = 5999; - sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60); + if (delta >= 100*60*60) + delta = 100*60*60 - 1; + if (delta < 60*60) + sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60); + else + sprintf(buf, "%s%02d:%02d:%02d", sign, delta/3600, (delta%3600)/60, delta%60); gtk_entry_set_text(GTK_ENTRY(timebox), buf); if (now >= alarm_time) it_tolls_for_thee(); @@ -70,12 +164,12 @@ on_second_timeout(gpointer data UNUSED) } static gint -on_timebox_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED) +on_box_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED) { if (!strcmp(ev->string, "\r")) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), !GTK_TOGGLE_BUTTON(togglebutton1)->active); else if (!strcmp(ev->string, "\033")) - gtk_main_quit(); + quit(); return FALSE; } @@ -83,28 +177,29 @@ static int parse_time(char *c) { int t = 0; + int parts = 0; - while (*c && *c != ':') + while (*c) { - if (*c >= '0' && *c <= '9') - t = 10*t + *c++ - '0'; - else + parts++; + if (parts > 3) return -1; - } - if (*c) - { int m = 0; - c++; - while (*c) + while (*c >= '0' && *c <= '9') + m = 10*m + *c++ - '0'; + t += m; + if (*c == ':') { - if (*c >= '0' && *c <= '9') - m = 10*m + *c++ - '0'; - else - return -1; + c++; + t = 60*t; } - t = 60*t + m; + else if (*c) + return -1; } - if (t >= 6000) + + if (!parts) + return -1; + if (t >= 100*60*60) return -1; return t; } @@ -134,51 +229,111 @@ on_togglebutton1_toggled(GtkToggleButton *togglebutton, gpointer user_data UNUSE gtk_timeout_remove(second_timer); second_timer = 0; } + kill_notify(); gtk_entry_set_text(GTK_ENTRY(timebox), old_text); gtk_entry_set_editable(GTK_ENTRY(timebox), 1); - gtk_widget_grab_focus (timebox); + gtk_widget_grab_focus(timebox); } } static void on_window_remove(GtkContainer *container UNUSED, GtkWidget *widget UNUSED, gpointer user_data UNUSED) { - gtk_main_quit(); + quit(); +} + +static void +set_timebox_font_size(int new_size) +{ + if (new_size == timebox_font_size) + return; + timebox_font_size = new_size; + + pango_font_description_set_size(timebox_font, timebox_font_size*PANGO_SCALE); + gtk_widget_modify_font(timebox, timebox_font); +} + +static void +on_window_resized(GtkWidget *widget UNUSED, GdkRectangle *rect, gpointer user_data UNUSED) +{ + int window_width = rect->width; + int window_height = rect->height; + + if (autoresize_timebox_font) + { + // Binary search for optimal font size + int beg = 12, end = 1024; + while (beg < end) + { + int mid = (beg + end) / 2; + pango_font_description_set_size(timebox_font, mid*PANGO_SCALE); + PangoFontMetrics *metric = pango_context_get_metrics(gtk_widget_get_pango_context(timebox), timebox_font, NULL); + int width = pango_font_metrics_get_approximate_digit_width(metric) * 8.3 / PANGO_SCALE; + int height = pango_font_metrics_get_height(metric) / PANGO_SCALE; + if (width > window_width - 50 || height > window_height - 45) + end = mid; + else + beg = mid + 1; + } + + set_timebox_font_size(beg); + } } static void open_window(void) { - win = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title (GTK_WINDOW (win), "Tea Timer"); - gtk_window_set_policy (GTK_WINDOW (win), TRUE, TRUE, TRUE); + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW (win), "Tea Timer"); + gtk_window_set_policy(GTK_WINDOW (win), TRUE, TRUE, TRUE); - hbox1 = gtk_hbox_new (FALSE, 0); - gtk_widget_show (hbox1); - gtk_container_add (GTK_CONTAINER (win), hbox1); + hbox1 = gtk_hbox_new(FALSE, 0); + gtk_widget_show(hbox1); + gtk_container_add(GTK_CONTAINER (win), hbox1); - timebox = gtk_entry_new_with_max_length (6); - gtk_widget_show (timebox); - gtk_box_pack_start (GTK_BOX (hbox1), timebox, TRUE, TRUE, 0); - gtk_entry_set_text (GTK_ENTRY (timebox), "00:00"); + vbox1 = gtk_vbox_new(FALSE, 0); + gtk_widget_show(vbox1); + gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0); - togglebutton1 = gtk_toggle_button_new_with_label ("Run"); - gtk_widget_show (togglebutton1); - gtk_box_pack_start (GTK_BOX (hbox1), togglebutton1, FALSE, FALSE, 0); + namebox = gtk_entry_new_with_max_length(30); + gtk_widget_show(namebox); + gtk_box_pack_start(GTK_BOX(vbox1), namebox, TRUE, TRUE, 0); + gtk_entry_set_text(GTK_ENTRY(namebox), default_name); - gtk_signal_connect(GTK_OBJECT (win), "remove", GTK_SIGNAL_FUNC (on_window_remove), NULL); - gtk_signal_connect(GTK_OBJECT (timebox), "key_press_event", GTK_SIGNAL_FUNC (on_timebox_key), NULL); - gtk_signal_connect(GTK_OBJECT (togglebutton1), "toggled", GTK_SIGNAL_FUNC (on_togglebutton1_toggled), NULL); + timebox = gtk_entry_new_with_max_length(9); + gtk_widget_show(timebox); + gtk_box_pack_start(GTK_BOX(vbox1), timebox, TRUE, TRUE, 0); + gtk_entry_set_text(GTK_ENTRY(timebox), "00:00"); + timebox_font = pango_font_description_from_string("Monospace"); + set_timebox_font_size(12); - gtk_widget_grab_focus (timebox); + togglebutton1 = gtk_toggle_button_new_with_label("Run"); + gtk_widget_show(togglebutton1); + gtk_box_pack_start(GTK_BOX(hbox1), togglebutton1, FALSE, FALSE, 0); + + gtk_signal_connect(GTK_OBJECT(win), "remove", GTK_SIGNAL_FUNC(on_window_remove), NULL); + gtk_signal_connect(GTK_OBJECT(win), "size-allocate", GTK_SIGNAL_FUNC(on_window_resized), NULL); + gtk_signal_connect(GTK_OBJECT(namebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL); + gtk_signal_connect(GTK_OBJECT(timebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL); + gtk_signal_connect(GTK_OBJECT(togglebutton1), "toggled", GTK_SIGNAL_FUNC(on_togglebutton1_toggled), NULL); + + gtk_widget_grab_focus(timebox); + + // Do not focus button + GList *focus_chain = NULL; + focus_chain = g_list_append(focus_chain, vbox1); + gtk_container_set_focus_chain(GTK_CONTAINER (hbox1), focus_chain); gtk_widget_show(win); } -static const char short_opts[] = "r:"; +static const char short_opts[] = "r:n:k:a"; static const struct option long_opts[] = { { "run", required_argument, NULL, 'r' }, + { "kill", required_argument, NULL, 'k' }, + { "timer-name", required_argument, NULL, 'n' }, + { "auto-resize", no_argument, NULL, 'a' }, { NULL, 0, NULL, 0 }, }; @@ -188,22 +343,48 @@ usage(void) fprintf(stderr, "Usage: teatimer [] []\n\n\ Options:\n\ -r, --run=\t\tRun a given program when the tea is ready\n\ +\t\t\t\t%%d will be expanded to timer name\n\ +\t\t\t\t%%%% will be expanded to %%\n\ +-k, --kill=\tKill run program by a given signal when the timer is stopped\n\ +-n, --timer-name=\tFill name box with \n\ +-a, --auto-resize\tAutomatically resize font to fit the box\n\ "); exit(1); } +void sig_handler(int signo) +{ + if (signo == SIGINT || signo == SIGTERM) + { + kill_notify(); + exit(0); + } +} + int main(int argc, char **argv) { gtk_set_locale(); gtk_init(&argc, &argv); + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + int opt; while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0) switch (opt) { case 'r': - run_cmd = optarg; + run_notify = optarg; + break; + case 'k': + kill_notify_by = atoi(optarg); + break; + case 'n': + default_name = optarg; + break; + case 'a': + autoresize_timebox_font = true; break; default: usage();