grim/guifications2

Doing this so I can fix our tarball
org.guifications.gf2 gf_2_13beta7
2007-05-01, John Bailey
d9f3834d8f71
Doing this so I can fix our tarball
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2005 Gary Kramlich
*
* 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; either version 2
* of the License, or (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _WIN32
# include <gdk/gdkx.h>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xatom.h>
#else
# ifndef WINVER
# define WINVER 0x0500 /* This is Windows 2000 */
# endif /* WINVER */
# include <windows.h>
typedef HMONITOR WINAPI GF_MonitorFromRect(LPCRECT,DWORD);
typedef BOOL WINAPI GF_GetMonitorInfo(HMONITOR,LPMONITORINFO);
#endif /* _WIN32 */
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <math.h>
#include <string.h>
#include <debug.h>
#include <prefs.h>
#include <version.h>
#include "gf_action.h"
#include "gf_event_info.h"
#include "gf_notification.h"
#include "gf_preferences.h"
typedef enum {
GF_DISPLAY_STATE_UNKNOWN = 0,
GF_DISPLAY_STATE_SHOWING,
GF_DISPLAY_STATE_SHOWN,
GF_DISPLAY_STATE_HIDING,
GF_DISPLAY_STATE_DESTROYED
} GfDisplayState;
struct _GfDisplay {
/* Widgets */
GtkWidget *window;
GtkWidget *event;
GtkWidget *image;
/* Display stuff */
GfDisplayState state;
GdkPixbuf *pixbuf;
GdkRectangle partial;
gboolean has_alpha;
gint height;
gint width;
gint x;
gint y;
/* Timer intervals */
gint anim_time;
gint disp_time;
/* Animation stuff */
gint round;
gint rounds;
/* Action stuff */
GfEventInfo *info;
guint button;
};
#include "gf_display.h"
/*******************************************************************************
* consts/macros
******************************************************************************/
#define GF_DISPLAY_ROUND(x) ((x)+0.5)
static const double DELTA_SIZE = 1.333;
static const gint DELTA_TIME = 33.33;
/*******************************************************************************
* Globals
******************************************************************************/
static GList *displays = NULL;
static gboolean vertical, animate;
static GfDisplayPosition position;
#if GTK_CHECK_VERSION(2,2,0)
static gint disp_screen = 0, disp_monitor = 0;
#endif /* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Prototypes...
*
* Yeah, I know they're bad, but sometimes you just can't avoid them...
*
* Well, I could, but it'd add more complication...
******************************************************************************/
static gboolean gf_display_shown_cb(gpointer data);
static gboolean gf_display_animate_cb(gpointer data);
/*******************************************************************************
* Callbacks
******************************************************************************/
static gboolean
gf_display_button_press_cb(GtkWidget *w, GdkEventButton *e, gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
gint x = 0, y = 0;
if(e->type == GDK_BUTTON_PRESS) {
display->button = e->button;
return TRUE;
} else if(e->type == GDK_BUTTON_RELEASE) {
GfAction *action = NULL;
const gchar *pref = NULL;
gdk_window_get_pointer(w->window, &x, &y, NULL);
if(x < 0 || x > display->width || y < 0 || y > display->height)
return FALSE;
switch(display->button) {
case 1: pref = GF_PREF_MOUSE_LEFT; break;
case 2: pref = GF_PREF_MOUSE_MIDDLE; break;
case 3: pref = GF_PREF_MOUSE_RIGHT; break;
default: pref = NULL; break;
}
if(!pref)
return FALSE;
action = gf_action_find_with_name(purple_prefs_get_string(pref));
if(!action)
return FALSE;
gf_action_execute(action, display, e);
return TRUE;
}
return FALSE;
}
/*******************************************************************************
* Uses _NET_WORKAREA or SPI_GETWORKAREA to get the geometry of a screen
******************************************************************************/
#ifdef _WIN32
static gboolean
win32_get_workarea_fallback(GdkRectangle *rect) {
RECT rcWork;
if (SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, FALSE)) {
rect->x = rcWork.left;
rect->y = rcWork.top;
rect->width = rcWork.right - rcWork.left;
rect->height = rcWork.bottom - rcWork.top;
return TRUE;
}
return FALSE;
}
/**
* This will only work on Win98+ and Win2K+
*/
static gboolean
win32_adjust_workarea_multi_monitor(GdkRectangle *rect) {
GF_GetMonitorInfo *the_GetMonitorInfo;
GF_MonitorFromRect *the_MonitorFromRect;
HMODULE hmod;
HMONITOR monitor;
MONITORINFO info;
RECT monitorRect;
gint virtual_screen_x, virtual_screen_y;
monitorRect.left = rect->x;
monitorRect.right = rect->x + rect->width;
monitorRect.top = rect->y;
monitorRect.bottom = rect->y + rect->height;
/* Convert the coordinates so that 0, 0 is the top left of the Primary
* Monitor, not the top left of the virtual screen
*/
virtual_screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
virtual_screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
if (virtual_screen_x != 0) {
monitorRect.left += virtual_screen_x;
monitorRect.right += virtual_screen_x;
}
if (virtual_screen_y != 0) {
monitorRect.top += virtual_screen_y;
monitorRect.bottom += virtual_screen_y;
}
if (!(hmod = GetModuleHandle("user32"))) {
return FALSE;
}
if (!(the_MonitorFromRect = (GF_MonitorFromRect*)
GetProcAddress(hmod, "MonitorFromRect"))) {
return FALSE;
}
if (!(the_GetMonitorInfo = (GF_GetMonitorInfo*)
GetProcAddress(hmod, "GetMonitorInfoA"))) {
return FALSE;
}
monitor = the_MonitorFromRect(&monitorRect, MONITOR_DEFAULTTONEAREST);
info.cbSize = sizeof(info);
if (!the_GetMonitorInfo(monitor, &info)) {
return FALSE;
}
rect->x = info.rcWork.left;
rect->y = info.rcWork.top;
rect->width = info.rcWork.right - info.rcWork.left;
rect->height = info.rcWork.bottom - info.rcWork.top;
/** Convert the coordinates so that 0, 0 is the top left of the virtual screen,
* not the top left of the primary monitor */
if (virtual_screen_x != 0) {
rect->x += -virtual_screen_x;
}
if (virtual_screen_y != 0) {
rect->y += -virtual_screen_y;
}
return TRUE;
}
static void
win32_adjust_workarea(GdkRectangle *rect) {
if (!win32_adjust_workarea_multi_monitor(rect)) {
if (!win32_get_workarea_fallback(rect)) {
/* I don't think this will ever happen */
rect->x = 0;
rect->y = 0;
rect->height = GetSystemMetrics(SM_CXSCREEN);
rect->width = GetSystemMetrics(SM_CYSCREEN);
}
}
}
#endif /** _WIN32 */
gboolean
gf_display_get_workarea(GdkRectangle *rect) {
#ifndef _WIN32
Atom xa_desktops, xa_current, xa_workarea, xa_type;
Display *x_display;
Window x_root;
guint32 desktops = 0, current = 0;
gulong *workareas, len, fill;
guchar *data;
gint format;
# if !GTK_CHECK_VERSION(2,2,0)
x_display = GDK_DISPLAY();
x_root = XDefaultRootWindow(x_display);
# else /* GTK 2.2.0 and up */
GdkDisplay *g_display;
GdkScreen *g_screen;
Screen *x_screen;
/* get the gdk display */
g_display = gdk_display_get_default();
if(!g_display)
return FALSE;
/* get the x display from the gdk display */
x_display = gdk_x11_display_get_xdisplay(g_display);
if(!x_display)
return FALSE;
/* get the screen according to the prefs */
g_screen = gdk_display_get_screen(g_display, disp_screen);
if(!g_screen)
return FALSE;
/* get the x screen from the gdk screen */
x_screen = gdk_x11_screen_get_xscreen(g_screen);
if(!x_screen)
return FALSE;
/* get the root window from the screen */
x_root = XRootWindowOfScreen(x_screen);
# endif /* !GTK_CHECK_VERSION(2,2,0) */
/* find the _NET_NUMBER_OF_DESKTOPS atom */
xa_desktops = XInternAtom(x_display, "_NET_NUMBER_OF_DESKTOPS", True);
if(xa_desktops == None)
return FALSE;
/* get the number of desktops */
if(XGetWindowProperty(x_display, x_root, xa_desktops, 0, 1, False,
XA_CARDINAL, &xa_type, &format, &len, &fill,
&data) != Success)
{
return FALSE;
}
if(!data)
return FALSE;
desktops = *(guint32 *)data;
XFree(data);
/* find the _NET_CURRENT_DESKTOP atom */
xa_current = XInternAtom(x_display, "_NET_CURRENT_DESKTOP", True);
if(xa_current == None)
return FALSE;
/* get the current desktop */
if(XGetWindowProperty(x_display, x_root, xa_current, 0, 1, False,
XA_CARDINAL, &xa_type, &format, &len, &fill,
&data) != Success)
{
return FALSE;
}
if(!data)
return FALSE;
current = *(guint32 *)data;
XFree(data);
/* find the _NET_WORKAREA atom */
xa_workarea = XInternAtom(x_display, "_NET_WORKAREA", True);
if(xa_workarea == None)
return FALSE;
if(XGetWindowProperty(x_display, x_root, xa_workarea, 0, (glong)(4 * 32),
False, AnyPropertyType, &xa_type, &format, &len,
&fill, &data) != Success)
{
return FALSE;
}
/* make sure the type and format are good */
if(xa_type == None || format == 0)
return FALSE;
/* make sure we don't have any leftovers */
if(fill)
return FALSE;
/* make sure len divides evenly by 4 */
if(len % 4)
return FALSE;
/* it's good, lets use it */
workareas = (gulong *)(guint32 *)data;
rect->x = (guint32)workareas[current * 4];
rect->y = (guint32)workareas[current * 4 + 1];
rect->width = (guint32)workareas[current * 4 + 2];
rect->height = (guint32)workareas[current * 4 + 3];
/* clean up our memory */
XFree(data);
#else
GdkDisplay *display;
GdkScreen *screen;
display = gdk_display_get_default();
screen = gdk_display_get_screen(display, disp_screen);
gdk_screen_get_monitor_geometry(screen, disp_monitor, rect);
win32_adjust_workarea(rect);
#endif /* _WIN32 */
return TRUE;
}
/*******************************************************************************
* Gtk 2.0.0 stuff
*
* This is the default behavior of 2.0 and 1.x
******************************************************************************/
#if !GTK_CHECK_VERSION(2,2,0)
static void
gf_display_get_geometry(gint *x, gint *y, gint *width, gint *height) {
*x = *y = 0;
*width = gdk_screen_width();
*height = gdk_screen_height();
}
static void
gf_display_shape(GfDisplay *display) {
if(display->has_alpha) {
GdkBitmap *bitmap = NULL;
GdkPixbuf *pixbuf;
if(display->state == GF_DISPLAY_STATE_SHOWING ||
display->state == GF_DISPLAY_STATE_HIDING)
{
pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(display->image));
if(!pixbuf) {
/* image has no pixbuf.. so we have no business continuing */
return;
}
} else {
pixbuf = display->pixbuf;
}
gdk_pixbuf_render_pixmap_and_mask(pixbuf, NULL, &bitmap, 255);
if(bitmap) {
gtk_widget_shape_combine_mask(display->window, bitmap, 0, 0);
g_object_unref(G_OBJECT(bitmap));
}
}
}
static void
gf_display_move(GfDisplay *display) {
gtk_window_move(GTK_WINDOW(display->window), display->x, display->y);
}
#else /* !GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Gtk 2.2.0 and up stuff
*
* Allows users to specify which screen to display the notifications on and
* in the case of xinerama, to position it correctly
******************************************************************************/
static void
gf_display_get_geometry(gint *x, gint *y, gint *width, gint *height) {
GdkDisplay *display;
GdkScreen *screen;
GdkRectangle geo, m_geo, w_geo;
display = gdk_display_get_default();
screen = gdk_display_get_screen(display, disp_screen);
gdk_screen_get_monitor_geometry(screen, disp_monitor, &m_geo);
if(gf_display_get_workarea(&w_geo)) {
gdk_rectangle_intersect(&w_geo, &m_geo, &geo);
} else {
geo.x = m_geo.x;
geo.y = m_geo.y;
geo.width = m_geo.width;
geo.height = m_geo.height;
}
*x = geo.x;
*y = geo.y;
*width = geo.width;
*height = geo.height;
}
static void
gf_display_shape(GfDisplay *display) {
if(display->has_alpha) {
GdkBitmap *bmap;
GdkColormap *cmap;
GdkPixbuf *pixbuf;
GdkScreen *screen;
screen = gdk_display_get_screen(gdk_display_get_default(), disp_screen);
cmap = gdk_screen_get_system_colormap(screen);
if(display->state == GF_DISPLAY_STATE_SHOWING ||
display->state == GF_DISPLAY_STATE_HIDING)
{
pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(display->image));
if(!pixbuf) {
/* image has no pixbuf.. so we have no business continuing */
return;
}
} else {
pixbuf = display->pixbuf;
}
gdk_pixbuf_render_pixmap_and_mask_for_colormap(pixbuf, cmap, NULL,
&bmap, 255);
if(bmap) {
gtk_widget_shape_combine_mask(display->window, bmap, 0, 0);
g_object_unref(G_OBJECT(bmap));
}
}
}
static void
gf_display_move(GfDisplay *display) {
GdkScreen *screen_t, *screen_s;
screen_t = gdk_display_get_screen(gdk_display_get_default(), disp_screen);
screen_s = gtk_window_get_screen(GTK_WINDOW(display->window));
if(gdk_screen_get_number(screen_s) != gdk_screen_get_number(screen_t)) {
if(display->has_alpha)
gtk_widget_shape_combine_mask(display->window, NULL, 0, 0);
gtk_window_set_screen(GTK_WINDOW(display->window), screen_t);
if(display->has_alpha)
gf_display_shape(display);
}
gtk_window_move(GTK_WINDOW(display->window), display->x, display->y);
}
gint
gf_display_get_default_screen() {
GdkScreen *screen;
screen = gdk_screen_get_default();
return gdk_screen_get_number(screen);
}
gint
gf_display_get_screen_count() {
return gdk_display_get_n_screens(gdk_display_get_default()) - 1;
}
gint
gf_display_get_default_monitor() {
return 0;
}
gint
gf_display_get_monitor_count() {
GdkDisplay *display;
GdkScreen *screen = NULL;
gint screens = 0, monitors = 0, i = 0;
display = gdk_display_get_default();
screens = gdk_display_get_n_screens(display);
for(i = 0; i < screens; i++) {
screen = gdk_display_get_screen(display, i);
monitors = MAX(monitors, gdk_screen_get_n_monitors(screen));
}
return monitors - 1;
}
#endif /* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* The normal code (tm)
******************************************************************************/
static void
gf_display_position(GfDisplay *new_display) {
GfDisplay *display;
GList *l = NULL;
gint pad_x = 0, pad_y = 0;
gint disp_width = 0, disp_height = 0;
gint width = 0, height = 0;
gint total = 0;
g_return_if_fail(new_display);
gf_display_get_geometry(&pad_x, &pad_y, &width, &height);
for(l = displays; l; l = l->next) {
display = GF_DISPLAY(l->data);
if(display == new_display)
break;
if(vertical)
total += display->height;
else
total += display->width;
}
if(new_display->state == GF_DISPLAY_STATE_SHOWING ||
new_display->state == GF_DISPLAY_STATE_HIDING)
{
disp_width = new_display->partial.width;
disp_height = new_display->partial.height;
} else {
disp_width = new_display->width;
disp_height = new_display->height;
}
/* set the correct size for the window */
gtk_widget_set_size_request(new_display->window, disp_width, disp_height);
switch(position) {
case GF_DISPLAY_POSITION_NW:
if(vertical) {
new_display->x = pad_x;
new_display->y = pad_y + total;
} else {
new_display->x = pad_x + total;
new_display->y = pad_y;
}
break;
case GF_DISPLAY_POSITION_NE:
if(vertical) {
new_display->x = pad_x + width - disp_width;
new_display->y = pad_y + total;
} else {
new_display->x = pad_x + width - (total + disp_width);
new_display->y = pad_y;
}
break;
case GF_DISPLAY_POSITION_SW:
if(vertical) {
new_display->x = pad_x;
new_display->y = pad_y + height - (total + disp_height);
} else {
new_display->x = pad_x + total;
new_display->y = pad_y + height - (disp_height);
}
break;
case GF_DISPLAY_POSITION_SE:
if(vertical) {
new_display->x = pad_x + width - disp_width;
new_display->y = pad_y + height - (total + disp_height);
} else {
new_display->x = pad_x + width - (total + disp_width);
new_display->y = pad_y + height - disp_height;
}
break;
default:
break;
}
gf_display_move(new_display);
}
static void
gf_displays_position() {
GfDisplay *display;
GList *l;
for(l = displays; l; l = l->next) {
display = GF_DISPLAY(l->data);
gf_display_position(display);
}
}
GfDisplay *
gf_display_new() {
GfDisplay *display;
display = g_new0(GfDisplay, 1);
return display;
}
void
gf_display_destroy(GfDisplay *display) {
g_return_if_fail(display);
displays = g_list_remove(displays, display);
if(display->window) {
gtk_widget_destroy(display->window);
display->window = NULL;
}
if(display->pixbuf) {
g_object_unref(G_OBJECT(display->pixbuf));
display->pixbuf = NULL;
}
if(display->info) {
gf_event_info_destroy(display->info);
display->info = NULL;
}
g_free(display);
display = NULL;
gf_displays_position();
}
static gboolean
gf_display_shown_cb(gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
guint timeout_id;
/* in case something wicked happened */
g_return_val_if_fail(display, FALSE);
display->state = GF_DISPLAY_STATE_HIDING;
timeout_id = g_timeout_add(DELTA_TIME, gf_display_animate_cb, data);
gf_event_info_set_timeout_id(display->info, timeout_id);
return FALSE;
}
static gint
gf_display_calculate_change(GfDisplay *display, gint maximum) {
/* This function calculates an an exponential change.
*
* It's a basic scalable exponential movement formula, given to us by
* Nathaniel Waters.
*/
gint ret;
/* this could probably use some type casting somewhere.. */
ret = GF_DISPLAY_ROUND(((gdouble)maximum /
pow(DELTA_SIZE, display->rounds)) *
pow(DELTA_SIZE, display->round));
return ret;
}
static gboolean
gf_display_animate(GfDisplay *display) {
GdkPixbuf *pixbuf;
GdkRectangle full;
gint total, current;
gboolean ret = TRUE;
/* figure out what we're modifing */
if(vertical)
total = display->height;
else
total = display->width;
/* calculate the change */
current = gf_display_calculate_change(display, total);
/* create our rects */
full.x = 0;
full.y = 0;
full.width = display->width;
full.height = display->height;
/* ugh too many possibilities */
switch(position) {
case GF_DISPLAY_POSITION_NW:
if(vertical) {
display->partial.x = full.x;
display->partial.y = full.height - current;
display->partial.width = full.width;
display->partial.height = current;
} else {
display->partial.x = full.width - current;
display->partial.y = full.y;
display->partial.width = current;
display->partial.height = full.height;
}
break;
case GF_DISPLAY_POSITION_NE:
if(vertical) {
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = full.width;
display->partial.height = current;
} else {
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = current;
display->partial.height = full.height;
}
break;
case GF_DISPLAY_POSITION_SW:
if(vertical) {
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = full.width;
display->partial.height = current;
} else {
display->partial.x = full.width - current;
display->partial.y = full.y;
display->partial.width = current;
display->partial.height = full.height;
}
break;
case GF_DISPLAY_POSITION_SE:
if(vertical) {
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = full.width;
display->partial.height = current;
} else {
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = current;
display->partial.height = full.height;
}
break;
default:
display->partial.x = full.x;
display->partial.y = full.y;
display->partial.width = full.width;
display->partial.height = full.height;
break;
}
/* Add some sanity checks */
if(display->partial.width <= 0)
display->partial.width = 1;
if(display->partial.height <= 0)
display->partial.height = 1;
/* create our partial pixbuf and fill it */
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, display->has_alpha, 8,
display->partial.width, display->partial.height);
if(!pixbuf) {
purple_debug_info("guifications", "failed to created partial pixbuf, "
"destroying display %p\n", display);
gf_display_destroy(display);
return FALSE;
}
gdk_pixbuf_copy_area(display->pixbuf,
display->partial.x, display->partial.y,
display->partial.width, display->partial.height,
pixbuf, 0, 0);
/* force the image and event box to be the same size as the partial
* pixbuf
*/
gtk_widget_set_size_request(display->image, display->partial.width,
display->partial.height);
gtk_widget_set_size_request(display->event, display->partial.width,
display->partial.height);
/* update the image's pixbuf */
gtk_image_set_from_pixbuf(GTK_IMAGE(display->image), pixbuf);
/* unref the partial pixbuf */
g_object_unref(G_OBJECT(pixbuf));
/* update the display and force gdk to redraw it */
gf_display_shape(display);
gf_display_position(display);
gdk_window_process_updates(GDK_WINDOW(display->window->window), TRUE);
/* Finish up */
if(display->state == GF_DISPLAY_STATE_SHOWING) {
display->round++;
if(display->round > display->rounds) {
guint timeout_id;
/* set rounds back to the max */
display->round = display->rounds - 1;
display->state = GF_DISPLAY_STATE_SHOWN;
timeout_id = gtk_timeout_add(display->disp_time,
gf_display_shown_cb,
(gpointer)display);
gf_event_info_set_timeout_id(display->info, timeout_id);
ret = FALSE;
}
} else { /* if(display->state == GF_DISPLAY_STATE_HIDING) { */
display->round--;
if(display->round <= 0) {
gf_display_destroy(display);
ret = FALSE;
}
}
return ret;
}
static gboolean
gf_display_animate_cb(gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
gboolean ret;
/* in the event something wicked happened... */
g_return_val_if_fail(display, FALSE);
/* we call a function to do our animation here to modularize the process
* so we can do other types of animation, like fading depending on other
* conditions. These conditions should be checked here, and then called
* accordingly.
*
* The animation function should return a boolean so we can tell the
* timer handler whether or not to keep the timer around.
*/
ret = gf_display_animate(display);
return ret;
}
static gboolean
gf_display_destroy_cb(gpointer data) {
GfDisplay *display = GF_DISPLAY(data);
gf_display_destroy(display);
return FALSE;
}
/* Yes this big ugly function has a purpose...
* It checks for existing notification from the same buddy, or conv with
* with the same target, and condenses contacts.
*
* It returns TRUE if the new notification/event should be destroyed because
* theres a matching notification that has a higher priority than the new
* notification/event
*/
static gboolean
gf_display_condense(GfEventInfo *info) {
GfDisplay *display = NULL;
GfEvent *event1 = NULL, *event2 = NULL;
GfEventPriority priority1, priority2;
PurpleBuddy *buddy1 = NULL, *buddy2 = NULL;
PurpleContact *contact1 = NULL, *contact2 = NULL;
PurpleConversation *conv1 = NULL, *conv2 = NULL;
GList *l = NULL, *ll = NULL;
gchar *ck1 = NULL, *ck2 = NULL;
const gchar *target1 = NULL, *target2 = NULL;
gboolean ret = FALSE;
event1 = gf_event_info_get_event(info);
priority1 = gf_event_get_priority(event1);
buddy1 = gf_event_info_get_buddy(info);
conv1 = gf_event_info_get_conversation(info);
target1 = gf_event_info_get_target(info);
if(buddy1)
contact1 = purple_buddy_get_contact(buddy1);
if(target1)
ck1 = g_utf8_collate_key(target1, -1);
for(l = displays; l; l = ll) {
display = GF_DISPLAY(l->data);
ll = l->next;
event2 = gf_event_info_get_event(display->info);
priority2 = gf_event_get_priority(event2);
conv2 = gf_event_info_get_conversation(display->info);
if(buddy1) {
buddy2 = gf_event_info_get_buddy(display->info);
/* check contacts */
if(buddy2) {
contact2 = purple_buddy_get_contact(buddy2);
if(contact1 && contact1 == contact2) {
ck2 = g_utf8_collate_key(buddy2->name, -1);
if(!(buddy1->account == buddy2->account &&
!strcmp(ck1, ck2)))
{
if(priority1 >= priority2) {
gf_event_info_set_is_contact(info, TRUE);
gf_display_destroy(display);
continue;
} else {
ret = TRUE;
break;
}
}
g_free(ck2);
}
}
/* check for duplicate buddies */
if(buddy1 == buddy2) {
if(priority1 >= priority2) {
gf_display_destroy(display);
continue;
} else {
ret = TRUE;
break;
}
}
}
/* check for duplicate targets from the same conv */
if(ck1 && conv1 && conv1 == conv2) {
target2 = gf_event_info_get_target(display->info);
if(target2)
ck2 = g_utf8_collate_key(target2, -1);
if(ck2 && !strcmp(ck1, ck2)) {
g_free(ck2);
if(priority1 >= priority2) {
gf_display_destroy(display);
continue;
} else {
ret = TRUE;
break;
}
}
}
}
if(ck1)
g_free(ck1);
/* we only check the stack count if ret is true, because that means one
* has been removed, so we have space.
*/
if(ret == FALSE) {
gint throttle = purple_prefs_get_int(GF_PREF_BEHAVIOR_THROTTLE);
if(throttle > 0 && g_list_length(displays) + 1 > throttle) {
display = GF_DISPLAY(g_list_nth_data(displays, 0));
if(display)
gf_display_destroy(display);
gf_displays_position();
}
}
return ret;
}
gboolean
gf_display_screen_saver_is_running() {
gboolean ret = FALSE;
#ifndef _WIN32
static Atom xss, locked, blanked;
static gboolean init = FALSE;
Atom ratom;
gint rtatom;
guchar *data = NULL;
gulong items, padding;
if(!init) {
xss = XInternAtom(GDK_DISPLAY(),"_SCREENSAVER_STATUS", FALSE);
locked = XInternAtom(GDK_DISPLAY(), "LOCK", FALSE);
blanked = XInternAtom(GDK_DISPLAY(), "BLANK", FALSE);
init = TRUE;
}
if(XGetWindowProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(), xss, 0, 999, FALSE,
XA_INTEGER, &ratom, &rtatom, &items, &padding, &data)
== Success)
{
if(ratom == XA_INTEGER || items >= 3) {
guint *item_data = (guint *)data;
if(item_data[0] == locked || item_data[0] == blanked)
ret = TRUE;
}
XFree(data);
}
#else
if(!SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, (unsigned int) NULL,
&ret, FALSE))
{
ret = FALSE;
}
#endif /* _WIN32 */
return ret;
}
void
gf_display_show_event(GfEventInfo *info, GfNotification *notification) {
GfDisplay *display = NULL;
gint display_time;
guint timeout_id = 0;
g_return_if_fail(info);
/* Here we kill the notification if the screen saver is running */
if(gf_display_screen_saver_is_running()) {
gf_event_info_destroy(info);
return;
}
/* we should never make it here with out a notification, but just in
* case we do....
*/
if(!notification) {
const gchar *event_name = gf_event_get_name(gf_event_info_get_event(info));
purple_debug_info("Guifications",
"could not find a notification for the event \"%s\"\n",
event_name ? event_name : "");
return;
}
/* condense contacts and same buddy notifications, a return value of TRUE
* means theres a higher priority notification already being displayed
*/
if(gf_display_condense(info)) {
gf_event_info_destroy(info);
return;
}
/* create the display */
display = gf_display_new();
display->info = info;
/* Render the pixbuf, if we get NULL destroy the display and return */
display->pixbuf = gf_notification_render(notification, display->info);
if(!display->pixbuf) {
GfTheme *theme = gf_notification_get_theme(notification);
GfThemeInfo *info = gf_theme_get_theme_info(theme);
purple_debug_info("Guifications", "render '%s' failed for theme '%s'\n",
gf_notification_get_type(notification),
gf_theme_info_get_name(info));
gf_display_destroy(display);
return;
}
/* grab info about the pixbuf */
display->has_alpha = gdk_pixbuf_get_has_alpha(display->pixbuf);
display->height = gdk_pixbuf_get_height(display->pixbuf);
display->width = gdk_pixbuf_get_width(display->pixbuf);
/* if we've made it this far, we can create the window safely */
display->window = gtk_window_new(GTK_WINDOW_POPUP);
gtk_window_set_role(GTK_WINDOW(display->window), "guification");
/* Create the event box, you know, so the mouse works! */
display->event = gtk_event_box_new();
if(!gtk_check_version(2,4,0))
g_object_set(G_OBJECT(display->event), "visible-window", FALSE, NULL);
gtk_container_add(GTK_CONTAINER(display->window), display->event);
/* now we create all our mouse signals that should play nice with
* other apps..
*/
g_signal_connect(G_OBJECT(display->window), "button-press-event",
G_CALLBACK(gf_display_button_press_cb),
(gpointer)display);
g_signal_connect(G_OBJECT(display->window), "button-release-event",
G_CALLBACK(gf_display_button_press_cb),
(gpointer)display);
/* create the image but don't put the pixbuf in it quite yet. */
display->image = gtk_image_new();
gtk_container_add(GTK_CONTAINER(display->event), display->image);
/* grab the display time */
display_time = 1000 * purple_prefs_get_int(GF_PREF_BEHAVIOR_DISPLAY_TIME);
/* animation is set, this is a new notification, so we animate it */
if(animate) {
/* we explicitly set the size request because the window is created
* with an image with no child, so it defaults to some insane size.
*/
gtk_widget_set_size_request(display->window,
display->width, display->height);
/* calculate our timer intervals. anim_time is used for showing and
* hiding, so that's 2/8ths which is 1/4th.
*
* Remember display time is stored in seconds!
*/
display->anim_time = display_time / 8;
display->disp_time = display_time * 3 / 4;
/* Calculate and store some more information about animating */
display->rounds = GF_DISPLAY_ROUND((gfloat)display->anim_time /
(gfloat)DELTA_TIME);
display->round = 0;
/* Set the state of the display */
display->state = GF_DISPLAY_STATE_SHOWING;
/* setup a timeout of DELTA_TIME ms for animating */
timeout_id = g_timeout_add(DELTA_TIME, gf_display_animate_cb,
(gpointer)display);
} else { /* no animation! */
/* set the image since we're not animating */
gtk_image_set_from_pixbuf(GTK_IMAGE(display->image), display->pixbuf);
/* shape it */
gf_display_shape(display);
/* We're showing! */
display->state = GF_DISPLAY_STATE_SHOWN;
/* build our timeout */
timeout_id = g_timeout_add(display_time, gf_display_destroy_cb,
(gpointer)display);
}
gf_event_info_set_timeout_id(info, timeout_id);
/* position and show the widget */
gf_display_position(display);
gtk_widget_show_all(display->window);
/* add the display to the list */
displays = g_list_append(displays, display);
}
GfEventInfo *
gf_display_get_event_info(GfDisplay *display) {
g_return_val_if_fail(display, NULL);
return display->info;
}
static void
gf_display_position_changed_cb(const gchar *name, PurplePrefType type,
gconstpointer val, gpointer data)
{
vertical = purple_prefs_get_bool(GF_PREF_APPEARANCE_VERTICAL);
position = purple_prefs_get_int(GF_PREF_APPEARANCE_POSITION);
gf_displays_position();
}
static void
gf_display_animate_changed_cb(const gchar *name, PurplePrefType type,
gconstpointer val, gpointer data)
{
animate = GPOINTER_TO_INT(val);
}
/*******************************************************************************
* Pref callbacks for the display/screen/monitor stuff
******************************************************************************/
#if GTK_CHECK_VERSION(2,2,0)
static void
gf_display_screen_changed_cb(const gchar *name, PurplePrefType type,
gconstpointer val, gpointer data)
{
disp_screen = GPOINTER_TO_INT(val);
gf_item_text_uninit();
gf_displays_position();
gf_item_text_init();
}
static void
gf_display_monitor_changed_cb(const gchar *name, PurplePrefType type,
gconstpointer val, gpointer data)
{
disp_monitor = GPOINTER_TO_INT(val);
gf_displays_position();
}
static guint scr_chg_id = 0, mon_chg_id = 0;
#endif /* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Regular pref callbacks
******************************************************************************/
static guint pos_chg_id = 0, ver_chg_id = 0, ani_chg_id = 0;
void
gf_display_init(PurplePlugin *plugin) {
/* since we just use the callbacks to get the changes we need to know
* what these are initially at.
*/
position = purple_prefs_get_int(GF_PREF_APPEARANCE_POSITION);
vertical = purple_prefs_get_bool(GF_PREF_APPEARANCE_VERTICAL);
animate = purple_prefs_get_bool(GF_PREF_APPEARANCE_ANIMATE);
/* Connect our pref callbacks */
pos_chg_id = purple_prefs_connect_callback(plugin,
GF_PREF_APPEARANCE_POSITION,
gf_display_position_changed_cb,
NULL);
ver_chg_id = purple_prefs_connect_callback(plugin,
GF_PREF_APPEARANCE_VERTICAL,
gf_display_position_changed_cb,
NULL);
ani_chg_id = purple_prefs_connect_callback(plugin,
GF_PREF_APPEARANCE_ANIMATE,
gf_display_animate_changed_cb,
NULL);
#if GTK_CHECK_VERSION(2,2,0)
/* setup our multi screen prefs */
disp_screen = purple_prefs_get_int(GF_PREF_ADVANCED_SCREEN);
disp_monitor = purple_prefs_get_int(GF_PREF_ADVANCED_MONITOR);
scr_chg_id = purple_prefs_connect_callback(plugin,
GF_PREF_ADVANCED_SCREEN,
gf_display_screen_changed_cb,
NULL);
mon_chg_id = purple_prefs_connect_callback(plugin,
GF_PREF_ADVANCED_MONITOR,
gf_display_monitor_changed_cb,
NULL);
#endif /* GTK_CHECK_VERSION(2,2,0) */
}
void
gf_display_uninit() {
purple_prefs_disconnect_callback(pos_chg_id);
purple_prefs_disconnect_callback(ver_chg_id);
#if GTK_CHECK_VERSION(2,2,0)
purple_prefs_disconnect_callback(scr_chg_id);
purple_prefs_disconnect_callback(mon_chg_id);
#endif /* GTK_CHECK_VERSION(2,2,0) */
}