1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2010 Red Hat, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  *
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 #include <strings.h>
25 #include <glib.h>
26 
27 #include <X11/Xproto.h>
28 #include <X11/Xlib.h>
29 #include <X11/Xutil.h>
30 #include <X11/Xatom.h>
31 #include <gdk/gdkx.h>
32 
33 #include "nd-stack.h"
34 
35 #define ND_STACK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ND_TYPE_STACK, NdStackPrivate))
36 
37 #define NOTIFY_STACK_SPACING 2
38 #define WORKAREA_PADDING 6
39 
40 struct NdStackPrivate
41 {
42         GdkScreen      *screen;
43         guint           monitor;
44         NdStackLocation location;
45         GList          *bubbles;
46         guint           update_id;
47 };
48 
49 static void     nd_stack_finalize    (GObject       *object);
50 
G_DEFINE_TYPE(NdStack,nd_stack,G_TYPE_OBJECT)51 G_DEFINE_TYPE (NdStack, nd_stack, G_TYPE_OBJECT)
52 
53 GList *
54 nd_stack_get_bubbles (NdStack *stack)
55 {
56         return stack->priv->bubbles;
57 }
58 
59 static int
get_current_desktop(GdkScreen * screen)60 get_current_desktop (GdkScreen *screen)
61 {
62         Display *display;
63         Window win;
64         Atom current_desktop, type;
65         int format;
66         unsigned long n_items, bytes_after;
67         unsigned char *data_return = NULL;
68         int workspace = 0;
69 
70         display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
71         win = XRootWindow (display, GDK_SCREEN_XNUMBER (screen));
72 
73         current_desktop = XInternAtom (display, "_NET_CURRENT_DESKTOP", True);
74 
75         XGetWindowProperty (display,
76                             win,
77                             current_desktop,
78                             0, G_MAXLONG,
79                             False, XA_CARDINAL,
80                             &type, &format, &n_items, &bytes_after,
81                             &data_return);
82 
83         if (type == XA_CARDINAL && format == 32 && n_items > 0)
84                 workspace = (int) data_return[0];
85         if (data_return)
86                 XFree (data_return);
87 
88         return workspace;
89 }
90 
91 static gboolean
get_work_area(NdStack * stack,GdkRectangle * rect)92 get_work_area (NdStack      *stack,
93                GdkRectangle *rect)
94 {
95         Atom            workarea;
96         Atom            type;
97         Window          win;
98         int             format;
99         gulong          num;
100         gulong          leftovers;
101         gulong          max_len = 4 * 32;
102         guchar         *ret_workarea;
103         long           *workareas;
104         int             result;
105         int             disp_screen;
106         int             desktop;
107         Display        *display;
108 
109         display = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (stack->priv->screen));
110         workarea = XInternAtom (display, "_NET_WORKAREA", True);
111 
112         disp_screen = GDK_SCREEN_XNUMBER (stack->priv->screen);
113 
114         /* Defaults in case of error */
115         rect->x = 0;
116         rect->y = 0;
117         rect->width = gdk_screen_get_width (stack->priv->screen);
118         rect->height = gdk_screen_get_height (stack->priv->screen);
119 
120         if (workarea == None)
121                 return FALSE;
122 
123         win = XRootWindow (display, disp_screen);
124         result = XGetWindowProperty (display,
125                                      win,
126                                      workarea,
127                                      0,
128                                      max_len,
129                                      False,
130                                      AnyPropertyType,
131                                      &type,
132                                      &format,
133                                      &num,
134                                      &leftovers,
135                                      &ret_workarea);
136 
137         if (result != Success
138             || type == None
139             || format == 0
140             || leftovers
141             || num % 4) {
142                 return FALSE;
143         }
144 
145         desktop = get_current_desktop (stack->priv->screen);
146 
147         workareas = (long *) ret_workarea;
148         rect->x = workareas[desktop * 4];
149         rect->y = workareas[desktop * 4 + 1];
150         rect->width = workareas[desktop * 4 + 2];
151         rect->height = workareas[desktop * 4 + 3];
152 
153         XFree (ret_workarea);
154 
155         return TRUE;
156 }
157 
158 static void
get_origin_coordinates(NdStackLocation stack_location,GdkRectangle * workarea,gint * x,gint * y,gint * shiftx,gint * shifty,gint width,gint height)159 get_origin_coordinates (NdStackLocation stack_location,
160                         GdkRectangle       *workarea,
161                         gint               *x,
162                         gint               *y,
163                         gint               *shiftx,
164                         gint               *shifty,
165                         gint                width,
166                         gint                height)
167 {
168         switch (stack_location) {
169         case ND_STACK_LOCATION_TOP_LEFT:
170                 *x = workarea->x;
171                 *y = workarea->y;
172                 *shifty = height;
173                 break;
174 
175         case ND_STACK_LOCATION_TOP_RIGHT:
176                 *x = workarea->x + workarea->width - width;
177                 *y = workarea->y;
178                 *shifty = height;
179                 break;
180 
181         case ND_STACK_LOCATION_BOTTOM_LEFT:
182                 *x = workarea->x;
183                 *y = workarea->y + workarea->height - height;
184                 break;
185 
186         case ND_STACK_LOCATION_BOTTOM_RIGHT:
187                 *x = workarea->x + workarea->width - width;
188                 *y = workarea->y + workarea->height - height;
189                 break;
190 
191         case ND_STACK_LOCATION_UNKNOWN:
192         default:
193                 g_assert_not_reached ();
194         }
195 }
196 
197 static void
translate_coordinates(NdStackLocation stack_location,GdkRectangle * workarea,gint * x,gint * y,gint * shiftx,gint * shifty,gint width,gint height)198 translate_coordinates (NdStackLocation stack_location,
199                        GdkRectangle       *workarea,
200                        gint               *x,
201                        gint               *y,
202                        gint               *shiftx,
203                        gint               *shifty,
204                        gint                width,
205                        gint                height)
206 {
207         switch (stack_location) {
208         case ND_STACK_LOCATION_TOP_LEFT:
209                 *x = workarea->x;
210                 *y += *shifty;
211                 *shifty = height;
212                 break;
213 
214         case ND_STACK_LOCATION_TOP_RIGHT:
215                 *x = workarea->x + workarea->width - width;
216                 *y += *shifty;
217                 *shifty = height;
218                 break;
219 
220         case ND_STACK_LOCATION_BOTTOM_LEFT:
221                 *x = workarea->x;
222                 *y -= height;
223                 break;
224 
225         case ND_STACK_LOCATION_BOTTOM_RIGHT:
226                 *x = workarea->x + workarea->width - width;
227                 *y -= height;
228                 break;
229 
230         case ND_STACK_LOCATION_UNKNOWN:
231         default:
232                 g_assert_not_reached ();
233         }
234 }
235 
236 static void
nd_stack_class_init(NdStackClass * klass)237 nd_stack_class_init (NdStackClass *klass)
238 {
239         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
240 
241         object_class->finalize = nd_stack_finalize;
242 
243         g_type_class_add_private (klass, sizeof (NdStackPrivate));
244 }
245 
246 static void
nd_stack_init(NdStack * stack)247 nd_stack_init (NdStack *stack)
248 {
249         stack->priv = ND_STACK_GET_PRIVATE (stack);
250         stack->priv->location = ND_STACK_LOCATION_DEFAULT;
251 }
252 
253 static void
nd_stack_finalize(GObject * object)254 nd_stack_finalize (GObject *object)
255 {
256         NdStack *stack;
257 
258         g_return_if_fail (object != NULL);
259         g_return_if_fail (ND_IS_STACK (object));
260 
261         stack = ND_STACK (object);
262 
263         g_return_if_fail (stack->priv != NULL);
264 
265         if (stack->priv->update_id != 0) {
266                 g_source_remove (stack->priv->update_id);
267         }
268 
269         g_list_free (stack->priv->bubbles);
270 
271         G_OBJECT_CLASS (nd_stack_parent_class)->finalize (object);
272 }
273 
274 void
nd_stack_set_location(NdStack * stack,NdStackLocation location)275 nd_stack_set_location (NdStack        *stack,
276                        NdStackLocation location)
277 {
278         g_return_if_fail (ND_IS_STACK (stack));
279 
280         stack->priv->location = location;
281 }
282 
283 NdStack *
nd_stack_new(GdkScreen * screen,guint monitor)284 nd_stack_new (GdkScreen *screen,
285               guint      monitor)
286 {
287         NdStack *stack;
288 
289         g_assert (screen != NULL && GDK_IS_SCREEN (screen));
290         g_assert (monitor < (guint)gdk_screen_get_n_monitors (screen));
291 
292         stack = g_object_new (ND_TYPE_STACK, NULL);
293         stack->priv->screen = screen;
294         stack->priv->monitor = monitor;
295 
296         return stack;
297 }
298 
299 
300 static void
add_padding_to_rect(GdkRectangle * rect)301 add_padding_to_rect (GdkRectangle *rect)
302 {
303         rect->x += WORKAREA_PADDING;
304         rect->y += WORKAREA_PADDING;
305         rect->width -= WORKAREA_PADDING * 2;
306         rect->height -= WORKAREA_PADDING * 2;
307 
308         if (rect->width < 0)
309                 rect->width = 0;
310         if (rect->height < 0)
311                 rect->height = 0;
312 }
313 
314 static void
nd_stack_shift_notifications(NdStack * stack,NdBubble * bubble,GList ** nw_l,gint init_width,gint init_height,gint * nw_x,gint * nw_y)315 nd_stack_shift_notifications (NdStack     *stack,
316                               NdBubble    *bubble,
317                               GList      **nw_l,
318                               gint         init_width,
319                               gint         init_height,
320                               gint        *nw_x,
321                               gint        *nw_y)
322 {
323         GdkRectangle    workarea;
324         GdkRectangle    monitor;
325         GdkRectangle   *positions;
326         GList          *l;
327         gint            x, y;
328         gint            shiftx = 0;
329         gint            shifty = 0;
330         int             i;
331         int             n_wins;
332 
333         get_work_area (stack, &workarea);
334         gdk_screen_get_monitor_geometry (stack->priv->screen,
335                                          stack->priv->monitor,
336                                          &monitor);
337         gdk_rectangle_intersect (&monitor, &workarea, &workarea);
338 
339         add_padding_to_rect (&workarea);
340 
341         n_wins = g_list_length (stack->priv->bubbles);
342         positions = g_new0 (GdkRectangle, n_wins);
343 
344         get_origin_coordinates (stack->priv->location,
345                                 &workarea,
346                                 &x, &y,
347                                 &shiftx,
348                                 &shifty,
349                                 init_width,
350                                 init_height);
351 
352         if (nw_x != NULL)
353                 *nw_x = x;
354 
355         if (nw_y != NULL)
356                 *nw_y = y;
357 
358         for (i = 0, l = stack->priv->bubbles; l != NULL; i++, l = l->next) {
359                 NdBubble       *nw2 = ND_BUBBLE (l->data);
360                 GtkRequisition  req;
361 
362                 if (bubble == NULL || nw2 != bubble) {
363                         gtk_widget_get_preferred_size (GTK_WIDGET (nw2), NULL, &req);
364 
365                         translate_coordinates (stack->priv->location,
366                                                &workarea,
367                                                &x,
368                                                &y,
369                                                &shiftx,
370                                                &shifty,
371                                                req.width,
372                                                req.height + NOTIFY_STACK_SPACING);
373                         positions[i].x = x;
374                         positions[i].y = y;
375                 } else if (nw_l != NULL) {
376                         *nw_l = l;
377                         positions[i].x = -1;
378                         positions[i].y = -1;
379                 }
380         }
381 
382         /* move bubbles at the bottom of the stack first
383            to avoid overlapping */
384         for (i = n_wins - 1, l = g_list_last (stack->priv->bubbles); l != NULL; i--, l = l->prev) {
385                 NdBubble *nw2 = ND_BUBBLE (l->data);
386 
387                 if (bubble == NULL || nw2 != bubble) {
388                         gtk_window_move (GTK_WINDOW (nw2), positions[i].x, positions[i].y);
389                 }
390         }
391 
392         g_free (positions);
393 }
394 
395 static void
update_position(NdStack * stack)396 update_position (NdStack *stack)
397 {
398         nd_stack_shift_notifications (stack,
399                                       NULL, /* window */
400                                       NULL, /* list pointer */
401                                       0, /* init width */
402                                       0, /* init height */
403                                       NULL, /* out window x */
404                                       NULL); /* out window y */
405 }
406 
407 static gboolean
update_position_idle(NdStack * stack)408 update_position_idle (NdStack *stack)
409 {
410         update_position (stack);
411 
412         stack->priv->update_id = 0;
413         return FALSE;
414 }
415 
416 void
nd_stack_queue_update_position(NdStack * stack)417 nd_stack_queue_update_position (NdStack *stack)
418 {
419         if (stack->priv->update_id != 0) {
420                 return;
421         }
422 
423         stack->priv->update_id = g_idle_add ((GSourceFunc) update_position_idle, stack);
424 }
425 
426 void
nd_stack_add_bubble(NdStack * stack,NdBubble * bubble,gboolean new_notification)427 nd_stack_add_bubble (NdStack  *stack,
428                      NdBubble *bubble,
429                      gboolean  new_notification)
430 {
431         GtkRequisition  req;
432         int             x, y;
433 
434         gtk_widget_get_preferred_size (GTK_WIDGET (bubble), NULL, &req);
435         nd_stack_shift_notifications (stack,
436                                       bubble,
437                                       NULL,
438                                       req.width,
439                                       req.height + NOTIFY_STACK_SPACING,
440                                       &x,
441                                       &y);
442         gtk_widget_show (GTK_WIDGET (bubble));
443         gtk_window_move (GTK_WINDOW (bubble), x, y);
444 
445         if (new_notification) {
446                 g_signal_connect_swapped (G_OBJECT (bubble),
447                                           "destroy",
448                                           G_CALLBACK (nd_stack_remove_bubble),
449                                           stack);
450                 stack->priv->bubbles = g_list_prepend (stack->priv->bubbles, bubble);
451         }
452 }
453 
454 void
nd_stack_remove_bubble(NdStack * stack,NdBubble * bubble)455 nd_stack_remove_bubble (NdStack  *stack,
456                         NdBubble *bubble)
457 {
458         GList *remove_l = NULL;
459 
460         nd_stack_shift_notifications (stack,
461                                       bubble,
462                                       &remove_l,
463                                       0,
464                                       0,
465                                       NULL,
466                                       NULL);
467 
468         if (remove_l != NULL)
469                 stack->priv->bubbles = g_list_delete_link (stack->priv->bubbles, remove_l);
470 
471         if (gtk_widget_get_realized (GTK_WIDGET (bubble)))
472                 gtk_widget_unrealize (GTK_WIDGET (bubble));
473 }
474 
475 void
nd_stack_remove_all(NdStack * stack)476 nd_stack_remove_all (NdStack  *stack)
477 {
478         GList *bubbles;
479 
480         bubbles = g_list_copy (stack->priv->bubbles);
481         g_list_foreach (bubbles, (GFunc)gtk_widget_destroy, NULL);
482         g_list_free (bubbles);
483 }
484