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