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