/* -*- Mode: C; c-basic-offset: 4 -*-
 * pygtk- Python bindings for the GTK toolkit.
 * Copyright (C) 1998-2003  James Henstridge
 * Copyright (C) 2004       Johan Dahlin
 *
 *   pygmainloop.c: GMainLoop wrapper
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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

#include "pygobject-private.h"
#include "pythread.h"

#ifdef DISABLE_THREADING
static GMainLoop *pyg_current_main_loop = NULL;;

static inline GMainLoop *
pyg_save_current_main_loop (GMainLoop *main_loop)
{
    GMainLoop *retval = pyg_current_main_loop;

    g_return_val_if_fail(main_loop != NULL, NULL);

    pyg_current_main_loop = g_main_loop_ref(main_loop);

    return retval;
}

static inline void
pyg_restore_current_main_loop (GMainLoop *main_loop)
{
    if (pyg_current_main_loop != NULL)
	g_main_loop_unref(pyg_current_main_loop);
    pyg_current_main_loop = main_loop;
}

static inline GMainLoop *
pyg_get_current_main_loop (void)
{
    return pyg_current_main_loop;
}
#else /* !defined(#ifndef DISABLE_THREADING) */

static int pyg_current_main_loop_key = -1;

static inline GMainLoop *
pyg_save_current_main_loop (GMainLoop *main_loop)
{
    GMainLoop *retval;

    g_return_val_if_fail(main_loop != NULL, NULL);

    if (pyg_current_main_loop_key == -1)
	pyg_current_main_loop_key = PyThread_create_key();

    retval = PyThread_get_key_value(pyg_current_main_loop_key);
    PyThread_delete_key_value(pyg_current_main_loop_key);
    PyThread_set_key_value(pyg_current_main_loop_key, 
			   g_main_loop_ref(main_loop));

    return retval;
}

static inline void
pyg_restore_current_main_loop (GMainLoop *main_loop)
{
    GMainLoop *prev;

    g_return_if_fail (pyg_current_main_loop_key != -1);

    prev = PyThread_get_key_value(pyg_current_main_loop_key);
    if (prev != NULL)
	g_main_loop_unref(prev);
    PyThread_delete_key_value(pyg_current_main_loop_key);
    if (main_loop != NULL)
	PyThread_set_key_value(pyg_current_main_loop_key, main_loop);
}

static inline GMainLoop *
pyg_get_current_main_loop (void)
{
    if (pyg_current_main_loop_key == -1)
	return NULL;
    return PyThread_get_key_value(pyg_current_main_loop_key);
}
#endif /* DISABLE_THREADING */

static gboolean
pyg_signal_watch_prepare(GSource *source,
			 int     *timeout)
{
    /* Python only invokes signal handlers from the main thread,
     * so if a thread other than the main thread receives the signal
     * from the kernel, PyErr_CheckSignals() from that thread will
     * do nothing. So, we need to time out and check for signals
     * regularily too.
     * Also, on Windows g_poll() won't be interrupted by a signal
     * (AFAIK), so we need the timeout there too.
     */
#ifndef PLATFORM_WIN32
    if (pyg_threads_enabled)
#endif
	*timeout = 1000;

    return FALSE;
}

static gboolean
pyg_signal_watch_check(GSource *source)
{
    PyGILState_STATE state;
    GMainLoop *main_loop;

    state = pyg_gil_state_ensure();

    main_loop = pyg_get_current_main_loop();

    if (PyErr_CheckSignals() == -1 && main_loop != NULL) {
	PyErr_SetNone(PyExc_KeyboardInterrupt);
	g_main_loop_quit(main_loop);
    }

    pyg_gil_state_release(state);

    return FALSE;
}

static gboolean
pyg_signal_watch_dispatch(GSource    *source,
			  GSourceFunc callback,
			  gpointer    user_data)
{
    /* We should never be dispatched */
    g_assert_not_reached();
    return TRUE;
}

static GSourceFuncs pyg_signal_watch_funcs =
{
    pyg_signal_watch_prepare,
    pyg_signal_watch_check,
    pyg_signal_watch_dispatch,
    NULL
};

