/* * Copyright (C) 2007 Ross Burton * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include "owlwindowmenu.h" #define MENU_PROP "owl::windowmenu" static Atom atom_custom, atom_protocol; /* * Ensures that the atoms we need are defined. */ static void ensure_atoms (GdkDrawable *drawable) { static gboolean done = FALSE; if (G_UNLIKELY (!done)) { GdkDisplay *display = gdk_drawable_get_display (drawable); atom_custom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_CONTEXT_CUSTOM"); atom_protocol = gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS"); g_assert (atom_custom); g_assert (atom_protocol); done = TRUE; } } /* * Menu positioning function, called by gtk_menu_popup. */ static void position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { GtkWidget *window = user_data; /* * Set the position of the menu to the origin of the window. This means that * the menu is in the top left of the application window. It might be * interesting to see if the theme could specify an offset, so that the menu * aligns itself with the decorations. */ gdk_window_get_origin (window->window, x, y); /* Maemo does this which I believe is a theme extension gtk_widget_style_get (GTK_WIDGET (menu), "horizontal-offset", x, "vertical-offset", y, NULL); *x += window_x; *y += window_y; */ } /* * Gdk event filter. This should be as fast as possible for non-client messages * as it gets called *frequently*. */ static GdkFilterReturn filter_func (GdkXEvent *xevent, GdkEvent *event, gpointer data) { if (G_UNLIKELY (((XEvent*)xevent)->type == ClientMessage)) { XClientMessageEvent *xev = xevent; if (xev->message_type == atom_protocol) { Atom protocol = xev->data.l[0]; if (protocol == atom_custom) { GtkMenu *menu; /* Sanity check the user data is the window */ g_return_val_if_fail (GTK_IS_WINDOW (data), GDK_FILTER_CONTINUE); menu = g_object_get_data (G_OBJECT (data), MENU_PROP); if (menu) { gtk_menu_popup (menu, NULL, NULL, position_func, data, 0, gtk_get_current_event_time()); } return GDK_FILTER_REMOVE; } } } return GDK_FILTER_CONTINUE; } /** * owl_set_window_menu: * @window: a top-level #GtkWindow * @menu: a #GtkMenu * * Make @menu pop up when the CUSTOM protocol is sent to @window. This means * that when the user clicks on the title bar with a suitable window manager, * the menu appears. */ void owl_set_window_menu (GtkWindow *window, GtkMenu *menu) { gboolean done_setup; GdkWindow *w; Status status; Atom *old_atoms = NULL, *atoms; int count = 0; g_return_if_fail (GTK_IS_WINDOW (window)); /* TODO: allow NULL menu to unset? */ g_return_if_fail (GTK_IS_MENU (menu)); /* Realize the window if it isn't already, as we need an X connection */ if (!GTK_WIDGET_REALIZED (window)) gtk_widget_realize (GTK_WIDGET (window)); w = GTK_WIDGET (window)->window; /* Make sure the atoms we need are defined */ ensure_atoms (w); /* We only need to setup if the property isn't already set */ done_setup = g_object_get_data (G_OBJECT (window), MENU_PROP) != NULL; /* Set the menu. If there is a menu already defined, it will be unreffed */ g_object_set_data_full (G_OBJECT (window), MENU_PROP, g_object_ref (menu), g_object_unref); /* It's possible that we are replacing the menu with another menu, so the filters and so on are already registered. */ if (done_setup) return; /* Get the protocols supported by this window */ status = XGetWMProtocols(GDK_WINDOW_XDISPLAY (w), GDK_WINDOW_XID (w), &old_atoms, &count); /* TODO: check error */ /* TODO: check if old_atoms already contains custom */ /* Add _NEW_WM_CONTEXT_CUSTOM to the list */ atoms = g_new0 (Atom, count+1); memcpy (atoms, old_atoms, count * sizeof (Atom)); atoms[count] = atom_custom; /* And set the list */ XSetWMProtocols (GDK_WINDOW_XDISPLAY (w), GDK_WINDOW_XID (w), atoms, count+1); /* TODO: check error */ XFree (old_atoms); g_free (atoms); /* Add a filter so that we can catch the CUSTOM event and display the menu. */ gdk_window_add_filter (w, filter_func, window); } /** * owl_set_window_menu_item: * @window: a top-level #GtkWindow * @menuitem: a #GtkMenuItem * * Make the submenu in @menuitem pop up when the CUSTOM protocol is sent to * @window. This means that when the user clicks on the title bar with a * suitable window manager, the menu appears. * * This is a convenience function for applications which use GtkUIManager to * construct their menus. */ void owl_set_window_menu_item (GtkWindow *window, GtkMenuItem *menuitem) { GtkWidget *menu; g_return_if_fail (GTK_IS_WINDOW (window)); /* TODO: allow NULL menu to unset? */ g_return_if_fail (GTK_IS_MENU_ITEM (menuitem)); menu = gtk_menu_item_get_submenu (menuitem); if (!menu) { g_warning ("%s: GtkMenuItem doesn't have a submenu", G_STRFUNC); return; } g_object_ref (menu); gtk_menu_item_remove_submenu (menuitem); owl_set_window_menu (window, GTK_MENU (menu)); g_object_unref (menu); }