/* $Id: openmoko.c,v 1.10 2012/01/03 21:40:58 khorben Exp $ */
/* Copyright (c) 2012 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Locker */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */
/* TODO:
 * - detect when charging (using apm_read()?) */



#include <System.h>
#include <gtk/gtk.h>
#ifdef DEBUG
# include <stdio.h>
#endif
#if defined(__NetBSD__)
# include <sys/param.h>
# include <sys/sysctl.h>
#elif defined(__linux__)
# include <linux/input.h>
# include <sys/ioctl.h>
# include <fcntl.h>
# include <unistd.h>
# include <stdint.h>
#else
# include <fcntl.h>
# include <unistd.h>
#endif
#include "Locker.h"


/* Openmoko */
/* private */
/* types */
typedef struct _Openmoko
{
	GtkWidget * window;
#if defined(__linux__)
	GIOChannel * channel;
	guint source;
#endif
} Openmoko;


/* prototypes */
/* plug-in */
static int _openmoko_init(LockerPlugin * plugin);
static void _openmoko_destroy(LockerPlugin * plugin);

/* useful */
static void _openmoko_show_dialog(LockerPlugin * plugin);

/* callbacks */
#if defined(__linux__)
static gboolean _openmoko_on_reset(gpointer data);
static gboolean _openmoko_on_watch_can_read(GIOChannel * source,
		GIOCondition condition, gpointer data);
#endif


/* public */
/* variables */
/* plug-in */
LockerPlugin plugin =
{
	NULL,
	"Openmoko",
	NULL,
	_openmoko_init,
	_openmoko_destroy,
	NULL,
	NULL
};


/* private */
/* functions */
/* openmoko_init */
static int _openmoko_init(LockerPlugin * plugin)
{
	Openmoko * openmoko;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((openmoko = object_new(sizeof(*openmoko))) == NULL)
		return -1;
	plugin->priv = openmoko;
	openmoko->window = NULL;
#if defined(__linux__)
	openmoko->channel = NULL;
	openmoko->source = 0;
	if(_openmoko_on_reset(plugin) == TRUE)
		openmoko->source = g_timeout_add(1000, _openmoko_on_reset,
				plugin);
#endif
	return 0;
}


/* openmoko_destroy */
static void _openmoko_destroy(LockerPlugin * plugin)
{
	Openmoko * openmoko = plugin->priv;
#if defined(__linux__)
	LockerPluginHelper * helper = plugin->helper;
	GError * error = NULL;
#endif

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(openmoko->window != NULL)
		gtk_widget_destroy(openmoko->window);
#if defined(__linux__)
	if(openmoko->source != 0)
		g_source_remove(openmoko->source);
	if(openmoko->channel != NULL && g_io_channel_shutdown(openmoko->channel,
				TRUE, &error) == G_IO_STATUS_ERROR)
	{
		helper->error(helper->locker, error->message, 1);
		g_error_free(error);
	}
#endif
	object_delete(openmoko);
}


/* useful */
/* openmoko_show_dialog */
static gboolean _dialog_on_closex(gpointer data);
static void _dialog_on_lock(gpointer data);
static void _dialog_on_suspend(gpointer data);
static void _dialog_on_shutdown(gpointer data);
enum { RES_CANCEL, RES_REBOOT, RES_SHUTDOWN };

static void _openmoko_show_dialog(LockerPlugin * plugin)
{
	Openmoko * openmoko = plugin->priv;
	GtkWidget * vbox;
	GtkWidget * widget;
	GtkWidget * image;

	if(openmoko->window != NULL)
	{
		gtk_window_present(GTK_WINDOW(openmoko->window));
		return;
	}
	openmoko->window = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(openmoko->window), "Power menu");
	g_signal_connect_swapped(openmoko->window, "delete-event", G_CALLBACK(
				_dialog_on_closex), plugin);
#if GTK_CHECK_VERSION(2, 14, 0)
	vbox = gtk_dialog_get_content_area(GTK_DIALOG(openmoko->window));
#else
	vbox = GTK_DIALOG(openmoko->window)->vbox;
