summaryrefslogtreecommitdiff
path: root/src/daemon/stack.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon/stack.c')
-rw-r--r--src/daemon/stack.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/daemon/stack.c b/src/daemon/stack.c
new file mode 100644
index 0000000..95b50da
--- /dev/null
+++ b/src/daemon/stack.c
@@ -0,0 +1,409 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2006 Christian Hammond <[email protected]>
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2011 Perberos <[email protected]>
+ *
+ * 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, 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.
+ */
+
+#include "config.h"
+#include "engines.h"
+#include "stack.h"
+
+#include <X11/Xproto.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <gdk/gdkx.h>
+
+#define NOTIFY_STACK_SPACING 2
+#define WORKAREA_PADDING 6
+
+struct _NotifyStack {
+ NotifyDaemon* daemon;
+ GdkScreen* screen;
+ guint monitor;
+ NotifyStackLocation location;
+ GList* windows;
+ guint update_id;
+};
+
+GList* notify_stack_get_windows(NotifyStack *stack)
+{
+ return stack->windows;
+}
+
+static gboolean
+get_work_area (NotifyStack *stack,
+ GdkRectangle *rect)
+{
+ Atom workarea;
+ Atom type;
+ Window win;
+ int format;
+ gulong num;
+ gulong leftovers;
+ gulong max_len = 4 * 32;
+ guchar *ret_workarea;
+ long *workareas;
+ int result;
+ int disp_screen;
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+ workarea = XInternAtom(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), "_NET_WORKAREA", True);
+ #else
+ workarea = XInternAtom(GDK_DISPLAY(), "_NET_WORKAREA", True);
+ #endif
+
+
+ disp_screen = GDK_SCREEN_XNUMBER (stack->screen);
+
+ /* Defaults in case of error */
+ rect->x = 0;
+ rect->y = 0;
+ rect->width = gdk_screen_get_width (stack->screen);
+ rect->height = gdk_screen_get_height (stack->screen);
+
+ if (workarea == None)
+ return FALSE;
+
+
+ #if GTK_CHECK_VERSION(3, 0, 0)
+
+ win = XRootWindow(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), disp_screen);
+
+ result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ win,
+ workarea,
+ 0,
+ max_len,
+ False,
+ AnyPropertyType,
+ &type,
+ &format,
+ &num,
+ &leftovers,
+ &ret_workarea);
+ #else
+
+ win = XRootWindow(GDK_DISPLAY(), disp_screen);
+
+ result = XGetWindowProperty(GDK_DISPLAY(),
+ win,
+ workarea,
+ 0,
+ max_len,
+ False,
+ AnyPropertyType,
+ &type,
+ &format,
+ &num,
+ &leftovers,
+ &ret_workarea);
+ #endif
+
+ if (result != Success
+ || type == None
+ || format == 0
+ || leftovers
+ || num % 4) {
+ return FALSE;
+ }
+
+ workareas = (long *) ret_workarea;
+ rect->x = workareas[disp_screen * 4];
+ rect->y = workareas[disp_screen * 4 + 1];
+ rect->width = workareas[disp_screen * 4 + 2];
+ rect->height = workareas[disp_screen * 4 + 3];
+
+ XFree (ret_workarea);
+
+ return TRUE;
+}
+
+static void
+get_origin_coordinates (NotifyStackLocation stack_location,
+ GdkRectangle *workarea,
+ gint *x,
+ gint *y,
+ gint *shiftx,
+ gint *shifty,
+ gint width,
+ gint height)
+{
+ switch (stack_location) {
+ case NOTIFY_STACK_LOCATION_TOP_LEFT:
+ *x = workarea->x;
+ *y = workarea->y;
+ *shifty = height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_TOP_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y = workarea->y;
+ *shifty = height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_BOTTOM_LEFT:
+ *x = workarea->x;
+ *y = workarea->y + workarea->height - height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_BOTTOM_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y = workarea->y + workarea->height - height;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+translate_coordinates (NotifyStackLocation stack_location,
+ GdkRectangle *workarea,
+ gint *x,
+ gint *y,
+ gint *shiftx,
+ gint *shifty,
+ gint width,
+ gint height)
+{
+ switch (stack_location) {
+ case NOTIFY_STACK_LOCATION_TOP_LEFT:
+ *x = workarea->x;
+ *y += *shifty;
+ *shifty = height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_TOP_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y += *shifty;
+ *shifty = height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_BOTTOM_LEFT:
+ *x = workarea->x;
+ *y -= height;
+ break;
+
+ case NOTIFY_STACK_LOCATION_BOTTOM_RIGHT:
+ *x = workarea->x + workarea->width - width;
+ *y -= height;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+NotifyStack *
+notify_stack_new (NotifyDaemon *daemon,
+ GdkScreen *screen,
+ guint monitor,
+ NotifyStackLocation location)
+{
+ NotifyStack *stack;
+
+ g_assert (daemon != NULL);
+ g_assert (screen != NULL && GDK_IS_SCREEN (screen));
+ g_assert (monitor < (guint)gdk_screen_get_n_monitors (screen));
+ g_assert (location != NOTIFY_STACK_LOCATION_UNKNOWN);
+
+ stack = g_new0 (NotifyStack, 1);
+ stack->daemon = daemon;
+ stack->screen = screen;
+ stack->monitor = monitor;
+ stack->location = location;
+
+ return stack;
+}
+
+void
+notify_stack_destroy (NotifyStack *stack)
+{
+ g_assert (stack != NULL);
+
+ if (stack->update_id != 0) {
+ g_source_remove (stack->update_id);
+ }
+
+ g_list_free (stack->windows);
+ g_free (stack);
+}
+
+void
+notify_stack_set_location (NotifyStack *stack,
+ NotifyStackLocation location)
+{
+ stack->location = location;
+}
+
+static void
+add_padding_to_rect (GdkRectangle *rect)
+{
+ rect->x += WORKAREA_PADDING;
+ rect->y += WORKAREA_PADDING;
+ rect->width -= WORKAREA_PADDING * 2;
+ rect->height -= WORKAREA_PADDING * 2;
+
+ if (rect->width < 0)
+ rect->width = 0;
+ if (rect->height < 0)
+ rect->height = 0;
+}
+
+static void
+notify_stack_shift_notifications (NotifyStack *stack,
+ GtkWindow *nw,
+ GList **nw_l,
+ gint init_width,
+ gint init_height,
+ gint *nw_x,
+ gint *nw_y)
+{
+ GdkRectangle workarea;
+ GdkRectangle monitor;
+ GdkRectangle *positions;
+ GList *l;
+ gint x, y;
+ gint shiftx = 0;
+ gint shifty = 0;
+ int i;
+ int n_wins;
+
+ get_work_area (stack, &workarea);
+ gdk_screen_get_monitor_geometry (stack->screen,
+ stack->monitor,
+ &monitor);
+ gdk_rectangle_intersect (&monitor, &workarea, &workarea);
+
+ add_padding_to_rect (&workarea);
+
+ n_wins = g_list_length (stack->windows);
+ positions = g_new0 (GdkRectangle, n_wins);
+
+ get_origin_coordinates (stack->location,
+ &workarea,
+ &x, &y,
+ &shiftx,
+ &shifty,
+ init_width,
+ init_height);
+
+ if (nw_x != NULL)
+ *nw_x = x;
+
+ if (nw_y != NULL)
+ *nw_y = y;
+
+ for (i = 0, l = stack->windows; l != NULL; i++, l = l->next) {
+ GtkWindow *nw2 = GTK_WINDOW (l->data);
+ GtkRequisition req;
+
+ if (nw == NULL || nw2 != nw) {
+ gtk_widget_size_request (GTK_WIDGET (nw2), &req);
+
+ translate_coordinates (stack->location,
+ &workarea,
+ &x,
+ &y,
+ &shiftx,
+ &shifty,
+ req.width,
+ req.height + NOTIFY_STACK_SPACING);
+ positions[i].x = x;
+ positions[i].y = y;
+ } else if (nw_l != NULL) {
+ *nw_l = l;
+ positions[i].x = -1;
+ positions[i].y = -1;
+ }
+ }
+
+ /* move bubbles at the bottom of the stack first
+ to avoid overlapping */
+ for (i = n_wins - 1, l = g_list_last (stack->windows); l != NULL; i--, l = l->prev) {
+ GtkWindow *nw2 = GTK_WINDOW (l->data);
+
+ if (nw == NULL || nw2 != nw) {
+ theme_move_notification (nw2, positions[i].x, positions[i].y);
+ }
+ }
+
+ g_free (positions);
+}
+
+static void update_position(NotifyStack* stack)
+{
+ /* notify_stack_shift_notifications(stack, window, list pointer, init width, init height, out window x, out window y) */
+ notify_stack_shift_notifications (stack, NULL, NULL, 0, 0, NULL, NULL);
+}
+
+static gboolean update_position_idle(NotifyStack* stack)
+{
+ update_position(stack);
+
+ stack->update_id = 0;
+ return FALSE;
+}
+
+void notify_stack_queue_update_position(NotifyStack* stack)
+{
+ if (stack->update_id != 0)
+ {
+ return;
+ }
+
+ stack->update_id = g_idle_add((GSourceFunc) update_position_idle, stack);
+}
+
+void notify_stack_add_window(NotifyStack* stack, GtkWindow* nw, gboolean new_notification)
+{
+ GtkRequisition req;
+ gint x, y;
+
+ gtk_widget_size_request(GTK_WIDGET(nw), &req);
+ notify_stack_shift_notifications(stack, nw, NULL, req.width, req.height + NOTIFY_STACK_SPACING, &x, &y);
+ theme_move_notification(nw, x, y);
+
+ if (new_notification)
+ {
+ g_signal_connect_swapped(G_OBJECT(nw), "destroy", G_CALLBACK(notify_stack_remove_window), stack);
+ stack->windows = g_list_prepend(stack->windows, nw);
+ }
+}
+
+void notify_stack_remove_window(NotifyStack* stack, GtkWindow* nw)
+{
+ GList* remove_l = NULL;
+
+ notify_stack_shift_notifications(stack, nw, &remove_l, 0, 0, NULL, NULL);
+
+ if (remove_l != NULL)
+ {
+ stack->windows = g_list_delete_link(stack->windows, remove_l);
+ }
+
+ #if GTK_CHECK_VERSION(2, 20, 0)
+ if (gtk_widget_get_realized(GTK_WIDGET(nw)))
+ gtk_widget_unrealize(GTK_WIDGET(nw));
+ #else
+ if (GTK_WIDGET_REALIZED(GTK_WIDGET(nw)))
+ gtk_widget_unrealize(GTK_WIDGET(nw));
+ #endif
+}