[gnome-applets/wip/muktupavels/async-command: 4/4] command: get output asynchronously



commit 9b95180f69f4060d8fe5847e0ef7b0e70f0d4623
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Mon Mar 23 22:07:11 2020 +0200

    command: get output asynchronously

 command/src/Makefile.am  |   2 +
 command/src/command.c    | 252 +++++++++++++-------------
 command/src/ga-command.c | 457 +++++++++++++++++++++++++++++++++++++++++++++++
 command/src/ga-command.h |  46 +++++
 4 files changed, 635 insertions(+), 122 deletions(-)
---
diff --git a/command/src/Makefile.am b/command/src/Makefile.am
index 72b8a25dc..023e1cecc 100644
--- a/command/src/Makefile.am
+++ b/command/src/Makefile.am
@@ -20,6 +20,8 @@ libcommand_applet_la_CFLAGS = \
 
 libcommand_applet_la_SOURCES = \
        command.c \
+       ga-command.c \
+       ga-command.h \
        $(NULL)
 
 libcommand_applet_la_LDFLAGS = \
diff --git a/command/src/command.c b/command/src/command.c
index 95e5c11ae..c366acdd3 100644
--- a/command/src/command.c
+++ b/command/src/command.c
@@ -22,9 +22,8 @@
  *      Stefano Karapetsas <stefano karapetsas com>
  */
 
-#ifdef HAVE_CONFIG_H
-#  include <config.h>
-#endif
+#include "config.h"
+#include "ga-command.h"
 
 #include <glib.h>
 #include <glib/gi18n.h>
@@ -51,24 +50,21 @@
 
 typedef struct
 {
-    PanelApplet       *applet;
+  PanelApplet *applet;
 
-    GSettings         *settings;
+  GSettings   *settings;
 
-    GtkLabel          *label;
-    GtkImage          *image;
-    GtkBox            *box;
+  GtkLabel    *label;
+  GtkImage    *image;
+  GtkBox      *box;
 
-    gchar             *command;
-    guint              interval;
-    guint              width;
+  guint        width;
 
-    guint              timeout_id;
+  GaCommand   *command;
 } CommandApplet;
 
 static void command_about_callback (GSimpleAction *action, GVariant *parameter, gpointer data);
 static void command_settings_callback (GSimpleAction *action, GVariant *parameter, gpointer data);