#endif
	/* lock screen */
	widget = gtk_button_new_with_label("Lock screen");
	image = gtk_image_new_from_icon_name("gnome-lockscreen",
			GTK_ICON_SIZE_BUTTON);
	gtk_button_set_image(GTK_BUTTON(widget), image);
	g_signal_connect_swapped(widget, "clicked", G_CALLBACK(_dialog_on_lock),
			plugin);
	gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, TRUE, 0);
	/* suspend */
	widget = gtk_button_new_with_label("Suspend");
	image = gtk_image_new_from_icon_name("gtk-media-pause",
			GTK_ICON_SIZE_BUTTON);
	gtk_button_set_image(GTK_BUTTON(widget), image);
	g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
				_dialog_on_suspend), plugin);
	/* shutdown */
	widget = gtk_button_new_with_label("Shutdown");
	image = gtk_image_new_from_icon_name("gnome-shutdown",
			GTK_ICON_SIZE_BUTTON);
	gtk_button_set_image(GTK_BUTTON(widget), image);
	g_signal_connect_swapped(widget, "clicked", G_CALLBACK(
				_dialog_on_shutdown), plugin);
	/* FIXME implement */
	gtk_widget_show(openmoko->window);
}

static gboolean _dialog_on_closex(gpointer data)
{
	LockerPlugin * plugin = data;
	Openmoko * openmoko = plugin->priv;

	gtk_widget_hide(openmoko->window);
	return TRUE;
}

static void _dialog_on_lock(gpointer data)
{
	LockerPlugin * plugin = data;
	LockerPluginHelper * helper = plugin->helper;

	helper->action(helper->locker, LOCKER_ACTION_LOCK);
}

/* FIXME code duplicated from Panel */
static void _dialog_on_shutdown(gpointer data)
{
	LockerPlugin * plugin = data;
	LockerPluginHelper * helper = plugin->helper;
	GtkWidget * window;
	GtkWidget * widget;
#ifdef EMBEDDED
	const char * message = "This will shutdown your device,"
			" therefore closing any application currently opened"
			" and losing any unsaved data.\n"
			"Do you really want to proceed?";
#else
	const char * message = "This will shutdown your computer,"
			" therefore closing any application currently opened"
			" and losing any unsaved data.\n"
			"Do you really want to proceed?";
#endif
	int res;
	char * reboot[] = { "/sbin/shutdown", "shutdown", "-r", "now", NULL };
	char * shutdown[] = { "/sbin/shutdown", "shutdown",
#if defined(__NetBSD__)
		"-p",
#else
		"-h",
#endif
		"now", NULL };
	char ** argv;
	GError * error = NULL;

	window = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
			GTK_BUTTONS_NONE, "%s",
#if GTK_CHECK_VERSION(2, 6, 0)
			"Shutdown");
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(window),
#endif
			"%s", message);
	gtk_dialog_add_buttons(GTK_DIALOG(window), GTK_STOCK_CANCEL,
			RES_CANCEL, "Restart", RES_REBOOT, NULL);
	widget = gtk_button_new_with_label("Shutdown");
	gtk_button_set_image(GTK_BUTTON(widget), gtk_image_new_from_icon_name(
				"gnome-shutdown", GTK_ICON_SIZE_BUTTON));
	gtk_widget_show_all(widget);
	gtk_dialog_add_action_widget(GTK_DIALOG(window), widget, RES_SHUTDOWN);
	gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
	gtk_window_set_title(GTK_WINDOW(window), "Shutdown");
	res = gtk_dialog_run(GTK_DIALOG(window));
	gtk_widget_destroy(window);
	if(res == RES_SHUTDOWN)
		argv = shutdown;
	else if(res == RES_REBOOT)
		argv = reboot;
	else
		return;
	if(g_spawn_async(NULL, argv, NULL, G_SPAWN_FILE_AND_ARGV_ZERO, NULL,
				NULL, NULL, &error) != TRUE)
	{
		helper->error(helper->locker, error->message, 1);
		g_error_free(error);
	}
}

/* FIXME code duplicated from Panel */
static void _dialog_on_suspend(gpointer data)
{
	LockerPlugin * plugin = data;
	LockerPluginHelper * helper = plugin->helper;
#if defined(__NetBSD__)
	int sleep_state = 3;
#else
	int fd;
	char * suspend[] = { "/usr/bin/sudo", "sudo", "/usr/bin/apm", "-s",
		NULL };
	GError * error = NULL;
#endif

#if defined(__NetBSD__)
	if(sysctlbyname("machdep.sleep_state", NULL, NULL, &sleep_state,
				sizeof(sleep_state)) != 0)
	{
		helper->error(helper->locker, "sysctl", 1);
		return;
	}
#else
	if((fd = open("/sys/power/state", O_WRONLY)) >= 0)
	{
		write(fd, "mem\n", 4);
		close(fd);
		return;
	}
	if(g_spawn_async(NULL, suspend, NULL, G_SPAWN_FILE_AND_ARGV_ZERO, NULL,
				NULL, NULL, &error) != TRUE)
	{
		helper->error(helper->locker, error->message, 1);
		g_error_free(error);
		return;
	}
#endif
	/* XXX may already be suspended */
	helper->action(helper->locker, LOCKER_ACTION_LOCK);
}


