From fff4ecc82f2bcfa7427596e7ad9c3769fcab040b Mon Sep 17 00:00:00 2001 From: Perberos Date: Fri, 4 Nov 2011 22:16:15 -0300 Subject: first commit --- src/gs-job.c | 567 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 src/gs-job.c (limited to 'src/gs-job.c') 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 + * + * 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 + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_SETPRIORITY) && defined(PRIO_PROCESS) +#include +#endif + +#include +#include +#include +#include + +#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; +} -- cgit v1.2.1