/** * Copyright (C) 2004 Billy Biggs . * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #ifdef ENABLE_NLS # define _(string) gettext (string) # include "gettext.h" #else # define _(string) string #endif #include "ewmhview.h" #include "xresview.h" #include "xwinview.h" static Atom net_wm_state; static Atom net_wm_state_above; static int atoms_loaded = 0; /** * Loads any atoms we will be using. Kinda pointless since we just use * these once. */ static void load_atoms( Display *dpy ) { static char *atom_names[] = { "_NET_WM_STATE", "_NET_WM_STATE_ABOVE" }; Atom atoms_return[ 2 ]; if( atoms_loaded ) return; XInternAtoms( dpy, atom_names, 2, False, atoms_return ); net_wm_state = atoms_return[ 0 ]; net_wm_state_above = atoms_return[ 1 ]; atoms_loaded = 1; } /** * This hacky code sets us to be always-on-top for supporting window * managers. There is a call for this in Gtk+ 2.4, but I would like to * be backwards compatible. We set this even if the WM does not support * it since we don't mind if the feature is not available. */ static void set_state_above( GdkWindow *gdkwin ) { Display *dpy = gdk_x11_get_default_xdisplay(); Window wm_window = gdk_x11_drawable_get_xid( gdkwin ); XEvent ev; load_atoms( dpy ); ev.type = ClientMessage; ev.xclient.window = wm_window; ev.xclient.message_type = net_wm_state; ev.xclient.format = 32; ev.xclient.data.l[ 0 ] = 1; ev.xclient.data.l[ 1 ] = net_wm_state_above; ev.xclient.data.l[ 2 ] = 0; XSendEvent( dpy, DefaultRootWindow( dpy ), False, SubstructureNotifyMask|SubstructureRedirectMask, &ev ); } static ewmhview_t *ev; static xwinview_t *wv; static xresview_t *rv; static int windowlist[ 4096 ]; static int numwindows = 0; static int lasttop = -1; static int paused = 0; static gboolean check_events( gpointer data ) { Window real_root, win; Display *dpy; int i; /* Don't update anything if we are paused. */ if( paused ) { return TRUE; } /* Query the list of windows underneath the pointer. */ dpy = gdk_x11_get_default_xdisplay(); real_root = gdk_x11_get_default_root_xwindow(); numwindows = 0; win = real_root; while( win ) { Window root, child; int x, y, rx, ry; unsigned int mask; windowlist[ numwindows++ ] = win; XQueryPointer( dpy, win, &root, &child, &rx, &ry, &x, &y, &mask ); win = child; } /* Don't update the tree if we have seen this list before. */ if( windowlist[ numwindows - 1 ] == lasttop ) { return TRUE; } lasttop = windowlist[ numwindows - 1 ]; /* We're about to start asking for windows that may not exist. * Let's make sure we don't give ourselves a BadWindow error. */ gdk_error_trap_push (); /* Generate information for the new list. */ ewmhview_clear( ev, dpy, real_root ); xwinview_clear( wv, dpy, real_root ); xresview_clear( rv, dpy, real_root ); for( i = 0; i < numwindows; i++ ) { xwinview_load( wv, dpy, windowlist[ i ], real_root ); ewmhview_load( ev, dpy, windowlist[ i ], real_root ); xresview_load( rv, dpy, windowlist[ i ], real_root ); } /* We're done with the possible causes of BadWindow errors. * We need to flush the event queue to make sure the errors * arrive (i.e., they are asynchronous). We could do * something to handle the errors, but it's probably okay to * just wait for the next time this function is called. */ gdk_flush (); gdk_error_trap_pop (); return TRUE; } /** * This can obviously be improved. */ static void about_dialog( GtkWindow *parent ) { GtkWidget *dialog; dialog = gtk_message_dialog_new( GTK_WINDOW( parent ), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, PACKAGE_STRING "\n\n" PACKAGE_BUGREPORT ); g_signal_connect( dialog, "response", G_CALLBACK( gtk_widget_destroy ), 0 ); gtk_widget_show( dialog ); } /** * Cheap callback for menu items. */ static void menuitem_cb( gpointer callback_data, guint callback_action, GtkWidget *widget ) { if( !strcmp( gtk_item_factory_path_from_widget( widget ), "
/Help/About" ) ) { about_dialog( (GtkWindow *) callback_data ); } else if( !strcmp( gtk_item_factory_path_from_widget( widget ), "
/Commands/Pause" ) ) { paused = !paused; } else if( !strcmp( gtk_item_factory_path_from_widget( widget ), "
/File/Quit" ) ) { gtk_main_quit(); } } static GtkItemFactoryEntry menu_items[] = { { "/_File", NULL, 0, 0, "" }, { "/File/_Quit", "Q", menuitem_cb, 0, "", GTK_STOCK_QUIT }, { "/_Commands", NULL, 0, 0, "" }, { "/Commands/_Pause", "P", menuitem_cb, 0 }, { "/_Help", NULL, 0, 0, "" }, { "/Help/_About", NULL, menuitem_cb, 0 }, }; /** * Window destroy. */ static void on_destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit(); } int main( int argc, char **argv ) { GtkWidget *window; GtkWidget *vbox; GtkWidget *notebook; GtkAccelGroup *accel_group; GtkItemFactory *item_factory; gtk_init( &argc, &argv ); window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_window_set_title( GTK_WINDOW( window ), _("X Window Information") ); vbox = gtk_vbox_new( FALSE, 0 ); gtk_container_add( GTK_CONTAINER( window ), vbox ); g_signal_connect( G_OBJECT( window ), "destroy", G_CALLBACK( on_destroy ), 0 ); accel_group = gtk_accel_group_new(); gtk_window_add_accel_group( GTK_WINDOW( window ), accel_group ); g_object_unref( accel_group ); item_factory = gtk_item_factory_new( GTK_TYPE_MENU_BAR, "
", accel_group ); g_object_ref( item_factory ); gtk_object_sink( GTK_OBJECT( item_factory ) ); g_object_set_data_full( G_OBJECT( window ), "
", item_factory, (GDestroyNotify) g_object_unref ); gtk_item_factory_create_items( item_factory, G_N_ELEMENTS( menu_items ), menu_items, window ); gtk_box_pack_start( GTK_BOX( vbox ), gtk_item_factory_get_widget( item_factory, "
" ), FALSE, FALSE, 0 ); notebook = gtk_notebook_new(); gtk_box_pack_start( GTK_BOX( vbox ), notebook, TRUE, TRUE, 0 ); wv = xwinview_new(); gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), xwinview_get_widget( wv ), gtk_label_new( _("Window List") ) ); ev = ewmhview_new(); gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), ewmhview_get_widget( ev ), gtk_label_new( _("Window Manager Hints") ) ); rv = xresview_new(); gtk_notebook_append_page( GTK_NOTEBOOK( notebook ), xresview_get_widget( rv ), gtk_label_new( _("Server Resources") ) ); g_timeout_add( 50, check_events, window ); gtk_widget_show_all( window ); set_state_above( ((GtkWidget *) window)->window ); gtk_main(); return 0; }