/* callbacks */
#if defined(__linux__)
/* openmoko_on_reset */
static gboolean _openmoko_on_reset(gpointer data)
{
	LockerPlugin * plugin = data;
	LockerPluginHelper * helper = plugin->helper;
	Openmoko * openmoko = plugin->priv;
	int fd;
#ifdef DEBUG
	char buf[256] = "Unknown";
#endif
	GError * error = NULL;

	/* FIXME open all of the relevant input event nodes */
	if((fd = open("/dev/input/event0", O_RDONLY)) < 0)
		return TRUE;
#ifdef DEBUG
	if(ioctl(fd, EVIOCGNAME(sizeof(buf)), buf) == 0)
		fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, buf);
#endif
	openmoko->channel = g_io_channel_unix_new(fd);
	if(g_io_channel_set_encoding(openmoko->channel, NULL, &error)
			!= G_IO_STATUS_NORMAL)
	{
		helper->error(helper->locker, error->message, 1);
		g_error_free(error);
	}
	g_io_channel_set_buffered(openmoko->channel, FALSE);
	openmoko->source = g_io_add_watch(openmoko->channel, G_IO_IN,
			_openmoko_on_watch_can_read, plugin);
	return FALSE;
}


/* openmoko_on_watch_can_read */
static gboolean _watch_can_read_event(LockerPlugin * plugin,
		struct input_event * event);
static void _watch_can_read_event_key(LockerPlugin * plugin, uint16_t code,
		int32_t value);
static gboolean _watch_can_read_reset(LockerPlugin * plugin);

static gboolean _openmoko_on_watch_can_read(GIOChannel * source,
		GIOCondition condition, gpointer data)
{
	LockerPlugin * plugin = data;
	LockerPluginHelper * helper = plugin->helper;
	Openmoko * openmoko = plugin->priv;
	struct input_event event;
	gsize cnt = 0;
	GError * error = NULL;
	GIOStatus status;

	if(condition != G_IO_IN || source != openmoko->channel)
		return FALSE; /* should not happen */
	status = g_io_channel_read_chars(source, (gchar *)&event, sizeof(event),
			&cnt, &error);
	switch(status)
	{
		case G_IO_STATUS_NORMAL:
			break;
		case G_IO_STATUS_ERROR:
			helper->error(helper->locker, error->message, 1);
			g_error_free(error);
		case G_IO_STATUS_EOF:
		default:
			return _watch_can_read_reset(plugin);
	}
	/* FIXME avoid this by using a regular read() */
	if(cnt != sizeof(event))
		return _watch_can_read_reset(plugin);
	return _watch_can_read_event(plugin, &event);
}

static gboolean _watch_can_read_event(LockerPlugin * plugin,
		struct input_event * event)
{
	switch(event->type)
	{
		case EV_KEY:
			_watch_can_read_event_key(plugin, event->code,
					event->value);
			break;
#ifdef DEBUG
		default:
			fprintf(stderr, "DEBUG: %s() Unknown event type %u\n",
					__func__, event->type);
			break;
#endif
	}
	return TRUE;
}

static void _watch_can_read_event_key(LockerPlugin * plugin, uint16_t code,
		int32_t value)
{
	LockerPluginHelper * helper = plugin->helper;

	switch(code)
	{
		case KEY_PHONE:
			if(value == 0) /* released */
				helper->action(helper->locker,
						LOCKER_ACTION_LOCK);
			break;
		case KEY_POWER:
			if(value == 0) /* released */
				_openmoko_show_dialog(plugin);
			break;
	}
}

static gboolean _watch_can_read_reset(LockerPlugin * plugin)
{
	LockerPluginHelper * helper = plugin->helper;
	Openmoko * openmoko = plugin->priv;
	GError * error = NULL;

	if(g_io_channel_shutdown(openmoko->channel, TRUE, &error)
			== G_IO_STATUS_ERROR)
	{
		helper->error(helper->locker, error->message, 1);
		g_error_free(error);
	}
	openmoko->source = g_timeout_add(1000, _openmoko_on_reset, plugin);
	return FALSE;
}
#endif