]> mj.ucw.cz Git - teatimer.git/blobdiff - teatimer.c
Fix expansion in cmd
[teatimer.git] / teatimer.c
index 881a8c0fbf2e9c37666b42fd6a36102229ad64c8..c58f7896aab1cb699932eae919c5eddf43915571 100644 (file)
@@ -1,7 +1,8 @@
 /*
  *     Trivial Tea Timer
  *
- *     (c) 2002, 2010 Martin Mares <mj@ucw.cz>
+ *     (c) 2002, 2010, 2013 Martin Mares <mj@ucw.cz>
+ *     (c) 2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
  *
  *     GPL'ed
  */
@@ -9,6 +10,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <time.h>
 #include <getopt.h>
 
 #define UNUSED __attribute__((unused))
 
 static guint second_timer;
-static char old_text[8];
-static GtkWidget *win, *hbox1, *timebox, *togglebutton1;
+static char old_text[16];
+static GtkWidget *win, *hbox1, *vbox1, *timebox, *namebox, *togglebutton1;
+static PangoFontDescription *timebox_font;
+static bool autoresize_timebox_font = false;
+static int timebox_font_size;
 static time_t alarm_time;
-static char *run_cmd;
+static char *run_notify;
+static char *default_name = "Tea";
 static int expired;
+static int notify_pid = 0;
+static gint notify_watch = 0;
+static int kill_notify_by = 0;
 
 static void
-it_tolls_for_thee(void)
+kill_notify(void)
 {
-  if (run_cmd)
+  if (kill_notify_by && notify_pid)
     {
-      if (!expired)
+      kill(notify_pid, kill_notify_by);
+      notify_pid = 0;
+    }
+  if (notify_watch)
+    {
+      g_source_remove(notify_watch);
+      notify_watch = 0;
+    }
+}
+
+static void
+quit(void)
+{
+  kill_notify();
+  gtk_main_quit();
+}
+
+static int // return pid of new process or 0 if failed
+expand_and_exec(char *cmd)
+{
+  GError *err = NULL;
+  gint argc;
+  gchar ** argv = NULL;
+  int pid;
+
+  g_shell_parse_argv(cmd, &argc, &argv, &err);
+  GString ** expanded_argv = malloc(sizeof(expanded_argv[0])*argc);
+  if (!expanded_argv)
+    return 0;
+  gchar ** expanded_argv_gchar = malloc(sizeof(expanded_argv_gchar[0])*(argc+1));
+  if (err)
+    {
+      fprintf(stderr, "teatimer: Unable to parse command: %s\n", err->message);
+      g_error_free(err);
+      return 0;
+    }
+  if(!expanded_argv_gchar) return 0;
+  for (int i=0; i<argc; i++)
+    {
+      expanded_argv[i] = g_string_new("");
+      for (int j=0; argv[i][j]; j++)
        {
-         GError *err = NULL;
-         g_spawn_command_line_async(run_cmd, &err);
-         if (err)
+         if (argv[i][j]=='%' && argv[i][j+1]=='%')
            {
-             fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
-             g_error_free(err);
+             j++;
+             g_string_append_c(expanded_argv[i], '%');
            }
+         else
+         if (argv[i][j]=='%' && argv[i][j+1]=='n')
+           {
+             j++;
+             const gchar * name = gtk_entry_get_text(GTK_ENTRY(namebox));
+             g_string_append(expanded_argv[i], name);
+           }
+         else
+           g_string_append_c(expanded_argv[i], argv[i][j]);
+       }
+      expanded_argv_gchar[i] = expanded_argv[i]->str;
+    }
+  expanded_argv_gchar[argc]=NULL;
+  g_spawn_async(NULL, expanded_argv_gchar, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &pid, &err);
+  g_strfreev(argv);
+  for (int i=0; i<argc; i++)
+    g_string_free(expanded_argv[i], 1);
+  if (err)
+    {
+      fprintf(stderr, "teatimer: Unable to run command: %s\n", err->message);
+      g_error_free(err);
+      return 0;
+    }
+  return pid;
+}
+
+static void
+notify_exit(GPid pid, gint status UNUSED, gpointer data UNUSED)
+{
+  if (notify_pid == pid)
+    notify_pid = 0;
+}
+
+static void
+it_tolls_for_thee(void)
+{
+  if (run_notify)
+    {
+      if (!expired)
+       {
+         if(notify_watch)
+           g_source_remove(notify_watch);
+         notify_pid = expand_and_exec(run_notify);
+         notify_watch = g_child_watch_add(notify_pid, notify_exit, NULL);
          expired = 1;
        }
     }
@@ -48,7 +139,7 @@ it_tolls_for_thee(void)
 static gint
 on_second_timeout(gpointer data UNUSED)
 {
-  char buf[8];
+  char buf[16];
   time_t now = time(NULL);
   int delta = alarm_time - now;
   char *sign = "";
@@ -58,9 +149,12 @@ on_second_timeout(gpointer data UNUSED)
       sign = "-";
       delta = -delta;
     }
-  if (delta >= 6000)
-    delta = 5999;
-  sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60);
+  if (delta >= 100*60*60)
+    delta = 100*60*60 - 1;
+  if (delta < 60*60)
+    sprintf(buf, "%s%02d:%02d", sign, delta/60, delta%60);
+  else
+    sprintf(buf, "%s%02d:%02d:%02d", sign, delta/3600, (delta%3600)/60, delta%60);
   gtk_entry_set_text(GTK_ENTRY(timebox), buf);
   if (now >= alarm_time)
     it_tolls_for_thee();
