[gnome-applets/wip/muktupavels/async-command: 4/4] command: get output asynchronously
- From: Alberts Muktupāvels <muktupavels src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-applets/wip/muktupavels/async-command: 4/4] command: get output asynchronously
- Date: Mon, 23 Mar 2020 20:14:48 +0000 (UTC)
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]