2
* Copyright (C) 2002 Red Hat, Inc.
3
* Copyright (C) 2003-2006 Vincent Untz
4
* Copyright (C) 2007 Christian Persch
6
* This program is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU General Public License as
8
* published by the Free Software Foundation; either version 2 of the
9
* License, or (at your option) any later version.
11
* This program is distributed in the hope that it will be useful, but
12
* WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27
#include "na-tray-manager.h"
32
#define ICON_SPACING 1
33
#define MIN_BOX_SIZE 3
37
NaTrayManager *tray_manager;
39
GHashTable *icon_table;
40
GHashTable *tip_table;
46
TraysScreen *trays_screen;
53
GtkOrientation orientation;
65
NaTray *tray; /* tray containing the tray icon */
66
GtkWidget *icon; /* tray icon sending the message */
69
glong id; /* id of the current message */
70
GSList *buffer; /* buffered messages */
80
static gboolean initialized = FALSE;
81
static TraysScreen *trays_screens = NULL;
83
static void icon_tip_show_next (IconTip *icontip);
85
/* NaBox, an instantiable GtkBox */
88
typedef GtkBoxClass NaBoxClass;
90
static GType na_box_get_type (void);
92
G_DEFINE_TYPE (NaBox, na_box, GTK_TYPE_BOX)
95
na_box_init (NaBox *box)
100
na_box_class_init (NaBoxClass *klass)
106
G_DEFINE_TYPE (NaTray, na_tray, GTK_TYPE_BIN)
109
get_tray (TraysScreen *trays_screen)
111
if (trays_screen->all_trays == NULL)
114
return trays_screen->all_trays->data;
117
const char *roles[] = {
126
const char *wmclass_roles[] = {
127
"Bluetooth-applet", "bluetooth",
128
"Gnome-volume-control-applet", "volume",
129
"Nm-applet", "network",
130
"Gnome-power-manager", "battery",
135
find_role (const char *wmclass)
139
for (i = 0; wmclass_roles[i]; i += 2)
141
if (strcmp (wmclass, wmclass_roles[i]) == 0)
142
return wmclass_roles[i + 1];
149
find_role_pos (const char *role)
153
for (i = 0; roles[i]; i++)
155
if (strcmp (role, roles[i]) == 0)
163
tray_added (NaTrayManager *manager,
165
TraysScreen *trays_screen)
175
tray = get_tray (trays_screen);
181
g_assert (priv->trays_screen == trays_screen);
183
g_hash_table_insert (trays_screen->icon_table, icon, tray);
188
na_tray_child_get_wm_class (NA_TRAY_CHILD (icon), NULL, &class_a);
192
role = find_role (class_a);
197
role_position = find_role_pos (role);
198
g_object_set_data (G_OBJECT (icon), "role-position", GINT_TO_POINTER (role_position));
200
children = gtk_container_get_children (GTK_CONTAINER (priv->box));
201
for (l = g_list_last (children); l; l = l->prev)
203
GtkWidget *child = l->data;
206
rp = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "role-position"));
207
if (rp == 0 || rp < role_position)
209
position = g_list_index (children, child) + 1;
213
g_list_free (children);
219
gtk_box_pack_start (GTK_BOX (priv->box), icon, FALSE, FALSE, 0);
220
gtk_box_reorder_child (GTK_BOX (priv->box), icon, position);
222
gtk_widget_show (icon);
226
tray_removed (NaTrayManager *manager,
228
TraysScreen *trays_screen)
232
tray = g_hash_table_lookup (trays_screen->icon_table, icon);
236
g_assert (tray->priv->trays_screen == trays_screen);
238
g_hash_table_remove (trays_screen->icon_table, icon);
239
/* this will also destroy the tip associated to this icon */
240
g_hash_table_remove (trays_screen->tip_table, icon);
244
icon_tip_buffer_free (gpointer data,
247
IconTipBuffer *buffer;
251
g_free (buffer->text);
258
icon_tip_free (gpointer data)
267
if (icontip->fixedtip != NULL)
268
gtk_widget_destroy (GTK_WIDGET (icontip->fixedtip));
269
icontip->fixedtip = NULL;
271
if (icontip->source_id != 0)
272
g_source_remove (icontip->source_id);
273
icontip->source_id = 0;
275
if (icontip->buffer != NULL)
277
g_slist_foreach (icontip->buffer, icon_tip_buffer_free, NULL);
278
g_slist_free (icontip->buffer);
280
icontip->buffer = NULL;
286
icon_tip_buffer_compare (gconstpointer a,
289
const IconTipBuffer *buffer_a = a;
290
const IconTipBuffer *buffer_b = b;
292
if (buffer_a == NULL || buffer_b == NULL)
293
return !(buffer_a == buffer_b);
295
return buffer_a->id - buffer_b->id;
299
icon_tip_show_next_clicked (GtkWidget *widget,
302
icon_tip_show_next ((IconTip *) data);
306
icon_tip_show_next_timeout (gpointer data)
308
IconTip *icontip = (IconTip *) data;
310
icon_tip_show_next (icontip);
316
icon_tip_show_next (IconTip *icontip)
318
IconTipBuffer *buffer;
320
if (icontip->buffer == NULL)
322
/* this will also destroy the tip window */
323
g_hash_table_remove (icontip->tray->priv->trays_screen->tip_table,
328
if (icontip->source_id != 0)
329
g_source_remove (icontip->source_id);
330
icontip->source_id = 0;
332
buffer = icontip->buffer->data;
333
icontip->buffer = g_slist_remove (icontip->buffer, buffer);
335
if (icontip->fixedtip == NULL)
337
icontip->fixedtip = na_fixed_tip_new (icontip->icon,
338
na_tray_get_orientation (icontip->tray));
340
g_signal_connect (icontip->fixedtip, "clicked",
341
G_CALLBACK (icon_tip_show_next_clicked), icontip);
344
na_fixed_tip_set_markup (icontip->fixedtip, buffer->text);
346
if (!GTK_WIDGET_MAPPED (icontip->fixedtip))
347
gtk_widget_show (icontip->fixedtip);
349
icontip->id = buffer->id;
351
if (buffer->timeout > 0)
352
icontip->source_id = g_timeout_add_seconds (buffer->timeout,
353
icon_tip_show_next_timeout,
356
icon_tip_buffer_free (buffer, NULL);
360
message_sent (NaTrayManager *manager,
365
TraysScreen *trays_screen)
368
IconTipBuffer find_buffer;
369
IconTipBuffer *buffer;
372
icontip = g_hash_table_lookup (trays_screen->tip_table, icon);
376
(icontip->id == id ||
377
g_slist_find_custom (icontip->buffer, &find_buffer,
378
icon_tip_buffer_compare) != NULL))
379
/* we already have this message, so ignore it */
380
/* FIXME: in an ideal world, we'd remember all the past ids and ignore them
390
tray = g_hash_table_lookup (trays_screen->icon_table, icon);
393
/* We don't know about the icon sending the message, so ignore it.
394
* But this should never happen since NaTrayManager shouldn't send
395
* us the message if there's no socket for it. */
396
g_critical ("Ignoring a message sent by a tray icon "
397
"we don't know: \"%s\".\n", text);
401
icontip = g_new0 (IconTip, 1);
402
icontip->tray = tray;
403
icontip->icon = icon;
405
g_hash_table_insert (trays_screen->tip_table, icon, icontip);
410
buffer = g_new0 (IconTipBuffer, 1);
412
buffer->text = g_strdup (text);
414
buffer->timeout = timeout;
416
icontip->buffer = g_slist_append (icontip->buffer, buffer);
419
icon_tip_show_next (icontip);
423
message_cancelled (NaTrayManager *manager,
426
TraysScreen *trays_screen)
429
IconTipBuffer find_buffer;
430
GSList *cancel_buffer_l;
431
IconTipBuffer *cancel_buffer;
433
icontip = g_hash_table_lookup (trays_screen->tip_table, icon);
437
if (icontip->id == id)
439
icon_tip_show_next (icontip);
444
cancel_buffer_l = g_slist_find_custom (icontip->buffer, &find_buffer,
445
icon_tip_buffer_compare);
446
if (cancel_buffer_l == NULL)
449
cancel_buffer = cancel_buffer_l->data;
450
icon_tip_buffer_free (cancel_buffer, NULL);
452
icontip->buffer = g_slist_remove_link (icontip->buffer, cancel_buffer_l);
453
g_slist_free_1 (cancel_buffer_l);
457
update_orientation_for_messages (gpointer key,
469
if (icontip->tray != tray)
472
if (icontip->fixedtip)
473
na_fixed_tip_set_orientation (icontip->fixedtip, tray->priv->orientation);
477
update_size_and_orientation (NaTray *tray)
479
NaTrayPrivate *priv = tray->priv;
481
gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), priv->orientation);
483
/* This only happens when setting the property during object construction */
484
if (!priv->trays_screen)
487
g_hash_table_foreach (priv->trays_screen->tip_table,
488
update_orientation_for_messages, tray);
490
if (get_tray (priv->trays_screen) == tray)
491
na_tray_manager_set_orientation (priv->trays_screen->tray_manager,
494
/* note, you want this larger if the frame has non-NONE relief by default. */
495
switch (priv->orientation)
497
case GTK_ORIENTATION_VERTICAL:
498
/* Give box a min size so the frame doesn't look dumb */
499
gtk_widget_set_size_request (priv->box, MIN_BOX_SIZE, -1);
501
case GTK_ORIENTATION_HORIZONTAL:
502
gtk_widget_set_size_request (priv->box, -1, MIN_BOX_SIZE);
507
/* Children with alpha channels have been set to be composited by calling
508
* gdk_window_set_composited(). We need to paint these children ourselves.
511
na_tray_expose_icon (GtkWidget *widget,
516
if (na_tray_child_has_alpha (NA_TRAY_CHILD (widget)))
518
gdk_cairo_set_source_pixmap (cr, widget->window,
519
widget->allocation.x,
520
widget->allocation.y);
526
na_tray_expose_box (GtkWidget *box,
527
GdkEventExpose *event)
529
cairo_t *cr = gdk_cairo_create (box->window);
531
gdk_cairo_region (cr, event->region);
534
gtk_container_foreach (GTK_CONTAINER (box), na_tray_expose_icon, cr);
540
na_tray_init (NaTray *tray)
544
priv = tray->priv = G_TYPE_INSTANCE_GET_PRIVATE (tray, NA_TYPE_TRAY, NaTrayPrivate);
547
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
549
priv->frame = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
550
gtk_container_add (GTK_CONTAINER (tray), priv->frame);
551
gtk_widget_show (priv->frame);
553
priv->box = g_object_new (na_box_get_type (), NULL);
554
g_signal_connect (priv->box, "expose-event",
555
G_CALLBACK (na_tray_expose_box), tray);
556
gtk_box_set_spacing (GTK_BOX (priv->box), ICON_SPACING);
557
gtk_container_add (GTK_CONTAINER (priv->frame), priv->box);
558
gtk_widget_show (priv->box);
562
na_tray_constructor (GType type,
563
guint n_construct_properties,
564
GObjectConstructParam *construct_params)
571
object = G_OBJECT_CLASS (na_tray_parent_class)->constructor (type,
572
n_construct_properties,
574
tray = NA_TRAY (object);
577
g_assert (priv->screen != NULL);
584
display = gdk_display_get_default ();
585
n_screens = gdk_display_get_n_screens (display);
586
trays_screens = g_new0 (TraysScreen, n_screens);
590
screen_number = gdk_screen_get_number (priv->screen);
592
if (trays_screens [screen_number].tray_manager == NULL)
594
NaTrayManager *tray_manager;
596
tray_manager = na_tray_manager_new ();
598
if (na_tray_manager_manage_screen (tray_manager, priv->screen))
600
trays_screens [screen_number].tray_manager = tray_manager;
602
na_tray_manager_set_padding (tray_manager, 0);
604
g_signal_connect (tray_manager, "tray_icon_added",
605
G_CALLBACK (tray_added),
606
&trays_screens [screen_number]);
607
g_signal_connect (tray_manager, "tray_icon_removed",
608
G_CALLBACK (tray_removed),
609
&trays_screens [screen_number]);
610
g_signal_connect (tray_manager, "message_sent",
611
G_CALLBACK (message_sent),
612
&trays_screens [screen_number]);
613
g_signal_connect (tray_manager, "message_cancelled",
614
G_CALLBACK (message_cancelled),
615
&trays_screens [screen_number]);
617
trays_screens [screen_number].icon_table = g_hash_table_new (NULL,
619
trays_screens [screen_number].tip_table = g_hash_table_new_full (
627
g_printerr ("System tray didn't get the system tray manager selection for screen %d\n",
629
g_object_unref (tray_manager);
633
priv->trays_screen = &trays_screens [screen_number];
634
trays_screens [screen_number].all_trays = g_slist_append (trays_screens [screen_number].all_trays,
637
update_size_and_orientation (tray);
643
na_tray_dispose (GObject *object)
645
NaTray *tray = NA_TRAY (object);
646
NaTrayPrivate *priv = tray->priv;
647
TraysScreen *trays_screen = priv->trays_screen;
649
if (trays_screen != NULL)
651
trays_screen->all_trays = g_slist_remove (trays_screen->all_trays, tray);
653
if (trays_screen->all_trays == NULL)
655
/* Make sure we drop the manager selection */
656
g_object_unref (trays_screen->tray_manager);
657
trays_screen->tray_manager = NULL;
659
g_hash_table_destroy (trays_screen->icon_table);
660
trays_screen->icon_table = NULL;
662
g_hash_table_destroy (trays_screen->tip_table);
663
trays_screen->tip_table = NULL;
669
new_tray = get_tray (trays_screen);
670
if (new_tray != NULL)
671
na_tray_manager_set_orientation (trays_screen->tray_manager,
672
na_tray_get_orientation (new_tray));
676
priv->trays_screen = NULL;
678
if (priv->idle_redraw_id != 0)
680
g_source_remove (priv->idle_redraw_id);
681
priv->idle_redraw_id = 0;
684
G_OBJECT_CLASS (na_tray_parent_class)->dispose (object);
688
na_tray_set_property (GObject *object,
693
NaTray *tray = NA_TRAY (object);
694
NaTrayPrivate *priv = tray->priv;
698
case PROP_ORIENTATION:
699
na_tray_set_orientation (tray, g_value_get_enum (value));
702
priv->screen = g_value_get_object (value);
705
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
711
na_tray_size_request (GtkWidget *widget,
712
GtkRequisition *requisition)
714
gtk_widget_size_request (gtk_bin_get_child (GTK_BIN (widget)), requisition);
718
na_tray_size_allocate (GtkWidget *widget,
719
GtkAllocation *allocation)
721
gtk_widget_size_allocate (gtk_bin_get_child (GTK_BIN (widget)), allocation);
725
na_tray_class_init (NaTrayClass *klass)
727
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
728
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
730
gobject_class->constructor = na_tray_constructor;
731
gobject_class->set_property = na_tray_set_property;
732
gobject_class->dispose = na_tray_dispose;
734
widget_class->size_request = na_tray_size_request;
735
widget_class->size_allocate = na_tray_size_allocate;
737
g_object_class_install_property
740
g_param_spec_enum ("orientation", "orientation", "orientation",
741
GTK_TYPE_ORIENTATION,
742
GTK_ORIENTATION_HORIZONTAL,
744
G_PARAM_CONSTRUCT_ONLY |
745
G_PARAM_STATIC_NAME |
746
G_PARAM_STATIC_NICK |
747
G_PARAM_STATIC_BLURB));
749
g_object_class_install_property
752
g_param_spec_object ("screen", "screen", "screen",
755
G_PARAM_CONSTRUCT_ONLY |
756
G_PARAM_STATIC_NAME |
757
G_PARAM_STATIC_NICK |
758
G_PARAM_STATIC_BLURB));
760
g_type_class_add_private (gobject_class, sizeof (NaTrayPrivate));
764
na_tray_new_for_screen (GdkScreen *screen,
765
GtkOrientation orientation)
767
return g_object_new (NA_TYPE_TRAY,
769
"orientation", orientation,
774
na_tray_set_orientation (NaTray *tray,
775
GtkOrientation orientation)
777
NaTrayPrivate *priv = tray->priv;
779
if (orientation == priv->orientation)
782
priv->orientation = orientation;
784
update_size_and_orientation (tray);
788
na_tray_get_orientation (NaTray *tray)
790
return tray->priv->orientation;
794
idle_redraw_cb (NaTray *tray)
796
NaTrayPrivate *priv = tray->priv;
798
gtk_container_foreach (GTK_CONTAINER (priv->box), (GtkCallback)na_tray_child_force_redraw, tray);
800
priv->idle_redraw_id = 0;
806
na_tray_set_padding (NaTray *tray,
809
NaTrayPrivate *priv = tray->priv;
811
if (get_tray (priv->trays_screen) == tray)
812
na_tray_manager_set_padding (priv->trays_screen->tray_manager, padding);
816
na_tray_force_redraw (NaTray *tray)
818
NaTrayPrivate *priv = tray->priv;
820
/* Force the icons to redraw their backgrounds.
822
if (priv->idle_redraw_id == 0)
823
priv->idle_redraw_id = g_idle_add ((GSourceFunc) idle_redraw_cb, tray);