/* pager.c -- pager module of fbpanel project * * Copyright (C) 2002-2003 Anatoly Asviyan * Joe MacDonald * * This file is part of fbpanel. * * fbpanel 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, or (at your option) * any later version. * * fbpanel 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 sawfish; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include "panel.h" #include "misc.h" #include "plugin.h" #include "gtkbgbox.h" //#define DEBUG #include "dbg.h" extern panel *p; /* managed window: all related info that wm holds about its managed windows */ typedef struct task { Window win; int x, y; guint w, h; gint refcount; guint stacking; guint desktop; char *name, *iname; int ws; net_wm_state nws; net_wm_window_type nwwt; guint focused:1; } task; typedef struct _desk desk; typedef struct _pager pager; #define MAX_DESK_NUM 20 /* map of a desktop */ struct _desk { GtkWidget *da; GdkPixmap *pix; int no, dirty, first; gfloat scalew, scaleh; pager *pg; }; struct _pager { GtkWidget *box, *eb; desk *desks[MAX_DESK_NUM]; guint desknum; guint curdesk; int dw, dh; gfloat scalex, scaley, ratio; Window *wins; int winnum, dirty; GHashTable* htable; task *focusedtask; }; #define TASK_VISIBLE(tk) \ (!( ((tk)->ws != NormalState) || (tk)->nws.hidden || (tk)->nws.skip_pager )) //if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ ) static void pager_rebuild_all(FbEv *ev, pager *pg); static inline void desk_set_dirty_by_win(pager *p, task *t); static inline void desk_set_dirty(desk *d); static inline void desk_set_dirty_all(pager *pg); /* static void desk_clear_pixmap(desk *d); static gboolean task_remove_stale(Window *win, task *t, pager *p); static gboolean task_remove_all(Window *win, task *t, pager *p); */ /***************************************************************** * Task Management Routines * *****************************************************************/ /* tell to remove element with zero refcount */ static gboolean task_remove_stale(Window *win, task *t, pager *p) { if (t->refcount-- == 0) { desk_set_dirty_by_win(p, t); if (p->focusedtask == t) p->focusedtask = NULL; DBG("del %x\n", t->win); g_free(t); return TRUE; } return FALSE; } /* tell to remove element with zero refcount */ static gboolean task_remove_all(Window *win, task *t, pager *p) { g_free(t); return TRUE; } static void task_get_sizepos(task *t) { Window root, junkwin; int rx, ry; guint dummy; XWindowAttributes win_attributes; ENTER; if (!XGetWindowAttributes(GDK_DISPLAY(), t->win, &win_attributes)) { if (!XGetGeometry (GDK_DISPLAY(), t->win, &root, &t->x, &t->y, &t->w, &t->h, &dummy, &dummy)) { t->x = t->y = t->w = t->h = 2; } } else { XTranslateCoordinates (GDK_DISPLAY(), t->win, win_attributes.root, -win_attributes.border_width, -win_attributes.border_width, &rx, &ry, &junkwin); t->x = rx; t->y = ry; t->w = win_attributes.width; t->h = win_attributes.height; DBG("win=0x%x WxH=%dx%d\n", t->win,t->w, t->h); } RET(); } static void task_update_pix(task *t, desk *d) { int x, y, w, h; GtkWidget *widget; ENTER; g_return_if_fail(d->pix != NULL); if (!TASK_VISIBLE(t)) RET();; if (t->desktop < p->desknum && t->desktop != d->no) RET(); x = (gfloat)t->x * d->scalew; y = (gfloat)t->y * d->scaleh; w = (gfloat)t->w * d->scalew; //h = (gfloat)t->h * d->scaleh; h = (t->nws.shaded) ? 3 : (gfloat)t->h * d->scaleh; if (w < 3 || h < 3) RET(); widget = GTK_WIDGET(d->da); gdk_draw_rectangle (d->pix, (d->pg->focusedtask == t) ? widget->style->bg_gc[GTK_STATE_SELECTED] : widget->style->bg_gc[GTK_STATE_NORMAL], TRUE, x+1, y+1, w-1, h-1); gdk_draw_rectangle (d->pix, (d->pg->focusedtask == t) ? widget->style->fg_gc[GTK_STATE_SELECTED] : widget->style->fg_gc[GTK_STATE_NORMAL], FALSE, x, y, w, h); RET(); } /***************************************************************** * Desk Functions * *****************************************************************/ static void desk_clear_pixmap(desk *d) { GtkWidget *widget; ENTER; DBG("d->no=%d\n", d->no); if (!d->pix) RET(); widget = GTK_WIDGET(d->da); gdk_draw_rectangle (d->pix, ((d->no == d->pg->curdesk) ? widget->style->dark_gc[GTK_STATE_SELECTED] : widget->style->dark_gc[GTK_STATE_NORMAL]), TRUE, 0, 0, widget->allocation.width, widget->allocation.height); RET(); } static inline void desk_set_dirty(desk *d) { ENTER; d->dirty = 1; gtk_widget_queue_draw(d->da); RET(); } static inline void desk_set_dirty_all(pager *pg) { int i; ENTER; for (i = 0; i < pg->desknum; i++) desk_set_dirty(pg->desks[i]); RET(); } static inline void desk_set_dirty_by_win(pager *p, task *t) { ENTER; if (t->nws.skip_pager || t->nwwt.desktop /*|| t->nwwt.dock || t->nwwt.splash*/ ) RET(); if (t->desktop < p->desknum) desk_set_dirty(p->desks[t->desktop]); else desk_set_dirty_all(p); RET(); } /* Redraw the screen from the backing pixmap */ static gint desk_expose_event (GtkWidget *widget, GdkEventExpose *event, desk *d) { ENTER; DBG("d->no=%d\n", d->no); if (d->dirty) { pager *pg = d->pg; task *t; int j; d->dirty = 0; desk_clear_pixmap(d); for (j = 0; j < pg->winnum; j++) { if (!(t = g_hash_table_lookup(pg->htable, &pg->wins[j]))) continue; task_update_pix(t, d); } } gdk_draw_drawable(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE (widget)], d->pix, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); RET(FALSE); } /* Upon realize and every resize creates a new backing pixmap of the appropriate size */ static gint desk_configure_event (GtkWidget *widget, GdkEventConfigure *event, desk *d) { int w, h; ENTER; DBG("d->no=%d %dx%d\n", d->no, widget->allocation.width, widget->allocation.height); if (d->pix) g_object_unref(d->pix); d->pix = gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1); d->scalew = (gfloat)widget->allocation.height / (gfloat)gdk_screen_height(); d->scaleh = (gfloat)widget->allocation.width / (gfloat)gdk_screen_width(); desk_set_dirty(d); //request best size if (p->orientation != ORIENT_HORIZ) { w = widget->allocation.width; h = (gfloat) w / d->pg->ratio; } else { h = widget->allocation.height; w = (gfloat) h * d->pg->ratio; } DBG("requesting %dx%d\n", w, h); gtk_widget_set_size_request(widget, w, h); RET(FALSE); } static gint desk_button_press_event(GtkWidget * widget, GdkEventButton * event, desk *d) { ENTER; DBG("s=%d\n", d->no); Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0); RET(TRUE); } /* static gint desk_button_release_event(GtkWidget * widget, GdkEventButton * event, desk *d) { ENTER; DBG("t=%d\n", d->no); Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, d->no, 0, 0, 0, 0); RET(TRUE); } */ static gint desk_scroll_event (GtkWidget *widget, GdkEventScroll *event, desk *d) { int i; ENTER; DBG("scroll direction = %d\n", event->direction); i = d->pg->curdesk; if (event->direction == GDK_SCROLL_UP ||event->direction == GDK_SCROLL_LEFT) { i--; if (i < 0) i = d->pg->desknum - 1; } else { i++; if (i >= d->pg->desknum) i = 0; } Xclimsg(GDK_ROOT_WINDOW(), a_NET_CURRENT_DESKTOP, i, 0, 0, 0, 0); RET(TRUE); } static void desk_new(pager *pg, int i) { desk *d; ENTER; g_assert(i < pg->desknum); d = pg->desks[i] = g_new0(desk, 1); d->pg = pg; d->pix = NULL; d->dirty = 0; d->first = 1; d->no = i; d->da = gtk_drawing_area_new(); //gtk_widget_set_size_request(GTK_WIDGET(d->da), 10, 10); gtk_box_pack_start(GTK_BOX(pg->box), d->da, TRUE, TRUE, 0); gtk_widget_add_events (d->da, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); g_signal_connect (G_OBJECT (d->da), "expose_event", (GCallback) desk_expose_event, (gpointer)d); g_signal_connect (G_OBJECT (d->da), "configure_event", (GCallback) desk_configure_event, (gpointer)d); g_signal_connect (G_OBJECT (d->da), "scroll-event", (GCallback) desk_scroll_event, (gpointer)d); g_signal_connect (G_OBJECT (d->da), "button_press_event", (GCallback) desk_button_press_event, (gpointer)d); //g_signal_connect (G_OBJECT (d->da), "button_release_event", // (GCallback) desk_button_release_event, (gpointer)d); gtk_widget_show(d->da); DBG("before pack\n"); DBG("after show\n"); RET(); } static void desk_free(pager *pg, int i) { desk *d; ENTER; d = pg->desks[i]; DBG("i=%d d->no=%d d->da=%p d->pix=%p\n", i, d->no, d->da, d->pix); if (d->pix) g_object_unref(d->pix); gtk_widget_destroy(d->da); g_free(d); RET(); } /***************************************************************** * Netwm/WM Interclient Communication * *****************************************************************/ static void do_net_active_window(FbEv *ev, pager *p) { Window *fwin; task *t; ENTER; fwin = get_xaproperty(GDK_ROOT_WINDOW(), a_NET_ACTIVE_WINDOW, XA_WINDOW, 0); DBG("win=%x\n", fwin ? *fwin : 0); if (fwin) { t = g_hash_table_lookup(p->htable, fwin); if (t != p->focusedtask) { if (p->focusedtask) desk_set_dirty_by_win(p, p->focusedtask); p->focusedtask = t; if (t) desk_set_dirty_by_win(p, t); } XFree(fwin); } else { if (p->focusedtask) { desk_set_dirty_by_win(p, p->focusedtask); p->focusedtask = NULL; } } RET(); } static void do_net_current_desktop(FbEv *ev, pager *p) { ENTER; desk_set_dirty(p->desks[p->curdesk]); p->curdesk = get_net_current_desktop (); if (p->curdesk >= p->desknum) p->curdesk = 0; desk_set_dirty(p->desks[p->curdesk]); RET(); } static void do_net_client_list_stacking(FbEv *ev, pager *p) { int i; task *t; ENTER; if (p->wins) XFree(p->wins); p->wins = get_xaproperty (GDK_ROOT_WINDOW(), a_NET_CLIENT_LIST_STACKING, XA_WINDOW, &p->winnum); if (!p->wins || !p->winnum) RET(); /* refresh existing tasks and add new */ for (i = 0; i < p->winnum; i++) { if ((t = g_hash_table_lookup(p->htable, &p->wins[i]))) { t->refcount++; if (t->stacking != i) { t->stacking = i; desk_set_dirty_by_win(p, t); } } else { t = g_new0(task, 1); t->refcount++; t->win = p->wins[i]; t->ws = get_wm_state (t->win); t->desktop = get_net_wm_desktop(t->win); get_net_wm_state(t->win, &t->nws); get_net_wm_window_type(t->win, &t->nwwt); task_get_sizepos(t); if (!FBPANEL_WIN(t->win)) XSelectInput (GDK_DISPLAY(), t->win, PropertyChangeMask | StructureNotifyMask); g_hash_table_insert(p->htable, &t->win, t); DBG("add %x\n", t->win); desk_set_dirty_by_win(p, t); } } /* pass throu hash table and delete stale windows */ g_hash_table_foreach_remove(p->htable, (GHRFunc) task_remove_stale, (gpointer)p); RET(); } /***************************************************************** * Pager Functions * *****************************************************************/ /* static void pager_unmapnotify(pager *p, XEvent *ev) { Window win = ev->xunmap.window; task *t; if (!(t = g_hash_table_lookup(p->htable, &win))) RET(); DBG("pager_unmapnotify: win=0x%x\n", win); RET(); t->ws = WithdrawnState; desk_set_dirty_by_win(p, t); RET(); } */ static void pager_configurenotify(pager *p, XEvent *ev) { Window win = ev->xconfigure.window; task *t; ENTER; if (!(t = g_hash_table_lookup(p->htable, &win))) RET(); DBG("win=0x%x\n", win); task_get_sizepos(t); desk_set_dirty_by_win(p, t); RET(); } static void pager_propertynotify(pager *p, XEvent *ev) { Atom at = ev->xproperty.atom; Window win = ev->xproperty.window; task *t; ENTER; if ((win == GDK_ROOT_WINDOW()) || !(t = g_hash_table_lookup(p->htable, &win))) RET(); DBG("window=0x%x\n", t->win); if (at == a_WM_STATE) { DBG("event=WM_STATE\n"); t->ws = get_wm_state (t->win); } else if (at == a_NET_WM_STATE) { DBG("event=NET_WM_STATE\n"); get_net_wm_state(t->win, &t->nws); } else if (at == a_NET_WM_DESKTOP) { DBG("event=NET_WM_DESKTOP\n"); desk_set_dirty_by_win(p, t); // to clean up desks where this task was t->desktop = get_net_wm_desktop(t->win); } else { RET(); } desk_set_dirty_by_win(p, t); RET(); } static GdkFilterReturn pager_event_filter( XEvent *xev, GdkEvent *event, pager *pg) { ENTER; if (xev->type == PropertyNotify ) pager_propertynotify(pg, xev); else if (xev->type == ConfigureNotify ) pager_configurenotify(pg, xev); RET(GDK_FILTER_CONTINUE); } static void pager_rebuild_all(FbEv *ev, pager *pg) { int desknum, curdesk, dif, i; ENTER; desknum = pg->desknum; curdesk = pg->curdesk; pg->desknum = get_net_number_of_desktops(); if (pg->desknum < 1) pg->desknum = 1; else if (pg->desknum > MAX_DESK_NUM) { pg->desknum = MAX_DESK_NUM; ERR("pager: max number of supported desks is %d\n", MAX_DESK_NUM); } pg->curdesk = get_net_current_desktop(); if (pg->curdesk >= pg->desknum) pg->curdesk = 0; DBG("desknum=%d curdesk=%d\n", desknum, curdesk); DBG("pg->desknum=%d pg->curdesk=%d\n", pg->desknum, pg->curdesk); dif = pg->desknum - desknum; if (dif == 0) RET(); if (dif < 0) { /* if desktops were deleted then delete their maps also */ for (i = pg->desknum; i < desknum; i++) desk_free(pg, i); } else { for (i = desknum; i < pg->desknum; i++) desk_new(pg, i); } do_net_client_list_stacking(NULL, pg); RET(); } static int pager_constructor(plugin *plug) { pager *pg; ENTER; pg = g_new0(pager, 1); g_return_val_if_fail(pg != NULL, 0); plug->priv = pg; pg->htable = g_hash_table_new (g_int_hash, g_int_equal); pg->box = plug->panel->my_box_new(TRUE, 1); gtk_container_set_border_width (GTK_CONTAINER (pg->box), 0); gtk_widget_show(pg->box); gtk_bgbox_set_background(plug->pwid, BG_STYLE, 0, 0); gtk_container_set_border_width (GTK_CONTAINER (plug->pwid), 1); gtk_container_add(GTK_CONTAINER(plug->pwid), pg->box); pg->eb = pg->box; pg->ratio = (gfloat)gdk_screen_width() / (gfloat)gdk_screen_height(); pg->scaley = (gfloat)pg->dh / (gfloat)gdk_screen_height(); pg->scalex = (gfloat)pg->dw / (gfloat)gdk_screen_width(); pager_rebuild_all(fbev, pg); //do_net_current_desktop(fbev, pg); //do_net_client_list_stacking(fbev, pg); gdk_window_add_filter(NULL, (GdkFilterFunc)pager_event_filter, pg ); g_signal_connect (G_OBJECT (fbev), "current_desktop", G_CALLBACK (do_net_current_desktop), (gpointer) pg); g_signal_connect (G_OBJECT (fbev), "active_window", G_CALLBACK (do_net_active_window), (gpointer) pg); g_signal_connect (G_OBJECT (fbev), "number_of_desktops", G_CALLBACK (pager_rebuild_all), (gpointer) pg); g_signal_connect (G_OBJECT (fbev), "client_list_stacking", G_CALLBACK (do_net_client_list_stacking), (gpointer) pg); RET(1); } static void pager_destructor(plugin *p) { pager *pg = (pager *)p->priv; ENTER; g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_current_desktop, pg); g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_active_window, pg); g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), pager_rebuild_all, pg); g_signal_handlers_disconnect_by_func(G_OBJECT (fbev), do_net_client_list_stacking, pg); gdk_window_remove_filter(NULL, (GdkFilterFunc)pager_event_filter, pg); while (--pg->desknum) { desk_free(pg, pg->desknum); } g_hash_table_foreach_remove(pg->htable, (GHRFunc) task_remove_all, (gpointer)pg); g_hash_table_destroy(pg->htable); gtk_widget_destroy(pg->eb); g_free(pg); RET(); } plugin_class pager_plugin_class = { fname: NULL, count: 0, type : "pager", name : "pager", version: "1.0", description : "Simple pager plugin", constructor : pager_constructor, destructor : pager_destructor, };