/* $Id$ */
/* Copyright (c) 2009 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Framer */
/* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include "framer.h"
#include "../config.h"

#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))


/* Framer */
/* private */
/* types */
typedef enum _FramerAtom
{
	FA_NET_ACTIVE_WINDOW = 0,
	FA_NET_CLIENT_LIST,
	FA_NET_CURRENT_DESKTOP,
	FA_NET_DESKTOP_GEOMETRY,
	FA_NET_DESKTOP_VIEWPORT,
	FA_NET_NUMBER_OF_DESKTOPS,
	FA_NET_SHOWING_DESKTOP,
	FA_NET_SUPPORTED,
	FA_NET_WM_STATE,
	FA_NET_WM_STATE_ADD,
	FA_NET_WM_STATE_FULLSCREEN,
	FA_NET_WM_STATE_REMOVE,
	FA_NET_WM_STATE_TOGGLE,
	FA_NET_WM_WINDOW_TYPE,
	FA_NET_WM_WINDOW_TYPE_DESKTOP,
	FA_NET_WM_WINDOW_TYPE_DOCK,
	FA_NET_WM_WINDOW_TYPE_NORMAL,
	FA_NET_WORKAREA
} FramerAtom;
#define FA_LAST				FA_NET_WORKAREA
#define FA_COUNT			(FA_LAST + 1)
#define FA_NET_WM_WINDOW_TYPE_FIRST	FA_NET_WM_WINDOW_TYPE_DESKTOP
#define FA_NET_WM_WINDOW_TYPE_LAST	FA_NET_WM_WINDOW_TYPE_NORMAL

struct _Framer
{
	/* essential */
	GdkDisplay * display;
	GdkScreen * screen;
	GdkWindow * root;

	/* screen */
	int width;
	int height;

	/* atoms */
	Atom atom[FA_COUNT];

	/* client list */
	Window * window;
	size_t window_cnt;
};


/* constants */
static char const * _framer_atom[FA_COUNT] =
{
	"_NET_ACTIVE_WINDOW",
	"_NET_CLIENT_LIST",
	"_NET_CURRENT_DESKTOP",
	"_NET_DESKTOP_GEOMETRY",
	"_NET_DESKTOP_VIEWPORT",
	"_NET_NUMBER_OF_DESKTOPS",
	"_NET_SHOWING_DESKTOP",
	"_NET_SUPPORTED",
	"_NET_WM_STATE",
	"_NET_WM_STATE_ADD",
	"_NET_WM_STATE_FULLSCREEN",
	"_NET_WM_STATE_REMOVE",
	"_NET_WM_STATE_TOGGLE",
	"_NET_WM_WINDOW_TYPE",
	"_NET_WM_WINDOW_TYPE_DESKTOP",
	"_NET_WM_WINDOW_TYPE_DOCK",
	"_NET_WM_WINDOW_TYPE_NORMAL",
	"_NET_WORKAREA"
};


/* prototypes */
static int _framer_error(char const * message, int ret);
static int _framer_window_get_property(Framer * framer, Window window,
		FramerAtom property, Atom atom, unsigned long * cnt,
		unsigned char ** ret);
static int _framer_window_is_manageable(Framer * framer, Window window);
static int _framer_window_set_property(Framer * framer, Window window,
		FramerAtom property, Atom atom, unsigned long cnt,
		unsigned char * data);

/* callbacks */
static GdkFilterReturn _framer_filter(GdkXEvent * xevent, GdkEvent * event,
		gpointer data);


/* public */
/* functions */
/* framer_new */
static void _new_atoms(Framer * framer);
static void _new_windows(Framer * framer);
static void _new_ewmh(Framer * framer);

Framer * framer_new(void)
{
	Framer * framer;

	if((framer = malloc(sizeof(*framer))) == NULL)
	{
		_framer_error(strerror(errno), 1);
		return NULL;
	}
	if((framer->display = gdk_display_get_default()) == NULL)
	{
		_framer_error("There is no default display", 1);
		free(framer);
		return NULL;
	}
	framer->screen = gdk_display_get_default_screen(framer->display);
	framer->root = gdk_screen_get_root_window(framer->screen);
	framer->width = gdk_screen_get_width(framer->screen);
	framer->height = gdk_screen_get_height(framer->screen);
	_new_atoms(framer);
	_new_windows(framer);
	_new_ewmh(framer);
	/* register as window manager */
	XSelectInput(gdk_x11_display_get_xdisplay(framer->display),
			GDK_WINDOW_XWINDOW(framer->root), NoEventMask
			| SubstructureNotifyMask
			| SubstructureRedirectMask);
	gdk_window_add_filter(NULL, _framer_filter, framer);
	return framer;
}

