/* $Id: xfce-exec.c 28003 2008-10-01 07:57:03Z kelnos $
 * 
 * Copyright (C) 2004-2006 The Xfce Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors:
 *      Jasper Huijsmans <jasper@xfce.org>
 *      Olivier Fourdan <fourdan@xfce.org>
 *      Benedikt Meurer <benny@xfce.org>
 *      
 * Startup notification based on gnome-desktop developed by 
 * Elliot Lee <sopwith@redhat.com> and Sid Vicious
 * 
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#include <gdk/gdk.h>

#include <libxfce4util/libxfce4util.h>

#include "xfce-exec.h"
#include "xfce-startup-notification.h"

/* Darwin does not have a variable named environ
   but has a function which you can get the environ
   variable with.  */
#if defined(__APPLE__) && defined(__MACH__)
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#else
extern char **environ;
#endif

static void
child_setup_async(gpointer user_data)
{
#if defined(HAVE_SETSID) && !defined(G_OS_WIN32)
    setsid();
#endif
}

static void
child_setup_sync(gpointer user_data)
{
#ifndef G_OS_WIN32
    gchar *dpyname = user_data;
    
    xfce_setenv ("DISPLAY", dpyname, TRUE);
#endif
}

static void 
child_watch_cb(GPid pid, gint status, gpointer data)
{
    char* id;

    waitpid (pid, NULL, 0);
    g_spawn_close_pid (pid);
    
    id = (char*) data;
    
    xfce_startup_notification_cancel (id);
    g_free (id);
}
                                                                                          
static gboolean
real_xfce_exec_with_envp (GdkScreen *screen,
                          char **argv, 
                          gboolean in_terminal, 
                          gboolean use_sn,
                          GError ** error,
                          char **envp,
                          gboolean async)
{
    gchar **free_envp = NULL;
    gboolean success;
    gint exit_status;
    GPid pid;
    gboolean want_child_watch = FALSE;
    int spawn_flags;
    char* id = NULL;

    if (!argv || !argv[0])
        return FALSE;
    
    free_envp = NULL;

#ifdef HAVE_LIBSTARTUP_NOTIFICATION
    want_child_watch = use_sn && async;
    if (use_sn)
    {
        id = xfce_startup_notification_start (screen, argv[0]);
        free_envp = xfce_startup_notification_modify_environment ((const char**) envp, id);
    }
#endif

    if (async)
    {
        gchar **cur = NULL;

        spawn_flags = G_SPAWN_SEARCH_PATH;
        
        if (want_child_watch) 
        {
            spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
        }

	if (!free_envp)
	  free_envp = g_strdupv (envp);

	for (cur = free_envp; *cur; cur++)
        {
	    if ((strlen (*cur) > 7) && (g_strncasecmp ("DISPLAY", *cur, 7) == 0))
            {
	        gchar *dpyname = gdk_screen_make_display_name (screen);

		g_free (*cur);
		*cur = g_strconcat ("DISPLAY=", dpyname, NULL);

		g_free (dpyname);
	    }	  
	}

        success = gdk_spawn_on_screen (screen, 
                                       NULL, argv, 
				       free_envp,
				       spawn_flags, 
				       child_setup_async, NULL, &pid, 
				       error);
    }
    else
    {
        gchar *dpyname = gdk_screen_make_display_name (screen);

        success = g_spawn_sync (NULL, argv, 
                                free_envp ? free_envp : envp,
                                G_SPAWN_SEARCH_PATH,
                                child_setup_sync, dpyname,
                                NULL, NULL,
                                &exit_status, error);
        g_free (dpyname);
    }
    
    if (want_child_watch) /* async && use_sn */
    {
        g_child_watch_add (pid, child_watch_cb, id);
        id = NULL; /* freed by child watch */
    }
    
#ifdef HAVE_LIBSTARTUP_NOTIFICATION
    if (use_sn)
    {
        if (!success) 
        {
            xfce_startup_notification_cancel (id);
        } 
        
	if (free_envp)
	{
	    g_strfreev (free_envp);
	    free_envp = NULL;
	}
    }
    g_free (id);
#endif /* HAVE_LIBSTARTUP_NOTIFICATION */

    return success;
}


static char **
xfce_exec_create_argv (const char *cmd, gboolean in_terminal, GError **error)
{
    char *realcmd = g_strdup (cmd);
    char **argv;
    
    /* remove quotes around command-line */
    if (realcmd[0] == '\"' && realcmd[strlen(realcmd)-1] == '\"')
    {
        char *s;
        
        for (s = realcmd + 1; *s != '\0' && *s != '\"'; ++s)
            *(s-1) = *s;
        *(s-1) = '\0';
    }
        
    if (g_path_is_absolute (realcmd) && 
            g_file_test (realcmd, G_FILE_TEST_IS_DIR))
    {
        argv = g_new (char *, 3);

        if (in_terminal)
            argv[0] = g_strdup ("xfterm4");
	else
	    argv[0] = g_strdup ("xftree4");
        
        argv[1] = realcmd;
        argv[2] = NULL;
    }
    else if (in_terminal)
    {
        argv = g_new (char *, 4);
        
        argv[0] = g_strdup ("xfterm4");
        argv[1] = g_strdup ("-e");
	argv[2] = realcmd;
        argv[3] = NULL;
    }
    else
    {
        if (!g_shell_parse_argv (realcmd, NULL, &argv, error))
        {
            g_free (realcmd);

            return NULL;
        }
        
        g_free (realcmd);

        /* remove quotes around command */
        if (argv[0][0] == '\"' && argv[0][strlen(argv[0])-1] == '\"')
        {
            char *s;
            
            for (s = argv[0] + 1; *s != '\0' && *s != '\"'; ++s)
                *(s-1) = *s;
	    *(s-1) = '\0';
        }
    }

    return argv;
}

