1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2006-2007 Christian Hammond <chipx86@chipx86.com>
4  * Copyright (C) 2009 Red Hat, Inc.
5  * Copyright (C) 2011 Perberos <perberos@gmail.com>
6  * Copyright (C) 2012-2021 MATE Developers
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301, USA.
22  */
23 #include "config.h"
24 
25 #include <glib/gi18n.h>
26 #include <gtk/gtk.h>
27 
28 #include <libxml/xpath.h>
29 
30 typedef void (*ActionInvokedCb) (GtkWindow* nw, const char* key);
31 typedef void (*UrlClickedCb) (GtkWindow* nw, const char* url);
32 
33 typedef struct {
34 	GtkWidget* win;
35 	GtkWidget* top_spacer;
36 	GtkWidget* bottom_spacer;
37 	GtkWidget* main_hbox;
38 	GtkWidget* iconbox;
39 	GtkWidget* icon;
40 	GtkWidget* content_hbox;
41 	GtkWidget* summary_label;
42 	GtkWidget* close_button;
43 	GtkWidget* body_label;
44 	GtkWidget* actions_box;
45 	GtkWidget* last_sep;
46 	GtkWidget* stripe_spacer;
47 	GtkWidget* pie_countdown;
48 
49 	gboolean has_arrow;
50 	gboolean composited;
51 	gboolean action_icons;
52 
53 	int point_x;
54 	int point_y;
55 
56 	int drawn_arrow_begin_x;
57 	int drawn_arrow_begin_y;
58 	int drawn_arrow_middle_x;
59 	int drawn_arrow_middle_y;
60 	int drawn_arrow_end_x;
61 	int drawn_arrow_end_y;
62 
63 	int width;
64 	int height;
65 
66 	GdkPoint* border_points;
67 	size_t num_border_points;
68 
69 	cairo_region_t *window_region;
70 
71 	guchar urgency;
72 	glong timeout;
73 	glong remaining;
74 
75 	UrlClickedCb url_clicked;
76 
77 } WindowData;
78 
79 enum {
80 	URGENCY_LOW,
81 	URGENCY_NORMAL,
82 	URGENCY_CRITICAL
83 };
84 
85 gboolean theme_check_init(unsigned int major_ver, unsigned int minor_ver,
86 			  unsigned int micro_ver);
87 void get_theme_info(char **theme_name, char **theme_ver, char **author,
88 		    char **homepage);
89 GtkWindow* create_notification(UrlClickedCb url_clicked);
90 void set_notification_text(GtkWindow *nw, const char *summary,
91 			   const char *body);
92 void set_notification_icon(GtkWindow *nw, GdkPixbuf *pixbuf);
93 void set_notification_arrow(GtkWidget *nw, gboolean visible, int x, int y);
94 void add_notification_action(GtkWindow *nw, const char *text, const char *key,
95 			     ActionInvokedCb cb);
96 void clear_notification_actions(GtkWindow *nw);
97 void move_notification(GtkWidget *nw, int x, int y);
98 void set_notification_timeout(GtkWindow *nw, glong timeout);
99 void set_notification_hints(GtkWindow *nw, GVariant *hints);
100 void notification_tick(GtkWindow *nw, glong remaining);
101 
102 //#define ENABLE_GRADIENT_LOOK
103 
104 #ifdef ENABLE_GRADIENT_LOOK
105 	#define STRIPE_WIDTH 45
106 #else
107 	#define STRIPE_WIDTH 30
108 #endif
109 
110 #define WIDTH         400
111 #define IMAGE_SIZE    32
112 #define IMAGE_PADDING 10
113 #define SPACER_LEFT   30
114 #define PIE_RADIUS    12
115 #define PIE_WIDTH     (2 * PIE_RADIUS)
116 #define PIE_HEIGHT    (2 * PIE_RADIUS)
117 #define BODY_X_OFFSET (IMAGE_SIZE + 8)
118 #define DEFAULT_ARROW_OFFSET  (SPACER_LEFT + 2)
119 #define DEFAULT_ARROW_HEIGHT  14
120 #define DEFAULT_ARROW_WIDTH   28
121 #define BACKGROUND_OPACITY    0.92
122 #define BOTTOM_GRADIENT_HEIGHT 30
123 
124 static void
get_background_color(GtkStyleContext * context,GtkStateFlags state,GdkRGBA * color)125 get_background_color (GtkStyleContext *context,
126                       GtkStateFlags    state,
127                       GdkRGBA         *color)
128 {
129         GdkRGBA *c;
130 
131         g_return_if_fail (color != NULL);
132         g_return_if_fail (GTK_IS_STYLE_CONTEXT (context));
133 
134         gtk_style_context_get (context, state,
135                                "background-color", &c,
136                                NULL);
137 
138         *color = *c;
139         gdk_rgba_free (c);
140 }
141 
fill_background(GtkWidget * widget,WindowData * windata,cairo_t * cr)142 static void fill_background(GtkWidget* widget, WindowData* windata, cairo_t* cr)
143 {
144     GtkStyleContext *context;
145     GdkRGBA bg;
146 
147     GtkAllocation allocation;
148 
149     gtk_widget_get_allocation(widget, &allocation);
150 
151     #ifdef ENABLE_GRADIENT_LOOK
152 
153         cairo_pattern_t *gradient;
154         int              gradient_y;
155 
156         gradient_y = allocation.height - BOTTOM_GRADIENT_HEIGHT;
157 
158     #endif
159 
160     context = gtk_widget_get_style_context (windata->win);
161 
162     gtk_style_context_save (context);
163     gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
164 
165     get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
166 
167     gtk_style_context_restore (context);
168 
169     if (windata->composited)
170     {
171         cairo_set_source_rgba(cr, bg.red, bg.green, bg.blue, BACKGROUND_OPACITY);
172     }
173     else
174     {
175         gdk_cairo_set_source_rgba (cr, &bg);
176     }
177 
178     cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
179 
180     cairo_fill(cr);
181 
182     #ifdef ENABLE_GRADIENT_LOOK
183         /* Add a very subtle gradient to the bottom of the notification */
184         gradient = cairo_pattern_create_linear(0, gradient_y, 0, allocation.height);
185         cairo_pattern_add_color_stop_rgba(gradient, 0, 0, 0, 0, 0);
186         cairo_pattern_add_color_stop_rgba(gradient, 1, 0, 0, 0, 0.15);
187         cairo_rectangle(cr, 0, gradient_y, allocation.width, BOTTOM_GRADIENT_HEIGHT);
188 
189         cairo_set_source(cr, gradient);
190         cairo_fill(cr);
191         cairo_pattern_destroy(gradient);
192     #endif
193 }
194 
draw_stripe(GtkWidget * widget,WindowData * windata,cairo_t * cr)195 static void draw_stripe(GtkWidget* widget, WindowData* windata, cairo_t* cr)
196 {
197 	GtkStyleContext* context;
198 	GdkRGBA bg;
199 	int              stripe_x;
200 	int              stripe_y;
201 	int              stripe_height;
202 	#ifdef ENABLE_GRADIENT_LOOK
203 		cairo_pattern_t* gradient;
204 		double           r, g, b;
205 	#endif
206 
207 	context = gtk_widget_get_style_context (widget);
208 
209 	gtk_style_context_save (context);
210 
211 	GtkAllocation alloc;
212 	gtk_widget_get_allocation(windata->main_hbox, &alloc);
213 
214 	stripe_x = alloc.x + 1;
215 
216 	if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
217 	{
218 		stripe_x = windata->width - STRIPE_WIDTH - stripe_x;
219 	}
220 
221 	stripe_y = alloc.y + 1;
222 	stripe_height = alloc.height - 2;
223 
224 	switch (windata->urgency)
225 	{
226 		case URGENCY_LOW: // LOW
227 			gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
228 			gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
229 			get_background_color (context, GTK_STATE_FLAG_NORMAL, &bg);
230 			gdk_cairo_set_source_rgba (cr, &bg);
231 			break;
232 
233 		case URGENCY_CRITICAL: // CRITICAL
234 			gdk_rgba_parse (&bg, "#CC0000");
235 			break;
236 
237 		case URGENCY_NORMAL: // NORMAL
238 		default:
239 			gtk_style_context_set_state (context, GTK_STATE_FLAG_SELECTED);
240 			gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
241 			get_background_color (context, GTK_STATE_FLAG_SELECTED, &bg);
242 			gdk_cairo_set_source_rgba (cr, &bg);
243 			break;
244 	}
245 
246 	gtk_style_context_restore (context);
247 
248 	cairo_rectangle(cr, stripe_x, stripe_y, STRIPE_WIDTH, stripe_height);
249 
250 	#ifdef ENABLE_GRADIENT_LOOK
251 		r = color.red / 65535.0;
252 		g = color.green / 65535.0;
253 		b = color.blue / 65535.0;
254 
255 		gradient = cairo_pattern_create_linear(stripe_x, 0, STRIPE_WIDTH, 0);
256 		cairo_pattern_add_color_stop_rgba(gradient, 0, r, g, b, 1);
257 		cairo_pattern_add_color_stop_rgba(gradient, 1, r, g, b, 0);
258 		cairo_set_source(cr, gradient);
259 		cairo_fill(cr);
260 		cairo_pattern_destroy(gradient);
261 	#else
262 		gdk_cairo_set_source_rgba (cr, &bg);
263 		cairo_fill(cr);
264 	#endif
265 }
266 
get_notification_arrow_type(GtkWidget * nw)267 static GtkArrowType get_notification_arrow_type(GtkWidget* nw)
268 {
269 	WindowData*     windata;
270 	GdkScreen*      screen;
271 	GdkRectangle    monitor_geometry;
272 	GdkDisplay*     display;
273 	GdkMonitor*     monitor;
274 
275 	windata = g_object_get_data(G_OBJECT(nw), "windata");
276 
277 	screen = gdk_window_get_screen(GDK_WINDOW( gtk_widget_get_window(nw)));
278 	display = gdk_screen_get_display (screen);
279 	monitor = gdk_display_get_monitor_at_point (display, windata->point_x, windata->point_y);
280 	gdk_monitor_get_geometry (monitor, &monitor_geometry);
281 
282 	if (windata->point_y - monitor_geometry.y + windata->height + DEFAULT_ARROW_HEIGHT > monitor_geometry.height)
283 	{
284 		return GTK_ARROW_DOWN;
285 	}
286 	else
287 	{
288 		return GTK_ARROW_UP;
289 	}
290 }
291 
292 #define ADD_POINT(_x, _y, shapeoffset_x, shapeoffset_y) \
293 	G_STMT_START { \
294 		windata->border_points[i].x = (_x); \
295 		windata->border_points[i].y = (_y); \
296 		shape_points[i].x = (_x) + (shapeoffset_x); \
297 		shape_points[i].y = (_y) + (shapeoffset_y); \
298 		i++;\
299 	} G_STMT_END
300 
create_border_with_arrow(GtkWidget * nw,WindowData * windata)301 static void create_border_with_arrow(GtkWidget* nw, WindowData* windata)
302 {
303 	int             width;
304 	int             height;
305 	int             y;
306 	int             norm_point_x;
307 	int             norm_point_y;
308 	GtkArrowType    arrow_type;
309 	GdkScreen*      screen;
310 	int             arrow_side1_width = DEFAULT_ARROW_WIDTH / 2;
311 	int             arrow_side2_width = DEFAULT_ARROW_WIDTH / 2;
312 	int             arrow_offset = DEFAULT_ARROW_OFFSET;
313 	GdkPoint*       shape_points = NULL;
314 	int             i = 0;
315 	GdkMonitor*     monitor;
316 	GdkDisplay*     display;
317 	GdkRectangle    monitor_geometry;
318 
319 	width = windata->width;
320 	height = windata->height;
321 
322 	screen = gdk_window_get_screen(GDK_WINDOW(gtk_widget_get_window(nw)));
323 	display = gdk_screen_get_display (screen);
324 	monitor = gdk_display_get_monitor_at_point (display, windata->point_x, windata->point_y);
325 	gdk_monitor_get_geometry (monitor, &monitor_geometry);
326 
327 	windata->num_border_points = 5;
328 
329 	arrow_type = get_notification_arrow_type(windata->win);
330 
331 	norm_point_x = windata->point_x - monitor_geometry.x;
332 	norm_point_y = windata->point_y - monitor_geometry.y;
333 
334 	/* Handle the offset and such */
335 	switch (arrow_type)
336 	{
337 		case GTK_ARROW_UP:
338 		case GTK_ARROW_DOWN:
339 
340 			if (norm_point_x < arrow_side1_width)
341 			{
342 				arrow_side1_width = 0;
343 				arrow_offset = 0;
344 			}
345 			else if (norm_point_x > monitor_geometry.width - arrow_side2_width)
346 			{
347 				arrow_side2_width = 0;
348 				arrow_offset = width - arrow_side1_width;
349 			}
350 			else
351 			{
352 				if (norm_point_x - arrow_side2_width + width >= monitor_geometry.width)
353 				{
354 					arrow_offset = width - monitor_geometry.width + norm_point_x;
355 				}
356 				else
357 				{
358 					arrow_offset = MIN(norm_point_x - arrow_side1_width, DEFAULT_ARROW_OFFSET);
359 				}
360 
361 				if (arrow_offset == 0 || arrow_offset == width - arrow_side1_width)
362 				{
363 					windata->num_border_points++;
364 				}
365 				else
366 				{
367 					windata->num_border_points += 2;
368 				}
369 			}
370 
371 			/*
372 			 * Why risk this for official builds? If it's somehow off the
373 			 * screen, it won't horribly impact the user. Definitely less
374 			 * than an assertion would...
375 			 */
376 			#if 0
377 				g_assert(arrow_offset + arrow_side1_width >= 0);
378 				g_assert(arrow_offset + arrow_side1_width + arrow_side2_width <= width);
379 			#endif
380 
381 			windata->border_points = g_new0(GdkPoint, windata->num_border_points);
382 			shape_points = g_new0(GdkPoint, windata->num_border_points);
383 
384 			windata->drawn_arrow_begin_x = arrow_offset;
385 			windata->drawn_arrow_middle_x = arrow_offset + arrow_side1_width;
386 			windata->drawn_arrow_end_x = arrow_offset + arrow_side1_width + arrow_side2_width;
387 
388 			if (arrow_type == GTK_ARROW_UP)
389 			{
390 				windata->drawn_arrow_begin_y = DEFAULT_ARROW_HEIGHT;
391 				windata->drawn_arrow_middle_y = 0;
392 				windata->drawn_arrow_end_y = DEFAULT_ARROW_HEIGHT;
393 
394 				if (arrow_side1_width == 0)
395 				{
396 					ADD_POINT(0, 0, 0, 0);
397 				}
398 				else
399 				{
400 					ADD_POINT(0, DEFAULT_ARROW_HEIGHT, 0, 0);
401 
402 					if (arrow_offset > 0)
403 					{
404 						ADD_POINT(arrow_offset - (arrow_side2_width > 0 ? 0 : 1), DEFAULT_ARROW_HEIGHT, 0, 0);
405 					}
406 
407 					ADD_POINT(arrow_offset + arrow_side1_width - (arrow_side2_width > 0 ? 0 : 1), 0, 0, 0);
408 				}
409 
410 				if (arrow_side2_width > 0)
411 				{
412 					ADD_POINT(windata->drawn_arrow_end_x, windata->drawn_arrow_end_y, 1, 0);
413 					ADD_POINT(width - 1, DEFAULT_ARROW_HEIGHT, 1, 0);
414 				}
415 
416 				ADD_POINT(width - 1, height - 1, 1, 1);
417 				ADD_POINT(0, height - 1, 0, 1);
418 
419 				y = windata->point_y;
420 			}
421 			else
422 			{
423 				windata->drawn_arrow_begin_y = height - DEFAULT_ARROW_HEIGHT;
424 				windata->drawn_arrow_middle_y = height;
425 				windata->drawn_arrow_end_y = height - DEFAULT_ARROW_HEIGHT;
426 
427 				ADD_POINT(0, 0, 0, 0);
428 				ADD_POINT(width - 1, 0, 1, 0);
429 
430 				if (arrow_side2_width == 0)
431 				{
432 					ADD_POINT(width - 1, height, (arrow_side1_width > 0 ? 0 : 1), 0);
433 				}
434 				else
435 				{
436 					ADD_POINT(width - 1, height - DEFAULT_ARROW_HEIGHT, 1, 1);
437 
438 					if (arrow_offset < width - arrow_side1_width)
439 					{
440 						ADD_POINT(arrow_offset + arrow_side1_width + arrow_side2_width, height - DEFAULT_ARROW_HEIGHT, 0, 1);
441 					}
442 
443 					ADD_POINT(arrow_offset + arrow_side1_width, height, 0, 1);
444 				}
445 
446 				if (arrow_side1_width > 0)
447 				{
448 					ADD_POINT(windata->drawn_arrow_begin_x - (arrow_side2_width > 0 ? 0 : 1), windata->drawn_arrow_begin_y, 0, 0);
449 					ADD_POINT(0, height - DEFAULT_ARROW_HEIGHT, 0, 1);
450 				}
451 
452 				y = windata->point_y - height;
453 			}
454 
455 			#if 0
456 				g_assert(i == windata->num_border_points);
457 				g_assert(windata->point_x - arrow_offset - arrow_side1_width >= 0);
458 			#endif
459 
460 			gtk_window_move(GTK_WINDOW(windata->win), windata->point_x - arrow_offset - arrow_side1_width, y);
461 
462 			break;
463 
464 		case GTK_ARROW_LEFT:
465 		case GTK_ARROW_RIGHT:
466 
467 			if (norm_point_y < arrow_side1_width)
468 			{
469 				arrow_side1_width = 0;
470 				arrow_offset = norm_point_y;
471 			}
472 			else if (norm_point_y > monitor_geometry.height - arrow_side2_width)
473 			{
474 				arrow_side2_width = 0;
475 				arrow_offset = norm_point_y - arrow_side1_width;
476 			}
477 			break;
478 
479 		default:
480 			g_assert_not_reached();
481 	}
482 
483 	g_assert(shape_points != NULL);
484 
485 	/* FIXME won't work with GTK+3, need a replacement */
486 	/*windata->window_region = gdk_region_polygon(shape_points, windata->num_border_points, GDK_EVEN_ODD_RULE);*/
487 	g_free(shape_points);
488 }
489 
draw_border(GtkWidget * widget,WindowData * windata,cairo_t * cr)490 static void draw_border(GtkWidget* widget, WindowData *windata, cairo_t* cr)
491 {
492 	cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
493 	cairo_set_line_width(cr, 1.0);
494 
495 	if (windata->has_arrow)
496 	{
497 		size_t i;
498 
499 		create_border_with_arrow(windata->win, windata);
500 
501 		cairo_move_to(cr, windata->border_points[0].x + 0.5, windata->border_points[0].y + 0.5);
502 
503 		for (i = 1; i < windata->num_border_points; i++)
504 		{
505 				cairo_line_to(cr, windata->border_points[i].x + 0.5, windata->border_points[i].y + 0.5);
506 		}
507 
508 		cairo_close_path(cr);
509 		/* FIXME window_region is not set up anyway, see previous fixme */
510 		/*gdk_window_shape_combine_region (gtk_widget_get_window (windata->win), windata->window_region, 0, 0);*/
511 		g_free(windata->border_points);
512 		windata->border_points = NULL;
513 	}
514 	else
515 	{
516 		cairo_rectangle(cr, 0.5, 0.5, windata->width - 0.5, windata->height - 0.5);
517 	}
518 
519 	cairo_stroke(cr);
520 }
521 
522 static void
paint_window(GtkWidget * widget,cairo_t * cr,WindowData * windata)523 paint_window (GtkWidget  *widget,
524 	      cairo_t    *cr,
525 	      WindowData *windata)
526 {
527 	cairo_t*         cr2;
528 	cairo_surface_t* surface;
529 	GtkAllocation    allocation;
530 
531 	gtk_widget_get_allocation(windata->win, &allocation);
532 
533 	if (windata->width == 0)
534 	{
535 			windata->width = allocation.width;
536 			windata->height = allocation.height;
537 	}
538 
539 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
540 
541 	gtk_widget_get_allocation(widget, &allocation);
542 
543 	surface = cairo_surface_create_similar (cairo_get_target (cr),
544 						CAIRO_CONTENT_COLOR_ALPHA,
545 						allocation.width,
546 						allocation.height);
547 
548 	cr2 = cairo_create (surface);
549 
550 	fill_background(widget, windata, cr2);
551 	draw_border(widget, windata, cr2);
552 	draw_stripe(widget, windata, cr2);
553 	cairo_fill (cr2);
554 	cairo_destroy (cr2);
555 
556 	cairo_set_source_surface (cr, surface, 0, 0);
557 	cairo_paint(cr);
558 	cairo_surface_destroy(surface);
559 }
560 
561 static gboolean
on_draw(GtkWidget * widget,cairo_t * cr,WindowData * windata)562 on_draw (GtkWidget *widget, cairo_t *cr, WindowData *windata)
563 {
564 	paint_window (widget, cr, windata);
565 
566 	return FALSE;
567 }
568 
destroy_windata(WindowData * windata)569 static void destroy_windata(WindowData* windata)
570 {
571 	if (windata->window_region != NULL)
572 	{
573 		cairo_region_destroy(windata->window_region);
574 	}
575 
576 	g_free(windata);
577 }
578 
update_spacers(GtkWidget * nw)579 static void update_spacers(GtkWidget* nw)
580 {
581 	WindowData* windata;
582 
583 	windata = g_object_get_data(G_OBJECT(nw), "windata");
584 
585 	if (windata->has_arrow)
586 	{
587 		switch (get_notification_arrow_type(GTK_WIDGET(nw)))
588 		{
589 			case GTK_ARROW_UP:
590 				gtk_widget_show(windata->top_spacer);
591 				gtk_widget_hide(windata->bottom_spacer);
592 				break;
593 
594 			case GTK_ARROW_DOWN:
595 				gtk_widget_hide(windata->top_spacer);
596 				gtk_widget_show(windata->bottom_spacer);
597 				break;
598 
599 			default:
600 				g_assert_not_reached();
601 		}
602 	}
603 	else
604 	{
605 		gtk_widget_hide(windata->top_spacer);
606 		gtk_widget_hide(windata->bottom_spacer);
607 	}
608 }
609 
update_content_hbox_visibility(WindowData * windata)610 static void update_content_hbox_visibility(WindowData* windata)
611 {
612 	/*
613 	 * This is all a hack, but until we have a libview-style ContentBox,
614 	 * it'll just have to do.
615 	 */
616 	if (gtk_widget_get_visible(windata->icon) || gtk_widget_get_visible(windata->body_label) || gtk_widget_get_visible(windata->actions_box))
617 	{
618 		gtk_widget_show(windata->content_hbox);
619 	}
620 	else
621 	{
622 		gtk_widget_hide(windata->content_hbox);
623 	}
624 }
625 
configure_event_cb(GtkWidget * nw,GdkEventConfigure * event,WindowData * windata)626 static gboolean configure_event_cb(GtkWidget* nw, GdkEventConfigure* event, WindowData* windata)
627 {
628 	windata->width = event->width;
629 	windata->height = event->height;
630 
631 	update_spacers(nw);
632 	gtk_widget_queue_draw(nw);
633 
634 	return FALSE;
635 }
636 
activate_link(GtkLabel * label,const char * url,WindowData * windata)637 static gboolean activate_link(GtkLabel* label, const char* url, WindowData* windata)
638 {
639 	windata->url_clicked(GTK_WINDOW(windata->win), url);
640 
641 	return TRUE;
642 }
643 
create_notification(UrlClickedCb url_clicked)644 GtkWindow* create_notification(UrlClickedCb url_clicked)
645 {
646 	GtkWidget* spacer;
647 	GtkWidget* win;
648 	GtkWidget* main_vbox;
649 	GtkWidget* hbox;
650 	GtkWidget* vbox;
651 	GtkWidget* close_button;
652 	GtkWidget* image;
653 	AtkObject* atkobj;
654 	WindowData* windata;
655 
656 	GdkVisual *visual;
657 	GdkScreen* screen;
658 
659 	windata = g_new0(WindowData, 1);
660 	windata->urgency = URGENCY_NORMAL;
661 	windata->url_clicked = url_clicked;
662 
663 	win = gtk_window_new(GTK_WINDOW_POPUP);
664 	gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
665 	windata->win = win;
666 
667 	windata->composited = FALSE;
668 
669 
670 	screen = gtk_window_get_screen(GTK_WINDOW(win));
671 
672 	visual = gdk_screen_get_rgba_visual(screen);
673 
674 	if (visual != NULL)
675 	{
676 		gtk_widget_set_visual(win, visual);
677 
678 		if (gdk_screen_is_composited(screen))
679 		{
680 			windata->composited = TRUE;
681 		}
682 	}
683 
684 	gtk_window_set_title(GTK_WINDOW(win), "Notification");
685 	gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_NOTIFICATION);
686 	gtk_widget_add_events(win, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
687 	gtk_widget_realize(win);
688 	gtk_widget_set_size_request(win, WIDTH, -1);
689 
690 	g_object_set_data_full(G_OBJECT(win), "windata", windata, (GDestroyNotify) destroy_windata);
691 	atk_object_set_role(gtk_widget_get_accessible(win), ATK_ROLE_ALERT);
692 
693 	g_signal_connect(G_OBJECT(win), "configure_event", G_CALLBACK(configure_event_cb), windata);
694 
695 	main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
696 	gtk_widget_show(main_vbox);
697 	gtk_container_add (GTK_CONTAINER (win), main_vbox);
698 	gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 1);
699 
700 	g_signal_connect (G_OBJECT (main_vbox), "draw", G_CALLBACK (on_draw), windata);
701 
702 	windata->top_spacer = gtk_image_new();
703 	gtk_box_pack_start(GTK_BOX(main_vbox), windata->top_spacer, FALSE, FALSE, 0);
704 	gtk_widget_set_size_request(windata->top_spacer, -1, DEFAULT_ARROW_HEIGHT);
705 
706 	windata->main_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
707 	gtk_widget_show(windata->main_hbox);
708 	gtk_box_pack_start(GTK_BOX(main_vbox), windata->main_hbox, FALSE, FALSE, 0);
709 
710 	windata->bottom_spacer = gtk_image_new();
711 	gtk_box_pack_start(GTK_BOX(main_vbox), windata->bottom_spacer, FALSE, FALSE, 0);
712 	gtk_widget_set_size_request(windata->bottom_spacer, -1, DEFAULT_ARROW_HEIGHT);
713 
714 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
715 	gtk_widget_show(vbox);
716 	gtk_box_pack_start(GTK_BOX(windata->main_hbox), vbox, TRUE, TRUE, 0);
717 	gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
718 
719 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
720 	gtk_widget_show(hbox);
721 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
722 
723 	spacer = gtk_image_new();
724 	gtk_widget_show(spacer);
725 	gtk_box_pack_start(GTK_BOX(hbox), spacer, FALSE, FALSE, 0);
726 	gtk_widget_set_size_request(spacer, SPACER_LEFT, -1);
727 
728 	windata->summary_label = gtk_label_new(NULL);
729 	gtk_widget_show(windata->summary_label);
730 	gtk_box_pack_start(GTK_BOX(hbox), windata->summary_label, TRUE, TRUE, 0);
731 	gtk_label_set_xalign (GTK_LABEL (windata->summary_label), 0.0);
732 	gtk_label_set_yalign (GTK_LABEL (windata->summary_label), 0.0);
733 	gtk_label_set_line_wrap(GTK_LABEL(windata->summary_label), TRUE);
734 	gtk_label_set_line_wrap_mode (GTK_LABEL (windata->summary_label), PANGO_WRAP_WORD_CHAR);
735 
736 	atkobj = gtk_widget_get_accessible(windata->summary_label);
737 	atk_object_set_description (atkobj, _("Notification summary text."));
738 
739 	/* Add the close button */
740 	close_button = gtk_button_new();
741 	windata->close_button = close_button;
742 	gtk_widget_set_halign (close_button, GTK_ALIGN_END);
743 	gtk_widget_set_valign (close_button, GTK_ALIGN_START);
744 	gtk_widget_show(close_button);
745 	gtk_box_pack_start(GTK_BOX(hbox), close_button, FALSE, FALSE, 0);
746 	gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
747 	gtk_container_set_border_width(GTK_CONTAINER(close_button), 0);
748 	//gtk_widget_set_size_request(close_button, 20, 20);
749 	g_signal_connect_swapped(G_OBJECT(close_button), "clicked", G_CALLBACK(gtk_widget_destroy), win);
750 
751 	atkobj = gtk_widget_get_accessible(close_button);
752 	atk_action_set_description(ATK_ACTION(atkobj), 0,
753                                _("Closes the notification."));
754 	atk_object_set_name(atkobj, "");
755 	atk_object_set_description (atkobj, _("Closes the notification."));
756 
757 	image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
758 	gtk_widget_show(image);
759 	gtk_container_add(GTK_CONTAINER(close_button), image);
760 
761 	windata->content_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
762 	gtk_box_pack_start(GTK_BOX(vbox), windata->content_hbox, FALSE, FALSE, 0);
763 
764 	windata->iconbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
765 	gtk_widget_show(windata->iconbox);
766 	gtk_box_pack_start(GTK_BOX(windata->content_hbox), windata->iconbox, FALSE, FALSE, 0);
767 	gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1);
768 
769 	windata->icon = gtk_image_new();
770 	gtk_box_pack_start(GTK_BOX(windata->iconbox), windata->icon, TRUE, TRUE, 0);
771 	gtk_widget_set_halign (windata->icon, GTK_ALIGN_CENTER);
772 	gtk_widget_set_valign (windata->icon, GTK_ALIGN_START);
773 
774 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
775 	gtk_widget_show(vbox);
776 	gtk_box_pack_start(GTK_BOX(windata->content_hbox), vbox, TRUE, TRUE, 0);
777 
778 	windata->body_label = gtk_label_new(NULL);
779 	gtk_widget_show(windata->body_label);
780 	gtk_box_pack_start(GTK_BOX(vbox), windata->body_label, TRUE, TRUE, 0);
781 	gtk_label_set_xalign (GTK_LABEL (windata->body_label), 0.0);
782 	gtk_label_set_yalign (GTK_LABEL (windata->body_label), 0.0);
783 	gtk_label_set_line_wrap(GTK_LABEL(windata->body_label), TRUE);
784 	gtk_label_set_line_wrap_mode (GTK_LABEL (windata->body_label), PANGO_WRAP_WORD_CHAR);
785     gtk_label_set_max_width_chars (GTK_LABEL (windata->body_label), 50);
786 	g_signal_connect(G_OBJECT(windata->body_label), "activate-link", G_CALLBACK(activate_link), windata);
787 
788 	atkobj = gtk_widget_get_accessible(windata->body_label);
789 	atk_object_set_description (atkobj, _("Notification body text."));
790 
791 	windata->actions_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
792 	gtk_widget_set_halign (windata->actions_box, GTK_ALIGN_END);
793 	gtk_widget_show(windata->actions_box);
794 	gtk_box_pack_start(GTK_BOX(vbox), windata->actions_box, FALSE, TRUE, 0);
795 
796 	return GTK_WINDOW(win);
797 }
798 
set_notification_hints(GtkWindow * nw,GVariant * hints)799 void set_notification_hints(GtkWindow *nw, GVariant *hints)
800 {
801 	WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata");
802 	guint8 urgency;
803 	gboolean action_icons;
804 
805 	g_assert(windata != NULL);
806 
807 	if (g_variant_lookup(hints, "urgency", "y", &urgency))
808 	{
809 		windata->urgency = urgency;
810 
811 		if (windata->urgency == URGENCY_CRITICAL) {
812 			gtk_window_set_title(GTK_WINDOW(nw), "Critical Notification");
813 		} else {
814 			gtk_window_set_title(GTK_WINDOW(nw), "Notification");
815 		}
816 	}
817 
818 	/* Determine if action-icons have been requested */
819 	if (g_variant_lookup(hints, "action-icons", "b", &action_icons))
820 	{
821 		windata->action_icons = action_icons;
822 	}
823 }
824 
set_notification_timeout(GtkWindow * nw,glong timeout)825 void set_notification_timeout(GtkWindow* nw, glong timeout)
826 {
827 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
828 
829 	g_assert(windata != NULL);
830 
831 	windata->timeout = timeout;
832 }
833 
notification_tick(GtkWindow * nw,glong remaining)834 void notification_tick(GtkWindow* nw, glong remaining)
835 {
836 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
837 
838 	windata->remaining = remaining;
839 
840 	if (windata->pie_countdown != NULL)
841 	{
842 		gtk_widget_queue_draw_area(windata->pie_countdown, 0, 0, PIE_WIDTH, PIE_HEIGHT);
843 	}
844 }
845 
set_notification_text(GtkWindow * nw,const char * summary,const char * body)846 void set_notification_text(GtkWindow* nw, const char* summary, const char* body)
847 {
848 	char* str;
849 	size_t str_len;
850 	char* quoted;
851 	GtkRequisition req;
852 	WindowData* windata;
853 
854 	windata = g_object_get_data(G_OBJECT(nw), "windata");
855 	g_assert(windata != NULL);
856 
857 	quoted = g_markup_escape_text(summary, -1);
858 	str = g_strdup_printf("<b><big>%s</big></b>", quoted);
859 	g_free(quoted);
860 
861 	gtk_label_set_markup(GTK_LABEL(windata->summary_label), str);
862 	g_free(str);
863 
864 	/* body */
865 	xmlDocPtr doc;
866 	xmlInitParser();
867 	str = g_strconcat ("<markup>", body, "</markup>", NULL);
868 	/* parse notification body */
869 	str_len = strlen (str);
870 	doc = xmlReadMemory(str, (int) str_len, "noname.xml", NULL, 0);
871 	g_free (str);
872 	if (doc != NULL) {
873 		xmlXPathContextPtr xpathCtx;
874 		xmlXPathObjectPtr  xpathObj;
875 		xmlNodeSetPtr      nodes;
876 		const char        *body_label_text;
877 		int i, size;
878 
879 		/* filterout img nodes */
880 		xpathCtx = xmlXPathNewContext(doc);
881 		xpathObj = xmlXPathEvalExpression((unsigned char *)"//img", xpathCtx);
882 		nodes = xpathObj->nodesetval;
883 		size = (nodes) ? nodes->nodeNr : 0;
884 		for(i = size - 1; i >= 0; i--) {
885 			xmlUnlinkNode (nodes->nodeTab[i]);
886 			xmlFreeNode (nodes->nodeTab[i]);
887 		}
888 
889 		/* write doc to string */
890 		xmlBufferPtr buf = xmlBufferCreate();
891 		(void) xmlNodeDump(buf, doc, xmlDocGetRootElement (doc), 0, 0);
892 		str = (char *)buf->content;
893 		gtk_label_set_markup (GTK_LABEL (windata->body_label), str);
894 
895 		/* cleanup */
896 		xmlBufferFree (buf);
897 		xmlXPathFreeObject (xpathObj);
898 		xmlXPathFreeContext (xpathCtx);
899 		xmlFreeDoc (doc);
900 
901 		/* Does it render properly? */
902 		body_label_text = gtk_label_get_text (GTK_LABEL (windata->body_label));
903 		if ((body_label_text == NULL) || (strlen (body_label_text) == 0)) {
904 			goto render_fail;
905 		}
906 		goto renrer_ok;
907 	}
908 
909 render_fail:
910 	/* could not parse notification body */
911 	quoted = g_markup_escape_text(body, -1);
912 	gtk_label_set_markup (GTK_LABEL (windata->body_label), quoted);
913 	g_free (quoted);
914 
915 renrer_ok:
916 	xmlCleanupParser ();
917 
918 	if (body == NULL || *body == '\0')
919 		gtk_widget_hide(windata->body_label);
920 	else
921 		gtk_widget_show(windata->body_label);
922 
923 	update_content_hbox_visibility(windata);
924 
925 	if (body != NULL && *body != '\0')
926 	{
927 		gtk_widget_get_preferred_size (windata->iconbox, NULL, &req);
928 		/* -1: border width for
929 		 * -6: spacing for hbox */
930 		gtk_widget_set_size_request(windata->body_label, WIDTH - (1 * 2) - (10 * 2) - req.width - 6, -1);
931 	}
932 
933 		gtk_widget_get_preferred_size (windata->close_button, NULL, &req);
934 	/* -1: main_vbox border width
935 	 * -10: vbox border width
936 	 * -6: spacing for hbox */
937 	gtk_widget_set_size_request(windata->summary_label, WIDTH - (1 * 2) - (10 * 2) - SPACER_LEFT - req.width - (6 * 2), -1);
938 }
939 
set_notification_icon(GtkWindow * nw,GdkPixbuf * pixbuf)940 void set_notification_icon(GtkWindow* nw, GdkPixbuf* pixbuf)
941 {
942 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
943 
944 	g_assert(windata != NULL);
945 
946 	gtk_image_set_from_pixbuf(GTK_IMAGE(windata->icon), pixbuf);
947 
948 	if (pixbuf != NULL)
949 	{
950 		int pixbuf_width = gdk_pixbuf_get_width(pixbuf);
951 
952 		gtk_widget_show(windata->icon);
953 		gtk_widget_set_size_request(windata->iconbox, MAX(BODY_X_OFFSET, pixbuf_width), -1);
954 	}
955 	else
956 	{
957 		gtk_widget_hide(windata->icon);
958 		gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1);
959 	}
960 
961 	update_content_hbox_visibility(windata);
962 }
963 
set_notification_arrow(GtkWidget * nw,gboolean visible,int x,int y)964 void set_notification_arrow(GtkWidget* nw, gboolean visible, int x, int y)
965 {
966 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
967 
968 	g_assert(windata != NULL);
969 
970 	windata->has_arrow = visible;
971 	windata->point_x = x;
972 	windata->point_y = y;
973 
974 	update_spacers(nw);
975 }
976 
977 static void
paint_countdown(GtkWidget * pie,cairo_t * cr,WindowData * windata)978 paint_countdown (GtkWidget  *pie,
979                  cairo_t    *cr,
980                  WindowData *windata)
981 {
982     GtkStyleContext *context;
983     GdkRGBA bg;
984     GtkAllocation alloc;
985     cairo_t* cr2;
986     cairo_surface_t* surface;
987 
988     context = gtk_widget_get_style_context (windata->win);
989 
990     gtk_style_context_save (context);
991     gtk_style_context_set_state (context, GTK_STATE_FLAG_SELECTED);
992 
993     get_background_color (context, GTK_STATE_FLAG_SELECTED, &bg);
994 
995     gtk_style_context_restore (context);
996 
997     gtk_widget_get_allocation(pie, &alloc);
998     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
999     surface = cairo_surface_create_similar (cairo_get_target(cr),
1000                                             CAIRO_CONTENT_COLOR_ALPHA,
1001                                             alloc.width,
1002                                             alloc.height);
1003 
1004     cr2 = cairo_create (surface);
1005 
1006     fill_background (pie, windata, cr2);
1007 
1008     if (windata->timeout > 0)
1009     {
1010         gdouble pct = (gdouble) windata->remaining / (gdouble) windata->timeout;
1011 
1012         gdk_cairo_set_source_rgba (cr2, &bg);
1013 
1014         cairo_move_to (cr2, PIE_RADIUS, PIE_RADIUS);
1015         cairo_arc_negative (cr2, PIE_RADIUS, PIE_RADIUS, PIE_RADIUS, -G_PI_2, -(pct * G_PI * 2) - G_PI_2);
1016         cairo_line_to (cr2, PIE_RADIUS, PIE_RADIUS);
1017         cairo_fill (cr2);
1018     }
1019 
1020     cairo_destroy(cr2);
1021 
1022     cairo_save (cr);
1023     cairo_set_source_surface (cr, surface, 0, 0);
1024     cairo_paint (cr);
1025     cairo_restore (cr);
1026 
1027     cairo_surface_destroy(surface);
1028 }
1029 
1030 static gboolean
on_countdown_draw(GtkWidget * widget,cairo_t * cr,WindowData * windata)1031 on_countdown_draw (GtkWidget *widget, cairo_t *cr, WindowData *windata)
1032 {
1033 	paint_countdown (widget, cr, windata);
1034 
1035 	return FALSE;
1036 }
1037 
action_clicked_cb(GtkWidget * w,GdkEventButton * event,ActionInvokedCb action_cb)1038 static void action_clicked_cb(GtkWidget* w, GdkEventButton* event, ActionInvokedCb action_cb)
1039 {
1040 	GtkWindow* nw;
1041 	const char* key;
1042 	nw = g_object_get_data(G_OBJECT(w), "_nw");
1043 	key = g_object_get_data(G_OBJECT(w), "_action_key");
1044 	action_cb(nw, key);
1045 }
1046 
add_notification_action(GtkWindow * nw,const char * text,const char * key,ActionInvokedCb cb)1047 void add_notification_action(GtkWindow* nw, const char* text, const char* key, ActionInvokedCb cb)
1048 {
1049 	WindowData* windata;
1050 	GtkWidget* label;
1051 	GtkWidget* button;
1052 	GtkWidget* hbox;
1053 	GdkPixbuf* pixbuf;
1054 	char* buf;
1055 
1056 	windata = g_object_get_data(G_OBJECT(nw), "windata");
1057 
1058 	g_assert(windata != NULL);
1059 
1060 	if (gtk_widget_get_visible(windata->actions_box))
1061 	{
1062 		gtk_widget_show(windata->actions_box);
1063 		update_content_hbox_visibility(windata);
1064 
1065 		/* Don't try to re-add a pie_countdown */
1066 		if (!windata->pie_countdown) {
1067 			windata->pie_countdown = gtk_drawing_area_new();
1068 			gtk_widget_set_halign (windata->pie_countdown, GTK_ALIGN_END);
1069 			gtk_widget_show(windata->pie_countdown);
1070 
1071 			gtk_box_pack_end (GTK_BOX (windata->actions_box), windata->pie_countdown, FALSE, TRUE, 0);
1072 			gtk_widget_set_size_request(windata->pie_countdown,
1073 						    PIE_WIDTH, PIE_HEIGHT);
1074 			g_signal_connect(G_OBJECT(windata->pie_countdown), "draw",
1075 					 G_CALLBACK(on_countdown_draw), windata);
1076 		}
1077 	}
1078 
1079 	if (windata->action_icons) {
1080 		button = gtk_button_new_from_icon_name(key, GTK_ICON_SIZE_BUTTON);
1081 		goto add_button;
1082 	}
1083 
1084 	button = gtk_button_new();
1085 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
1086 	gtk_widget_show(hbox);
1087 	gtk_container_add(GTK_CONTAINER(button), hbox);
1088 
1089 	/* Try to be smart and find a suitable icon. */
1090 	buf = g_strdup_printf("stock_%s", key);
1091 	pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_for_screen(gdk_window_get_screen(gtk_widget_get_window(GTK_WIDGET(nw)))),
1092 																	buf, 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
1093 	g_free(buf);
1094 
1095 	if (pixbuf != NULL)
1096 	{
1097 		GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
1098 		gtk_widget_show(image);
1099 		gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
1100 		gtk_widget_set_halign (image, GTK_ALIGN_CENTER);
1101 		gtk_widget_set_valign (image, GTK_ALIGN_CENTER);
1102 	}
1103 
1104 	label = gtk_label_new(NULL);
1105 	gtk_widget_show(label);
1106 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1107 	gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1108 	gtk_label_set_yalign (GTK_LABEL (label), 0.5);
1109 	buf = g_strdup_printf("<small>%s</small>", text);
1110 	gtk_label_set_markup(GTK_LABEL(label), buf);
1111 	g_free(buf);
1112 
1113 add_button:
1114 	gtk_widget_show(button);
1115 	gtk_box_pack_start(GTK_BOX(windata->actions_box), button, FALSE, FALSE, 0);
1116 
1117 	g_object_set_data(G_OBJECT(button), "_nw", nw);
1118 	g_object_set_data_full(G_OBJECT(button), "_action_key", g_strdup(key), g_free);
1119 	g_signal_connect(G_OBJECT(button), "button-release-event", G_CALLBACK(action_clicked_cb), cb);
1120 
1121 	gtk_widget_show_all(windata->actions_box);
1122 }
1123 
clear_notification_actions(GtkWindow * nw)1124 void clear_notification_actions(GtkWindow* nw)
1125 {
1126 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
1127 
1128 	windata->pie_countdown = NULL;
1129 
1130 	gtk_widget_hide(windata->actions_box);
1131 	gtk_container_foreach(GTK_CONTAINER(windata->actions_box), (GtkCallback) gtk_widget_destroy, NULL);
1132 }
1133 
move_notification(GtkWidget * nw,int x,int y)1134 void move_notification(GtkWidget* nw, int x, int y)
1135 {
1136 	WindowData* windata = g_object_get_data(G_OBJECT(nw), "windata");
1137 
1138 	g_assert(windata != NULL);
1139 
1140 	if (windata->has_arrow)
1141 	{
1142 		gtk_widget_queue_resize(nw);
1143 	}
1144 	else
1145 	{
1146 		gtk_window_move(GTK_WINDOW(nw), x, y);
1147 	}
1148 }
1149 
get_theme_info(char ** theme_name,char ** theme_ver,char ** author,char ** homepage)1150 void get_theme_info(char** theme_name, char** theme_ver, char** author, char** homepage)
1151 {
1152 	*theme_name = g_strdup("Standard");
1153 
1154 	/* If they are constants, maybe we can remove printf and use G_STRINGIFY() */
1155 	*theme_ver = g_strdup_printf("%d.%d.%d", NOTIFICATION_DAEMON_MAJOR_VERSION, NOTIFICATION_DAEMON_MINOR_VERSION, NOTIFICATION_DAEMON_MICRO_VERSION);
1156 	*author = g_strdup("Christian Hammond");
1157 	*homepage = g_strdup("http://www.galago-project.org/");
1158 }
1159 
theme_check_init(unsigned int major_ver,unsigned int minor_ver,unsigned int micro_ver)1160 gboolean theme_check_init(unsigned int major_ver, unsigned int minor_ver, unsigned int micro_ver)
1161 {
1162 	return major_ver == NOTIFICATION_DAEMON_MAJOR_VERSION && minor_ver == NOTIFICATION_DAEMON_MINOR_VERSION && micro_ver == NOTIFICATION_DAEMON_MICRO_VERSION;
1163 }
1164