static void _new_atoms(Framer * framer)
{
	size_t i;

	for(i = 0; i < FA_COUNT; i++)
		framer->atom[i] = gdk_x11_get_xatom_by_name_for_display(
				framer->display, _framer_atom[i]);
}

static void _new_windows(Framer * framer)
{
	Window root;
	Window parent;
	Window * children = NULL;
	unsigned int nchildren = 0;
	unsigned int i;

	framer->window = NULL;
	framer->window_cnt = 0;
	if(XQueryTree(GDK_DISPLAY_XDISPLAY(framer->display),
				GDK_WINDOW_XWINDOW(framer->root), &root,
				&parent, &children, &nchildren) == 0)
	{
#ifdef DEBUG
		fprintf(stderr, "DEBUG: %s() failed\n", __func__);
#endif
		return;
	}
	for(i = 0; i < nchildren; i++)
		framer_window_add(framer, children[i]);
	XFree(children);
}

static void _new_ewmh(Framer * framer)
{
	long data[4];
	size_t i;
	Atom type = XA_CARDINAL;
	unsigned char * p = (unsigned char *)&data;
	long cnt = 0;

	memset(&data, 0, sizeof(data));
	for(i = 0; i < FA_COUNT; i++)
	{
		switch(i)
		{
			case FA_NET_ACTIVE_WINDOW:
				type = XA_WINDOW;
				data[0] = None;
				cnt = 1;
				break;
			case FA_NET_CLIENT_LIST:
				type = XA_WINDOW;
				p = (unsigned char *)framer->window;
				cnt = framer->window_cnt;
				break;
			case FA_NET_CURRENT_DESKTOP:
				cnt = 1;
				break;
			case FA_NET_NUMBER_OF_DESKTOPS:
				data[0] = 1;
				cnt = 1;
				break;
			case FA_NET_DESKTOP_GEOMETRY:
				data[0] = framer->width;
				data[1] = framer->height;
				cnt = 2;
				break;
			case FA_NET_DESKTOP_VIEWPORT:
				cnt = 2;
				break;
			case FA_NET_SUPPORTED:
				type = XA_ATOM;
				p = (unsigned char *)framer->atom;
				cnt = FA_COUNT;
				break;
			case FA_NET_WORKAREA:
				data[2] = framer->width;
				data[3] = max(framer->height - 64, 1);
				cnt = 4;
				break;
			default:
				continue;
		}
		_framer_window_set_property(framer, GDK_WINDOW_XWINDOW(
					framer->root), i, type, cnt, p);
		type = XA_CARDINAL;
		cnt = 0;
		memset(&data, 0, sizeof(data));
		p = (unsigned char *)&data;
	}
}


/* framer_delete */
void framer_delete(Framer * framer)
{
	int i;

	for(i = 0; i < FA_COUNT; i++)
		XDeleteProperty(GDK_DISPLAY_XDISPLAY(framer->display),
				GDK_WINDOW_XWINDOW(framer->root),
				framer->atom[i]);
	free(framer);
}


/* accessors */
/* framer_set_show_desktop */
void framer_set_show_desktop(Framer * framer, gboolean shown)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%s)\n", __func__, shown ? "TRUE" : "FALSE");
#endif
	if(shown != TRUE)
		return;
	for(i = 0; i < framer->window_cnt; i++)
		framer_window_iconify(framer, framer->window[i]);
}


/* framer_window_is_fullscreen */
int framer_window_is_fullscreen(Framer * framer, Window window,
		gboolean * fullscreen)
{
	XWindowAttributes wa;

	gdk_error_trap_push();
	XGetWindowAttributes(GDK_DISPLAY_XDISPLAY(framer->display), window,
			&wa);
	if(gdk_error_trap_pop() != 0)
		return 1;
	*fullscreen = (wa.x == 0 && wa.y == 0 && wa.height == framer->height
			&& wa.width == framer->width) ? TRUE : FALSE;
	return 0;
}


/* framer_window_set_active */
int framer_window_set_active(Framer * framer, Window window)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window);
#endif
	XMapRaised(GDK_DISPLAY_XDISPLAY(framer->display), window);
	return _framer_window_set_property(framer, window, FA_NET_ACTIVE_WINDOW,
			XA_WINDOW, 1, (unsigned char *)&window);
}