/**
 * xfce_exec_argv
 * @argv        : argument vector
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 *
 * See also: xfce_exec_with_envp()
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_argv (char **argv, 
	        gboolean in_terminal, 
	        gboolean use_sn,
	        GError ** error)
{
    return real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                     argv, in_terminal, use_sn, error, 
                                     environ, TRUE);
}

/**
 * xfce_exec_argv_with_envp
 * @argv        : argument vector
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 * @envp	: the environment
 *
 * See also #xfce_exec_argv.
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_argv_with_envp (char **argv, 
	                  gboolean in_terminal, 
		          gboolean use_sn,
		          GError ** error,
		          char **envp)
{
    return real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                     argv, in_terminal, use_sn, error, envp,
                                     TRUE);
}

/**
 * xfce_exec
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 *
 * Use #xfce_exec_with_envp if you want to specify the environment.
 * 
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean xfce_exec (const char *cmd, 
		    gboolean in_terminal, 
		    gboolean use_sn, 
		    GError **error)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                        argv, in_terminal, use_sn, error,
                                        environ, TRUE);
    g_strfreev (argv);
    return success;
}

/**
 * xfce_exec_with_envp
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 * @envp	: the environment
 *
 * See also #xfce_exec.
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_with_envp (const char *cmd, 
		     gboolean in_terminal, 
		     gboolean use_sn,
		     GError ** error,
		     char **envp)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                        argv, in_terminal, use_sn, error, envp,
                                        TRUE);
    g_strfreev (argv);
    return success;
}

/**
 * xfce_exec_sync
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 *
 * Use #xfce_exec_sync_with_envp if you want to specify the environment.
 * 
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean xfce_exec_sync (const char *cmd, 
			 gboolean in_terminal, 
			 gboolean use_sn, 
			 GError **error)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                        argv, in_terminal, use_sn, error,
                                        environ, FALSE);
    g_strfreev (argv);
    return success;
}

/**
 * xfce_exec_sync_with_envp
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 * @envp	: the environment
 *
 * See also #xfce_exec.
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_sync_with_envp (const char *cmd, 
			  gboolean in_terminal, 
			  gboolean use_sn,
			  GError ** error,
			  char **envp)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (gdk_screen_get_default (), 
                                        argv, in_terminal, use_sn, error, envp,
                                        FALSE);
    g_strfreev (argv);
    return success;
}

/* run on specific screen */

/**
 * xfce_exec_argv_on_screen
 * @screen      : a #GdkScreen
 * @argv        : argument vector
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 *
 * See also: xfce_exec_argv_with_envp_on_screen()
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_argv_on_screen (GdkScreen *screen,
                          char **argv, 
                          gboolean in_terminal, 
                          gboolean use_sn,
                          GError ** error)
{
    return real_xfce_exec_with_envp (screen, argv, in_terminal, use_sn, error, 
                                     environ, TRUE);
}

/**
 * xfce_exec_argv_with_envp_on_screen
 * @screen      : a #GdkScreen
 * @argv        : argument vector
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 * @envp	: the environment
 *
 * See also #xfce_exec_argv_on_screen.
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_argv_with_envp_on_screen (GdkScreen *screen,
                                    char **argv, 
                                    gboolean in_terminal, 
                                    gboolean use_sn,
                                    GError ** error,
                                    char **envp)
{
    return real_xfce_exec_with_envp (screen, argv, in_terminal, use_sn, error, 
                                     envp, TRUE);
}

/**
 * xfce_exec_on_screen
 * @screen      : a #GdkScreen
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 *
 * Use #xfce_exec_with_envp_on_screen if you want to specify the environment.
 * 
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean xfce_exec_on_screen (GdkScreen *screen,
                              const char *cmd, 
                              gboolean in_terminal, 
                              gboolean use_sn, 
                              GError **error)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (screen, argv, in_terminal, use_sn, 
                                        error, environ, TRUE);
    g_strfreev (argv);
    return success;
}

/**
 * xfce_exec_with_envp_on_screen
 * @screen      : a #GdkScreen
 * @cmd         : command line to run
 * @in_terminal : whether to run @cmd in a terminal
 * @use_sn      : whether to use startup notification
 * @error       : location for a GError or NULL
 * @envp	: the environment
 *
 * See also #xfce_exec_on_screen.
 *
 * Returns: TRUE on success, FALSE on failure. 
 */
gboolean
xfce_exec_with_envp_on_screen (GdkScreen *screen, 
                               const char *cmd, 
                               gboolean in_terminal, 
                               gboolean use_sn,
                               GError ** error,
                               char **envp)
{
    char **argv;
    gboolean success;
    
    if (!(argv = xfce_exec_create_argv (cmd, in_terminal, error)))
        return FALSE;
            
    success = real_xfce_exec_with_envp (screen, argv, in_terminal, use_sn, 
                                        error, envp, TRUE);
    g_strfreev (argv);
    return success;
}


