The buttons are no longer destroyed and created again from scratch when the amount of desktops changes. They are now only created when adding desktops, and simply updated otherwise.
421 lines
10 KiB
C
421 lines
10 KiB
C
/* $Id$ */
|
|
/* Copyright (c) 2010-2017 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)
|
|
|
|
#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 =
|
|
{
|
|
"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;
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|