4 * (c) 2002, 2010, 2013 Martin Mares <mj@ucw.cz>
5 * (c) 2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
19 #define UNUSED __attribute__((unused))
21 static guint second_timer;
22 static char old_text[16];
23 static GtkWidget *win, *hbox1, *vbox1, *timebox, *namebox, *togglebutton1;
24 static time_t alarm_time;
25 static char *run_notify;
26 static char *default_name = "Tea";
28 static int notify_pid = 0;
29 static gint notify_watch = 0;
30 static int kill_notify_by = 0;
35 if (kill_notify_by && notify_pid)
37 kill(notify_pid, kill_notify_by);
42 g_source_remove(notify_watch);
54 static int // return pid of new process or 0 if failed
55 expand_and_exec(char *cmd)
57 GString *expanded_cmd = g_string_new("");
60 for (int i=0; cmd[i]; i++)
62 if (cmd[i]=='%' && cmd[i+1]=='%')
65 g_string_append_c(expanded_cmd, '%');
68 if (cmd[i]=='%' && cmd[i+1]=='n')
71 const gchar *name = gtk_entry_get_text(GTK_ENTRY(namebox));
72 g_string_append(expanded_cmd, name);
75 g_string_append_c(expanded_cmd, cmd[i]);
83 g_shell_parse_argv(expanded_cmd->str, &argc, &argv, &err);
84 g_string_free(expanded_cmd, 1);
87 fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
91 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err);
95 fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
103 notify_exit(GPid pid, gint status UNUSED, gpointer data UNUSED)
105 if (notify_pid == pid)
110 it_tolls_for_thee(void)
117 g_source_remove(notify_watch);
118 notify_pid = expand_and_exec(run_notify);
119 notify_watch = g_child_watch_add(notify_pid, notify_exit, NULL);
128 on_second_timeout(gpointer data UNUSED)
131 time_t now = time(NULL);
132 int delta = alarm_time - now;
140 if (delta >= 100*60*60)
141 delta = 100*60*60 - 1;
143 sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60);
145 sprintf(buf, "%s%02d:%02d:%02d", sign, delta/3600, (delta%3600)/60, delta%60);
146 gtk_entry_set_text(GTK_ENTRY(timebox), buf);
147 if (now >= alarm_time)
155 on_box_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED)
157 if (!strcmp(ev->string, "\r"))
158 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), !GTK_TOGGLE_BUTTON(togglebutton1)->active);
159 else if (!strcmp(ev->string, "\033"))
176 while (*c >= '0' && *c <= '9')
177 m = 10*m + *c++ - '0';
196 on_togglebutton1_toggled(GtkToggleButton *togglebutton, gpointer user_data UNUSED)
198 if (togglebutton->active)
201 strcpy(old_text, gtk_entry_get_text(GTK_ENTRY(timebox)));
202 t = parse_time(old_text);
205 gtk_toggle_button_set_active(togglebutton, 0);
208 alarm_time = time(NULL) + t;
209 gtk_entry_set_editable(GTK_ENTRY(timebox), 0);
210 on_second_timeout(NULL);
211 second_timer = gtk_timeout_add(1000, on_second_timeout, NULL);
217 gtk_timeout_remove(second_timer);
221 gtk_entry_set_text(GTK_ENTRY(timebox), old_text);
222 gtk_entry_set_editable(GTK_ENTRY(timebox), 1);
223 gtk_widget_grab_focus(timebox);
228 on_window_remove(GtkContainer *container UNUSED, GtkWidget *widget UNUSED, gpointer user_data UNUSED)
236 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
237 gtk_window_set_title(GTK_WINDOW (win), "Tea Timer");
238 gtk_window_set_policy(GTK_WINDOW (win), TRUE, TRUE, TRUE);
240 hbox1 = gtk_hbox_new(FALSE, 0);
241 gtk_widget_show(hbox1);
242 gtk_container_add(GTK_CONTAINER (win), hbox1);
244 vbox1 = gtk_vbox_new(FALSE, 0);
245 gtk_widget_show(vbox1);
246 gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0);
248 namebox = gtk_entry_new_with_max_length(30);
249 gtk_widget_show(namebox);
250 gtk_box_pack_start(GTK_BOX(vbox1), namebox, TRUE, TRUE, 0);
251 gtk_entry_set_text(GTK_ENTRY(namebox), default_name);
253 timebox = gtk_entry_new_with_max_length(9);
254 gtk_widget_show(timebox);
255 gtk_box_pack_start(GTK_BOX(vbox1), timebox, TRUE, TRUE, 0);
256 gtk_entry_set_text(GTK_ENTRY(timebox), "00:00");
258 togglebutton1 = gtk_toggle_button_new_with_label("Run");
259 gtk_widget_show(togglebutton1);
260 gtk_box_pack_start(GTK_BOX(hbox1), togglebutton1, FALSE, FALSE, 0);
262 gtk_signal_connect(GTK_OBJECT(win), "remove", GTK_SIGNAL_FUNC(on_window_remove), NULL);
263 gtk_signal_connect(GTK_OBJECT(namebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
264 gtk_signal_connect(GTK_OBJECT(timebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
265 gtk_signal_connect(GTK_OBJECT(togglebutton1), "toggled", GTK_SIGNAL_FUNC(on_togglebutton1_toggled), NULL);
267 gtk_widget_grab_focus(timebox);
269 // Do not focus button
270 GList *focus_chain = NULL;
271 focus_chain = g_list_append(focus_chain, vbox1);
272 gtk_container_set_focus_chain(GTK_CONTAINER (hbox1), focus_chain);
274 gtk_widget_show(win);
277 static const char short_opts[] = "r:n:k:";
279 static const struct option long_opts[] = {
280 { "run", required_argument, NULL, 'r' },
281 { "kill", required_argument, NULL, 'k' },
282 { "timer-name", required_argument, NULL, 'n' },
283 { NULL, 0, NULL, 0 },
289 fprintf(stderr, "Usage: teatimer [<options>] [<mm:ss>]\n\n\
291 -r, --run=<cmd>\t\tRun a given program when the tea is ready\n\
292 \t\t\t\t%%d will be expanded to timer name\n\
293 \t\t\t\t%%%% will be expanded to %%\n\
294 -k, --kill=<int>\tKill run program by a given signal when the timer is stopped\n\
295 -n, --timer-name=<str>\tFill name box with <str>\n\
300 void sig_handler(int signo)
302 if (signo == SIGINT || signo == SIGTERM)
310 main(int argc, char **argv)
313 gtk_init(&argc, &argv);
315 signal(SIGINT, sig_handler);
316 signal(SIGTERM, sig_handler);
319 while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
326 kill_notify_by = atoi(optarg);
329 default_name = optarg;
334 if (optind != argc && optind+1 != argc)
340 gtk_entry_set_text(GTK_ENTRY(timebox), argv[optind]);
341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), 1);