/* framer_window_set_fullscreen */
int framer_window_set_fullscreen(Framer * framer, Window window,
		gboolean fullscreen)
{
	XWindowChanges wc;
	unsigned long mask = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%s)\n", __func__, fullscreen ? "TRUE"
			: "FALSE");
#endif
	memset(&wc, 0, sizeof(wc));
	if(fullscreen == TRUE)
	{
		wc.width = framer->width;
		wc.height = framer->height;
		mask |= CWX | CWY | CWHeight | CWWidth;
	}
	else
#ifdef EMBEDDED
	{
		wc.width = framer->width;
		wc.height = framer->height - 64;
		mask |= CWX | CWY | CWHeight | CWWidth;
	}
#else
		return 1; /* FIXME implement */
#endif
	gdk_error_trap_push();
	XConfigureWindow(GDK_DISPLAY_XDISPLAY(framer->display), window,
			mask, &wc);
	if(gdk_error_trap_pop() != 0)
		return 1;
	return 0;
}


/* useful */
/* framer_window_add */
int framer_window_add(Framer * framer, Window window)
{
	size_t i;
	Window * p;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window);
#endif
	if(!_framer_window_is_manageable(framer, window))
		return 0;
	for(i = 0; i < framer->window_cnt; i++)
		if(framer->window[i] == window)
			return 0;
	if((p = realloc(framer->window, sizeof(*p) * (framer->window_cnt + 1)))
			== NULL)
		return -1;
	framer->window = p;
	p = &framer->window[framer->window_cnt++];
	*p = window;
	_framer_window_set_property(framer, GDK_WINDOW_XWINDOW(framer->root),
			FA_NET_CLIENT_LIST, XA_WINDOW, framer->window_cnt,
			(unsigned char *)framer->window);
	return 0;
}


/* framer_window_iconify */
int framer_window_iconify(Framer * framer, Window window)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window);
#endif
	gdk_error_trap_push();
	XUnmapWindow(GDK_DISPLAY_XDISPLAY(framer->display), window);
	if(gdk_error_trap_pop() != 0)
		return 1;
	return 0;
}


/* framer_window_remove */
int framer_window_remove(Framer * framer, Window window)
{
	size_t i;
	Window * p;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d)\n", __func__, (int)window);
#endif
	for(i = 0; i < framer->window_cnt; i++)
		if(framer->window[i] == window)
			break;
	if(i == framer->window_cnt) /* we don't know this window anyway */
		return 0;
	memmove(&framer->window[i], &framer->window[i + 1],
			(--framer->window_cnt - i) * sizeof(*p));
	if((p = realloc(framer->window, framer->window_cnt * sizeof(*p)))
			!= NULL) /* we can ignore errors */
		framer->window = p;
	_framer_window_set_property(framer, GDK_WINDOW_XWINDOW(framer->root),
			FA_NET_CLIENT_LIST, XA_WINDOW, framer->window_cnt,
			(unsigned char *)framer->window);
	return 0;
}


/* private */
/* functions */
/* framer_error */
static int _framer_error(char const * message, int ret)
{
	fprintf(stderr, "%s: %s\n", PACKAGE, message);
	return ret;
}


/* framer_window_get_property */
static int _framer_window_get_property(Framer * framer, Window window,
		FramerAtom property, Atom atom, unsigned long * cnt,
		unsigned char ** ret)
{
	int res;
	Atom type;
	int format;
	unsigned long bytes;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d, %s, %lu)\n", __func__, (int)window,
			_framer_atom[property], atom);
#endif
	gdk_error_trap_push();
	res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(framer->display), window,
			framer->atom[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;
}


/* framer_window_is_manageable */
static int _framer_window_is_manageable(Framer * framer, Window window)
{
	Atom typehint;
	Atom * p = NULL;
	unsigned long cnt = 0;

	if(_framer_window_get_property(framer, window, FA_NET_WM_WINDOW_TYPE,
				XA_ATOM, &cnt, (void*)&p) != 0)
		return 0;
	typehint = *p;
	XFree(p);
	return typehint == framer->atom[FA_NET_WM_WINDOW_TYPE_NORMAL] ? 1 : 0;
}


