summaryrefslogtreecommitdiff
path: root/src/gs-job.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-04 22:16:15 -0300
committerPerberos <[email protected]>2011-11-04 22:16:15 -0300
commitfff4ecc82f2bcfa7427596e7ad9c3769fcab040b (patch)
treeac4f1812a7991609c9c32c776daede2d8492f7b5 /src/gs-job.c
downloadmate-screensaver-fff4ecc82f2bcfa7427596e7ad9c3769fcab040b.tar.bz2
mate-screensaver-fff4ecc82f2bcfa7427596e7ad9c3769fcab040b.tar.xz
first commit
Diffstat (limited to 'src/gs-job.c')
-rw-r--r--src/gs-job.c567
1 files changed, 567 insertions, 0 deletions
diff --git a/src/gs-job.c b/src/gs-job.c
new file mode 100644
index 0000000..30d1f34
--- /dev/null
+++ b/src/gs-job.c
@@ -0,0 +1,567 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2004-2006 William Jon McCann <[email protected]>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authors: William Jon McCann <[email protected]>
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <string.h>
+
+#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+#include <sys/resource.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "gs-debug.h"
+#include "gs-job.h"
+
+#include "subprocs.h"
+
+static void gs_job_class_init (GSJobClass *klass);
+static void gs_job_init (GSJob *job);
+static void gs_job_finalize (GObject *object);
+
+#define GS_JOB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GS_TYPE_JOB, GSJobPrivate))
+
+typedef enum
+{
+ GS_JOB_INVALID,
+ GS_JOB_RUNNING,
+ GS_JOB_STOPPED,
+ GS_JOB_KILLED,
+ GS_JOB_DEAD
+} GSJobStatus;
+
+struct GSJobPrivate
+{
+ GtkWidget *widget;
+
+ GSJobStatus status;
+ gint pid;
+ guint watch_id;
+
+ char *command;
+};
+
+G_DEFINE_TYPE (GSJob, gs_job, G_TYPE_OBJECT)
+
+static char *
+widget_get_id_string (GtkWidget *widget)
+{
+ char *id = NULL;
+
+ g_return_val_if_fail (widget != NULL, NULL);
+
+ id = g_strdup_printf ("0x%X",
+ (guint32)GDK_WINDOW_XID (widget->window));
+ return id;
+}
+
+static void
+gs_job_class_init (GSJobClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gs_job_finalize;
+
+ g_type_class_add_private (klass, sizeof (GSJobPrivate));
+}
+
+static void
+gs_job_init (GSJob *job)
+{
+ job->priv = GS_JOB_GET_PRIVATE (job);
+}
+
+/* adapted from gspawn.c */
+static int
+wait_on_child (int pid)
+{
+ int status;
+
+wait_again:
+ if (waitpid (pid, &status, 0) < 0)
+ {
+ if (errno == EINTR)
+ {
+ goto wait_again;
+ }
+ else if (errno == ECHILD)
+ {
+ ; /* do nothing, child already reaped */
+ }
+ else
+ {
+ gs_debug ("waitpid () should not fail in 'GSJob'");
+ }
+ }
+
+ return status;
+}
+
+static void
+gs_job_died (GSJob *job)
+{
+ if (job->priv->pid > 0)
+ {
+ int exit_status;
+
+ gs_debug ("Waiting on process %d", job->priv->pid);
+ exit_status = wait_on_child (job->priv->pid);
+
+ job->priv->status = GS_JOB_DEAD;
+
+ if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0))
+ {
+ gs_debug ("Wait on child process failed");
+ }
+ else
+ {
+ /* exited normally */
+ }
+ }
+ g_spawn_close_pid (job->priv->pid);
+ job->priv->pid = 0;
+
+ gs_debug ("Job died");
+}
+
+static void
+gs_job_finalize (GObject *object)
+{
+ GSJob *job;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GS_IS_JOB (object));
+
+ job = GS_JOB (object);
+
+ g_return_if_fail (job->priv != NULL);
+
+ if (job->priv->pid > 0)
+ {
+ signal_pid (job->priv->pid, SIGTERM);
+ gs_job_died (job);
+ }
+
+ g_free (job->priv->command);
+ job->priv->command = NULL;
+
+ G_OBJECT_CLASS (gs_job_parent_class)->finalize (object);
+}
+
+void
+gs_job_set_widget (GSJob *job,
+ GtkWidget *widget)
+{
+ g_return_if_fail (job != NULL);
+ g_return_if_fail (GS_IS_JOB (job));
+
+ if (widget != job->priv->widget)
+ {
+ job->priv->widget = widget;
+
+ /* restart job */
+ if (gs_job_is_running (job))
+ {
+ gs_job_stop (job);
+ gs_job_start (job);
+ }
+ }
+}
+
+gboolean
+gs_job_set_command (GSJob *job,
+ const char *command)
+{
+ g_return_val_if_fail (GS_IS_JOB (job), FALSE);
+
+ gs_debug ("Setting command for job: '%s'",
+ command != NULL ? command : "NULL");
+
+ g_free (job->priv->command);
+ job->priv->command = g_strdup (command);
+
+ return TRUE;
+}
+
+GSJob *
+gs_job_new (void)
+{
+ GObject *job;
+
+ job = g_object_new (GS_TYPE_JOB, NULL);
+
+ return GS_JOB (job);
+}
+
+GSJob *
+gs_job_new_for_widget (GtkWidget *widget)
+{
+ GObject *job;
+
+ job = g_object_new (GS_TYPE_JOB, NULL);
+
+ gs_job_set_widget (GS_JOB (job), widget);
+
+ return GS_JOB (job);
+}
+
+static void
+nice_process (int pid,
+ int nice_level)
+{
+ g_return_if_fail (pid > 0);
+
+ if (nice_level == 0)
+ {
+ return;
+ }
+
+#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS)
+ gs_debug ("Setting child process priority to: %d", nice_level);
+ if (setpriority (PRIO_PROCESS, pid, nice_level) != 0)
+ {
+ gs_debug ("setpriority(PRIO_PROCESS, %lu, %d) failed",
+ (unsigned long) pid, nice_level);
+ }
+#else
+ gs_debug ("don't know how to change process priority on this system.");
+#endif
+}
+
+static GPtrArray *
+get_env_vars (GtkWidget *widget)
+{
+ GPtrArray *env;
+ char *str;
+ int i;
+ static const char *allowed_env_vars [] =
+ {
+ "PATH",
+ "SESSION_MANAGER",
+ "XAUTHORITY",
+ "XAUTHLOCALHOSTNAME",
+ "LANG",
+ "LANGUAGE",
+ "DBUS_SESSION_BUS_ADDRESS"
+ };
+
+ env = g_ptr_array_new ();
+
+ str = gdk_screen_make_display_name (gtk_widget_get_screen (widget));
+ g_ptr_array_add (env, g_strdup_printf ("DISPLAY=%s", str));
+ g_free (str);
+
+ g_ptr_array_add (env, g_strdup_printf ("HOME=%s",
+ g_get_home_dir ()));
+
+ for (i = 0; i < G_N_ELEMENTS (allowed_env_vars); i++)
+ {
+ const char *var;
+ const char *val;
+ var = allowed_env_vars [i];
+ val = g_getenv (var);
+ if (val != NULL)
+ {
+ g_ptr_array_add (env, g_strdup_printf ("%s=%s",
+ var,
+ val));
+ }
+ }
+
+ str = widget_get_id_string (widget);
+ g_ptr_array_add (env, g_strdup_printf ("XSCREENSAVER_WINDOW=%s", str));
+ g_free (str);
+
+ g_ptr_array_add (env, NULL);
+
+ return env;
+}
+
+static gboolean
+spawn_on_widget (GtkWidget *widget,
+ const char *command,
+ int *pid,
+ GIOFunc watch_func,
+ gpointer user_data,
+ guint *watch_id)
+{
+ char **argv;
+ GPtrArray *env;
+ gboolean result;
+ GIOChannel *channel;
+ GError *error = NULL;
+ int standard_error;
+ int child_pid;
+ int id;
+ int i;
+
+ if (command == NULL)
+ {
+ return FALSE;
+ }
+
+ if (! g_shell_parse_argv (command, NULL, &argv, &error))
+ {
+ gs_debug ("Could not parse command: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ env = get_env_vars (widget);
+
+ error = NULL;
+ result = gdk_spawn_on_screen_with_pipes (gtk_widget_get_screen (widget),
+ NULL,
+ argv,
+ (char **)env->pdata,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL,
+ NULL,
+ &child_pid,
+ NULL,
+ NULL,
+ &standard_error,
+ &error);
+ for (i = 0; i < env->len; i++)
+ {
+ g_free (g_ptr_array_index (env, i));
+ }
+ g_ptr_array_free (env, TRUE);
+
+ if (! result)
+ {
+ gs_debug ("Could not start command '%s': %s", command, error->message);
+ g_error_free (error);
+ g_strfreev (argv);
+ return FALSE;
+ }
+
+ g_strfreev (argv);
+
+ nice_process (child_pid, 10);
+
+ if (pid != NULL)
+ {
+ *pid = child_pid;
+ }
+ else
+ {
+ g_spawn_close_pid (child_pid);
+ }
+
+ channel = g_io_channel_unix_new (standard_error);
+ g_io_channel_set_close_on_unref (channel, TRUE);
+ g_io_channel_set_flags (channel,
+ g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
+ NULL);
+ id = g_io_add_watch (channel,
+ G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ watch_func,
+ user_data);
+ if (watch_id != NULL)
+ {
+ *watch_id = id;
+ }
+
+ g_io_channel_unref (channel);
+
+ return result;
+}
+
+static gboolean
+command_watch (GIOChannel *source,
+ GIOCondition condition,
+ GSJob *job)
+{
+ GIOStatus status;
+ GError *error = NULL;
+ gboolean done = FALSE;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+
+ if (condition & G_IO_IN)
+ {
+ char *str;
+
+ status = g_io_channel_read_line (source, &str, NULL, NULL, &error);
+
+ if (status == G_IO_STATUS_NORMAL)
+ {
+ gs_debug ("command output: %s", str);
+
+ }
+ else if (status == G_IO_STATUS_EOF)
+ {
+ done = TRUE;
+
+ }
+ else if (error != NULL)
+ {
+ gs_debug ("command error: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (str);
+ }
+ else if (condition & G_IO_HUP)
+ {
+ done = TRUE;
+ }
+
+ if (done)
+ {
+ gs_job_died (job);
+
+ job->priv->watch_id = 0;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_job_is_running (GSJob *job)
+{
+ gboolean running;
+
+ g_return_val_if_fail (GS_IS_JOB (job), FALSE);
+
+ running = (job->priv->pid > 0);
+
+ return running;
+}
+
+gboolean
+gs_job_start (GSJob *job)
+{
+ gboolean result;
+
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (GS_IS_JOB (job), FALSE);
+
+ gs_debug ("starting job");
+
+ if (job->priv->pid != 0)
+ {
+ gs_debug ("Cannot restart active job.");
+ return FALSE;
+ }
+
+ if (job->priv->widget == NULL)
+ {
+ gs_debug ("Could not start job: screensaver window is not set.");
+ return FALSE;
+ }
+
+ if (job->priv->command == NULL)
+ {
+ /* no warning here because a NULL command is interpreted
+ as a no-op job */
+ gs_debug ("No command set for job.");
+ return FALSE;
+ }
+
+ result = spawn_on_widget (job->priv->widget,
+ job->priv->command,
+ &job->priv->pid,
+ (GIOFunc)command_watch,
+ job,
+ &job->priv->watch_id);
+
+ if (result)
+ {
+ job->priv->status = GS_JOB_RUNNING;
+ }
+
+ return result;
+}
+
+static void
+remove_command_watch (GSJob *job)
+{
+ if (job->priv->watch_id != 0)
+ {
+ g_source_remove (job->priv->watch_id);
+ job->priv->watch_id = 0;
+ }
+}
+
+gboolean
+gs_job_stop (GSJob *job)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (GS_IS_JOB (job), FALSE);
+
+ gs_debug ("stopping job");
+
+ if (job->priv->pid == 0)
+ {
+ gs_debug ("Could not stop job: pid not defined");
+ return FALSE;
+ }
+
+ if (job->priv->status == GS_JOB_STOPPED)
+ {
+ gs_job_suspend (job, FALSE);
+ }
+
+ remove_command_watch (job);
+
+ signal_pid (job->priv->pid, SIGTERM);
+
+ job->priv->status = GS_JOB_KILLED;
+
+ gs_job_died (job);
+
+ return TRUE;
+}
+
+gboolean
+gs_job_suspend (GSJob *job,
+ gboolean suspend)
+{
+ g_return_val_if_fail (job != NULL, FALSE);
+ g_return_val_if_fail (GS_IS_JOB (job), FALSE);
+
+ gs_debug ("suspending job");
+
+ if (job->priv->pid == 0)
+ {
+ return FALSE;
+ }
+
+ signal_pid (job->priv->pid, (suspend ? SIGSTOP : SIGCONT));
+
+ job->priv->status = (suspend ? GS_JOB_STOPPED : GS_JOB_RUNNING);
+
+ return TRUE;
+}