/* $Id$ */
/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Panel */
/* 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/>. */



#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include <System.h>
#include "Panel/applet.h"
#define _(string) gettext(string)
#define N_(string) string


/* Title */
/* private */
/* types */
typedef struct _PanelApplet
{
	PanelAppletHelper * helper;
	GtkWidget * widget;
	gulong source;

	GdkDisplay * display;
	GdkScreen * screen;
	GdkWindow * root;

	Atom atom_active;
	Atom atom_name;
	Atom atom_utf8_string;
	Atom atom_visible_name;
} Title;


/* prototypes */
/* title */
static Title * _title_init(PanelAppletHelper * helper, GtkWidget ** widget);
static void _title_destroy(Title * title);

/* accessors */
static int _title_get_text_property(Title * title, Window window, Atom property,
		char ** ret);

/* useful */
static void _title_do(Title * title);

/* callbacks */
static GdkFilterReturn _title_on_filter(GdkXEvent * xevent, GdkEvent * event,
		gpointer data);
static void _title_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
		gpointer data);


/* public */
/* variables */
PanelAppletDefinition applet =
{
	N_("Title"),
	NULL,
	NULL,
	_title_init,
	_title_destroy,
	NULL,
	FALSE,
	TRUE
};


/* private */
/* functions */
/* title_init */
static Title * _title_init(PanelAppletHelper * helper, GtkWidget ** widget)
{
	Title * title;
	PangoFontDescription * bold;

	if((title = malloc(sizeof(*title))) == NULL)
	{
		error_set("%s: %s", applet.name, strerror(errno));
		return NULL;
	}
	title->helper = helper;
	bold = pango_font_description_new();
	pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD);
	title->widget = gtk_label_new("");
#if GTK_CHECK_VERSION(3, 0, 0)
	gtk_widget_override_font(title->widget, bold);
#else
	gtk_widget_modify_font(title->widget, bold);
#endif
	pango_font_description_free(bold);
	title->source = g_signal_connect(title->widget, "screen-changed",
			G_CALLBACK(_title_on_screen_changed), title);
	title->display = NULL;
	title->screen = NULL;
	title->root = NULL;
	title->atom_active = 0;
	title->atom_name = 0;
	title->atom_visible_name = 0;
	gtk_widget_show(title->widget);
	*widget = title->widget;
	return title;
}


/* title_destroy */
static void _title_destroy(Title * title)
{
	if(title->source != 0)
		g_signal_handler_disconnect(title->widget, title->source);
	title->source = 0;
	if(title->root != NULL)
		gdk_window_remove_filter(title->root, _title_on_filter, title);
	gtk_widget_destroy(title->widget);
	free(title);
}


/* accessors */
/* title_get_window_property */
static int _title_get_window_property(Title * title, Window window,
		Atom property, Atom atom, unsigned long * cnt,
		unsigned char ** ret)
{
	int res;
	Atom type;
	int format;
	unsigned long bytes;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(title, window, %lu, %lu)\n", __func__,
			property, atom);
#endif
	gdk_error_trap_push();
	res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(title->display), window,
			property, 0, G_MAXLONG, False, atom, &type, &format,
			cnt, &bytes, ret);
	if(gdk_error_trap_pop() != 0 || res != Success)
		return 1;
	if(type != atom)
	{
		if(*ret != NULL)
			XFree(*ret);
		*ret = NULL;
		return 1;
	}
	return 0;
}


/* title_get_text_property */
static int _title_get_text_property(Title * title, Window window, Atom property,
		char ** ret)
{
	int res;
	XTextProperty text;
	GdkAtom atom;
	int cnt;
	char ** list;
	int i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(title, window, %lu)\n", __func__, property);
#endif
	gdk_error_trap_push();
	res = XGetTextProperty(GDK_DISPLAY_XDISPLAY(title->display), window,
			&text, property);
	if(gdk_error_trap_pop() != 0 || res == 0)
		return 1;
	atom = gdk_x11_xatom_to_atom(text.encoding);
#if GTK_CHECK_VERSION(2, 24, 0)
	cnt = gdk_x11_display_text_property_to_text_list(title->display,
			atom, text.format, text.value, text.nitems, &list);
#else
	cnt = gdk_text_property_to_utf8_list(atom, text.format, text.value,
			text.nitems, &list);
#endif
	if(cnt > 0)
	{
		*ret = list[0];
		for(i = 1; i < cnt; i++)
			g_free(list[i]);
		g_free(list);
	}
	else
		*ret = NULL;
	if(text.value != NULL)
		XFree(text.value);
	return 0;
}