/* framer_window_set_property */
static int _framer_window_set_property(Framer * framer, Window window,
		FramerAtom property, Atom atom, unsigned long cnt,
		unsigned char * data)
{
	int res;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%d, %s, %lu, %lu)\n", __func__,
			(int)window, _framer_atom[property], atom, cnt);
#endif
	gdk_error_trap_push();
	res = XChangeProperty(GDK_DISPLAY_XDISPLAY(framer->display), window,
			framer->atom[property], atom, 32, PropModeReplace,
			data, cnt);
	if(gdk_error_trap_pop() != 0 || res != Success)
		return 1;
	return 0;
}


/* callbacks */
/* framer_filter */
static GdkFilterReturn _filter_client_message(XClientMessageEvent * xclient,
		Framer * framer);
static GdkFilterReturn _filter_configure_notify(XConfigureEvent * xconfigure);
static GdkFilterReturn _filter_configure_request(
		XConfigureRequestEvent * xconfigure, Framer * framer);
static GdkFilterReturn _filter_create_notify(XCreateWindowEvent * xcreate,
		Framer * framer);
static GdkFilterReturn _filter_destroy_notify(XDestroyWindowEvent * xdestroy,
		Framer * framer);
static GdkFilterReturn _filter_leave_notify(XCrossingEvent * xleave);
static GdkFilterReturn _filter_map_notify(XMapEvent * xmap);
static GdkFilterReturn _filter_map_request(XMapRequestEvent * xmap);
static GdkFilterReturn _filter_motion_notify(XMotionEvent * xmotion);
static GdkFilterReturn _filter_property_notify(XPropertyEvent * xproperty,
		Framer * framer);
static GdkFilterReturn _filter_unmap_notify(XUnmapEvent * xunmap);

static GdkFilterReturn _framer_filter(GdkXEvent * xevent, GdkEvent * event,
		gpointer data)
{
	Framer * framer = data;
	XEvent * xev = xevent;

	switch(xev->type)
	{
		case ClientMessage:
			return _filter_client_message(&xev->xclient, framer);
		case ConfigureNotify:
			return _filter_configure_notify(&xev->xconfigure);
		case ConfigureRequest:
			return _filter_configure_request(
					&xev->xconfigurerequest, framer);
		case CreateNotify:
			return _filter_create_notify(&xev->xcreatewindow,
					framer);
		case DestroyNotify:
			return _filter_destroy_notify(&xev->xdestroywindow,
					framer);
		case LeaveNotify:
			return _filter_leave_notify(&xev->xcrossing);
		case MapNotify:
			return _filter_map_notify(&xev->xmap);
		case MapRequest:
			return _filter_map_request(&xev->xmaprequest);
		case MotionNotify:
			return _filter_motion_notify(&xev->xmotion);
		case PropertyNotify:
			return _filter_property_notify(&xev->xproperty,
					framer);
		case UnmapNotify:
			return _filter_unmap_notify(&xev->xunmap);
		default:
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() type=%d\n", __func__,
					xev->type);
#endif
			break;
	}
	return GDK_FILTER_CONTINUE;
}

static void _message_state(Framer * framer, Window window, unsigned long hint,
		unsigned long property);

static GdkFilterReturn _filter_client_message(XClientMessageEvent * xclient,
		Framer * framer)
{
	GdkAtom atom;
	size_t i;
	char const * name;
	char * p = NULL;

	for(i = 0; i < FA_COUNT; i++)
		if(xclient->message_type == framer->atom[i])
			break;
	switch(i)
	{
		case FA_NET_ACTIVE_WINDOW:
			framer_window_set_active(framer, xclient->window);
			break;
		case FA_NET_SHOWING_DESKTOP:
			framer_set_show_desktop(framer, TRUE);
			break;
		case FA_NET_WM_STATE:
			_message_state(framer, xclient->window,
					xclient->data.l[0], xclient->data.l[1]);
			_message_state(framer, xclient->window,
					xclient->data.l[0], xclient->data.l[2]);
			break;
		default:
			if(i < FA_COUNT)
				name = _framer_atom[i];
			else
			{
				atom = gdk_x11_xatom_to_atom_for_display(
						framer->display,
						xclient->message_type);
				p = gdk_atom_name(atom);
				name = p;
			}
			fprintf(stderr, "%s: %s: %s\n", PACKAGE, name,
					"Unsupported message");
			if(p != NULL)
				g_free(p);
			break;
	}
	return GDK_FILTER_CONTINUE;
}

