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