-static gboolean command_execute (CommandApplet *command_applet);
 
 static const GActionEntry applet_menu_actions [] = {
     {"preferences", command_settings_callback, NULL, NULL, NULL},
@@ -91,17 +87,7 @@ command_applet_destroy (PanelApplet *applet_widget, CommandApplet *command_apple
 {
     g_assert (command_applet);
 
-    if (command_applet->timeout_id != 0)
-    {
-        g_source_remove (command_applet->timeout_id);
-        command_applet->timeout_id = 0;
-    }
-
-    if (command_applet->command != NULL)
-    {
-        g_free (command_applet->command);
-        command_applet->command = NULL;
-    }
+    g_clear_object (&command_applet->command);
 
     g_object_unref (command_applet->settings);
 }
@@ -190,127 +176,152 @@ command_settings_callback (GSimpleAction *action, GVariant *parameter, gpointer
     gtk_widget_show_all (GTK_WIDGET (dialog));
 }
 
-/* GSettings signal callbacks */
 static void
-settings_command_changed (GSettings *settings, gchar *key, CommandApplet *command_applet)
+output_cb (GaCommand     *command,
+           const char    *output,
+           CommandApplet *self)
 {
-    gchar *command;
+  if (output == NULL || *output == '\0')
+    {
+      gtk_label_set_text (self->label, ERROR_OUTPUT);
+      return;
+    }
 
-    command = g_settings_get_string (command_applet->settings, COMMAND_KEY);
+  if (g_str_has_prefix (output, "[Command]"))
+    {
+      GKeyFile *file;
 
-    if (command_applet->command)
-        g_free (command_applet->command);
+      file = g_key_file_new ();
 
-    if (command != NULL && command[0] != 0)
-        command_applet->command = command;
-    else
-        command_applet->command = g_strdup ("");
-}
+      if (g_key_file_load_from_data (file, output, -1, G_KEY_FILE_NONE, NULL))
+        {
+          char *markup;
+          char *icon;
+
+          markup = g_key_file_get_string (file,
+                                          GK_COMMAND_GROUP,
+                                          GK_COMMAND_OUTPUT,
+                                          NULL);
+          icon = g_key_file_get_string (file,
+                                        GK_COMMAND_GROUP,
+                                        GK_COMMAND_ICON,
+                                        NULL);
+
+          if (markup)
+            {
+              gtk_label_set_use_markup (self->label, TRUE);
+              gtk_label_set_markup (self->label, markup);
+            }
 
-static void
-settings_width_changed (GSettings *settings, gchar *key, CommandApplet *command_applet)
-{
-    guint width;
+          if (icon)
+            gtk_image_set_from_icon_name (self->image, icon, 24);
+
+          g_free (markup);
+          g_free (icon);
+        }
+      else
+        {
+          gtk_label_set_text (self->label, ERROR_OUTPUT);
+        }
+
+      g_key_file_free (file);
+    }
+  else
+    {
+      char *tmp;
+
+      if (strlen (output) > self->width)
+        {
+          GString *strip_output;
+
+          strip_output = g_string_new_len (output, self->width);
+          tmp = g_string_free (strip_output, FALSE);
+        }
+      else
+        {
+          tmp = g_strdup (output);
+        }
 
-    width = g_settings_get_uint (command_applet->settings, WIDTH_KEY);
+      if (g_str_has_suffix (tmp, "\n"))
+        tmp[strlen (tmp) - 1] = 0;
 
-    command_applet->width = width;
+      gtk_label_set_text (self->label, tmp);
+      g_free (tmp);
+    }
+}
 
-    /* execute command to start new timer */
-    command_execute (command_applet);
+static void
+error_cb (GaCommand     *command,
+          GError        *error,
+          CommandApplet *self)
+{
+  gtk_label_set_text (self->label, ERROR_OUTPUT);
 }
 
 static void
-settings_interval_changed (GSettings *settings, gchar *key, CommandApplet *command_applet)
+create_command (CommandApplet *self)
 {
-    guint interval;
+  char *command;
+  unsigned int interval;
+  GError *error;
+
+  command = g_settings_get_string (self->settings, COMMAND_KEY);
+  interval = g_settings_get_uint (self->settings, INTERVAL_KEY);
+  error = NULL;
 
-    interval = g_settings_get_uint (command_applet->settings, INTERVAL_KEY);
+  g_clear_object (&self->command);
+  self->command = ga_command_new (command, interval, &error);
 
-    command_applet->interval = interval;
+  gtk_widget_set_tooltip_text (GTK_WIDGET (self->label), command);
+  g_free (command);
 
-    /* stop current timer */
-    if (command_applet->timeout_id != 0)
+  if (error != NULL)
     {
-        g_source_remove (command_applet->timeout_id);
-        command_applet->timeout_id = 0;
+      gtk_label_set_text (self->label, ERROR_OUTPUT);
+
+      g_warning ("%s", error->message);
+      g_error_free (error);
+      return;
     }
 
-    /* execute command to start new timer */
-    command_execute (command_applet);
+  g_signal_connect (self->command, "output", G_CALLBACK (output_cb), self);
+  g_signal_connect (self->command, "error", G_CALLBACK (error_cb), self);
+
+  ga_command_start (self->command);
 }
 
-static gboolean
-command_execute (CommandApplet *command_applet)
+/* GSettings signal callbacks */
+static void
+settings_command_changed (GSettings     *settings,
+                          const char    *key,
+                          CommandApplet *self)
 {
-    GError *error = NULL;
-    gchar *output = NULL;
-    gint ret = 0;
+  create_command (self);
+}
 
-    if (g_spawn_command_line_sync (command_applet->command, &output, NULL, &ret, &error))
-    {
-        gtk_widget_set_tooltip_text (GTK_WIDGET (command_applet->label), command_applet->command);
+static void
+settings_width_changed (GSettings     *settings,
+                        const char    *key,
+                        CommandApplet *self)
+{
+  self->width = g_settings_get_uint (self->settings, WIDTH_KEY);
 
-        if ((output != NULL) && (output[0] != 0))
-        {
-            /* check if output is a custom GKeyFile */
-            if (g_str_has_prefix (output, "[Command]"))
-            {
-                GKeyFile *file = g_key_file_new ();
-                if (g_key_file_load_from_data (file, output, -1, G_KEY_FILE_NONE, NULL))
-                {
-                    gchar *goutput = g_key_file_get_string (file, GK_COMMAND_GROUP, GK_COMMAND_OUTPUT, NULL);
-                    gchar *icon = g_key_file_get_string (file, GK_COMMAND_GROUP, GK_COMMAND_ICON, NULL);
-
-                    if (goutput)
-                    {
-                        gtk_label_set_use_markup (command_applet->label, TRUE);
-                        gtk_label_set_markup (command_applet->label, goutput);
-                    }
-                    if (icon)
-                        gtk_image_set_from_icon_name (command_applet->image, icon, 24);
-
-                    g_free (goutput);
-                    g_free (icon);
-                }
-                else
-                    gtk_label_set_text (command_applet->label, ERROR_OUTPUT);
-                g_key_file_free (file);
-            }
-            else
-            {
-                /* check output length */
-                if (strlen(output) > command_applet->width)
-                {
-                    GString *strip_output;
-                    strip_output = g_string_new_len (output, command_applet->width);
-                    g_free (output);
-                    output = strip_output->str;
-                    g_string_free (strip_output, FALSE);
-                }
-                /* remove last char if it is '\n' to avoid aligment problems */
-                if (g_str_has_suffix (output, "\n"))
-                {
-                    output[strlen(output) - 1] = 0;
-                }
-
-                gtk_label_set_text (command_applet->label, output);
-            }
-        }
-        else
-            gtk_label_set_text (command_applet->label, ERROR_OUTPUT);
-    }
-    else
-        gtk_label_set_text (command_applet->label, ERROR_OUTPUT);
+  if (self->command == NULL)
+    return;
 
-    g_free (output);
+  ga_command_restart (self->command);
+}
 
-    /* start timer for next execution */
-    command_applet->timeout_id = g_timeout_add_seconds (command_applet->interval,
-                                                        (GSourceFunc) command_execute,
-                                                        command_applet);
+static void
+settings_interval_changed (GSettings     *settings,
+                           const char    *key,
+                           CommandApplet *self)
+{
+  if (self->command == NULL)
+    return;
 
-    return FALSE;
+  ga_command_set_interval (self->command,
+                           g_settings_get_uint (self->settings, INTERVAL_KEY));
 }
 
 static gboolean
@@ -325,14 +336,11 @@ command_applet_fill (PanelApplet* applet)
     command_applet->applet = applet;
     command_applet->settings = panel_applet_settings_new (applet, COMMAND_SCHEMA);
 
-    command_applet->interval = g_settings_get_uint (command_applet->settings, INTERVAL_KEY);
-    command_applet->command = g_settings_get_string (command_applet->settings, COMMAND_KEY);
     command_applet->width = g_settings_get_uint (command_applet->settings, WIDTH_KEY);
 
     command_applet->box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
     command_applet->image = GTK_IMAGE (gtk_image_new_from_icon_name (APPLET_ICON, 24));
     command_applet->label = GTK_LABEL (gtk_label_new (ERROR_OUTPUT));
-    command_applet->timeout_id = 0;
 
     /* we add the Gtk label into the applet */
     gtk_box_pack_start (command_applet->box,
@@ -379,7 +387,7 @@ command_applet_fill (PanelApplet* applet)
     gtk_widget_insert_action_group (GTK_WIDGET (applet), "command", G_ACTION_GROUP (action_group));
 
     /* first command execution */
-    command_execute (command_applet);
+    create_command (command_applet);
 
     return TRUE;
 }
diff --git a/command/src/ga-command.c b/command/src/ga-command.c
new file mode 100644
index 000000000..5c84d9dca
--- /dev/null
+++ b/command/src/ga-command.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2018-2020 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ga-command.h"
+
+#define BUFFER_SIZE 64
+
+struct _GaCommand
+{
+  GObject        parent;
+
+  char          *command;
+  unsigned int   interval;
+
+  char         **argv;
+
+  gboolean       started;
+
+  GPid           pid;
+
+  GIOChannel    *channel;
+
+  GString       *input;
+
+  unsigned int   io_watch_id;
+  unsigned int   child_watch_id;
+
+  unsigned int   timeout_id;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_COMMAND,
+  PROP_INTERVAL,
+
+  LAST_PROP
+};
+
+static GParamSpec *command_properties[LAST_PROP] = { NULL };
+
+enum
+{
+  OUTPUT,
+  ERROR,
+
+  LAST_SIGNAL
+};
+
+static unsigned int command_signals[LAST_SIGNAL] = { 0 };
+
+static void initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GaCommand, ga_command, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                initable_iface_init))
+
+static void command_execute (GaCommand *self);
+
+static void
+command_clear (GaCommand *self)
+{
+  if (self->pid != 0)
+    {
+      g_spawn_close_pid (self->pid);
+      self->pid = 0;
+    }
+
+  if (self->channel != NULL)
+    {
+      g_io_channel_unref (self->channel);
+      self->channel = NULL;
+    }
+
+  if (self->input != NULL)
+    {
+      g_string_free (self->input, TRUE);
+      self->input = NULL;
+    }
+
+  if (self->io_watch_id != 0)
+    {
+      g_source_remove (self->io_watch_id);
+      self->io_watch_id = 0;
+    }
+
+  if (self->child_watch_id != 0)
+    {
+      g_source_remove (self->child_watch_id);
+      self->child_watch_id = 0;
+    }
+}
+
+static gboolean
+execute_cb (gpointer user_data)
+{
+  GaCommand *self;
+
+  self = GA_COMMAND (user_data);
+  self->timeout_id = 0;
+
+  command_execute (self);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+start_timeout (GaCommand *self)
+{
+  command_clear (self);
+
+  g_assert (self->timeout_id == 0);
+  self->timeout_id = g_timeout_add_seconds (self->interval, execute_cb, self);
+  g_source_set_name_by_id (self->timeout_id, "[gnome-applets] execute_cb");
+}
+
+static gboolean
+read_cb (GIOChannel   *source,
+         GIOCondition  condition,
+         gpointer      user_data)
+{
+  GaCommand *self;
+  char buffer[BUFFER_SIZE];
+  gsize bytes_read;
+  GError *error;
+  GIOStatus status;
+
+  self = GA_COMMAND (user_data);
+
+  error = NULL;
+  status = g_io_channel_read_chars (source,
+                                    buffer,
+                                    BUFFER_SIZE,
+                                    &bytes_read,
+                                    &error);
+
+  if (status == G_IO_STATUS_AGAIN)
+    {
+      g_clear_error (&error);
+
+      return G_SOURCE_CONTINUE;
+    }
+  else if (status != G_IO_STATUS_NORMAL)
+    {
+      if (error != NULL)
+        {
+          g_signal_emit (self, command_signals[ERROR], 0, error);
+          g_error_free (error);
+
+          start_timeout (self);
+        }
+
+      self->io_watch_id = 0;
+
+      return G_SOURCE_REMOVE;
+    }
+
+  g_string_append_len (self->input, buffer, bytes_read);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+child_watch_cb (GPid     pid,
+                gint     status,
+                gpointer user_data)
+{
+  GaCommand *self;
+
+  self = GA_COMMAND (user_data);
+
+  g_signal_emit (self, command_signals[OUTPUT], 0, self->input->str);
+  start_timeout (self);
+}
+
+static void
+command_execute (GaCommand *self)
+{
+  GSpawnFlags spawn_flags;
+  GError *error;
+  int command_stdout;
+  GIOChannel *channel;
+  GIOStatus status;
+  GIOCondition condition;
+
+  spawn_flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
+  error = NULL;
+
+  if (!g_spawn_async_with_pipes (NULL, self->argv, NULL, spawn_flags,
+                                 NULL, NULL, &self->pid, NULL, &command_stdout,
+                                 NULL, &error))
+    {
+      g_signal_emit (self, command_signals[ERROR], 0, error);
+      g_error_free (error);
+
+      start_timeout (self);
+      return;
+    }
+
+  channel = self->channel = g_io_channel_unix_new (command_stdout);
+  g_io_channel_set_close_on_unref (channel, TRUE);
+
+  g_assert (error == NULL);
+  status = g_io_channel_set_encoding (channel, NULL, &error);
+
+  if (status != G_IO_STATUS_NORMAL)
+    {
+      g_signal_emit (self, command_signals[ERROR], 0, error);
+      g_error_free (error);
+
+      start_timeout (self);
+      return;
+    }
+
+  g_assert (error == NULL);
+  status = g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, &error);
+
+  if (status != G_IO_STATUS_NORMAL)
+    {
+      g_signal_emit (self, command_signals[ERROR], 0, error);
+      g_error_free (error);
+
+      start_timeout (self);
+      return;
+    }
+
+  self->input = g_string_new (NULL);
+
+  condition = G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP;
+  self->io_watch_id = g_io_add_watch (channel, condition, read_cb, self);
+
+  self->child_watch_id = g_child_watch_add (self->pid, child_watch_cb, self);
+}
+
+static gboolean
+ga_command_initable_init (GInitable     *initable,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  GaCommand *self;
+
+  self = GA_COMMAND (initable);
+
+  if (self->command == NULL || *self->command == '\0')
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                   "Empty command");
+
+      return FALSE;
+    }
+
+  if (!g_shell_parse_argv (self->command, NULL, &self->argv, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+initable_iface_init (GInitableIface *iface)
+{
+  iface->init = ga_command_initable_init;
+}
+
+static void
+ga_command_finalize (GObject *object)
+{
+  GaCommand *self;
+
+  self = GA_COMMAND (object);
+
+  ga_command_stop (self);
+
+  g_clear_pointer (&self->command, g_free);
+  g_clear_pointer (&self->argv, g_strfreev);
+
+  G_OBJECT_CLASS (ga_command_parent_class)->finalize (object);
+}
+
+static void
+ga_command_set_property (GObject      *object,
+                         guint         property_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  GaCommand *self;
+
+  self = GA_COMMAND (object);
+
+  switch (property_id)
+    {
+      case PROP_COMMAND:
+        g_assert (self->command == NULL);
+        self->command = g_value_dup_string (value);
+        break;
+
+      case PROP_INTERVAL:
+        self->interval = g_value_get_uint (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+static void
+install_properties (GObjectClass *object_class)
+{
+  command_properties[PROP_COMMAND] =
+    g_param_spec_string ("command",
+                         "command",
+                         "command",
+                         NULL,
+                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                         G_PARAM_STATIC_STRINGS);
+
+  command_properties[PROP_INTERVAL] =
+    g_param_spec_uint ("interval",
+                       "interval",
+                       "interval",
+                       1,
+                       600,
+                       1,
+                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                       G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class,
+                                     LAST_PROP,
+                                     command_properties);
+}
+
+static void
+install_signal (void)
+{
+  command_signals[OUTPUT] =
+    g_signal_new ("output",
+                  GA_TYPE_COMMAND,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL,
+                  NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_STRING);
+
+  command_signals[ERROR] =
+    g_signal_new ("error",
+                  GA_TYPE_COMMAND,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL,
+                  NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_ERROR);
+}
+
+static void
+ga_command_class_init (GaCommandClass *self_class)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (self_class);
+
+  object_class->finalize = ga_command_finalize;
+  object_class->set_property = ga_command_set_property;
+
+  install_properties (object_class);
+  install_signal ();
+}
+
+static void
+ga_command_init (GaCommand *self)
+{
+}
+
+GaCommand *
+ga_command_new (const char    *command,
+                unsigned int   interval,
+                GError       **error)
+{
+  return g_initable_new (GA_TYPE_COMMAND, NULL, error,
+                         "command", command,
+                         "interval", interval,
+                         NULL);
+}
+
+const char *
+ga_command_get_command (GaCommand *self)
+{
+  return self->command;
+}
+
+void
+ga_command_set_interval (GaCommand    *self,
+                         unsigned int  interval)
+{
+  if (self->interval == interval)
+    return;
+
+  self->interval = interval;
+
+  if (!self->started)
+    return;
+
+  ga_command_restart (self);
+}
+
+void
+ga_command_start (GaCommand *self)
+{
+  if (self->started)
+    return;
+
+  self->started = TRUE;
+
+  command_execute (self);
+}
+
+void
+ga_command_stop (GaCommand *self)
+{
+  if (self->timeout_id != 0)
+    {
+      g_source_remove (self->timeout_id);
+      self->timeout_id = 0;
+    }
+
+  command_clear (self);
+
+  self->started = FALSE;
+}
+
+void
+ga_command_restart (GaCommand *self)
+{
+  ga_command_stop (self);
+  ga_command_start (self);
+}
diff --git a/command/src/ga-command.h b/command/src/ga-command.h
new file mode 100644
index 000000000..e35ad602c
--- /dev/null
+++ b/command/src/ga-command.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018-2020 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GA_COMMAND_H
+#define GA_COMMAND_H
+
+#include <gio/gio.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GA_TYPE_COMMAND (ga_command_get_type ())
+G_DECLARE_FINAL_TYPE (GaCommand, ga_command, GA, COMMAND, GObject)
+
+GaCommand  *ga_command_new          (const char    *command,
+                                     unsigned int   interval,
+                                     GError       **error);
+
+const char *ga_command_get_command  (GaCommand     *self);
+
+void        ga_command_set_interval (GaCommand     *self,
+                                     unsigned int   interval);
+
+void        ga_command_start        (GaCommand     *self);
+
+void        ga_command_stop         (GaCommand     *self);
+
+void        ga_command_restart      (GaCommand     *self);
+
+G_END_DECLS
+
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]