/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@purple.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This library 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 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <glib/gi18n-lib.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <gplugin.h>
#include <gplugin-native.h>

#include <purple.h>

#define PURPLE_IDLE_XSCREENSAVER_DOMAIN (g_quark_from_static_string("idle-xscreensaver"))
#define PURPLE_IDLE_XSCREENSAVER_IDLE_SOURCE ("purple-xscreensaver")

/******************************************************************************
 * Globals
 *****************************************************************************/
static GDateTime *idle_at = NULL;
static guint idle_source = 0;

static Display *display = NULL;
static Atom XA_BLANK = 0;
static Atom XA_SCREENSAVER_STATUS = 0;

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static gboolean
purple_idle_xscreensaver_idle_handler(gpointer data) {
	Display *display = data;

	while(XPending(display) > 0) {
		Atom type;
		Window root_window;
		XEvent event;
		int result = 0;
		int format = 0;
		gulong n_items = 0;
		gulong bytes_after = 0;
		guchar *x_data_ptr = NULL;

		XNextEvent(display, &event);

		if(event.type != PropertyNotify) {
			continue;
		}

		if(event.xproperty.state != PropertyNewValue) {
			continue;
		}

		if(event.xproperty.atom != XA_SCREENSAVER_STATUS) {
			continue;
		}

		root_window = RootWindow(display, 0);

		/* We don't use format or bytes_after, but if you pass NULL we
		 * segfault, so we need them to avoid that.
		 */
		result = XGetWindowProperty(display, root_window,
		                            XA_SCREENSAVER_STATUS, 0, 999, False,
		                            XA_INTEGER, &type, &format, &n_items,
		                            &bytes_after, &x_data_ptr);
		if(result == Success && type != 0 && x_data_ptr != NULL) {
			gulong *x_data = (gulong *)x_data_ptr;
			gboolean need_to_set = FALSE;
			const char *state = NULL;

			if(type != XA_INTEGER || n_items < 3) {
				g_clear_pointer(&x_data, XFree);

				g_message("malformed status, ignoring");

				return FALSE;
			}

			if(x_data[0] == XA_BLANK && idle_at == NULL) {
				state = "blank";
				idle_at = g_date_time_new_from_unix_local((gint64)x_data[1]);
				need_to_set = TRUE;
			} else if(x_data[0] == 0) {
				need_to_set = TRUE;
				state = "unblank";
				g_clear_pointer(&idle_at, g_date_time_unref);
			}

			if(need_to_set) {
				PurpleCore *core = NULL;
				PurpleIdleManager *manager = NULL;

				core = purple_core_get_default();
				manager = purple_core_get_idle_manager(core);
				purple_idle_manager_set_source(manager,
				                               PURPLE_IDLE_XSCREENSAVER_IDLE_SOURCE,
				                               idle_at);
			}

			if(state != NULL) {
				g_message("state changed to %s", state);
			}

			g_clear_pointer(&x_data, XFree);
		}
	}

	return G_SOURCE_CONTINUE;
}

/******************************************************************************
 * Plugin Exports
 *****************************************************************************/
static GPluginPluginInfo *
purple_idle_xscreensaver_query(G_GNUC_UNUSED GError **error) {
	const char * const authors[] = {
		"Pidgin Developers <devel@pidgin.im>",
		NULL
	};
	const char *description = N_("Reads the idle status from XScreenSaver. "
		"If you aren't using XScreenSaver directly this will not do anything "
		"useful for you.");

	return purple_plugin_info_new(
		"id", "purple-idle-xscreensaver",
		"abi-version", PURPLE_ABI_VERSION,
		"name", N_("XScreenSaver idle reporting"),
		"version", DISPLAY_VERSION,
		"category", N_("Presence"),
		"summary", N_("Read idle state from XScreenSaver"),
		"description", description,
		"authors", authors,
		"website", PURPLE_WEBSITE,
		NULL);
}

static gboolean
purple_idle_xscreensaver_load(G_GNUC_UNUSED GPluginPlugin *plugin,
                              GError **error)
{
	PurpleIdleManager *manager = NULL;
	Window root_window;
	XWindowAttributes attributes;

	/* Remove any previous idle source we had to make sure we're in a known
	 * state.
	 */
	manager = purple_core_get_idle_manager(purple_core_get_default());
	purple_idle_manager_set_source(manager,
	                               PURPLE_IDLE_XSCREENSAVER_IDLE_SOURCE,
	                               NULL);

	display = XOpenDisplay(NULL);
	if(display == NULL) {
		g_set_error_literal(error, PURPLE_IDLE_XSCREENSAVER_DOMAIN, 0,
		                    _("failed to open an X11 display"));

		return FALSE;
	}

	root_window = RootWindow(display, 0);
	XGetWindowAttributes(display, root_window, &attributes);
	XSelectInput(display, root_window,
	             attributes.your_event_mask | PropertyChangeMask);

	/* Grab our atoms. */
	XA_BLANK = XInternAtom(display, "BLANK", FALSE);
	XA_SCREENSAVER_STATUS = XInternAtom(display, "_SCREENSAVER_STATUS", FALSE);

	XSelectInput(display, root_window, PropertyChangeMask);

	idle_source = g_idle_add(purple_idle_xscreensaver_idle_handler, display);

	return TRUE;
}

static gboolean
purple_idle_xscreensaver_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
                                G_GNUC_UNUSED gboolean shutdown,
                                G_GNUC_UNUSED GError **error)
{
	g_clear_pointer(&idle_at, g_date_time_unref);

	g_clear_handle_id(&idle_source, g_source_remove);
	g_clear_pointer(&display, XCloseDisplay);

	return TRUE;
}

GPLUGIN_NATIVE_PLUGIN_DECLARE(purple_idle_xscreensaver)
