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