[134] | 1 | /* na-tray-manager.c |
---|
| 2 | * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org> |
---|
| 3 | * Copyright (C) 2003-2006 Vincent Untz |
---|
| 4 | * |
---|
| 5 | * This library is free software; you can redistribute it and/or |
---|
| 6 | * modify it under the terms of the GNU Lesser General Public |
---|
| 7 | * License as published by the Free Software Foundation; either |
---|
| 8 | * version 2 of the License, or (at your option) any later version. |
---|
| 9 | * |
---|
| 10 | * This library 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 GNU |
---|
| 13 | * Lesser General Public License for more details. |
---|
| 14 | * |
---|
| 15 | * You should have received a copy of the GNU Lesser General Public |
---|
| 16 | * License along with this library; if not, write to the |
---|
| 17 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
---|
| 18 | * Boston, MA 02111-1307, USA. |
---|
| 19 | * |
---|
| 20 | * Used to be: eggtraymanager.c |
---|
| 21 | */ |
---|
| 22 | |
---|
| 23 | #include <config.h> |
---|
| 24 | #include <string.h> |
---|
| 25 | #include <libintl.h> |
---|
| 26 | |
---|
| 27 | #include "na-tray-manager.h" |
---|
| 28 | |
---|
| 29 | #include <gdkconfig.h> |
---|
| 30 | #include <glib/gi18n.h> |
---|
| 31 | #if defined (GDK_WINDOWING_X11) |
---|
| 32 | #include <gdk/gdkx.h> |
---|
| 33 | #include <X11/Xatom.h> |
---|
| 34 | #elif defined (GDK_WINDOWING_WIN32) |
---|
| 35 | #include <gdk/gdkwin32.h> |
---|
| 36 | #endif |
---|
| 37 | #include <gtk/gtk.h> |
---|
| 38 | |
---|
| 39 | #include "na-marshal.h" |
---|
| 40 | |
---|
| 41 | /* Signals */ |
---|
| 42 | enum |
---|
| 43 | { |
---|
| 44 | TRAY_ICON_ADDED, |
---|
| 45 | TRAY_ICON_REMOVED, |
---|
| 46 | MESSAGE_SENT, |
---|
| 47 | MESSAGE_CANCELLED, |
---|
| 48 | LOST_SELECTION, |
---|
| 49 | LAST_SIGNAL |
---|
| 50 | }; |
---|
| 51 | |
---|
| 52 | enum { |
---|
| 53 | PROP_0, |
---|
| 54 | PROP_ORIENTATION |
---|
| 55 | }; |
---|
| 56 | |
---|
| 57 | typedef struct |
---|
| 58 | { |
---|
| 59 | long id, len; |
---|
| 60 | long remaining_len; |
---|
| 61 | |
---|
| 62 | long timeout; |
---|
| 63 | char *str; |
---|
| 64 | #ifdef GDK_WINDOWING_X11 |
---|
| 65 | Window window; |
---|
| 66 | #endif |
---|
| 67 | } PendingMessage; |
---|
| 68 | |
---|
| 69 | static guint manager_signals[LAST_SIGNAL]; |
---|
| 70 | |
---|
| 71 | #define SYSTEM_TRAY_REQUEST_DOCK 0 |
---|
| 72 | #define SYSTEM_TRAY_BEGIN_MESSAGE 1 |
---|
| 73 | #define SYSTEM_TRAY_CANCEL_MESSAGE 2 |
---|
| 74 | |
---|
| 75 | #define SYSTEM_TRAY_ORIENTATION_HORZ 0 |
---|
| 76 | #define SYSTEM_TRAY_ORIENTATION_VERT 1 |
---|
| 77 | |
---|
| 78 | #ifdef GDK_WINDOWING_X11 |
---|
| 79 | static gboolean na_tray_manager_check_running_screen_x11 (GdkScreen *screen); |
---|
| 80 | #endif |
---|
| 81 | |
---|
| 82 | static void na_tray_manager_finalize (GObject *object); |
---|
| 83 | static void na_tray_manager_set_property (GObject *object, |
---|
| 84 | guint prop_id, |
---|
| 85 | const GValue *value, |
---|
| 86 | GParamSpec *pspec); |
---|
| 87 | static void na_tray_manager_get_property (GObject *object, |
---|
| 88 | guint prop_id, |
---|
| 89 | GValue *value, |
---|
| 90 | GParamSpec *pspec); |
---|
| 91 | |
---|
| 92 | static void na_tray_manager_unmanage (NaTrayManager *manager); |
---|
| 93 | |
---|
| 94 | G_DEFINE_TYPE (NaTrayManager, na_tray_manager, G_TYPE_OBJECT) |
---|
| 95 | |
---|
| 96 | static void |
---|
| 97 | na_tray_manager_init (NaTrayManager *manager) |
---|
| 98 | { |
---|
| 99 | manager->invisible = NULL; |
---|
| 100 | manager->socket_table = g_hash_table_new (NULL, NULL); |
---|
| 101 | } |
---|
| 102 | |
---|
| 103 | static void |
---|
| 104 | na_tray_manager_class_init (NaTrayManagerClass *klass) |
---|
| 105 | { |
---|
| 106 | GObjectClass *gobject_class; |
---|
| 107 | |
---|
| 108 | gobject_class = (GObjectClass *)klass; |
---|
| 109 | |
---|
| 110 | gobject_class->finalize = na_tray_manager_finalize; |
---|
| 111 | gobject_class->set_property = na_tray_manager_set_property; |
---|
| 112 | gobject_class->get_property = na_tray_manager_get_property; |
---|
| 113 | |
---|
| 114 | g_object_class_install_property (gobject_class, |
---|
| 115 | PROP_ORIENTATION, |
---|
| 116 | g_param_spec_enum ("orientation", |
---|
| 117 | "orientation", |
---|
| 118 | "orientation", |
---|
| 119 | GTK_TYPE_ORIENTATION, |
---|
| 120 | GTK_ORIENTATION_HORIZONTAL, |
---|
| 121 | G_PARAM_READWRITE | |
---|
| 122 | G_PARAM_CONSTRUCT | |
---|
| 123 | G_PARAM_STATIC_NAME | |
---|
| 124 | G_PARAM_STATIC_NICK | |
---|
| 125 | G_PARAM_STATIC_BLURB)); |
---|
| 126 | |
---|
| 127 | manager_signals[TRAY_ICON_ADDED] = |
---|
| 128 | g_signal_new ("tray_icon_added", |
---|
| 129 | G_OBJECT_CLASS_TYPE (klass), |
---|
| 130 | G_SIGNAL_RUN_LAST, |
---|
| 131 | G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_added), |
---|
| 132 | NULL, NULL, |
---|
| 133 | g_cclosure_marshal_VOID__OBJECT, |
---|
| 134 | G_TYPE_NONE, 1, |
---|
| 135 | GTK_TYPE_SOCKET); |
---|
| 136 | |
---|
| 137 | manager_signals[TRAY_ICON_REMOVED] = |
---|
| 138 | g_signal_new ("tray_icon_removed", |
---|
| 139 | G_OBJECT_CLASS_TYPE (klass), |
---|
| 140 | G_SIGNAL_RUN_LAST, |
---|
| 141 | G_STRUCT_OFFSET (NaTrayManagerClass, tray_icon_removed), |
---|
| 142 | NULL, NULL, |
---|
| 143 | g_cclosure_marshal_VOID__OBJECT, |
---|
| 144 | G_TYPE_NONE, 1, |
---|
| 145 | GTK_TYPE_SOCKET); |
---|
| 146 | manager_signals[MESSAGE_SENT] = |
---|
| 147 | g_signal_new ("message_sent", |
---|
| 148 | G_OBJECT_CLASS_TYPE (klass), |
---|
| 149 | G_SIGNAL_RUN_LAST, |
---|
| 150 | G_STRUCT_OFFSET (NaTrayManagerClass, message_sent), |
---|
| 151 | NULL, NULL, |
---|
| 152 | _na_marshal_VOID__OBJECT_STRING_LONG_LONG, |
---|
| 153 | G_TYPE_NONE, 4, |
---|
| 154 | GTK_TYPE_SOCKET, |
---|
| 155 | G_TYPE_STRING, |
---|
| 156 | G_TYPE_LONG, |
---|
| 157 | G_TYPE_LONG); |
---|
| 158 | manager_signals[MESSAGE_CANCELLED] = |
---|
| 159 | g_signal_new ("message_cancelled", |
---|
| 160 | G_OBJECT_CLASS_TYPE (klass), |
---|
| 161 | G_SIGNAL_RUN_LAST, |
---|
| 162 | G_STRUCT_OFFSET (NaTrayManagerClass, message_cancelled), |
---|
| 163 | NULL, NULL, |
---|
| 164 | _na_marshal_VOID__OBJECT_LONG, |
---|
| 165 | G_TYPE_NONE, 2, |
---|
| 166 | GTK_TYPE_SOCKET, |
---|
| 167 | G_TYPE_LONG); |
---|
| 168 | manager_signals[LOST_SELECTION] = |
---|
| 169 | g_signal_new ("lost_selection", |
---|
| 170 | G_OBJECT_CLASS_TYPE (klass), |
---|
| 171 | G_SIGNAL_RUN_LAST, |
---|
| 172 | G_STRUCT_OFFSET (NaTrayManagerClass, lost_selection), |
---|
| 173 | NULL, NULL, |
---|
| 174 | g_cclosure_marshal_VOID__VOID, |
---|
| 175 | G_TYPE_NONE, 0); |
---|
| 176 | |
---|
| 177 | #if defined (GDK_WINDOWING_X11) |
---|
| 178 | /* Nothing */ |
---|
| 179 | #elif defined (GDK_WINDOWING_WIN32) |
---|
| 180 | g_warning ("Port NaTrayManager to Win32"); |
---|
| 181 | #else |
---|
| 182 | g_warning ("Port NaTrayManager to this GTK+ backend"); |
---|
| 183 | #endif |
---|
| 184 | } |
---|
| 185 | |
---|
| 186 | static void |
---|
| 187 | na_tray_manager_finalize (GObject *object) |
---|
| 188 | { |
---|
| 189 | NaTrayManager *manager; |
---|
| 190 | |
---|
| 191 | manager = NA_TRAY_MANAGER (object); |
---|
| 192 | |
---|
| 193 | na_tray_manager_unmanage (manager); |
---|
| 194 | |
---|
| 195 | g_list_free (manager->messages); |
---|
| 196 | g_hash_table_destroy (manager->socket_table); |
---|
| 197 | |
---|
| 198 | G_OBJECT_CLASS (na_tray_manager_parent_class)->finalize (object); |
---|
| 199 | } |
---|
| 200 | |
---|
| 201 | static void |
---|
| 202 | na_tray_manager_set_property (GObject *object, |
---|
| 203 | guint prop_id, |
---|
| 204 | const GValue *value, |
---|
| 205 | GParamSpec *pspec) |
---|
| 206 | { |
---|
| 207 | NaTrayManager *manager = NA_TRAY_MANAGER (object); |
---|
| 208 | |
---|
| 209 | switch (prop_id) |
---|
| 210 | { |
---|
| 211 | case PROP_ORIENTATION: |
---|
| 212 | na_tray_manager_set_orientation (manager, g_value_get_enum (value)); |
---|
| 213 | break; |
---|
| 214 | default: |
---|
| 215 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
---|
| 216 | break; |
---|
| 217 | } |
---|
| 218 | } |
---|
| 219 | |
---|
| 220 | static void |
---|
| 221 | na_tray_manager_get_property (GObject *object, |
---|
| 222 | guint prop_id, |
---|
| 223 | GValue *value, |
---|
| 224 | GParamSpec *pspec) |
---|
| 225 | { |
---|
| 226 | NaTrayManager *manager = NA_TRAY_MANAGER (object); |
---|
| 227 | |
---|
| 228 | switch (prop_id) |
---|
| 229 | { |
---|
| 230 | case PROP_ORIENTATION: |
---|
| 231 | g_value_set_enum (value, manager->orientation); |
---|
| 232 | break; |
---|
| 233 | default: |
---|
| 234 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
---|
| 235 | break; |
---|
| 236 | } |
---|
| 237 | } |
---|
| 238 | |
---|
| 239 | NaTrayManager * |
---|
| 240 | na_tray_manager_new (void) |
---|
| 241 | { |
---|
| 242 | NaTrayManager *manager; |
---|
| 243 | |
---|
| 244 | manager = g_object_new (NA_TYPE_TRAY_MANAGER, NULL); |
---|
| 245 | |
---|
| 246 | return manager; |
---|
| 247 | } |
---|
| 248 | |
---|
| 249 | #ifdef GDK_WINDOWING_X11 |
---|
| 250 | |
---|
| 251 | static gboolean |
---|
| 252 | na_tray_manager_plug_removed (GtkSocket *socket, |
---|
| 253 | NaTrayManager *manager) |
---|
| 254 | { |
---|
| 255 | Window *window; |
---|
| 256 | |
---|
| 257 | window = g_object_get_data (G_OBJECT (socket), "na-tray-child-window"); |
---|
| 258 | |
---|
| 259 | g_hash_table_remove (manager->socket_table, GINT_TO_POINTER (*window)); |
---|
| 260 | g_object_set_data (G_OBJECT (socket), "na-tray-child-window", |
---|
| 261 | NULL); |
---|
| 262 | |
---|
| 263 | g_signal_emit (manager, manager_signals[TRAY_ICON_REMOVED], 0, socket); |
---|
| 264 | |
---|
| 265 | /* This destroys the socket. */ |
---|
| 266 | return FALSE; |
---|
| 267 | } |
---|
| 268 | |
---|
| 269 | static void |
---|
| 270 | na_tray_manager_make_socket_transparent (GtkWidget *widget, |
---|
| 271 | gpointer user_data) |
---|
| 272 | { |
---|
| 273 | if (GTK_WIDGET_NO_WINDOW (widget)) |
---|
| 274 | return; |
---|
| 275 | |
---|
| 276 | gdk_window_set_back_pixmap (widget->window, NULL, TRUE); |
---|
| 277 | } |
---|
| 278 | |
---|
| 279 | static gboolean |
---|
| 280 | na_tray_manager_socket_exposed (GtkWidget *widget, |
---|
| 281 | GdkEventExpose *event, |
---|
| 282 | gpointer user_data) |
---|
| 283 | { |
---|
| 284 | gdk_window_clear_area (widget->window, |
---|
| 285 | event->area.x, event->area.y, |
---|
| 286 | event->area.width, event->area.height); |
---|
| 287 | return FALSE; |
---|
| 288 | } |
---|
| 289 | |
---|
| 290 | static void |
---|
| 291 | na_tray_manager_socket_style_set (GtkWidget *widget, |
---|
| 292 | GtkStyle *previous_style, |
---|
| 293 | gpointer user_data) |
---|
| 294 | { |
---|
| 295 | if (widget->window == NULL) |
---|
| 296 | return; |
---|
| 297 | |
---|
| 298 | na_tray_manager_make_socket_transparent (widget, user_data); |
---|
| 299 | } |
---|
| 300 | |
---|
| 301 | static void |
---|
| 302 | na_tray_manager_handle_dock_request (NaTrayManager *manager, |
---|
| 303 | XClientMessageEvent *xevent) |
---|
| 304 | { |
---|
| 305 | GtkWidget *socket; |
---|
| 306 | Window *window; |
---|
| 307 | GtkRequisition req; |
---|
| 308 | |
---|
| 309 | if (g_hash_table_lookup (manager->socket_table, GINT_TO_POINTER (xevent->data.l[2]))) |
---|
| 310 | { |
---|
| 311 | /* We already got this notification earlier, ignore this one */ |
---|
| 312 | return; |
---|
| 313 | } |
---|
| 314 | |
---|
| 315 | socket = gtk_socket_new (); |
---|
| 316 | |
---|
| 317 | gtk_widget_set_app_paintable (socket, TRUE); |
---|
| 318 | //FIXME: need to find a theme where this (and expose event) is needed |
---|
| 319 | gtk_widget_set_double_buffered (socket, FALSE); |
---|
| 320 | g_signal_connect (socket, "realize", |
---|
| 321 | G_CALLBACK (na_tray_manager_make_socket_transparent), NULL); |
---|
| 322 | g_signal_connect (socket, "expose_event", |
---|
| 323 | G_CALLBACK (na_tray_manager_socket_exposed), NULL); |
---|
| 324 | g_signal_connect_after (socket, "style_set", |
---|
| 325 | G_CALLBACK (na_tray_manager_socket_style_set), NULL); |
---|
| 326 | |
---|
| 327 | /* We need to set the child window here |
---|
| 328 | * so that the client can call _get functions |
---|
| 329 | * in the signal handler |
---|
| 330 | */ |
---|
| 331 | window = g_new (Window, 1); |
---|
| 332 | *window = xevent->data.l[2]; |
---|
| 333 | |
---|
| 334 | g_object_set_data_full (G_OBJECT (socket), |
---|
| 335 | "na-tray-child-window", |
---|
| 336 | window, g_free); |
---|
| 337 | g_signal_emit (manager, manager_signals[TRAY_ICON_ADDED], 0, |
---|
| 338 | socket); |
---|
| 339 | |
---|
| 340 | /* Add the socket only if it's been attached */ |
---|
| 341 | if (GTK_IS_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (socket)))) |
---|
| 342 | { |
---|
| 343 | g_signal_connect (socket, "plug_removed", |
---|
| 344 | G_CALLBACK (na_tray_manager_plug_removed), manager); |
---|
| 345 | |
---|
| 346 | gtk_socket_add_id (GTK_SOCKET (socket), *window); |
---|
| 347 | |
---|
| 348 | g_hash_table_insert (manager->socket_table, GINT_TO_POINTER (*window), socket); |
---|
| 349 | |
---|
| 350 | /* |
---|
| 351 | * Make sure the icons have a meaningfull size ... |
---|
| 352 | */ |
---|
| 353 | req.width = req.height = 1; |
---|
| 354 | gtk_widget_size_request (socket, &req); |
---|
| 355 | /* |
---|
| 356 | if ((req.width < 16) || (req.height < 16)) |
---|
| 357 | { |
---|
| 358 | gint nw = MAX (24, req.width); |
---|
| 359 | gint nh = MAX (24, req.height); |
---|
| 360 | g_warning (_("tray icon has requested a size of (%i x %i), resizing to (%i x %i)"), |
---|
| 361 | req.width, req.height, nw, nh); |
---|
| 362 | gtk_widget_set_size_request(icon, nw, nh); |
---|
| 363 | } |
---|
| 364 | */ |
---|
| 365 | gtk_widget_show(socket); |
---|
| 366 | } |
---|
| 367 | else |
---|
| 368 | gtk_widget_destroy (socket); |
---|
| 369 | } |
---|
| 370 | |
---|
| 371 | static void |
---|
| 372 | pending_message_free (PendingMessage *message) |
---|
| 373 | { |
---|
| 374 | g_free (message->str); |
---|
| 375 | g_free (message); |
---|
| 376 | } |
---|
| 377 | |
---|
| 378 | static GdkFilterReturn |
---|
| 379 | na_tray_manager_handle_client_message_message_data (GdkXEvent *xev, |
---|
| 380 | GdkEvent *event, |
---|
| 381 | gpointer data) |
---|
| 382 | { |
---|
| 383 | XClientMessageEvent *xevent; |
---|
| 384 | NaTrayManager *manager; |
---|
| 385 | GList *p; |
---|
| 386 | int len; |
---|
| 387 | |
---|
| 388 | xevent = (XClientMessageEvent *) xev; |
---|
| 389 | manager = data; |
---|
| 390 | |
---|
| 391 | /* Try to see if we can find the pending message in the list */ |
---|
| 392 | for (p = manager->messages; p; p = p->next) |
---|
| 393 | { |
---|
| 394 | PendingMessage *msg = p->data; |
---|
| 395 | |
---|
| 396 | if (xevent->window == msg->window) |
---|
| 397 | { |
---|
| 398 | /* Append the message */ |
---|
| 399 | len = MIN (msg->remaining_len, 20); |
---|
| 400 | |
---|
| 401 | memcpy ((msg->str + msg->len - msg->remaining_len), |
---|
| 402 | &xevent->data, len); |
---|
| 403 | msg->remaining_len -= len; |
---|
| 404 | |
---|
| 405 | if (msg->remaining_len == 0) |
---|
| 406 | { |
---|
| 407 | GtkSocket *socket; |
---|
| 408 | |
---|
| 409 | socket = g_hash_table_lookup (manager->socket_table, |
---|
| 410 | GINT_TO_POINTER (msg->window)); |
---|
| 411 | |
---|
| 412 | if (socket) |
---|
| 413 | g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, |
---|
| 414 | socket, msg->str, msg->id, msg->timeout); |
---|
| 415 | |
---|
| 416 | pending_message_free (msg); |
---|
| 417 | manager->messages = g_list_remove_link (manager->messages, p); |
---|
| 418 | g_list_free_1 (p); |
---|
| 419 | } |
---|
| 420 | |
---|
| 421 | break; |
---|
| 422 | } |
---|
| 423 | } |
---|
| 424 | |
---|
| 425 | return GDK_FILTER_REMOVE; |
---|
| 426 | } |
---|
| 427 | |
---|
| 428 | static void |
---|
| 429 | na_tray_manager_handle_begin_message (NaTrayManager *manager, |
---|
| 430 | XClientMessageEvent *xevent) |
---|
| 431 | { |
---|
| 432 | GtkSocket *socket; |
---|
| 433 | GList *p; |
---|
| 434 | PendingMessage *msg; |
---|
| 435 | long timeout; |
---|
| 436 | long len; |
---|
| 437 | long id; |
---|
| 438 | |
---|
| 439 | socket = g_hash_table_lookup (manager->socket_table, |
---|
| 440 | GINT_TO_POINTER (xevent->window)); |
---|
| 441 | /* we don't know about this tray icon, so ignore the message */ |
---|
| 442 | if (!socket) |
---|
| 443 | return; |
---|
| 444 | |
---|
| 445 | /* Check if the same message is already in the queue and remove it if so */ |
---|
| 446 | for (p = manager->messages; p; p = p->next) |
---|
| 447 | { |
---|
| 448 | PendingMessage *message = p->data; |
---|
| 449 | |
---|
| 450 | if (xevent->window == message->window && |
---|
| 451 | xevent->data.l[4] == message->id) |
---|
| 452 | { |
---|
| 453 | /* Hmm, we found it, now remove it */ |
---|
| 454 | pending_message_free (message); |
---|
| 455 | manager->messages = g_list_remove_link (manager->messages, p); |
---|
| 456 | g_list_free_1 (p); |
---|
| 457 | break; |
---|
| 458 | } |
---|
| 459 | } |
---|
| 460 | |
---|
| 461 | timeout = xevent->data.l[2]; |
---|
| 462 | len = xevent->data.l[3]; |
---|
| 463 | id = xevent->data.l[4]; |
---|
| 464 | |
---|
| 465 | if (len == 0) |
---|
| 466 | { |
---|
| 467 | g_signal_emit (manager, manager_signals[MESSAGE_SENT], 0, |
---|
| 468 | socket, "", id, timeout); |
---|
| 469 | } |
---|
| 470 | else |
---|
| 471 | { |
---|
| 472 | /* Now add the new message to the queue */ |
---|
| 473 | msg = g_new0 (PendingMessage, 1); |
---|
| 474 | msg->window = xevent->window; |
---|
| 475 | msg->timeout = timeout; |
---|
| 476 | msg->len = len; |
---|
| 477 | msg->id = id; |
---|
| 478 | msg->remaining_len = msg->len; |
---|
| 479 | msg->str = g_malloc (msg->len + 1); |
---|
| 480 | msg->str[msg->len] = '\0'; |
---|
| 481 | manager->messages = g_list_prepend (manager->messages, msg); |
---|
| 482 | } |
---|
| 483 | } |
---|
| 484 | |
---|
| 485 | static void |
---|
| 486 | na_tray_manager_handle_cancel_message (NaTrayManager *manager, |
---|
| 487 | XClientMessageEvent *xevent) |
---|
| 488 | { |
---|
| 489 | GList *p; |
---|
| 490 | GtkSocket *socket; |
---|
| 491 | |
---|
| 492 | /* Check if the message is in the queue and remove it if so */ |
---|
| 493 | for (p = manager->messages; p; p = p->next) |
---|
| 494 | { |
---|
| 495 | PendingMessage *msg = p->data; |
---|
| 496 | |
---|
| 497 | if (xevent->window == msg->window && |
---|
| 498 | xevent->data.l[4] == msg->id) |
---|
| 499 | { |
---|
| 500 | pending_message_free (msg); |
---|
| 501 | manager->messages = g_list_remove_link (manager->messages, p); |
---|
| 502 | g_list_free_1 (p); |
---|
| 503 | break; |
---|
| 504 | } |
---|
| 505 | } |
---|
| 506 | |
---|
| 507 | socket = g_hash_table_lookup (manager->socket_table, |
---|
| 508 | GINT_TO_POINTER (xevent->window)); |
---|
| 509 | |
---|
| 510 | if (socket) |
---|
| 511 | { |
---|
| 512 | g_signal_emit (manager, manager_signals[MESSAGE_CANCELLED], 0, |
---|
| 513 | socket, xevent->data.l[2]); |
---|
| 514 | } |
---|
| 515 | } |
---|
| 516 | |
---|
| 517 | static GdkFilterReturn |
---|
| 518 | na_tray_manager_handle_client_message_opcode (GdkXEvent *xev, |
---|
| 519 | GdkEvent *event, |
---|
| 520 | gpointer data) |
---|
| 521 | { |
---|
| 522 | XClientMessageEvent *xevent; |
---|
| 523 | NaTrayManager *manager; |
---|
| 524 | |
---|
| 525 | xevent = (XClientMessageEvent *) xev; |
---|
| 526 | manager = data; |
---|
| 527 | |
---|
| 528 | switch (xevent->data.l[1]) |
---|
| 529 | { |
---|
| 530 | case SYSTEM_TRAY_REQUEST_DOCK: |
---|
| 531 | /* Ignore this one since we don't know on which window this was received |
---|
| 532 | * and so we can't know for which screen this is. It will be handled |
---|
| 533 | * in na_tray_manager_window_filter() since we also receive it there */ |
---|
| 534 | break; |
---|
| 535 | |
---|
| 536 | case SYSTEM_TRAY_BEGIN_MESSAGE: |
---|
| 537 | na_tray_manager_handle_begin_message (manager, xevent); |
---|
| 538 | return GDK_FILTER_REMOVE; |
---|
| 539 | |
---|
| 540 | case SYSTEM_TRAY_CANCEL_MESSAGE: |
---|
| 541 | na_tray_manager_handle_cancel_message (manager, xevent); |
---|
| 542 | return GDK_FILTER_REMOVE; |
---|
| 543 | default: |
---|
| 544 | break; |
---|
| 545 | } |
---|
| 546 | |
---|
| 547 | return GDK_FILTER_CONTINUE; |
---|
| 548 | } |
---|
| 549 | |
---|
| 550 | static GdkFilterReturn |
---|
| 551 | na_tray_manager_window_filter (GdkXEvent *xev, |
---|
| 552 | GdkEvent *event, |
---|
| 553 | gpointer data) |
---|
| 554 | { |
---|
| 555 | XEvent *xevent = (GdkXEvent *)xev; |
---|
| 556 | NaTrayManager *manager = data; |
---|
| 557 | |
---|
| 558 | if (xevent->type == ClientMessage) |
---|
| 559 | { |
---|
| 560 | /* We handle this client message here. See comment in |
---|
| 561 | * na_tray_manager_handle_client_message_opcode() for details */ |
---|
| 562 | if (xevent->xclient.message_type == manager->opcode_atom && |
---|
| 563 | xevent->xclient.data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) |
---|
| 564 | { |
---|
| 565 | na_tray_manager_handle_dock_request (manager, |
---|
| 566 | (XClientMessageEvent *) xevent); |
---|
| 567 | return GDK_FILTER_REMOVE; |
---|
| 568 | } |
---|
| 569 | } |
---|
| 570 | else if (xevent->type == SelectionClear) |
---|
| 571 | { |
---|
| 572 | g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); |
---|
| 573 | na_tray_manager_unmanage (manager); |
---|
| 574 | } |
---|
| 575 | |
---|
| 576 | return GDK_FILTER_CONTINUE; |
---|
| 577 | } |
---|
| 578 | |
---|
| 579 | #if 0 |
---|
| 580 | //FIXME investigate why this doesn't work |
---|
| 581 | static gboolean |
---|
| 582 | na_tray_manager_selection_clear_event (GtkWidget *widget, |
---|
| 583 | GdkEventSelection *event, |
---|
| 584 | NaTrayManager *manager) |
---|
| 585 | { |
---|
| 586 | g_signal_emit (manager, manager_signals[LOST_SELECTION], 0); |
---|
| 587 | na_tray_manager_unmanage (manager); |
---|
| 588 | |
---|
| 589 | return FALSE; |
---|
| 590 | } |
---|
| 591 | #endif |
---|
| 592 | #endif |
---|
| 593 | |
---|
| 594 | static void |
---|
| 595 | na_tray_manager_unmanage (NaTrayManager *manager) |
---|
| 596 | { |
---|
| 597 | #ifdef GDK_WINDOWING_X11 |
---|
| 598 | GdkDisplay *display; |
---|
| 599 | guint32 timestamp; |
---|
| 600 | GtkWidget *invisible; |
---|
| 601 | |
---|
| 602 | if (manager->invisible == NULL) |
---|
| 603 | return; |
---|
| 604 | |
---|
| 605 | invisible = manager->invisible; |
---|
| 606 | g_assert (GTK_IS_INVISIBLE (invisible)); |
---|
| 607 | g_assert (GTK_WIDGET_REALIZED (invisible)); |
---|
| 608 | g_assert (GDK_IS_WINDOW (invisible->window)); |
---|
| 609 | |
---|
| 610 | display = gtk_widget_get_display (invisible); |
---|
| 611 | |
---|
| 612 | if (gdk_selection_owner_get_for_display (display, manager->selection_atom) == |
---|
| 613 | invisible->window) |
---|
| 614 | { |
---|
| 615 | timestamp = gdk_x11_get_server_time (invisible->window); |
---|
| 616 | gdk_selection_owner_set_for_display (display, |
---|
| 617 | NULL, |
---|
| 618 | manager->selection_atom, |
---|
| 619 | timestamp, |
---|
| 620 | TRUE); |
---|
| 621 | } |
---|
| 622 | |
---|
| 623 | //FIXME: we should also use gdk_remove_client_message_filter when it's |
---|
| 624 | //available |
---|
| 625 | // See bug #351254 |
---|
| 626 | gdk_window_remove_filter (invisible->window, |
---|
| 627 | na_tray_manager_window_filter, manager); |
---|
| 628 | |
---|
| 629 | manager->invisible = NULL; /* prior to destroy for reentrancy paranoia */ |
---|
| 630 | gtk_widget_destroy (invisible); |
---|
| 631 | g_object_unref (G_OBJECT (invisible)); |
---|
| 632 | #endif |
---|
| 633 | } |
---|
| 634 | |
---|
| 635 | static void |
---|
| 636 | na_tray_manager_set_orientation_property (NaTrayManager *manager) |
---|
| 637 | { |
---|
| 638 | #ifdef GDK_WINDOWING_X11 |
---|
| 639 | GdkDisplay *display; |
---|
| 640 | Atom orientation_atom; |
---|
| 641 | gulong data[1]; |
---|
| 642 | |
---|
| 643 | if (!manager->invisible || !manager->invisible->window) |
---|
| 644 | return; |
---|
| 645 | |
---|
| 646 | display = gtk_widget_get_display (manager->invisible); |
---|
| 647 | orientation_atom = gdk_x11_get_xatom_by_name_for_display (display, |
---|
| 648 | "_NET_SYSTEM_TRAY_ORIENTATION"); |
---|
| 649 | |
---|
| 650 | data[0] = manager->orientation == GTK_ORIENTATION_HORIZONTAL ? |
---|
| 651 | SYSTEM_TRAY_ORIENTATION_HORZ : |
---|
| 652 | SYSTEM_TRAY_ORIENTATION_VERT; |
---|
| 653 | |
---|
| 654 | XChangeProperty (GDK_DISPLAY_XDISPLAY (display), |
---|
| 655 | GDK_WINDOW_XWINDOW (manager->invisible->window), |
---|
| 656 | orientation_atom, |
---|
| 657 | XA_CARDINAL, 32, |
---|
| 658 | PropModeReplace, |
---|
| 659 | (guchar *) &data, 1); |
---|
| 660 | #endif |
---|
| 661 | } |
---|
| 662 | |
---|
| 663 | #ifdef GDK_WINDOWING_X11 |
---|
| 664 | |
---|
| 665 | static gboolean |
---|
| 666 | na_tray_manager_manage_screen_x11 (NaTrayManager *manager, |
---|
| 667 | GdkScreen *screen) |
---|
| 668 | { |
---|
| 669 | GdkDisplay *display; |
---|
| 670 | Screen *xscreen; |
---|
| 671 | GtkWidget *invisible; |
---|
| 672 | char *selection_atom_name; |
---|
| 673 | guint32 timestamp; |
---|
| 674 | |
---|
| 675 | g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), FALSE); |
---|
| 676 | g_return_val_if_fail (manager->screen == NULL, FALSE); |
---|
| 677 | |
---|
| 678 | /* If there's already a manager running on the screen |
---|
| 679 | * we can't create another one. |
---|
| 680 | */ |
---|
| 681 | #if 0 |
---|
| 682 | if (na_tray_manager_check_running_screen_x11 (screen)) |
---|
| 683 | return FALSE; |
---|
| 684 | #endif |
---|
| 685 | display = gdk_screen_get_display (screen); |
---|
| 686 | xscreen = GDK_SCREEN_XSCREEN (screen); |
---|
| 687 | |
---|
| 688 | invisible = gtk_invisible_new_for_screen (screen); |
---|
| 689 | gtk_widget_realize (invisible); |
---|
| 690 | |
---|
| 691 | gtk_widget_add_events (invisible, |
---|
| 692 | GDK_PROPERTY_CHANGE_MASK | GDK_STRUCTURE_MASK); |
---|
| 693 | |
---|
| 694 | selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", |
---|
| 695 | gdk_screen_get_number (screen)); |
---|
| 696 | manager->selection_atom = gdk_atom_intern (selection_atom_name, FALSE); |
---|
| 697 | g_free (selection_atom_name); |
---|
| 698 | |
---|
| 699 | na_tray_manager_set_orientation_property (manager); |
---|
| 700 | |
---|
| 701 | timestamp = gdk_x11_get_server_time (invisible->window); |
---|
| 702 | |
---|
| 703 | /* Check if we could set the selection owner successfully */ |
---|
| 704 | if (gdk_selection_owner_set_for_display (display, |
---|
| 705 | invisible->window, |
---|
| 706 | manager->selection_atom, |
---|
| 707 | timestamp, |
---|
| 708 | TRUE)) |
---|
| 709 | { |
---|
| 710 | XClientMessageEvent xev; |
---|
| 711 | GdkAtom opcode_atom; |
---|
| 712 | GdkAtom message_data_atom; |
---|
| 713 | |
---|
| 714 | xev.type = ClientMessage; |
---|
| 715 | xev.window = RootWindowOfScreen (xscreen); |
---|
| 716 | xev.message_type = gdk_x11_get_xatom_by_name_for_display (display, |
---|
| 717 | "MANAGER"); |
---|
| 718 | |
---|
| 719 | xev.format = 32; |
---|
| 720 | xev.data.l[0] = timestamp; |
---|
| 721 | xev.data.l[1] = gdk_x11_atom_to_xatom_for_display (display, |
---|
| 722 | manager->selection_atom); |
---|
| 723 | xev.data.l[2] = GDK_WINDOW_XWINDOW (invisible->window); |
---|
| 724 | xev.data.l[3] = 0; /* manager specific data */ |
---|
| 725 | xev.data.l[4] = 0; /* manager specific data */ |
---|
| 726 | |
---|
| 727 | XSendEvent (GDK_DISPLAY_XDISPLAY (display), |
---|
| 728 | RootWindowOfScreen (xscreen), |
---|
| 729 | False, StructureNotifyMask, (XEvent *)&xev); |
---|
| 730 | |
---|
| 731 | manager->invisible = invisible; |
---|
| 732 | g_object_ref (G_OBJECT (manager->invisible)); |
---|
| 733 | |
---|
| 734 | opcode_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_OPCODE", FALSE); |
---|
| 735 | manager->opcode_atom = gdk_x11_atom_to_xatom_for_display (display, |
---|
| 736 | opcode_atom); |
---|
| 737 | |
---|
| 738 | message_data_atom = gdk_atom_intern ("_NET_SYSTEM_TRAY_MESSAGE_DATA", |
---|
| 739 | FALSE); |
---|
| 740 | |
---|
| 741 | /* Add a window filter */ |
---|
| 742 | #if 0 |
---|
| 743 | /* This is for when we lose the selection of _NET_SYSTEM_TRAY_Sx */ |
---|
| 744 | g_signal_connect (invisible, "selection-clear-event", |
---|
| 745 | G_CALLBACK (na_tray_manager_selection_clear_event), |
---|
| 746 | manager); |
---|
| 747 | #endif |
---|
| 748 | /* This is for SYSTEM_TRAY_REQUEST_DOCK and SelectionClear */ |
---|
| 749 | gdk_window_add_filter (invisible->window, |
---|
| 750 | na_tray_manager_window_filter, manager); |
---|
| 751 | /* This is for SYSTEM_TRAY_BEGIN_MESSAGE and SYSTEM_TRAY_CANCEL_MESSAGE */ |
---|
| 752 | gdk_display_add_client_message_filter (display, opcode_atom, |
---|
| 753 | na_tray_manager_handle_client_message_opcode, |
---|
| 754 | manager); |
---|
| 755 | /* This is for _NET_SYSTEM_TRAY_MESSAGE_DATA */ |
---|
| 756 | gdk_display_add_client_message_filter (display, message_data_atom, |
---|
| 757 | na_tray_manager_handle_client_message_message_data, |
---|
| 758 | manager); |
---|
| 759 | return TRUE; |
---|
| 760 | } |
---|
| 761 | else |
---|
| 762 | { |
---|
| 763 | gtk_widget_destroy (invisible); |
---|
| 764 | |
---|
| 765 | return FALSE; |
---|
| 766 | } |
---|
| 767 | } |
---|
| 768 | |
---|
| 769 | #endif |
---|
| 770 | |
---|
| 771 | gboolean |
---|
| 772 | na_tray_manager_manage_screen (NaTrayManager *manager, |
---|
| 773 | GdkScreen *screen) |
---|
| 774 | { |
---|
| 775 | g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); |
---|
| 776 | g_return_val_if_fail (manager->screen == NULL, FALSE); |
---|
| 777 | |
---|
| 778 | #ifdef GDK_WINDOWING_X11 |
---|
| 779 | return na_tray_manager_manage_screen_x11 (manager, screen); |
---|
| 780 | #else |
---|
| 781 | return FALSE; |
---|
| 782 | #endif |
---|
| 783 | } |
---|
| 784 | |
---|
| 785 | #ifdef GDK_WINDOWING_X11 |
---|
| 786 | |
---|
| 787 | static gboolean |
---|
| 788 | na_tray_manager_check_running_screen_x11 (GdkScreen *screen) |
---|
| 789 | { |
---|
| 790 | GdkDisplay *display; |
---|
| 791 | Atom selection_atom; |
---|
| 792 | char *selection_atom_name; |
---|
| 793 | |
---|
| 794 | display = gdk_screen_get_display (screen); |
---|
| 795 | selection_atom_name = g_strdup_printf ("_NET_SYSTEM_TRAY_S%d", |
---|
| 796 | gdk_screen_get_number (screen)); |
---|
| 797 | selection_atom = gdk_x11_get_xatom_by_name_for_display (display, |
---|
| 798 | selection_atom_name); |
---|
| 799 | g_free (selection_atom_name); |
---|
| 800 | |
---|
| 801 | if (XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), |
---|
| 802 | selection_atom) != None) |
---|
| 803 | return TRUE; |
---|
| 804 | else |
---|
| 805 | return FALSE; |
---|
| 806 | } |
---|
| 807 | |
---|
| 808 | #endif |
---|
| 809 | |
---|
| 810 | gboolean |
---|
| 811 | na_tray_manager_check_running (GdkScreen *screen) |
---|
| 812 | { |
---|
| 813 | g_return_val_if_fail (GDK_IS_SCREEN (screen), FALSE); |
---|
| 814 | |
---|
| 815 | #ifdef GDK_WINDOWING_X11 |
---|
| 816 | return na_tray_manager_check_running_screen_x11 (screen); |
---|
| 817 | #else |
---|
| 818 | return FALSE; |
---|
| 819 | #endif |
---|
| 820 | } |
---|
| 821 | |
---|
| 822 | char * |
---|
| 823 | na_tray_manager_get_child_title (NaTrayManager *manager, |
---|
| 824 | NaTrayManagerChild *child) |
---|
| 825 | { |
---|
| 826 | char *retval = NULL; |
---|
| 827 | #ifdef GDK_WINDOWING_X11 |
---|
| 828 | GdkDisplay *display; |
---|
| 829 | Window *child_window; |
---|
| 830 | Atom utf8_string, atom, type; |
---|
| 831 | int result; |
---|
| 832 | int format; |
---|
| 833 | gulong nitems; |
---|
| 834 | gulong bytes_after; |
---|
| 835 | gchar *val; |
---|
| 836 | |
---|
| 837 | g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), NULL); |
---|
| 838 | g_return_val_if_fail (GTK_IS_SOCKET (child), NULL); |
---|
| 839 | |
---|
| 840 | display = gdk_screen_get_display (manager->screen); |
---|
| 841 | |
---|
| 842 | child_window = g_object_get_data (G_OBJECT (child), |
---|
| 843 | "na-tray-child-window"); |
---|
| 844 | |
---|
| 845 | utf8_string = gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"); |
---|
| 846 | atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_NAME"); |
---|
| 847 | |
---|
| 848 | gdk_error_trap_push (); |
---|
| 849 | |
---|
| 850 | result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), |
---|
| 851 | *child_window, |
---|
| 852 | atom, |
---|
| 853 | 0, G_MAXLONG, |
---|
| 854 | False, utf8_string, |
---|
| 855 | &type, &format, &nitems, |
---|
| 856 | &bytes_after, (guchar **)&val); |
---|
| 857 | |
---|
| 858 | if (gdk_error_trap_pop () || result != Success) |
---|
| 859 | return NULL; |
---|
| 860 | |
---|
| 861 | if (type != utf8_string || |
---|
| 862 | format != 8 || |
---|
| 863 | nitems == 0) |
---|
| 864 | { |
---|
| 865 | if (val) |
---|
| 866 | XFree (val); |
---|
| 867 | return NULL; |
---|
| 868 | } |
---|
| 869 | |
---|
| 870 | if (!g_utf8_validate (val, nitems, NULL)) |
---|
| 871 | { |
---|
| 872 | XFree (val); |
---|
| 873 | return NULL; |
---|
| 874 | } |
---|
| 875 | |
---|
| 876 | retval = g_strndup (val, nitems); |
---|
| 877 | |
---|
| 878 | XFree (val); |
---|
| 879 | #endif |
---|
| 880 | return retval; |
---|
| 881 | } |
---|
| 882 | |
---|
| 883 | void |
---|
| 884 | na_tray_manager_set_orientation (NaTrayManager *manager, |
---|
| 885 | GtkOrientation orientation) |
---|
| 886 | { |
---|
| 887 | g_return_if_fail (NA_IS_TRAY_MANAGER (manager)); |
---|
| 888 | |
---|
| 889 | if (manager->orientation != orientation) |
---|
| 890 | { |
---|
| 891 | manager->orientation = orientation; |
---|
| 892 | |
---|
| 893 | na_tray_manager_set_orientation_property (manager); |
---|
| 894 | |
---|
| 895 | g_object_notify (G_OBJECT (manager), "orientation"); |
---|
| 896 | } |
---|
| 897 | } |
---|
| 898 | |
---|
| 899 | GtkOrientation |
---|
| 900 | na_tray_manager_get_orientation (NaTrayManager *manager) |
---|
| 901 | { |
---|
| 902 | g_return_val_if_fail (NA_IS_TRAY_MANAGER (manager), GTK_ORIENTATION_HORIZONTAL); |
---|
| 903 | |
---|
| 904 | return manager->orientation; |
---|
| 905 | } |
---|