Panel
/* $Id$ */
							/* Copyright (c) 2010-2020 Pierre Pronchery <khorben@defora.org> */
							/* This file is part of DeforaOS Pager 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 <stdlib.h>
							#include <string.h>
							#include <errno.h>
							#include <libintl.h>
							#include <gdk/gdkx.h>
							#include <X11/Xatom.h>
							#include <System.h>
							#include <Desktop.h>
							#include "Panel/applet.h"
							#define _(string) gettext(string)
							#define N_(string) string
							#if !GTK_CHECK_VERSION(3, 0, 0)
							# define gdk_error_trap_pop_ignored() gdk_error_trap_pop()
							#endif
							/* Pager */
							/* private */
							/* types */
							typedef enum _PagerAtom
							{
								PAGER_ATOM_NET_CURRENT_DESKTOP = 0,
								PAGER_ATOM_NET_DESKTOP_NAMES,
								PAGER_ATOM_NET_NUMBER_OF_DESKTOPS,
								PAGER_ATOM_UTF8_STRING
							} PagerAtom;
							#define PAGER_ATOM_LAST PAGER_ATOM_UTF8_STRING
							#define PAGER_ATOM_COUNT (PAGER_ATOM_LAST + 1)
							typedef struct _PanelApplet
							{
								PanelAppletHelper * helper;
								GtkWidget * box;
								gulong source;
								GtkWidget ** widgets;
								size_t widgets_cnt;
								Atom atoms[PAGER_ATOM_COUNT];
								GdkDisplay * display;
								GdkScreen * screen;
								GdkWindow * root;
							} Pager;
							/* constants */
							static const char * _pager_atom[PAGER_ATOM_COUNT] =
							{
								"_NET_CURRENT_DESKTOP",
								"_NET_DESKTOP_NAMES",
								"_NET_NUMBER_OF_DESKTOPS",
								"UTF8_STRING"
							};
							/* prototypes */
							static Pager * _pager_init(PanelAppletHelper * helper, GtkWidget ** widget);
							static void _pager_destroy(Pager * pager);
							/* accessors */
							static int _pager_get_current_desktop(Pager * pager);
							static char ** _pager_get_desktop_names(Pager * pager);
							static int _pager_get_window_property(Pager * pager, Window window,
									PagerAtom property, Atom atom, unsigned long * cnt,
									unsigned char ** ret);
							/* useful */
							static void _pager_do(Pager * pager);
							static void _pager_refresh(Pager * pager);
							/* callbacks */
							static void _pager_on_clicked(GtkWidget * widget, gpointer data);
							static GdkFilterReturn _pager_on_filter(GdkXEvent * xevent, GdkEvent * event,
									gpointer data);
							static void _pager_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
									gpointer data);
							/* public */
							/* variables */
							PanelAppletDefinition applet =
							{
								N_("Pager"),
								NULL,
								NULL,
								_pager_init,
								_pager_destroy,
								NULL,
								FALSE,
								TRUE
							};
							/* private */
							/* functions */
							/* pager_init */
							static Pager * _pager_init(PanelAppletHelper * helper, GtkWidget ** widget)
							{
								Pager * pager;
								GtkOrientation orientation;
								if((pager = malloc(sizeof(*pager))) == NULL)
								{
									error_set("%s: %s", applet.name, strerror(errno));
									return NULL;
								}
								pager->helper = helper;
								orientation = panel_window_get_orientation(helper->window);
							#if GTK_CHECK_VERSION(3, 0, 0)
								pager->box = gtk_box_new(orientation, 0);
								gtk_box_set_homogeneous(GTK_BOX(pager->box), TRUE);
							#else
								pager->box = (orientation == GTK_ORIENTATION_VERTICAL)
									? gtk_vbox_new(TRUE, 0) : gtk_hbox_new(TRUE, 0);
							#endif
								pager->source = g_signal_connect(pager->box, "screen-changed",
										G_CALLBACK(_pager_on_screen_changed), pager);
								pager->widgets = NULL;
								pager->widgets_cnt = 0;
								pager->display = NULL;
								pager->screen = NULL;
								pager->root = NULL;
								*widget = pager->box;
								return pager;
							}
							/* pager_destroy */
							static void _pager_destroy(Pager * pager)
							{
								if(pager->source != 0)
									g_signal_handler_disconnect(pager->box, pager->source);
								pager->source = 0;
								if(pager->root != NULL)
									gdk_window_remove_filter(pager->root, _pager_on_filter, pager);
								gtk_widget_destroy(pager->box);
								free(pager);
							}
							/* accessors */
							/* pager_get_current_desktop */
							static int _pager_get_current_desktop(Pager * pager)
							{
								unsigned long cnt;
								unsigned long * p;
								if(_pager_get_window_property(pager, GDK_WINDOW_XID(pager->root),
											PAGER_ATOM_NET_CURRENT_DESKTOP, XA_CARDINAL,
											&cnt, (void*)&p) != 0)
									return -1;
								cnt = *p;
								XFree(p);
								return cnt;
							}
							/* pager_get_desktop_names */
							static char ** _pager_get_desktop_names(Pager * pager)
							{
								char ** ret = NULL;
								size_t ret_cnt = 0;
								unsigned long cnt;
								char * p;
								unsigned long i;
								unsigned long last = 0;
								char ** q;
								if(_pager_get_window_property(pager, GDK_WINDOW_XID(pager->root),
											PAGER_ATOM_NET_DESKTOP_NAMES,
											pager->atoms[PAGER_ATOM_UTF8_STRING], &cnt,
											(void*)&p) != 0)
									return NULL;
								for(i = 0; i < cnt; i++)
								{
									if(p[i] != '\0')
										continue;
									if((q = realloc(ret, (ret_cnt + 2) * (sizeof(*q)))) == NULL)
									{
										free(ret);
										XFree(p);
										return NULL;
									}
									ret = q;
									/* FIXME validate the UTF8 string */
									ret[ret_cnt++] = g_strdup(&p[last]);
									last = i + 1;
								}
								XFree(p);
								if(ret == NULL)
									return ret;
								ret[ret_cnt] = NULL;
								return ret;
							}
							/* pager_get_window_property */
							static int _pager_get_window_property(Pager * pager, Window window,
									PagerAtom property, Atom atom, unsigned long * cnt,
									unsigned char ** ret)
							{
								int res;
								Atom type;
								int format;
								unsigned long bytes;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s(pager, window, %s, %lu)\n", __func__,
										_pager_atom[property], atom);
							#endif
								gdk_error_trap_push();
								res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(pager->display), window,
										pager->atoms[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;
							}
							/* useful */
							/* pager_do */
							static void _pager_do(Pager * pager)
							{
								unsigned long cnt = 0;
								unsigned long l;
								unsigned long * p;
								unsigned long i;
								GtkWidget ** q;
								char ** names;
								char buf[64];
								if(_pager_get_window_property(pager, GDK_WINDOW_XID(pager->root),
											PAGER_ATOM_NET_NUMBER_OF_DESKTOPS,
											XA_CARDINAL, &cnt, (void*)&p) != 0)
									return;
								l = *p;
							#ifdef DEBUG
								fprintf(stderr, "DEBUG: %s() l=%ld\n", __func__, l);
							#endif
								XFree(p);
								for(i = l; i < pager->widgets_cnt; i++)
									if(pager->widgets[i] != NULL)
										gtk_widget_destroy(pager->widgets[i]);
								if((q = realloc(pager->widgets, l * sizeof(*q))) == NULL
										&& l != 0)
									return;
								pager->widgets = q;
								names = _pager_get_desktop_names(pager);
								for(i = 0; i < l; i++)
								{
									if(names != NULL && names[i] != NULL)
									{
										snprintf(buf, sizeof(buf), "%s", names[i]);
										g_free(names[i]);
									}
									else
										snprintf(buf, sizeof(buf), _("Desk %lu"), i + 1);
									if(i < pager->widgets_cnt)
										gtk_button_set_label(GTK_BUTTON(pager->widgets[i]),
												buf);
									else
									{
										pager->widgets[i] = gtk_button_new_with_label(buf);
										g_signal_connect(pager->widgets[i], "clicked",
												G_CALLBACK( _pager_on_clicked), pager);
										gtk_box_pack_start(GTK_BOX(pager->box),
												pager->widgets[i], FALSE, TRUE, 0);
									}
								}
								free(names);
								pager->widgets_cnt = l;
								_pager_refresh(pager);
								if(pager->widgets_cnt <= 1)
									gtk_widget_hide(pager->box);
								else
									gtk_widget_show_all(pager->box);
							}
							/* pager_refresh */
							static void _pager_refresh(Pager * pager)
							{
								size_t i;
								int cur;
								char buf[64];
								cur = _pager_get_current_desktop(pager);
								for(i = 0; i < pager->widgets_cnt; i++)
									if(cur < 0 || i != (unsigned int)cur)
									{
										gtk_widget_set_sensitive(pager->widgets[i], TRUE);
							#if GTK_CHECK_VERSION(2, 12, 0)
										snprintf(buf, sizeof(buf), _("Switch to %s"),
												gtk_button_get_label(
													GTK_BUTTON(pager->widgets[i])));
										gtk_widget_set_tooltip_text(pager->widgets[i], buf);
							#endif
									}
									else
									{
										gtk_widget_set_sensitive(pager->widgets[i], FALSE);
							#if GTK_CHECK_VERSION(2, 12, 0)
										snprintf(buf, sizeof(buf), _("On %s"),
												gtk_button_get_label(
													GTK_BUTTON(pager->widgets[i])));
										gtk_widget_set_tooltip_text(pager->widgets[i], buf);
							#endif
									}
							}
							/* callbacks */
							/* pager_on_clicked */
							static void _pager_on_clicked(GtkWidget * widget, gpointer data)
							{
								Pager * pager = data;
								size_t i;
								GdkScreen * screen;
								GdkDisplay * display;
								GdkWindow * root;
								XEvent xev;
								for(i = 0; i < pager->widgets_cnt; i++)
									if(pager->widgets[i] == widget)
										break;
								if(i == pager->widgets_cnt)
									return;
								screen = gtk_widget_get_screen(widget);
								display = gtk_widget_get_display(widget);
								root = gdk_screen_get_root_window(screen);
								xev.xclient.type = ClientMessage;
								xev.xclient.window = GDK_WINDOW_XID(root);
								xev.xclient.message_type = gdk_x11_get_xatom_by_name_for_display(
										display, "_NET_CURRENT_DESKTOP");
								xev.xclient.format = 32;
								memset(&xev.xclient.data, 0, sizeof(xev.xclient.data));
								xev.xclient.data.l[0] = i;
								xev.xclient.data.l[1] = gdk_x11_display_get_user_time(display);
								gdk_error_trap_push();
								XSendEvent(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(root),
										False,
										SubstructureNotifyMask | SubstructureRedirectMask,
										&xev);
								gdk_error_trap_pop_ignored();
							}
							/* pager_on_filter */
							static GdkFilterReturn _pager_on_filter(GdkXEvent * xevent, GdkEvent * event,
									gpointer data)
							{
								Pager * pager = data;
								XEvent * xev = xevent;
								int cur;
								(void) event;
								if(xev->type != PropertyNotify)
									return GDK_FILTER_CONTINUE;
								if(xev->xproperty.atom == pager->atoms[PAGER_ATOM_NET_CURRENT_DESKTOP])
								{
									if((cur = _pager_get_current_desktop(pager)) >= 0)
										_pager_refresh(pager);
									return GDK_FILTER_CONTINUE;
								}
								if(xev->xproperty.atom == pager->atoms[
										PAGER_ATOM_NET_NUMBER_OF_DESKTOPS]
										|| xev->xproperty.atom == pager->atoms[
										PAGER_ATOM_NET_DESKTOP_NAMES])
									_pager_do(pager);
								return GDK_FILTER_CONTINUE;
							}
							/* pager_on_screen_changed */
							static void _pager_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
									gpointer data)
							{
								Pager * pager = data;
								GdkEventMask events;
								size_t i;
								(void) previous;
								if(pager->root != NULL)
									gdk_window_remove_filter(pager->root, _pager_on_filter, pager);
								pager->screen = gtk_widget_get_screen(widget);
								pager->display = gdk_screen_get_display(pager->screen);
								pager->root = gdk_screen_get_root_window(pager->screen);
								events = gdk_window_get_events(pager->root);
								gdk_window_set_events(pager->root, events | GDK_PROPERTY_CHANGE_MASK);
								gdk_window_add_filter(pager->root, _pager_on_filter, pager);
								/* atoms */
								for(i = 0; i < PAGER_ATOM_COUNT; i++)
									pager->atoms[i] = gdk_x11_get_xatom_by_name_for_display(
											pager->display, _pager_atom[i]);
								_pager_do(pager);
							}
							