@@ -70,12 +164,12 @@ on_second_timeout(gpointer data UNUSED)
 }
 
 static gint
-on_timebox_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED)
+on_box_key(GtkWidget *widget UNUSED, GdkEventKey *ev, gpointer user_data UNUSED)
 {
   if (!strcmp(ev->string, "\r"))
     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton1), !GTK_TOGGLE_BUTTON(togglebutton1)->active);
   else if (!strcmp(ev->string, "\033"))
-    gtk_main_quit();
+    quit();
   return FALSE;
 }
 
@@ -83,28 +177,29 @@ static int
 parse_time(char *c)
 {
   int t = 0;
+  int parts = 0;
 
-  while (*c && *c != ':')
+  while (*c)
     {
-      if (*c >= '0' && *c <= '9')
-       t = 10*t + *c++ - '0';
-      else
+      parts++;
+      if (parts > 3)
        return -1;
-    }
-  if (*c)
-    {
       int m = 0;
-      c++;
-      while (*c)
+      while (*c >= '0' && *c <= '9')
+       m = 10*m + *c++ - '0';
+      t += m;
+      if (*c == ':')
        {
-         if (*c >= '0' && *c <= '9')
-           m = 10*m + *c++ - '0';
-         else
-           return -1;
+         c++;
+         t = 60*t;
        }
-      t = 60*t + m;
+      else if (*c)
+       return -1;
     }
-  if (t >= 6000)
+
+  if (!parts)
+    return -1;
+  if (t >= 100*60*60)
     return -1;
   return t;
 }
@@ -134,51 +229,111 @@ on_togglebutton1_toggled(GtkToggleButton *togglebutton, gpointer user_data UNUSE
          gtk_timeout_remove(second_timer);
          second_timer = 0;
        }
+      kill_notify();
       gtk_entry_set_text(GTK_ENTRY(timebox), old_text);
       gtk_entry_set_editable(GTK_ENTRY(timebox), 1);
-      gtk_widget_grab_focus (timebox);
+      gtk_widget_grab_focus(timebox);
     }
 }
 
 static void
 on_window_remove(GtkContainer *container UNUSED, GtkWidget *widget UNUSED, gpointer user_data UNUSED)
 {
-  gtk_main_quit();
+  quit();
+}
+
+static void
+set_timebox_font_size(int new_size)
+{
+  if (new_size == timebox_font_size)
+    return;
+  timebox_font_size = new_size;
+
+  pango_font_description_set_size(timebox_font, timebox_font_size*PANGO_SCALE);
+  gtk_widget_modify_font(timebox, timebox_font);
+}
+
+static void
+on_window_resized(GtkWidget *widget UNUSED, GdkRectangle *rect, gpointer user_data UNUSED)
+{
+  int window_width = rect->width;
+  int window_height = rect->height;
+
+  if (autoresize_timebox_font)
+  {
+    // Binary search for optimal font size
+    int beg = 12, end = 1024;
+    while (beg < end)
+      {
+       int mid = (beg + end) / 2;
+       pango_font_description_set_size(timebox_font, mid*PANGO_SCALE);
+       PangoFontMetrics *metric = pango_context_get_metrics(gtk_widget_get_pango_context(timebox), timebox_font, NULL);
+       int width = pango_font_metrics_get_approximate_digit_width(metric) * 8.3 / PANGO_SCALE;
+       int height = pango_font_metrics_get_height(metric) / PANGO_SCALE;
+       if (width > window_width - 50 || height > window_height - 45)
+         end = mid;
+       else
+         beg = mid + 1;
+      }
+
+    set_timebox_font_size(beg);
+  }
 }
 
 static void
 open_window(void)
 {
-  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_title (GTK_WINDOW (win), "Tea Timer");
-  gtk_window_set_policy (GTK_WINDOW (win), TRUE, TRUE, TRUE);
+  win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title(GTK_WINDOW (win), "Tea Timer");
+  gtk_window_set_policy(GTK_WINDOW (win), TRUE, TRUE, TRUE);
 
-  hbox1 = gtk_hbox_new (FALSE, 0);
-  gtk_widget_show (hbox1);
-  gtk_container_add (GTK_CONTAINER (win), hbox1);
+  hbox1 = gtk_hbox_new(FALSE, 0);
+  gtk_widget_show(hbox1);
+  gtk_container_add(GTK_CONTAINER (win), hbox1);
 
-  timebox = gtk_entry_new_with_max_length (6);
-  gtk_widget_show (timebox);
-  gtk_box_pack_start (GTK_BOX (hbox1), timebox, TRUE, TRUE, 0);
-  gtk_entry_set_text (GTK_ENTRY (timebox), "00:00");
+  vbox1 = gtk_vbox_new(FALSE, 0);
+  gtk_widget_show(vbox1);
+  gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0);
 
