4 * (c) 2002, 2010, 2013 Martin Mares <mj@ucw.cz>
5 * (c) 2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
20 #define UNUSED __attribute__((unused))
22 static guint second_timer;
23 static char old_text[16];
24 static GtkWidget *win, *hbox1, *vbox1, *timebox, *namebox, *togglebutton1;
25 static PangoFontDescription *timebox_font;
26 static bool autoresize_timebox_font = false;
27 static int timebox_font_size;
28 static time_t alarm_time;
29 static char *run_notify;
30 static char *default_name = "Tea";
32 static int notify_pid = 0;
33 static gint notify_watch = 0;
34 static int kill_notify_by = 0;
39 if (kill_notify_by && notify_pid)
41 kill(notify_pid, kill_notify_by);
46 g_source_remove(notify_watch);
58 static int // return pid of new process or 0 if failed
59 expand_and_exec(char *cmd)
61 GString *expanded_cmd = g_string_new("");
64 for (int i=0; cmd[i]; i++)
66 if (cmd[i]=='%' && cmd[i+1]=='%')
69 g_string_append_c(expanded_cmd, '%');
72 if (cmd[i]=='%' && cmd[i+1]=='n')
75 const gchar *name = gtk_entry_get_text(GTK_ENTRY(namebox));
76 g_string_append(expanded_cmd, name);
79 g_string_append_c(expanded_cmd, cmd[i]);
87 g_shell_parse_argv(expanded_cmd->str, &argc, &argv, &err);
88 g_string_free(expanded_cmd, 1);
91 fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
95 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err);
99 fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
107 notify_exit(GPid pid, gint status UNUSED, gpointer data UNUSED)
109 if (notify_pid == pid)
114 it_tolls_for_thee(void)
121 g_source_remove(notify_watch);
122 notify_pid = expand_and_exec(run_notify);
123 notify_watch = g_child_watch_add(notify_pid, notify_exit, NULL);
132 on_second_timeout(gpointer data UNUSED)
135 time_t now = time(NULL);
136 int delta = alarm_time - now;
144 if (delta >= 100*60*60)
145 delta = 100*60*60 - 1;
147 sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60);
149 sprintf(buf, "%s%02d:%02d:%02d", sign, delta/3600, (delta%3600)/60, delta%60);
150 gtk_entry_set_text(GTK_ENTRY(timebox), buf);
151 if (now >= alarm_time)
159 on_box_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED)
161 if (!strcmp(ev->string, "\r"))
162 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), !GTK_TOGGLE_BUTTON(togglebutton1)->active);
163 else if (!strcmp(ev->string, "\033"))
180 while (*c >= '0' && *c <= '9')
181 m = 10*m + *c++ - '0';
200 on_togglebutton1_toggled(GtkToggleButton *togglebutton, gpointer user_data UNUSED)
202 if (togglebutton->active)
205 strcpy(old_text, gtk_entry_get_text(GTK_ENTRY(timebox)));
206 t = parse_time(old_text);
209 gtk_toggle_button_set_active(togglebutton, 0);
212 alarm_time = time(NULL) + t;
213 gtk_entry_set_editable(GTK_ENTRY(timebox), 0);
214 on_second_timeout(NULL);
215 second_timer = gtk_timeout_add(1000, on_second_timeout, NULL);
221 gtk_timeout_remove(second_timer);
225 gtk_entry_set_text(GTK_ENTRY(timebox), old_text);
226 gtk_entry_set_editable(GTK_ENTRY(timebox), 1);
227 gtk_widget_grab_focus(timebox);
232 on_window_remove(GtkContainer *container UNUSED, GtkWidget *widget UNUSED, gpointer user_data UNUSED)
238 set_timebox_font_size(int new_size)
240 if (new_size == timebox_font_size)
242 timebox_font_size = new_size;
244 pango_font_description_set_size(timebox_font, timebox_font_size*PANGO_SCALE);
245 gtk_widget_modify_font(timebox, timebox_font);
249 on_window_resized(GtkWidget *widget UNUSED, GdkRectangle *rect, gpointer user_data UNUSED)
251 int window_width = rect->width;
252 int window_height = rect->height;
254 if (autoresize_timebox_font)
256 // Binary search for optimal font size
257 int beg = 12, end = 1024;
260 int mid = (beg + end) / 2;
261 pango_font_description_set_size(timebox_font, mid*PANGO_SCALE);
262 PangoFontMetrics *metric = pango_context_get_metrics(gtk_widget_get_pango_context(timebox), timebox_font, NULL);
263 int width = pango_font_metrics_get_approximate_digit_width(metric) * 8.3 / PANGO_SCALE;
264 int height = pango_font_metrics_get_height(metric) / PANGO_SCALE;
265 if (width > window_width - 50 || height > window_height - 45)
271 set_timebox_font_size(beg);
278 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
279 gtk_window_set_title(GTK_WINDOW (win), "Tea Timer");
280 gtk_window_set_policy(GTK_WINDOW (win), TRUE, TRUE, TRUE);
282 hbox1 = gtk_hbox_new(FALSE, 0);
283 gtk_widget_show(hbox1);
284 gtk_container_add(GTK_CONTAINER (win), hbox1);
286 vbox1 = gtk_vbox_new(FALSE, 0);
287 gtk_widget_show(vbox1);
288 gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0);
290 namebox = gtk_entry_new_with_max_length(30);
291 gtk_widget_show(namebox);
292 gtk_box_pack_start(GTK_BOX(vbox1), namebox, TRUE, TRUE, 0);
293 gtk_entry_set_text(GTK_ENTRY(namebox), default_name);
295 timebox = gtk_entry_new_with_max_length(9);
296 gtk_widget_show(timebox);
297 gtk_box_pack_start(GTK_BOX(vbox1), timebox, TRUE, TRUE, 0);
298 gtk_entry_set_text(GTK_ENTRY(timebox), "00:00");
299 timebox_font = pango_font_description_from_string("Monospace");
300 set_timebox_font_size(12);
302 togglebutton1 = gtk_toggle_button_new_with_label("Run");
303 gtk_widget_show(togglebutton1);
304 gtk_box_pack_start(GTK_BOX(hbox1), togglebutton1, FALSE, FALSE, 0);
306 gtk_signal_connect(GTK_OBJECT(win), "remove", GTK_SIGNAL_FUNC(on_window_remove), NULL);
307 gtk_signal_connect(GTK_OBJECT(win), "size-allocate", GTK_SIGNAL_FUNC(on_window_resized), NULL);
308 gtk_signal_connect(GTK_OBJECT(namebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
309 gtk_signal_connect(GTK_OBJECT(timebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
310 gtk_signal_connect(GTK_OBJECT(togglebutton1), "toggled", GTK_SIGNAL_FUNC(on_togglebutton1_toggled), NULL);
312 gtk_widget_grab_focus(timebox);
314 // Do not focus button
315 GList *focus_chain = NULL;
316 focus_chain = g_list_append(focus_chain, vbox1);
317 gtk_container_set_focus_chain(GTK_CONTAINER (hbox1), focus_chain);
319 gtk_widget_show(win);
322 static const char short_opts[] = "r:n:k:a";
324 static const struct option long_opts[] = {
325 { "run", required_argument, NULL, 'r' },
326 { "kill", required_argument, NULL, 'k' },
327 { "timer-name", required_argument, NULL, 'n' },
328 { "auto-resize", no_argument, NULL, 'a' },
329 { NULL, 0, NULL, 0 },
335 fprintf(stderr, "Usage: teatimer [<options>] [<mm:ss>]\n\n\
337 -r, --run=<cmd>\t\tRun a given program when the tea is ready\n\
338 \t\t\t\t%%d will be expanded to timer name\n\
339 \t\t\t\t%%%% will be expanded to %%\n\
340 -k, --kill=<int>\tKill run program by a given signal when the timer is stopped\n\
341 -n, --timer-name=<str>\tFill name box with <str>\n\
342 -a, --auto-resize\tAutomatically resize font to fit the box\n\
347 void sig_handler(int signo)
349 if (signo == SIGINT || signo == SIGTERM)
357 main(int argc, char **argv)
360 gtk_init(&argc, &argv);
362 signal(SIGINT, sig_handler);
363 signal(SIGTERM, sig_handler);
366 while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
373 kill_notify_by = atoi(optarg);
376 default_name = optarg;
379 autoresize_timebox_font = true;
384 if (optind != argc && optind+1 != argc)
390 gtk_entry_set_text(GTK_ENTRY(timebox), argv[optind]);
391 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), 1);