1 /**
2  * Copyright (C) 2004 Billy Biggs <vektor@dumbterm.net>.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
25 #include <stdio.h>
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdk.h>
29 #include <gdk/gdkx.h>
30 #include <X11/Xlib.h>
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
34 #ifdef ENABLE_NLS
35 # define _(string) gettext (string)
36 # include "gettext.h"
37 #else
38 # define _(string) string
39 #endif
40 #include "ewmhview.h"
41 #include "xresview.h"
42 #include "xwinview.h"
43 
44 static Atom net_wm_state;
45 static Atom net_wm_state_above;
46 static int atoms_loaded = 0;
47 
48 /**
49  * Loads any atoms we will be using.  Kinda pointless since we just use
50  * these once.
51  */
load_atoms(Display * dpy)52 static void load_atoms( Display *dpy )
53 {
54     static char *atom_names[] = {
55         "_NET_WM_STATE",
56         "_NET_WM_STATE_ABOVE"
57     };
58     Atom atoms_return[ 2 ];
59 
60     if( atoms_loaded ) return;
61     XInternAtoms( dpy, atom_names, 2, False, atoms_return );
62     net_wm_state = atoms_return[ 0 ];
63     net_wm_state_above = atoms_return[ 1 ];
64     atoms_loaded = 1;
65 }
66 
67 /**
68  * This hacky code sets us to be always-on-top for supporting window
69  * managers.  There is a call for this in Gtk+ 2.4, but I would like to
70  * be backwards compatible.  We set this even if the WM does not support
71  * it since we don't mind if the feature is not available.
72  */
set_state_above(GdkWindow * gdkwin)73 static void set_state_above( GdkWindow *gdkwin )
74 {
75     Display *dpy = gdk_x11_get_default_xdisplay();
76     Window wm_window = gdk_x11_drawable_get_xid( gdkwin );
77     XEvent ev;
78 
79     load_atoms( dpy );
80 
81     ev.type = ClientMessage;
82     ev.xclient.window = wm_window;
83     ev.xclient.message_type = net_wm_state;
84     ev.xclient.format = 32;
85     ev.xclient.data.l[ 0 ] = 1;
86     ev.xclient.data.l[ 1 ] = net_wm_state_above;
87     ev.xclient.data.l[ 2 ] = 0;
88 
89     XSendEvent( dpy, DefaultRootWindow( dpy ), False,
90                 SubstructureNotifyMask|SubstructureRedirectMask, &ev );
91 }
92 
93 
94 static ewmhview_t *ev;
95 static xwinview_t *wv;
96 static xresview_t *rv;
97 static int windowlist[ 4096 ];
98 static int numwindows = 0;
99 static int lasttop = -1;
100 static int paused = 0;
101 
check_events(gpointer data)102 static gboolean check_events( gpointer data )
103 {
104     Window real_root, win;
105     Display *dpy;
106     int i;
107 
108     /* Don't update anything if we are paused. */
109     if( paused ) {
110         return TRUE;
111     }
112 
113     /* Query the list of windows underneath the pointer. */
114     dpy = gdk_x11_get_default_xdisplay();
115     real_root = gdk_x11_get_default_root_xwindow();
116     numwindows = 0;
117     win = real_root;
118     while( win ) {
119         Window root, child;
120         int x, y, rx, ry;
121         unsigned int mask;
122 
123         windowlist[ numwindows++ ] = win;
124         XQueryPointer( dpy, win, &root, &child, &rx, &ry, &x, &y, &mask );
125 
126         win = child;
127     }
128 
129     /* Don't update the tree if we have seen this list before. */
130     if( windowlist[ numwindows - 1 ] == lasttop ) {
131         return TRUE;
132     }
133     lasttop = windowlist[ numwindows - 1 ];
134 
135     /* We're about to start asking for windows that may not exist.
136      * Let's make sure we don't give ourselves a BadWindow error.
137      */
138     gdk_error_trap_push ();
139 
140     /* Generate information for the new list. */
141     ewmhview_clear( ev, dpy, real_root );
142     xwinview_clear( wv, dpy, real_root );
143     xresview_clear( rv, dpy, real_root );
144     for( i = 0; i < numwindows; i++ ) {
145         xwinview_load( wv, dpy, windowlist[ i ], real_root );
146         ewmhview_load( ev, dpy, windowlist[ i ], real_root );
147         xresview_load( rv, dpy, windowlist[ i ], real_root );
148     }
149 
150     /* We're done with the possible causes of BadWindow errors.
151      * We need to flush the event queue to make sure the errors
152      * arrive (i.e., they are asynchronous).  We could do
153      * something to handle the errors, but it's probably okay to
154      * just wait for the next time this function is called.
155      */
156     gdk_flush ();
157     gdk_error_trap_pop ();
158 
159     return TRUE;
160 }
161 
162 /**
163  * This can obviously be improved.
164  */
about_dialog(GtkWindow * parent)165 static void about_dialog( GtkWindow *parent )
166 {
167     GtkWidget *dialog;
168 
169     dialog = gtk_message_dialog_new( GTK_WINDOW( parent ),
170                                      GTK_DIALOG_DESTROY_WITH_PARENT,
171                                      GTK_MESSAGE_INFO,
172                                      GTK_BUTTONS_CLOSE,
173                                      PACKAGE_STRING "\n\n" PACKAGE_BUGREPORT );
174 
175     g_signal_connect( dialog, "response",
176                       G_CALLBACK( gtk_widget_destroy ), 0 );
177     gtk_widget_show( dialog );
178 }
179 
180 /**
181  * Cheap callback for menu items.
182  */
menuitem_cb(gpointer callback_data,guint callback_action,GtkWidget * widget)183 static void menuitem_cb( gpointer callback_data, guint callback_action,
184                          GtkWidget *widget )
185 {
186 
187     if( !strcmp( gtk_item_factory_path_from_widget( widget ),
188                  "<main>/Help/About" ) ) {
189         about_dialog( (GtkWindow *) callback_data );
190     } else if( !strcmp( gtk_item_factory_path_from_widget( widget ),
191                         "<main>/Commands/Pause" ) ) {
192         paused = !paused;
193     } else if( !strcmp( gtk_item_factory_path_from_widget( widget ),
194                         "<main>/File/Quit" ) ) {
195         gtk_main_quit();
196     }
197 }
198 
199 static GtkItemFactoryEntry menu_items[] =
200 {
201   { "/_File",            NULL,         0,           0, "<Branch>" },
202   { "/File/_Quit",       "<control>Q", menuitem_cb, 0, "<StockItem>", GTK_STOCK_QUIT },
203 
204   { "/_Commands",                      NULL, 0,     0, "<Branch>" },
205   { "/Commands/_Pause",  "<control>P", menuitem_cb, 0 },
206 
207   { "/_Help",            NULL,         0,           0, "<Branch>" },
208   { "/Help/_About",      NULL,         menuitem_cb, 0 },
209 };
210 
211 /**
212  * Window destroy.
213  */
on_destroy(GtkWidget * widget,gpointer data)214 static void on_destroy( GtkWidget *widget, gpointer data )
215 {
216     gtk_main_quit();
217 }
218 
main(int argc,char ** argv)219 int main( int argc, char **argv )
220 {
221     GtkWidget *window;
222     GtkWidget *vbox;
223     GtkWidget *notebook;
224     GtkAccelGroup *accel_group;
225     GtkItemFactory *item_factory;
226 
227     gtk_init( &argc, &argv );
228 
229     window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
230     gtk_window_set_title( GTK_WINDOW( window ), _("X Window Information") );
231 
232     vbox = gtk_vbox_new( FALSE, 0 );
233     gtk_container_add( GTK_CONTAINER( window ), vbox );
234 
235     g_signal_connect( G_OBJECT( window ), "destroy",
236                       G_CALLBACK( on_destroy ), 0 );
237 
238     accel_group = gtk_accel_group_new();
239     gtk_window_add_accel_group( GTK_WINDOW( window ), accel_group );
240     g_object_unref( accel_group );
241 
242     item_factory = gtk_item_factory_new( GTK_TYPE_MENU_BAR, "<main>",
243                                          accel_group );
244 
245     g_object_ref( item_factory );
246     gtk_object_sink( GTK_OBJECT( item_factory ) );
247     g_object_set_data_full( G_OBJECT( window ), "<main>",
248                             item_factory, (GDestroyNotify) g_object_unref );
249 
250     gtk_item_factory_create_items( item_factory, G_N_ELEMENTS( menu_items ),
251                                    menu_items, window );
252     gtk_box_pack_start( GTK_BOX( vbox ),
253                         gtk_item_factory_get_widget( item_factory, "<main>" ),
254                         FALSE, FALSE, 0 );
255 
256     notebook = gtk_notebook_new();
257     gtk_box_pack_start( GTK_BOX( vbox ), notebook,
258                         TRUE, TRUE, 0 );
259 
260     wv = xwinview_new();
261     gtk_notebook_append_page( GTK_NOTEBOOK( notebook ),
262                               xwinview_get_widget( wv ),
263                               gtk_label_new( _("Window List") ) );
264 
265     ev = ewmhview_new();
266     gtk_notebook_append_page( GTK_NOTEBOOK( notebook ),
267                               ewmhview_get_widget( ev ),
268                               gtk_label_new( _("Window Manager Hints") ) );
269 
270     rv = xresview_new();
271     gtk_notebook_append_page( GTK_NOTEBOOK( notebook ),
272                               xresview_get_widget( rv ),
273                               gtk_label_new( _("Server Resources") ) );
274 
275     g_timeout_add( 50, check_events, window );
276 
277     gtk_widget_show_all( window );
278     set_state_above( ((GtkWidget *) window)->window );
279 
280     gtk_main();
281     return 0;
282 }
283 
284