static void _message_state(Framer * framer, Window window, unsigned long hint,
		unsigned long property)
{
	int i;
	gboolean enabled;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%ld, %ld)\n", __func__, hint, property);
#endif
	if(property == 0)
		return;
	for(i = 0; i < FA_COUNT; i++)
		if(framer->atom[i] == property)
			break;
	switch(i)
	{
		case FA_NET_WM_STATE_FULLSCREEN:
			if(hint == framer->atom[FA_NET_WM_STATE_ADD])
				enabled = TRUE;
			else if(hint == framer->atom[FA_NET_WM_STATE_REMOVE])
				enabled = FALSE;
			else /* assume toggle */
			{
				if(framer_window_is_fullscreen(framer, window,
							&enabled) != 0)
					break;
				enabled = !enabled;
			}
			framer_window_set_fullscreen(framer, window, enabled);
			break;
	}
}

static GdkFilterReturn _filter_configure_notify(XConfigureEvent * xconfigure)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_configure_request(
		XConfigureRequestEvent * xconfigure, Framer * framer)
{
	FramerAtom type = FA_NET_WM_WINDOW_TYPE_NORMAL;
	Atom typehint;
	Atom * p;
	unsigned long cnt = 0;
	int i;
	XWindowChanges wc;
	unsigned long mask = xconfigure->value_mask;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() (%d,%d) %dx%d %lu\n", __func__,
			xconfigure->x, xconfigure->y, xconfigure->width,
			xconfigure->height, xconfigure->value_mask);
#endif
	if(_framer_window_get_property(framer, xconfigure->window,
		FA_NET_WM_WINDOW_TYPE, XA_ATOM, &cnt, (void*)&p) == 0)
	{
		typehint = *p;
		XFree(p);
		for(i = FA_NET_WM_WINDOW_TYPE_FIRST;
				i < FA_NET_WM_WINDOW_TYPE_LAST; i++)
			if(typehint == framer->atom[i])
			{
				type = i;
				break;
			}
	}
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %s\n", __func__, _framer_atom[type]);
#endif
	memset(&wc, 0, sizeof(wc));
#ifndef EMBEDDED
	if(xconfigure->value_mask & CWX)
		wc.x = max(xconfigure->x, 0);
	if(xconfigure->value_mask & CWY)
		wc.y = max(xconfigure->y, 0);
	if(xconfigure->value_mask & CWWidth)
		wc.width = min(xconfigure->width, framer->width);
	if(xconfigure->value_mask & CWHeight)
		wc.height = min(xconfigure->height, framer->height - 64);
#else
	if(type == FA_NET_WM_WINDOW_TYPE_DOCK)
	{
		wc.x = xconfigure->x;
		wc.y = xconfigure->y;
		wc.width = xconfigure->width;
		wc.height = xconfigure->height;
	}
	else
	{
		wc.x = 0;
		wc.width = framer->width;
		wc.y = 0;
		wc.height = framer->height - 64;
		mask |= CWX | CWWidth | CWY | CWHeight;
	}
	if(xconfigure->value_mask & CWBorderWidth)
		wc.border_width = 0;
#endif
	gdk_error_trap_push();
	XConfigureWindow(xconfigure->display, xconfigure->window, mask, &wc);
	gdk_error_trap_pop();
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_create_notify(XCreateWindowEvent * xcreate,
		Framer * framer)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	framer_window_add(framer, xcreate->window);
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_destroy_notify(XDestroyWindowEvent * xdestroy,
		Framer * framer)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	framer_window_remove(framer, xdestroy->window);
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_leave_notify(XCrossingEvent * xleave)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_map_notify(XMapEvent * xmap)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_map_request(XMapRequestEvent * xmap)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	gdk_error_trap_push();
	XMapWindow(xmap->display, xmap->window);
	gdk_error_trap_pop(); /* we ignore errors */
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_motion_notify(XMotionEvent * xmotion)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_property_notify(XPropertyEvent * xproperty,
		Framer * framer)
{
	GdkAtom atom;
	char * name;

	atom = gdk_x11_xatom_to_atom_for_display(framer->display,
		xproperty->atom);
	name = gdk_atom_name(atom);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %s, %s\n", __func__, name,
			xproperty->send_event ? "TRUE" : "FALSE");
#endif
	g_free(name);
	return GDK_FILTER_CONTINUE;
}

static GdkFilterReturn _filter_unmap_notify(XUnmapEvent * xunmap)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	return GDK_FILTER_CONTINUE;
}
