/* $Id: xfce-mcs-manager.c 21674 2006-05-15 14:54:42Z benny $ */
/*-
 * Copyright (c) 2002-2004 Olivier Fourdan <fourdan@xfce.org>
 * Copyright (c) 2003-2006 Benedikt Meurer <benny@xfce.org>
 *
 * 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.
 */

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

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef ENABLE_LOADPROF
#include <sys/time.h>
#endif

#include <gdk/gdkx.h>
#include <gmodule.h>

#include <libxfce4mcs/mcs-common.h>
#include <libxfce4mcs/mcs-manager.h>
#include <libxfcegui4/libxfcegui4.h>

#include <xfce-mcs-manager/manager-plugin.h>
#include <xfce-mcs-manager/xfce-mcs-dialog.h>

#define SOEXT           ("." G_MODULE_SUFFIX)
#define SOEXT_LEN       (strlen (SOEXT))

static gchar **rargv = NULL;
static GSList *plugin_list = NULL;
static GSList *module_list = NULL;

static McsManager *manager;

static enum
{
    NOSIGNAL,
    RESTART,
    QUIT,
    NUM_SIGNALS
} sigstate = NOSIGNAL;

G_MODULE_EXPORT gchar * mcs_plugin_check_version (gint version)
{
    if (XFCE_MCS_PLUGIN_VERSION == version)
        return NULL;

    return _("Incorrect module version");
}

static gint
compare_plugins (gconstpointer plugin_a, gconstpointer plugin_b)
{
    return strcmp (((const McsPlugin *) plugin_a)->plugin_name, ((const McsPlugin *) plugin_b)->plugin_name);
}

static gint
lookup_plugins (gconstpointer plugin, gconstpointer name)
{
    return strcmp (((const McsPlugin *) plugin)->plugin_name, name);
}

static void
load_plugin (gchar * path)
{
    McsPluginInitResult (*init) (McsPlugin *);
    McsPlugin *plugin;
    GModule *module;

#ifdef G_ENABLE_DEBUG
    g_message ("- attempting to load module %s", path);
#endif

    if ((module = g_module_open (path, 0)) == NULL)
    {
        g_warning ("Module %s cannot be opened (%s)", path, g_module_error ());
        return;
    }

    if (g_module_symbol (module, "mcs_plugin_init", (gpointer) & init))
    {
        plugin = g_new0 (McsPlugin, 1);
        plugin->manager = manager;

        if ((*init) (plugin) != MCS_PLUGIN_INIT_OK)
        {
            if (G_UNLIKELY (plugin->icon != NULL))
                g_object_unref (plugin->icon);
            g_free (plugin->plugin_name);
            g_free (plugin->caption);
            g_free (plugin);
            g_module_close (module);
            return;
        }

        if (g_slist_find_custom (plugin_list, plugin, compare_plugins))
        {
            g_warning ("  module %s (\"%s\") has already been loaded before", path, plugin->plugin_name);

            if (G_LIKELY (plugin->icon != NULL))
                g_object_unref (plugin->icon);
            g_free (plugin->plugin_name);
            g_free (plugin->caption);
            g_module_close (module);
            g_free (plugin);
        }
        else
        {
#ifdef G_ENABLE_DEBUG
            g_message ("  module %s (\"%s\") successfully loaded", path, plugin->plugin_name);
#endif
            plugin_list = g_slist_append (plugin_list, plugin);
            module_list = g_slist_append (module_list, module);
        }
    }
    else
    {
        g_warning ("  incompatible module %s", path);
        g_module_close (module);
    }
}

#ifdef ENABLE_LOADPROF
static void
compute_delta_t (struct timeval *start, struct timeval *stop, struct timeval *delta)
{
    delta->tv_sec = stop->tv_sec - start->tv_sec;
    delta->tv_usec = stop->tv_usec - start->tv_usec;

    if (delta->tv_usec >= 1000000)
    {
        delta->tv_sec += 1;
        delta->tv_usec -= 1000000;
    }

    if (delta->tv_usec < 0)
    {
        delta->tv_sec -= 1;
        delta->tv_usec += 1000000;
    }
}
#endif

static void
plugins_load_dir (const gchar * dir)
{
    const gchar *f;
    gchar *plugin;
    GDir *d;

    d = g_dir_open (dir, 0, NULL);
    if (d != NULL)
    {
#ifdef ENABLE_LOADPROF
        struct timeval t0;

        gettimeofday (&t0, NULL);
#endif
        while ((f = g_dir_read_name (d)) != NULL)
        {
            if (g_str_has_suffix (f, SOEXT))
            {
#ifdef ENABLE_LOADPROF
                struct timeval t1;
                struct timeval delta_t;
#endif

                plugin = g_build_filename (dir, f, NULL);
                load_plugin (plugin);
#ifdef ENABLE_LOADPROF
                gettimeofday (&t1, NULL);

                compute_delta_t (&t0, &t1, &delta_t);
                g_print ("Started %s in %u.%06u sec.\n", plugin, (unsigned int) delta_t.tv_sec, (unsigned int) delta_t.tv_usec);

                t0.tv_sec = t1.tv_sec;
                t0.tv_usec = t1.tv_usec;
#endif
                g_free (plugin);
            }
        }

        g_dir_close (d);
    }
}

static void
plugin_load_all (void)
{
#ifdef G_ENABLE_DEBUG
    g_message ("Loading mcs plugins");
#endif

    /* load system plugins */
    plugins_load_dir (PLUGINSDIR);
}

