]> mj.ucw.cz Git - teatimer.git/blob - teatimer.c
b21d15d57cb2551d96da6364db92d46002d43003
[teatimer.git] / teatimer.c
1 /*
2  *      Trivial Tea Timer
3  *
4  *      (c) 2002, 2010, 2013 Martin Mares <mj@ucw.cz>
5  *      (c) 2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
6  *
7  *      GPL'ed
8  */
9
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <time.h>
14 #include <getopt.h>
15
16 #include <glib.h>
17 #include <gtk/gtk.h>
18
19 #define UNUSED __attribute__((unused))
20
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_cmd;
26 static int expired;
27
28 static void
29 it_tolls_for_thee(void)
30 {
31   if (run_cmd)
32     {
33       if (!expired)
34         {
35           GError *err = NULL;
36           g_spawn_command_line_async(run_cmd, &err);
37           if (err)
38             {
39               fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
40               g_error_free(err);
41             }
42           expired = 1;
43         }
44     }
45   else
46     gdk_beep();
47 }
48
49 static gint
50 on_second_timeout(gpointer data UNUSED)
51 {
52   char buf[16];
53   time_t now = time(NULL);
54   int delta = alarm_time - now;
55   char *sign = "";
56
57   if (delta < 0)
58     {
59       sign = "-";
60       delta = -delta;
61     }
62   if (delta >= 100*60*60)
63     delta = 100*60*60 - 1;
64   if (delta < 60*60)
65     sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60);
66   else
67     sprintf(buf, "%s%02d:%02d:%02d", sign, delta/3600, (delta%3600)/60, delta%60);
68   gtk_entry_set_text(GTK_ENTRY(timebox), buf);
69   if (now >= alarm_time)
70     it_tolls_for_thee();
71   else
72     expired = 0;
73   return 1;
74 }
75
76 static gint
77 on_box_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED)
78 {
79   if (!strcmp(ev->string, "\r"))
80     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), !GTK_TOGGLE_BUTTON(togglebutton1)->active);
81   else if (!strcmp(ev->string, "\033"))
82     gtk_main_quit();
83   return FALSE;
84 }
85
86 static int
87 parse_time(char *c)
88 {
89   int t = 0;
90   int parts = 0;
91
92   while (*c)
93     {
94       parts++;
95       if (parts > 3)
96         return -1;
97       int m = 0;
98       while (*c >= '0' && *c <= '9')
99         m = 10*m + *c++ - '0';
100       t += m;
101       if (*c == ':')
102         {
103           c++;
104           t = 60*t;
105         }
106       else if (*c)
107         return -1;
108     }
109
110   if (!parts)
111     return -1;
112   if (t >= 100*60*60)
113     return -1;
114   return t;
115 }
116
117 static void
118 on_togglebutton1_toggled(GtkToggleButton *togglebutton, gpointer user_data UNUSED)
119 {
120   if (togglebutton->active)
121     {
122       int t;
123       strcpy(old_text, gtk_entry_get_text(GTK_ENTRY(timebox)));
124       t = parse_time(old_text);
125       if (t < 0)
126         {
127           gtk_toggle_button_set_active(togglebutton, 0);
128           return;
129         }
130       alarm_time = time(NULL) + t;
131       gtk_entry_set_editable(GTK_ENTRY(timebox), 0);
132       on_second_timeout(NULL);
133       second_timer = gtk_timeout_add(1000, on_second_timeout, NULL);
134     }
135   else
136     {
137       if (second_timer)
138         {
139           gtk_timeout_remove(second_timer);
140           second_timer = 0;
141         }
142       gtk_entry_set_text(GTK_ENTRY(timebox), old_text);
143       gtk_entry_set_editable(GTK_ENTRY(timebox), 1);
144       gtk_widget_grab_focus(timebox);
145     }
146 }
147
148 static void
149 on_window_remove(GtkContainer *container UNUSED, GtkWidget *widget UNUSED, gpointer user_data UNUSED)
150 {
151   gtk_main_quit();
152 }
153
154 static void
155 open_window(void)
156 {
157   win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
158   gtk_window_set_title(GTK_WINDOW (win), "Tea Timer");
159   gtk_window_set_policy(GTK_WINDOW (win), TRUE, TRUE, TRUE);
160
161   hbox1 = gtk_hbox_new(FALSE, 0);
162   gtk_widget_show(hbox1);
163   gtk_container_add(GTK_CONTAINER (win), hbox1);
164
165   vbox1 = gtk_vbox_new(FALSE, 0);
166   gtk_widget_show(vbox1);
167   gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0);
168
169   namebox = gtk_entry_new_with_max_length(30);
170   gtk_widget_show(namebox);
171   gtk_box_pack_start(GTK_BOX(vbox1), namebox, TRUE, TRUE, 0);
172   gtk_entry_set_text(GTK_ENTRY(namebox), "Tea");
173
174   timebox = gtk_entry_new_with_max_length(9);
175   gtk_widget_show(timebox);
176   gtk_box_pack_start(GTK_BOX(vbox1), timebox, TRUE, TRUE, 0);
177   gtk_entry_set_text(GTK_ENTRY(timebox), "00:00");
178
179   togglebutton1 = gtk_toggle_button_new_with_label("Run");
180   gtk_widget_show(togglebutton1);
181   gtk_box_pack_start(GTK_BOX(hbox1), togglebutton1, FALSE, FALSE, 0);
182
183   gtk_signal_connect(GTK_OBJECT(win), "remove", GTK_SIGNAL_FUNC(on_window_remove), NULL);
184   gtk_signal_connect(GTK_OBJECT(namebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
185   gtk_signal_connect(GTK_OBJECT(timebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
186   gtk_signal_connect(GTK_OBJECT(togglebutton1), "toggled", GTK_SIGNAL_FUNC(on_togglebutton1_toggled), NULL);
187
188   gtk_widget_grab_focus(timebox);
189
190   // Do not focus button
191   GList *focus_chain = NULL;
192   focus_chain = g_list_append(focus_chain, vbox1);
193   gtk_container_set_focus_chain(GTK_CONTAINER (hbox1), focus_chain);
194
195   gtk_widget_show(win);
196 }
197
198 static const char short_opts[] = "r:";
199
200 static const struct option long_opts[] = {
201   { "run",              required_argument,      NULL,   'r' },
202   { NULL,               0,                      NULL,   0   },
203 };
204
205 static void
206 usage(void)
207 {
208   fprintf(stderr, "Usage: teatimer [<options>] [<mm:ss>]\n\n\
209 Options:\n\
210 -r, --run=<cmd>\t\tRun a given program when the tea is ready\n\
211 ");
212   exit(1);
213 }
214
215 int
216 main(int argc, char **argv)
217 {
218   gtk_set_locale();
219   gtk_init(&argc, &argv);
220
221   int opt;
222   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
223     switch (opt)
224       {
225       case 'r':
226         run_cmd = optarg;
227         break;
228       default:
229         usage();
230       }
231   if (optind != argc && optind+1 != argc)
232     usage();
233
234   open_window();
235   if (optind < argc)
236     {
237       gtk_entry_set_text(GTK_ENTRY(timebox), argv[optind]);
238       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), 1);
239     }
240   gtk_main();
241   return 0;
242 }