-  togglebutton1 = gtk_toggle_button_new_with_label ("Run");
-  gtk_widget_show (togglebutton1);
-  gtk_box_pack_start (GTK_BOX (hbox1), togglebutton1, FALSE, FALSE, 0);
+  namebox = gtk_entry_new_with_max_length(30);
+  gtk_widget_show(namebox);
+  gtk_box_pack_start(GTK_BOX(vbox1), namebox, TRUE, TRUE, 0);
+  gtk_entry_set_text(GTK_ENTRY(namebox), default_name);
 
-  gtk_signal_connect(GTK_OBJECT (win), "remove", GTK_SIGNAL_FUNC (on_window_remove), NULL);
-  gtk_signal_connect(GTK_OBJECT (timebox), "key_press_event", GTK_SIGNAL_FUNC (on_timebox_key), NULL);
-  gtk_signal_connect(GTK_OBJECT (togglebutton1), "toggled", GTK_SIGNAL_FUNC (on_togglebutton1_toggled), NULL);
+  timebox = gtk_entry_new_with_max_length(9);
+  gtk_widget_show(timebox);
+  gtk_box_pack_start(GTK_BOX(vbox1), timebox, TRUE, TRUE, 0);
+  gtk_entry_set_text(GTK_ENTRY(timebox), "00:00");
+  timebox_font = pango_font_description_from_string("Monospace");
+  set_timebox_font_size(12);
 
-  gtk_widget_grab_focus (timebox);
+  togglebutton1 = gtk_toggle_button_new_with_label("Run");
+  gtk_widget_show(togglebutton1);
+  gtk_box_pack_start(GTK_BOX(hbox1), togglebutton1, FALSE, FALSE, 0);
+
+  gtk_signal_connect(GTK_OBJECT(win), "remove", GTK_SIGNAL_FUNC(on_window_remove), NULL);
+  gtk_signal_connect(GTK_OBJECT(win), "size-allocate", GTK_SIGNAL_FUNC(on_window_resized), NULL);
+  gtk_signal_connect(GTK_OBJECT(namebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
+  gtk_signal_connect(GTK_OBJECT(timebox), "key_press_event", GTK_SIGNAL_FUNC(on_box_key), NULL);
+  gtk_signal_connect(GTK_OBJECT(togglebutton1), "toggled", GTK_SIGNAL_FUNC(on_togglebutton1_toggled), NULL);
+
+  gtk_widget_grab_focus(timebox);
+
+  // Do not focus button
+  GList *focus_chain = NULL;
+  focus_chain = g_list_append(focus_chain, vbox1);
+  gtk_container_set_focus_chain(GTK_CONTAINER (hbox1), focus_chain);
 
   gtk_widget_show(win);
 }
 
-static const char short_opts[] = "r:";
+static const char short_opts[] = "r:n:k:a";
 
 static const struct option long_opts[] = {
   { "run",             required_argument,      NULL,   'r' },
+  { "kill",            required_argument,      NULL,   'k' },
+  { "timer-name",      required_argument,      NULL,   'n' },
+  { "auto-resize",     no_argument,    NULL,   'a' },
   { NULL,              0,                      NULL,   0   },
 };
 
@@ -188,22 +343,48 @@ usage(void)
   fprintf(stderr, "Usage: teatimer [<options>] [<mm:ss>]\n\n\
 Options:\n\
 -r, --run=<cmd>\t\tRun a given program when the tea is ready\n\
+\t\t\t\t%%d will be expanded to timer name\n\
+\t\t\t\t%%%% will be expanded to %%\n\
+-k, --kill=<int>\tKill run program by a given signal when the timer is stopped\n\
+-n, --timer-name=<str>\tFill name box with <str>\n\
+-a, --auto-resize\tAutomatically resize font to fit the box\n\
 ");
   exit(1);
 }
 
+void sig_handler(int signo)
+{
+  if (signo == SIGINT || signo == SIGTERM)
+    {
+      kill_notify();
+      exit(0);
+    }
+}
+
 int
 main(int argc, char **argv)
 {
   gtk_set_locale();
   gtk_init(&argc, &argv);
 
+  signal(SIGINT, sig_handler);
+  signal(SIGTERM, sig_handler);
+
   int opt;
   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
     switch (opt)
       {
       case 'r':
-       run_cmd = optarg;
+       run_notify = optarg;
+       break;
+      case 'k':
+       kill_notify_by = atoi(optarg);
+       break;
+      case 'n':
+       default_name = optarg;
+       break;
+      case 'a':
+       autoresize_timebox_font = true;
        break;
       default:
        usage();