static void
unload_plugins (void)
{
#ifdef G_ENABLE_DEBUG
    g_message ("Unloading mcs plugins");
#endif

    /*
     * NOTE: Grabbing the X-Server while reloading the
     * plugins will not work (Don't know why)
     */
    g_slist_foreach (module_list, (GFunc) g_module_close, NULL);
    g_slist_free (module_list);
    module_list = NULL;

    g_slist_foreach (plugin_list, (GFunc) g_free, NULL);
    g_slist_free (plugin_list);
    plugin_list = NULL;
}

static void
terminate_cb (void *data)
{
#ifdef G_ENABLE_DEBUG
    g_print ("Releasing the selection and exitingn");
#endif

    *(gboolean *) data = TRUE;
    gtk_main_quit ();
}

static void
show_cb (char *name, void *cb_data)
{
    GSList *plugin_search;
    McsPlugin *plugin;

    if (strcmp ("", name) == 0 || g_ascii_strcasecmp ("all", name) == 0)
    {
        run_manager_dialog (plugin_list);
        return;
    }

    plugin_search = g_slist_find_custom (plugin_list, name, lookup_plugins);

    if (plugin_search != NULL)
    {
        plugin = (McsPlugin *) plugin_search->data;
        plugin->run_dialog (plugin);
    }
    else
    {
        xfce_err (_("Xfce Settings Manager error:\nNo such plugin \"%s\""), name);
        run_manager_dialog (plugin_list);
    }
}

static GdkFilterReturn
manager_event_filter (GdkXEvent * xevent, GdkEvent * event, gpointer data)
{
    if (mcs_manager_process_event (manager, (XEvent *) xevent))
        return GDK_FILTER_REMOVE;
    else
        return GDK_FILTER_CONTINUE;
}

static void
sighandler (int signo)
{
    switch (signo)
    {
        case SIGUSR1:
            sigstate = RESTART;
            break;

        default:
            sigstate = QUIT;
    }
}

static gboolean
check_signal_state (void)
{
    if (sigstate == RESTART)
    {
        /* unload all plugins and destroy the manager */
        unload_plugins ();
        mcs_manager_destroy (manager);

        /* just run the MCS manager from scratch */
        execvp (rargv[0], rargv);
        _exit (EXIT_SUCCESS);
    }
    else if (sigstate == QUIT)
    {
        gtk_main_quit ();
        return FALSE;
    }

    return TRUE;
}

int
main (int argc, char **argv)
{
#ifdef HAVE_SIGACTION
    struct sigaction act;
#endif
    McsManagerCheck status;
    gboolean daemon_mode;
    gboolean std_mgr;
    int i;

    /* save argv for restart */
    rargv = g_strdupv (argv);

    daemon_mode = TRUE;
    std_mgr = TRUE;

    xfce_textdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");

    gtk_init (&argc, &argv);

    /* Fake a SM client ID, so smproxy does not catch us */
    gdk_set_sm_client_id ("FAKED CLIENTID");

    for (i = 1; i < argc; i++)
        if (strcmp (argv[i], "--no-daemon-debug") == 0)
            daemon_mode = FALSE;

    status = mcs_manager_check_running (gdk_display, DefaultScreen (gdk_display));

    switch (status)
    {
        case MCS_MANAGER_MULTI_CHANNEL:
        case MCS_MANAGER_BOTH:
            g_warning ("Multi channel MCS manager already detected for screen %i", DefaultScreen (gdk_display));
            return EXIT_FAILURE;

        case MCS_MANAGER_STD:
            g_message ("Standard XSETTINGS manager already detected for screen %i", DefaultScreen (gdk_display));
            std_mgr = FALSE;

        default:
            break;
    }

    manager = mcs_manager_new (std_mgr, gdk_display, DefaultScreen (gdk_display), terminate_cb, show_cb, NULL);
    if (manager == NULL)
    {
        g_warning ("Could not create manager!");
        return EXIT_FAILURE;
    }

    gdk_window_add_filter (NULL, manager_event_filter, NULL);

    plugin_load_all ();
    gdk_flush ();

#ifdef HAVE_SIGACTION
    act.sa_handler = sighandler;
#ifdef SA_RESTART
    act.sa_flags = SA_RESTART;
#else
    act.sa_flags = 0;
#endif
    sigaction (SIGTERM, &act, NULL);
    sigaction (SIGHUP, &act, NULL);
    sigaction (SIGINT, &act, NULL);
    sigaction (SIGUSR1, &act, NULL);
#else /* !HAVE_SIGACTION */
    signal (SIGTERM, sighandler);
    signal (SIGHUP, sighandler);
    signal (SIGINT, sighandler);
    signal (SIGUSR1, sighandler);
#endif

    if (daemon_mode)
    {
#ifdef HAVE_DAEMON
        if (daemon (TRUE, TRUE) < 0)
        {
            g_warning ("Failed to switch to daemon mode: %s", g_strerror (errno));
            return (EXIT_FAILURE);
        }
#else /* !HAVE_DAEMON */
        switch (fork ())
        {
            case -1:
                g_warning ("fork() failed");
                return (EXIT_FAILURE);

            case 0:
#ifdef HAVE_SETSID
                /* detach from session */
                if (setsid () < 0)
                {
                    g_warning ("Failed to detach from terminal " "session: %s", g_strerror (errno));
                }
#endif /* !HAVE_SETSID */
                break;

            default:
                /* parent */
                _exit (EXIT_SUCCESS);
        }
#endif /* !HAVE_DAEMON */
    }
    else
        g_message ("Daemon mode disabled (for debug purpose only!)");

    /* Establish signal-check timer */
    g_timeout_add (500, (GSourceFunc) check_signal_state, NULL);

    gtk_main ();

    unload_plugins ();

    mcs_manager_destroy (manager);

    return EXIT_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1