static GSource *
pyg_signal_watch_new(void)
{
    return g_source_new(&pyg_signal_watch_funcs, sizeof(GSource));
}

static int
pyg_main_loop_new(PyGMainLoop *self, PyObject *args, PyObject *kwargs)
{

    static char *kwlist[] = { "context", "is_running", NULL };
    PyObject *py_context = Py_None;
    int is_running;
    GMainContext *context;
    
    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
				     "|Ob:GMainLoop.__init__",
				     kwlist, &py_context, &is_running))
        return -1;

    if (!PyObject_TypeCheck(py_context, &PyGMainContext_Type) &&
	py_context != Py_None) {
	PyErr_SetString(PyExc_TypeError,
			"context must be a gobject.GMainContext or None");
	return -1;
    }

    if (py_context != Py_None) {
	context = ((PyGMainContext*)py_context)->context;
    } else {
	context = NULL;
    }

    self->loop = g_main_loop_new(context, is_running);

    self->signal_source = pyg_signal_watch_new();
    g_source_attach(self->signal_source, context);

    return 0;
}

static void
pyg_main_loop_dealloc(PyGMainLoop *self)
{
    if (self->signal_source != NULL) {
	g_source_destroy(self->signal_source);
	self->signal_source = NULL;
    }

    if (self->loop != NULL) {
	g_main_loop_unref(self->loop);
	self->loop = NULL;
    }

    PyObject_Del(self);
}

static int
pyg_main_loop_compare(PyGMainLoop *self, PyGMainLoop *v)
{
    if (self->loop == v->loop) return 0;
    if (self->loop > v->loop) return -1;
    return 1;
}

static PyObject *
_wrap_g_main_loop_get_context (PyGMainLoop *loop)
{
    return pyg_main_context_new(g_main_loop_get_context(loop->loop));
}

static PyObject *
_wrap_g_main_loop_is_running (PyGMainLoop *self)
{
    return PyBool_FromLong(g_main_loop_is_running(self->loop));
}

static PyObject *
_wrap_g_main_loop_quit (PyGMainLoop *self)
{
    g_main_loop_quit(self->loop);
    
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
_wrap_g_main_loop_run (PyGMainLoop *self)
{
    GMainLoop *prev_loop;

    prev_loop = pyg_save_current_main_loop(self->loop);

    pyg_begin_allow_threads;
    g_main_loop_run(self->loop);
    pyg_end_allow_threads;

    pyg_restore_current_main_loop(prev_loop);
   
    if (PyErr_Occurred())
        return NULL;

    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef _PyGMainLoop_methods[] = {
    { "get_context", (PyCFunction)_wrap_g_main_loop_get_context, METH_NOARGS },
    { "is_running", (PyCFunction)_wrap_g_main_loop_is_running, METH_NOARGS },
    { "quit", (PyCFunction)_wrap_g_main_loop_quit, METH_NOARGS },
    { "run", (PyCFunction)_wrap_g_main_loop_run, METH_NOARGS },
    { NULL, NULL, 0 }
};

PyTypeObject PyGMainLoop_Type = {
    PyObject_HEAD_INIT(NULL)
    0,			
    "gobject.MainLoop",	
    sizeof(PyGMainLoop),	
    0,			
    /* methods */
    (destructor)pyg_main_loop_dealloc,	
    (printfunc)0,	
    (getattrfunc)0,	
    (setattrfunc)0,	
    (cmpfunc)pyg_main_loop_compare,
    (reprfunc)0,	
    0,			
    0,		
    0,		
    (hashfunc)0,		
    (ternaryfunc)0,		
    (reprfunc)0,		
    (getattrofunc)0,		
    (setattrofunc)0,		
    0,				
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 
    NULL,
    (traverseproc)0,		
    (inquiry)0,
    (richcmpfunc)0,	
    0,             
    (getiterfunc)0,
    (iternextfunc)0,
    _PyGMainLoop_methods,
    0,				
    0,		       	
    NULL,		
    NULL,		
    (descrgetfunc)0,	
    (descrsetfunc)0,	
    0,                 
    (initproc)pyg_main_loop_new,
};


syntax highlighted by Code2HTML, v. 0.9.1