/* title_do */
static char * _do_name(Title * title, Window window);
static char * _do_name_text(Title * title, Window window, Atom property);
static char * _do_name_utf8(Title * title, Window window, Atom property);

static void _title_do(Title * title)
{
	unsigned long cnt = 0;
	Window * window;
	char * name;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(_title_get_window_property(title, GDK_WINDOW_XID(title->root),
				title->atom_active, XA_WINDOW, &cnt,
				(void*)&window) != 0 || cnt != 1)
	{
		gtk_label_set_text(GTK_LABEL(title->widget), "");
		return;
	}
	name = _do_name(title, *window);
	XFree(window);
	gtk_label_set_text(GTK_LABEL(title->widget), (name != NULL)
			? name : "");
	free(name);
}

static char * _do_name(Title * title, Window window)
{
	char * ret;

	if((ret = _do_name_utf8(title, window, title->atom_visible_name))
			!= NULL)
		return ret;
	if((ret = _do_name_utf8(title, window, title->atom_name)) != NULL)
		return ret;
	if((ret = _do_name_text(title, window, XA_WM_NAME)) != NULL)
		return ret;
	return g_strdup(_("(Untitled)"));
}

static char * _do_name_text(Title * title, Window window, Atom property)
{
	char * ret = NULL;

	if(_title_get_text_property(title, window, property, (void*)&ret) != 0)
		return NULL;
	return ret;
}

static char * _do_name_utf8(Title * title, Window window, Atom property)
{
	char * ret = NULL;
	char * str = NULL;
	unsigned long cnt = 0;

	if(_title_get_window_property(title, window, property,
				title->atom_utf8_string, &cnt, (void*)&str)
			!= 0)
		return NULL;
	if(g_utf8_validate(str, cnt, NULL))
		ret = g_strndup(str, cnt);
	XFree(str);
	return ret;
}


/* callbacks */
/* title_on_filter */
static GdkFilterReturn _title_on_filter(GdkXEvent * xevent, GdkEvent * event,
		gpointer data)
{
	Title * title = data;
	XEvent * xev = xevent;
	(void) event;

	if(xev->type != PropertyNotify)
		return GDK_FILTER_CONTINUE;
	if(xev->xproperty.atom != title->atom_active)
		return GDK_FILTER_CONTINUE;
	_title_do(title);
	return GDK_FILTER_CONTINUE;
}


/* title_on_screen_changed */
static void _title_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
		gpointer data)
{
	Title * title = data;
	GdkEventMask events;
	(void) previous;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(title->root != NULL)
		gdk_window_remove_filter(title->root, _title_on_filter, title);
	title->screen = gtk_widget_get_screen(widget);
	title->display = gdk_screen_get_display(title->screen);
	title->root = gdk_screen_get_root_window(title->screen);
	events = gdk_window_get_events(title->root);
	gdk_window_set_events(title->root, events
			| GDK_PROPERTY_CHANGE_MASK);
	gdk_window_add_filter(title->root, _title_on_filter, title);
	title->atom_active = gdk_x11_get_xatom_by_name_for_display(
			title->display, "_NET_ACTIVE_WINDOW");
	title->atom_name = gdk_x11_get_xatom_by_name_for_display(
			title->display, "_NET_WM_NAME");
	title->atom_utf8_string = gdk_x11_get_xatom_by_name_for_display(
			title->display, "UTF8_STRING");
	title->atom_visible_name = gdk_x11_get_xatom_by_name_for_display(
			title->display, "_NET_WM_VISIBLE_NAME");
	_title_do(title);
}
